style: 🎨 Format code with Biome

This commit is contained in:
Jesse Wierzbinski 2025-12-09 22:32:22 +01:00
parent 7ff9d2302a
commit 3627ac0ef8
No known key found for this signature in database
296 changed files with 3257 additions and 2808 deletions

View file

@ -1,15 +1,13 @@
<template> <template>
<TooltipProvider> <TooltipProvider>
<Component is="style"> <Component is="style">{{ preferences.custom_css }}</Component>
{{ preferences.custom_css }} <NuxtPwaAssets/>
</Component>
<NuxtPwaAssets />
<NuxtLayout> <NuxtLayout>
<NuxtPage /> <NuxtPage/>
</NuxtLayout> </NuxtLayout>
<ConfirmationModal /> <ConfirmationModal/>
<!-- pointer-events-auto fixes https://github.com/unovue/shadcn-vue/issues/462 --> <!-- pointer-events-auto fixes https://github.com/unovue/shadcn-vue/issues/462 -->
<Toaster class="pointer-events-auto" /> <Toaster class="pointer-events-auto"/>
</TooltipProvider> </TooltipProvider>
</template> </template>
@ -91,7 +89,10 @@ body {
html.theme-changing * { html.theme-changing * {
/* Stroke and fill aren't animatable */ /* Stroke and fill aren't animatable */
transition: background-color 1s ease, border 1s ease, color 1s ease, transition:
background-color 1s ease,
border 1s ease,
color 1s ease,
box-shadow 1s ease !important; box-shadow 1s ease !important;
} }

View file

@ -1,69 +1,105 @@
<template> <template>
<div v-if="relation" class="overflow-auto max-h-72"> <div v-if="relation" class="overflow-auto max-h-72">
<Note :note="relation.note" :hide-actions="true" :small-layout="true" /> <Note :note="relation.note" :hide-actions="true" :small-layout="true"/>
</div> </div>
<InputGroup class="p-1"> <InputGroup class="p-1">
<InputGroupAddon v-if="store.sensitive" align="block-start" class="pt-3"> <InputGroupAddon
<Input v-model:model-value="store.contentWarning" placeholder="Put your content warning here" /> v-if="store.sensitive"
align="block-start"
class="pt-3"
>
<Input
v-model:model-value="store.contentWarning"
placeholder="Put your content warning here"
/>
</InputGroupAddon> </InputGroupAddon>
<EditorContent data-slot="input-group-control" @paste-files="uploadFiles" v-model:content="store.content" <EditorContent
v-model:raw-content="store.rawContent" :placeholder="getRandomSplash()" data-slot="input-group-control"
@paste-files="uploadFiles"
v-model:content="store.content"
v-model:raw-content="store.rawContent"
:placeholder="getRandomSplash()"
class=" placeholder:text-muted-foreground flex field-sizing-content min-h-58 w-full px-4 text-base disabled:opacity-50 md:text-sm flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none" class=" placeholder:text-muted-foreground flex field-sizing-content min-h-58 w-full px-4 text-base disabled:opacity-50 md:text-sm flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none"
:disabled="store.sending" :mode="store.contentType === 'text/html' ? 'rich' : 'plain'" /> :disabled="store.sending"
:mode="store.contentType === 'text/html' ? 'rich' : 'plain'"
/>
<InputGroupAddon v-if="store.files.length > 0" align="block-end" class="overflow-x-auto *:shrink-0"> <InputGroupAddon
<Files v-model:files="store.files" :composer-key="composerKey" /> v-if="store.files.length > 0"
align="block-end"
class="overflow-x-auto *:shrink-0"
>
<Files v-model:files="store.files" :composer-key="composerKey"/>
</InputGroupAddon> </InputGroupAddon>
<InputGroupAddon align="block-end"> <InputGroupAddon align="block-end">
<Select v-model:model-value="store.contentType"> <Select v-model:model-value="store.contentType">
<SelectTrigger as-child disable-default-classes disable-select-icon> <SelectTrigger
as-child
disable-default-classes
disable-select-icon
>
<InputGroupButton variant="ghost" size="icon-sm"> <InputGroupButton variant="ghost" size="icon-sm">
<LetterText v-if="store.contentType === 'text/html'" /> <LetterText v-if="store.contentType === 'text/html'"/>
<Type v-else /> <Type v-else/>
</InputGroupButton> </InputGroupButton>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem value="text/plain"> <SelectItem value="text/plain">Plain text</SelectItem>
Plain text <SelectItem value="text/html">Rich text</SelectItem>
</SelectItem>
<SelectItem value="text/html">
Rich text
</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
<VisibilityPicker v-model:visibility="store.visibility"> <VisibilityPicker v-model:visibility="store.visibility">
<InputGroupButton variant="ghost" size="icon-sm" :disabled="store.relation?.type === 'edit'"> <InputGroupButton
<component :is="visibilities[store.visibility].icon" /> variant="ghost"
size="icon-sm"
:disabled="store.relation?.type === 'edit'"
>
<component :is="visibilities[store.visibility].icon"/>
</InputGroupButton> </InputGroupButton>
</VisibilityPicker> </VisibilityPicker>
<InputGroupButton variant="ghost" size="icon-sm" @click="fileInput?.click()"> <InputGroupButton
<FilePlus2 /> variant="ghost"
size="icon-sm"
@click="fileInput?.click()"
>
<FilePlus2/>
</InputGroupButton> </InputGroupButton>
<Toggle size="sm" v-model="store.sensitive"> <Toggle size="sm" v-model="store.sensitive">
<TriangleAlert /> <TriangleAlert/>
</Toggle> </Toggle>
<InputGroupText :class="['ml-auto', charactersLeft < 0 && 'text-destructive']"> <InputGroupText
:class="['ml-auto', charactersLeft < 0 && 'text-destructive']"
>
{{ charactersLeft.toLocaleString(getLocale(), { {{ charactersLeft.toLocaleString(getLocale(), {
maximumFractionDigits: 2, maximumFractionDigits: 2,
notation: 'compact', notation: 'compact',
compactDisplay: 'short', compactDisplay: 'short',
}) }} }) }}
</InputGroupText> </InputGroupText>
<Separator orientation="vertical" class="h-4!" /> <Separator orientation="vertical" class="h-4!"/>
<InputGroupButton variant="default" size="icon-sm" :disabled="store.sending || !store.canSend" <InputGroupButton
@click="send"> variant="default"
<Spinner v-if="store.sending" /> size="icon-sm"
<ArrowUp v-else /> :disabled="store.sending || !store.canSend"
@click="send"
>
<Spinner v-if="store.sending"/>
<ArrowUp v-else/>
<span class="sr-only">Send</span> <span class="sr-only">Send</span>
</InputGroupButton> </InputGroupButton>
</InputGroupAddon> </InputGroupAddon>
</InputGroup> </InputGroup>
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple /> <input
type="file"
ref="fileInput"
@change="uploadFileFromEvent"
class="hidden"
multiple
>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -101,7 +101,7 @@ const relation = ref(
: m.brief_cool_capybara_fear() : m.brief_cool_capybara_fear()
}} }}
</DialogDescription> </DialogDescription>
<Composer :relation="relation ?? undefined" /> <Composer :relation="relation ?? undefined"/>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
</template> </template>

View file

@ -5,26 +5,37 @@
:disabled="file.uploading || file.updating" :disabled="file.uploading || file.updating"
class="block bg-card text-card-foreground shadow-sm h-28 overflow-hidden rounded relative min-w-28 *:disabled:opacity-50" class="block bg-card text-card-foreground shadow-sm h-28 overflow-hidden rounded relative min-w-28 *:disabled:opacity-50"
> >
<img v-if="file.file?.type.startsWith('image/')" :src="createObjectURL(file.file)" class="object-contain h-28 w-full" :alt="file.alt" /> <img
<FileIcon v-else class="size-6 m-auto text-muted-foreground" /> v-if="file.file?.type.startsWith('image/')"
:src="createObjectURL(file.file)"
class="object-contain h-28 w-full"
:alt="file.alt"
>
<FileIcon v-else class="size-6 m-auto text-muted-foreground"/>
<Badge <Badge
v-if="file.file && !(file.uploading || file.updating)" v-if="file.file && !(file.uploading || file.updating)"
class="absolute bottom-1 right-1" class="absolute bottom-1 right-1"
variant="default" variant="default"
>{{ formatBytes(file.file.size) }}</Badge
> >
<Spinner v-else-if="file.file" class="absolute bottom-1 right-1 size-8 p-1.5" /> {{ formatBytes(file.file.size) }}
</Badge>
<Spinner
v-else-if="file.file"
class="absolute bottom-1 right-1 size-8 p-1.5"
/>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="min-w-48"> <DropdownMenuContent class="min-w-48">
<DropdownMenuLabel v-if="file.file">{{ file.file.name }}</DropdownMenuLabel> <DropdownMenuLabel v-if="file.file">
<DropdownMenuSeparator /> {{ file.file.name }}
</DropdownMenuLabel>
<DropdownMenuSeparator/>
<DropdownMenuItem @click="editCaption"> <DropdownMenuItem @click="editCaption">
<Captions /> <Captions/>
Add caption Add caption
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> <DropdownMenuSeparator/>
<DropdownMenuItem @click="emit('remove')"> <DropdownMenuItem @click="emit('remove')">
<Delete /> <Delete/>
Remove Remove
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View file

@ -1,6 +1,12 @@
<template> <template>
<FilePreview v-for="(file, index) in files" :key="file.apiId" :file="file" @update:file="files[index] = $event" :composer-key="composerKey" <FilePreview
@remove="files.splice(index, 1)" /> v-for="(file, index) in files"
:key="file.apiId"
:file="file"
@update:file="files[index] = $event"
:composer-key="composerKey"
@remove="files.splice(index, 1)"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,12 +1,19 @@
<template> <template>
<Select v-model:model-value="visibility"> <Select v-model:model-value="visibility">
<SelectTrigger as-child disable-default-classes disable-select-icon> <SelectTrigger as-child disable-default-classes disable-select-icon>
<slot /> <slot/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem v-for="(v, k) in visibilities" :key="k" @click="visibility = k" :value="k"> <SelectItem
<div class="flex flex-row gap-3 items-center w-full justify-between"> v-for="(v, k) in visibilities"
<component :is="v.icon" class="size-4" /> :key="k"
@click="visibility = k"
:value="k"
>
<div
class="flex flex-row gap-3 items-center w-full justify-between"
>
<component :is="v.icon" class="size-4"/>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="font-semibold">{{ v.name }}</span> <span class="font-semibold">{{ v.name }}</span>
<span>{{ v.text }}</span> <span>{{ v.text }}</span>

View file

@ -69,30 +69,31 @@ watch(active, (value) => {
<template> <template>
<BubbleMenu :editor="editor"> <BubbleMenu :editor="editor">
<ToggleGroup type="multiple" <ToggleGroup
type="multiple"
v-model="active" v-model="active"
class="bg-popover rounded-md" class="bg-popover rounded-md"
> >
<ToggleGroupItem value="bold"> <ToggleGroupItem value="bold">
<BoldIcon /> <BoldIcon/>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="italic"> <ToggleGroupItem value="italic">
<ItalicIcon /> <ItalicIcon/>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="underline"> <ToggleGroupItem value="underline">
<UnderlineIcon /> <UnderlineIcon/>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="code"> <ToggleGroupItem value="code">
<CurlyBracesIcon /> <CurlyBracesIcon/>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="strike"> <ToggleGroupItem value="strike">
<StrikethroughIcon /> <StrikethroughIcon/>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="subscript"> <ToggleGroupItem value="subscript">
<SubscriptIcon /> <SubscriptIcon/>
</ToggleGroupItem> </ToggleGroupItem>
<ToggleGroupItem value="superscript"> <ToggleGroupItem value="superscript">
<SuperscriptIcon /> <SuperscriptIcon/>
</ToggleGroupItem> </ToggleGroupItem>
</ToggleGroup> </ToggleGroup>
</BubbleMenu> </BubbleMenu>

View file

@ -1,8 +1,6 @@
<template> <template>
<BubbleMenu :editor="editor" /> <BubbleMenu :editor="editor"/>
<EditorContent :editor="editor" <EditorContent :editor="editor" v-bind="$attrs" :class="$style.content"/>
v-bind="$attrs"
:class="$style.content" />
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -122,7 +120,7 @@ onUnmounted(() => {
@apply font-bold rounded-sm text-primary-foreground bg-primary px-1 py-0.5; @apply font-bold rounded-sm text-primary-foreground bg-primary px-1 py-0.5;
} }
.tiptap .emoji>img { .tiptap .emoji > img {
@apply h-lh align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out; @apply h-lh align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out;
} }
</style> </style>

View file

@ -1,10 +1,23 @@
<template> <template>
<Command class="rounded border shadow-md min-w-[200px] h-fit not-prose" :selected-value="emojis[selectedIndex]?.id"> <Command
class="rounded border shadow-md min-w-[200px] h-fit not-prose"
:selected-value="emojis[selectedIndex]?.id"
>
<CommandList> <CommandList>
<CommandEmpty>No results found.</CommandEmpty> <CommandEmpty>No results found.</CommandEmpty>
<CommandGroup class="emojis-group" heading="Emojis"> <CommandGroup class="emojis-group" heading="Emojis">
<CommandItem :value="emoji.id" v-for="emoji, index in emojis" :key="emoji.id" @click="selectItem(index)" class="scroll-m-10"> <CommandItem
<img class="h-[1lh] align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out" :src="emoji.url" :title="emoji.shortcode" /> :value="emoji.id"
v-for="emoji, index in emojis"
:key="emoji.id"
@click="selectItem(index)"
class="scroll-m-10"
>
<img
class="h-[1lh] align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out"
:src="emoji.url"
:title="emoji.shortcode"
>
<span>{{ emoji.shortcode }}</span> <span>{{ emoji.shortcode }}</span>
</CommandItem> </CommandItem>
</CommandGroup> </CommandGroup>

View file

@ -1,11 +1,26 @@
<template> <template>
<Command class="rounded border shadow-md min-w-[200px] h-fit not-prose" :selected-value="items[selectedIndex]?.key"> <Command
class="rounded border shadow-md min-w-[200px] h-fit not-prose"
:selected-value="items[selectedIndex]?.key"
>
<CommandList> <CommandList>
<CommandEmpty>No results found.</CommandEmpty> <CommandEmpty>No results found.</CommandEmpty>
<CommandGroup class="mentions-group" heading="Users"> <CommandGroup class="mentions-group" heading="Users">
<CommandItem :value="user.key" v-for="user, index in items" :key="user.key" @click="selectItem(index)" class="scroll-m-10"> <CommandItem
<Avatar class="size-4" :src="user.value.avatar" :name="user.value.display_name" /> :value="user.key"
<span v-render-emojis="user.value.emojis">{{ user.value.display_name }}</span> v-for="user, index in items"
:key="user.key"
@click="selectItem(index)"
class="scroll-m-10"
>
<Avatar
class="size-4"
:src="user.value.avatar"
:name="user.value.display_name"
/>
<span v-render-emojis="user.value.emojis"
>{{ user.value.display_name }}</span
>
</CommandItem> </CommandItem>
</CommandGroup> </CommandGroup>
</CommandList> </CommandList>

View file

@ -1,9 +1,7 @@
<template> <template>
<Alert> <Alert>
<AlertTitle>{{ m.fine_arable_lemming_fold() }}</AlertTitle> <AlertTitle>{{ m.fine_arable_lemming_fold() }}</AlertTitle>
<AlertDescription> <AlertDescription>{{ m.petty_honest_fish_stir() }}</AlertDescription>
{{ m.petty_honest_fish_stir() }}
</AlertDescription>
</Alert> </Alert>
</template> </template>

View file

@ -1,9 +1,7 @@
<template> <template>
<Alert> <Alert>
<AlertTitle>{{ m.empty_awful_lark_dart() }}</AlertTitle> <AlertTitle>{{ m.empty_awful_lark_dart() }}</AlertTitle>
<AlertDescription> <AlertDescription>{{ m.clean_even_mayfly_tap() }}</AlertDescription>
{{ m.clean_even_mayfly_tap() }}
</AlertDescription>
</Alert> </Alert>
</template> </template>

View file

@ -1,9 +1,7 @@
<template> <template>
<Alert> <Alert>
<AlertTitle>{{ m.steep_suave_fish_snap() }}</AlertTitle> <AlertTitle>{{ m.steep_suave_fish_snap() }}</AlertTitle>
<AlertDescription> <AlertDescription>{{ m.muddy_bland_shark_accept() }}</AlertDescription>
{{ m.muddy_bland_shark_accept() }}
</AlertDescription>
</Alert> </Alert>
</template> </template>

View file

@ -3,23 +3,33 @@ import SquarePattern from "../graphics/square-pattern.vue";
</script> </script>
<template> <template>
<div class="grid min-h-screen place-items-center px-6 py-24 sm:py-32 lg:px-8 fixed inset-0 z-[1000000] bg-dark-900"> <div
<SquarePattern /> class="grid min-h-screen place-items-center px-6 py-24 sm:py-32 lg:px-8 fixed inset-0 z-[1000000] bg-dark-900"
>
<SquarePattern/>
<div class="prose prose-invert max-w-lg"> <div class="prose prose-invert max-w-lg">
<h1 class="mt-4 text-3xl font-bold tracking-tight text-gray-100 sm:text-5xl">JavaScript is disabled <h1
class="mt-4 text-3xl font-bold tracking-tight text-gray-100 sm:text-5xl"
>
JavaScript is disabled
</h1> </h1>
<p class="mt-6 text-base leading-7 text-gray-400"> <p class="mt-6 text-base leading-7 text-gray-400">
This website requires JavaScript to function properly. Please enable JavaScript in your browser This website requires JavaScript to function properly. Please
settings. enable JavaScript in your browser settings.
</p> </p>
<p class="mt-6 text-base leading-7 text-gray-400"> <p class="mt-6 text-base leading-7 text-gray-400">
If you are using a browser that does not support JavaScript, please consider using a modern browser If you are using a browser that does not support JavaScript,
like <a href="https://www.mozilla.org/firefox/new/" class="underline">Firefox</a> or <a please consider using a modern browser like
href="https://www.google.com/chrome/" class="underline">Chrome</a>. <a href="https://www.mozilla.org/firefox/new/" class="underline"
>Firefox</a
>or <a href="https://www.google.com/chrome/" class="underline"
>Chrome</a
>.
</p> </p>
<p class="mt-6 text-base leading-7 text-gray-400"> <p class="mt-6 text-base leading-7 text-gray-400">
This application does not track you, collect user data, use cookies of any kind or send requests to This application does not track you, collect user data, use
servers outside of your account's instance. cookies of any kind or send requests to servers outside of your
account's instance.
</p> </p>
</div> </div>
</div> </div>

View file

@ -1,18 +1,23 @@
<template> <template>
<Card> <Card>
<FormItem class="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2"> <FormItem
class="grid grid-cols-[minmax(0,1fr)_auto] items-center gap-2"
>
<CardHeader class="flex flex-col gap-1.5 p-0"> <CardHeader class="flex flex-col gap-1.5 p-0">
<FormLabel class="font-semibold tracking-tight" :as="CardTitle"> <FormLabel class="font-semibold tracking-tight" :as="CardTitle">
{{ title }} {{ title }}
</FormLabel> </FormLabel>
<FormDescription class="text-xs leading-none" v-if="description"> <FormDescription
class="text-xs leading-none"
v-if="description"
>
{{ description }} {{ description }}
</FormDescription> </FormDescription>
</CardHeader> </CardHeader>
<FormControl> <FormControl>
<slot /> <slot/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage/>
</FormItem> </FormItem>
</Card> </Card>
</template> </template>

View file

@ -1,15 +1,11 @@
<template> <template>
<FormItem> <FormItem>
<FormLabel> <FormLabel>{{ title }}</FormLabel>
{{ title }}
</FormLabel>
<FormControl> <FormControl>
<slot /> <slot/>
</FormControl> </FormControl>
<FormDescription v-if="description"> <FormDescription v-if="description">{{ description }}</FormDescription>
{{ description }} <FormMessage/>
</FormDescription>
<FormMessage />
</FormItem> </FormItem>
</template> </template>

View file

@ -1,6 +1,6 @@
<template> <template>
<Card class="flex items-center justify-center"> <Card class="flex items-center justify-center">
<Loader class="size-6 animate-spin" /> <Loader class="size-6 animate-spin"/>
</Card> </Card>
</template> </template>

View file

@ -1,19 +1,34 @@
<template> <template>
<svg class="absolute inset-x-0 top-0 h-full w-full stroke-primary/[0.07] [mask-image:radial-gradient(100%_100%_at_top_right,var(--primary-foreground),transparent)] pointer-events-none" <svg
aria-hidden="true"> class="absolute inset-x-0 top-0 h-full w-full stroke-primary/[0.07] [mask-image:radial-gradient(100%_100%_at_top_right,var(--primary-foreground),transparent)] pointer-events-none"
aria-hidden="true"
>
<defs> <defs>
<pattern id="983e3e4c-de6d-4c3f-8d64-b9761d1534cc" width="200" height="200" x="50%" y="-1" <pattern
patternUnits="userSpaceOnUse"> id="983e3e4c-de6d-4c3f-8d64-b9761d1534cc"
width="200"
height="200"
x="50%"
y="-1"
patternUnits="userSpaceOnUse"
>
<path d="M.5 200V.5H200" fill="none"></path> <path d="M.5 200V.5H200" fill="none"></path>
</pattern> </pattern>
</defs><svg x="50%" y="-1" class="overflow-visible fill-primary/[0.03]"> </defs>
<path d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z" <svg x="50%" y="-1" class="overflow-visible fill-primary/[0.03]">
stroke-width="0"></path> <path
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
stroke-width="0"
></path>
</svg> </svg>
<rect width="100%" height="100%" stroke-width="0" fill="url(#983e3e4c-de6d-4c3f-8d64-b9761d1534cc)"></rect> <rect
width="100%"
height="100%"
stroke-width="0"
fill="url(#983e3e4c-de6d-4c3f-8d64-b9761d1534cc)"
></rect>
</svg> </svg>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
</script> </script>

View file

@ -1,8 +1,11 @@
<template> <template>
<Card class="grid grid-cols-[auto_1fr] gap-2"> <Card class="grid grid-cols-[auto_1fr] gap-2">
<Avatar :src="instance.thumbnail?.url ?? <Avatar
:src="instance.thumbnail?.url ??
'https://cdn.versia.pub/branding/icon.svg' 'https://cdn.versia.pub/branding/icon.svg'
" :name="instance.title" /> "
:name="instance.title"
/>
<div class="grid text-sm leading-tight *:line-clamp-1"> <div class="grid text-sm leading-tight *:line-clamp-1">
<span class="truncate font-semibold"> <span class="truncate font-semibold">
{{ {{
@ -14,7 +17,6 @@
instance.versia_version || instance.version instance.versia_version || instance.version
}} }}
</span> </span>
</div> </div>
<h1 class="line-clamp-1 text-sm font-semibold col-span-2"> <h1 class="line-clamp-1 text-sm font-semibold col-span-2">
{{ {{

View file

@ -30,7 +30,7 @@ const inputValue = ref<string>("");
<template> <template>
<Dialog> <Dialog>
<DialogTrigger :as-child="true"> <DialogTrigger :as-child="true">
<slot /> <slot/>
</DialogTrigger> </DialogTrigger>
<DialogContent class="sm:max-w-[425px]"> <DialogContent class="sm:max-w-[425px]">
<DialogHeader> <DialogHeader>
@ -40,17 +40,35 @@ const inputValue = ref<string>("");
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div v-if="modalOptions.inputType === 'text'" class="grid gap-4 py-4"> <div
v-if="modalOptions.inputType === 'text'"
class="grid gap-4 py-4"
>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="confirmInput" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label> <Label for="confirmInput" class="text-right">
<Input id="confirmInput" v-model="inputValue" class="col-span-3" /> {{ m.mean_mean_badger_inspire() }}
</Label>
<Input
id="confirmInput"
v-model="inputValue"
class="col-span-3"
/>
</div> </div>
</div> </div>
<div v-else-if="modalOptions.inputType === 'textarea'" class="grid gap-4 py-4"> <div
v-else-if="modalOptions.inputType === 'textarea'"
class="grid gap-4 py-4"
>
<div class="grid grid-cols-4 items-center gap-4"> <div class="grid grid-cols-4 items-center gap-4">
<Label for="confirmTextarea" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label> <Label for="confirmTextarea" class="text-right">
<Textarea id="confirmTextarea" v-model="inputValue" class="col-span-3" /> {{ m.mean_mean_badger_inspire() }}
</Label>
<Textarea
id="confirmTextarea"
v-model="inputValue"
class="col-span-3"
/>
</div> </div>
</div> </div>
@ -58,10 +76,12 @@ const inputValue = ref<string>("");
<Button variant="outline" @click="() => emit('cancel')"> <Button variant="outline" @click="() => emit('cancel')">
{{ modalOptions.cancelText }} {{ modalOptions.cancelText }}
</Button> </Button>
<Button @click="() => emit('confirm', { <Button
@click="() => emit('confirm', {
confirmed: true, confirmed: true,
value: inputValue, value: inputValue,
})"> })"
>
{{ modalOptions.confirmText }} {{ modalOptions.confirmText }}
</Button> </Button>
</DialogFooter> </DialogFooter>

View file

@ -75,7 +75,11 @@ const isValid = ref(false);
</script> </script>
<template> <template>
<AlertDialog :key="String(isOpen)" :open="isOpen" @update:open="isOpen = false"> <AlertDialog
:key="String(isOpen)"
:open="isOpen"
@update:open="isOpen = false"
>
<AlertDialogContent class="sm:max-w-[425px] flex flex-col"> <AlertDialogContent class="sm:max-w-[425px] flex flex-col">
<AlertDialogHeader> <AlertDialogHeader>
<AlertDialogTitle>{{ modalOptions.title }}</AlertDialogTitle> <AlertDialogTitle>{{ modalOptions.title }}</AlertDialogTitle>
@ -84,11 +88,23 @@ const isValid = ref(false);
</AlertDialogDescription> </AlertDialogDescription>
</AlertDialogHeader> </AlertDialogHeader>
<Input v-if="modalOptions.inputType === 'text'" v-model="inputValue" /> <Input
v-if="modalOptions.inputType === 'text'"
v-model="inputValue"
/>
<UrlInput v-if="modalOptions.inputType === 'url'" v-model="inputValue" placeholder="google.com" v-model:is-valid="isValid" /> <UrlInput
v-if="modalOptions.inputType === 'url'"
v-model="inputValue"
placeholder="google.com"
v-model:is-valid="isValid"
/>
<Textarea v-else-if="modalOptions.inputType === 'textarea'" v-model="inputValue" rows="6" /> <Textarea
v-else-if="modalOptions.inputType === 'textarea'"
v-model="inputValue"
rows="6"
/>
<AlertDialogFooter class="w-full"> <AlertDialogFooter class="w-full">
<AlertDialogCancel :as-child="true"> <AlertDialogCancel :as-child="true">
@ -97,7 +113,10 @@ const isValid = ref(false);
</Button> </Button>
</AlertDialogCancel> </AlertDialogCancel>
<AlertDialogAction :as-child="true"> <AlertDialogAction :as-child="true">
<Button @click="handleConfirm" :disabled="!isValid && modalOptions.inputType === 'url'"> <Button
@click="handleConfirm"
:disabled="!isValid && modalOptions.inputType === 'url'"
>
{{ modalOptions.confirmText }} {{ modalOptions.confirmText }}
</Button> </Button>
</AlertDialogAction> </AlertDialogAction>

View file

@ -1,6 +1,6 @@
<template> <template>
<DrawerContent class="flex flex-col gap-2 px-4 mb-4 [&>:nth-child(2)]:mt-4"> <DrawerContent class="flex flex-col gap-2 px-4 mb-4 [&>:nth-child(2)]:mt-4">
<slot /> <slot/>
</DrawerContent> </DrawerContent>
</template> </template>

View file

@ -3,7 +3,7 @@
class="fixed md:hidden bottom-0 inset-x-0 border-t h-16 bg-background z-10 flex items-center justify-around *:h-full *:w-full gap-6 px-4 py-2 [&>a>svg]:size-5 [&>button>svg]:size-5" class="fixed md:hidden bottom-0 inset-x-0 border-t h-16 bg-background z-10 flex items-center justify-around *:h-full *:w-full gap-6 px-4 py-2 [&>a>svg]:size-5 [&>button>svg]:size-5"
> >
<Button :as="NuxtLink" href="/" variant="ghost" size="icon"> <Button :as="NuxtLink" href="/" variant="ghost" size="icon">
<Home /> <Home/>
</Button> </Button>
<Button <Button
:as="NuxtLink" :as="NuxtLink"
@ -11,10 +11,10 @@
variant="ghost" variant="ghost"
size="icon" size="icon"
> >
<Bell /> <Bell/>
</Button> </Button>
<Button variant="ghost" size="icon"> <Button variant="ghost" size="icon">
<User /> <User/>
</Button> </Button>
<Button <Button
variant="default" variant="default"
@ -22,7 +22,7 @@
:title="m.salty_aloof_turkey_nudge()" :title="m.salty_aloof_turkey_nudge()"
@click="useEvent('composer:open')" @click="useEvent('composer:open')"
> >
<Pen /> <Pen/>
</Button> </Button>
</div> </div>
</template> </template>

View file

@ -1,9 +1,15 @@
<template> <template>
<Tabs v-model:model-value="current"> <Tabs v-model:model-value="current">
<TabsList> <TabsList>
<TabsTrigger v-for="timeline in timelines.filter( <TabsTrigger
v-for="timeline in timelines.filter(
i => i.requiresLogin ? authStore.isSignedIn : true, i => i.requiresLogin ? authStore.isSignedIn : true,
)" :key="timeline.value" :value="timeline.value" :as="NuxtLink" :href="timeline.url"> )"
:key="timeline.value"
:value="timeline.value"
:as="NuxtLink"
:href="timeline.url"
>
{{ timeline.name }} {{ timeline.name }}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>

View file

@ -1,7 +1,7 @@
<template> <template>
<Button variant="ghost" class="max-w-14 w-full" size="sm"> <Button variant="ghost" class="max-w-14 w-full" size="sm">
<component :is="icon" class="size-4" /> <component :is="icon" class="size-4"/>
<slot /> <slot/>
</Button> </Button>
</template> </template>

View file

@ -1,20 +1,58 @@
<template> <template>
<div class="flex flex-row w-full max-w-sm items-stretch justify-between"> <div class="flex flex-row w-full max-w-sm items-stretch justify-between">
<ActionButton :icon="Reply" @click="emit('reply')" :title="m.drab_tense_turtle_comfort()" :disabled="!authStore.isSignedIn"> <ActionButton
:icon="Reply"
@click="emit('reply')"
:title="m.drab_tense_turtle_comfort()"
:disabled="!authStore.isSignedIn"
>
{{ numberFormat(replyCount) }} {{ numberFormat(replyCount) }}
</ActionButton> </ActionButton>
<ActionButton :icon="Heart" @click="liked ? unlike() : like()" :title="liked ? m.vexed_fluffy_clownfish_dance() : m.royal_close_samuel_scold()" :disabled="!authStore.isSignedIn" :class="liked && '*:fill-red-600 *:text-red-600'"> <ActionButton
:icon="Heart"
@click="liked ? unlike() : like()"
:title="liked ? m.vexed_fluffy_clownfish_dance() : m.royal_close_samuel_scold()"
:disabled="!authStore.isSignedIn"
:class="liked && '*:fill-red-600 *:text-red-600'"
>
{{ numberFormat(likeCount) }} {{ numberFormat(likeCount) }}
</ActionButton> </ActionButton>
<ActionButton :icon="Repeat" @click="reblogged ? unreblog() : reblog()" :title="reblogged ? m.lime_neat_ox_stab() : m.aware_helpful_marlin_drop()" :disabled="!authStore.isSignedIn" :class="reblogged && '*:text-green-600'"> <ActionButton
:icon="Repeat"
@click="reblogged ? unreblog() : reblog()"
:title="reblogged ? m.lime_neat_ox_stab() : m.aware_helpful_marlin_drop()"
:disabled="!authStore.isSignedIn"
:class="reblogged && '*:text-green-600'"
>
{{ numberFormat(reblogCount) }} {{ numberFormat(reblogCount) }}
</ActionButton> </ActionButton>
<ActionButton :icon="Quote" @click="emit('quote')" :title="m.true_shy_jackal_drip()" :disabled="!authStore.isSignedIn" /> <ActionButton
:icon="Quote"
@click="emit('quote')"
:title="m.true_shy_jackal_drip()"
:disabled="!authStore.isSignedIn"
/>
<Picker @pick="react"> <Picker @pick="react">
<ActionButton :icon="Smile" :title="m.bald_cool_kangaroo_jump()" :disabled="!authStore.isSignedIn" /> <ActionButton
:icon="Smile"
:title="m.bald_cool_kangaroo_jump()"
:disabled="!authStore.isSignedIn"
/>
</Picker> </Picker>
<Menu :api-note-string="apiNoteString" :url="url" :remote-url="remoteUrl" :is-remote="isRemote" :author-id="authorId" @edit="emit('edit')" :note-id="noteId" @delete="emit('delete')"> <Menu
<ActionButton :icon="Ellipsis" :title="m.busy_merry_cowfish_absorb()" /> :api-note-string="apiNoteString"
:url="url"
:remote-url="remoteUrl"
:is-remote="isRemote"
:author-id="authorId"
@edit="emit('edit')"
:note-id="noteId"
@delete="emit('delete')"
>
<ActionButton
:icon="Ellipsis"
:title="m.busy_merry_cowfish_absorb()"
/>
</Menu> </Menu>
</div> </div>
</template> </template>

View file

@ -1,8 +1,17 @@
<template> <template>
<ImageAttachment v-if="attachment.type === 'image'" :attachment="attachment" /> <ImageAttachment
<VideoAttachment v-else-if="attachment.type === 'video' || attachment.type === 'gifv'" :attachment="attachment" /> v-if="attachment.type === 'image'"
<AudioAttachment v-else-if="attachment.type === 'audio'" :attachment="attachment" /> :attachment="attachment"
<FileAttachment v-else :attachment="attachment" /> />
<VideoAttachment
v-else-if="attachment.type === 'video' || attachment.type === 'gifv'"
:attachment="attachment"
/>
<AudioAttachment
v-else-if="attachment.type === 'audio'"
:attachment="attachment"
/>
<FileAttachment v-else :attachment="attachment"/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,7 +1,13 @@
<template> <template>
<!-- [&:has(>:last-child:nth-child(1))] means "when this element has 1 child" --> <!-- [&:has(>:last-child:nth-child(1))] means "when this element has 1 child" -->
<div class="grid gap-4 grid-cols-2 *:max-h-56 [&:has(>:last-child:nth-child(1))]:grid-cols-1 sm:[&:has(>:last-child:nth-child(1))>*]:max-h-72"> <div
<Attachment v-for="attachment in attachments" :key="attachment.id" :attachment="attachment" /> class="grid gap-4 grid-cols-2 *:max-h-56 [&:has(>:last-child:nth-child(1))]:grid-cols-1 sm:[&:has(>:last-child:nth-child(1))>*]:max-h-72"
>
<Attachment
v-for="attachment in attachments"
:key="attachment.id"
:attachment="attachment"
/>
</div> </div>
</template> </template>

View file

@ -1,16 +1,22 @@
<template> <template>
<Dialog> <Dialog>
<Card class="w-full h-full overflow-hidden relative p-0 *:first:w-full *:first:h-full *:first:object-contain *:first:bg-muted/20"> <Card
class="w-full h-full overflow-hidden relative p-0 *:first:w-full *:first:h-full *:first:object-contain *:first:bg-muted/20"
>
<DialogTrigger v-if="lightbox" :as-child="true"> <DialogTrigger v-if="lightbox" :as-child="true">
<slot /> <slot/>
</DialogTrigger> </DialogTrigger>
<slot v-else /> <slot v-else/>
<!-- Alt text viewer --> <!-- Alt text viewer -->
<Popover v-if="attachment.description"> <Popover v-if="attachment.description">
<div class="absolute top-0 right-0 p-2"> <div class="absolute top-0 right-0 p-2">
<PopoverTrigger :as-child="true"> <PopoverTrigger :as-child="true">
<Button variant="outline" size="icon" title="View alt text"> <Button
<Captions /> variant="outline"
size="icon"
title="View alt text"
>
<Captions/>
</Button> </Button>
</PopoverTrigger> </PopoverTrigger>
</div> </div>
@ -19,26 +25,42 @@
</PopoverContent> </PopoverContent>
</Popover> </Popover>
</Card> </Card>
<DialogContent :hide-close="true" <DialogContent
class="duration-200 bg-transparent border-none overflow-hidden !animate-none gap-6 w-screen h-screen !max-w-none"> :hide-close="true"
class="duration-200 bg-transparent border-none overflow-hidden !animate-none gap-6 w-screen h-screen !max-w-none"
>
<div class="grid grid-rows-[auto_1fr_auto]"> <div class="grid grid-rows-[auto_1fr_auto]">
<div class="flex flex-row gap-2 w-full"> <div class="flex flex-row gap-2 w-full">
<DialogTitle class="sr-only">{{ attachment.type }}</DialogTitle> <DialogTitle class="sr-only">
<Button as="a" :href="attachment?.url" target="_blank" :download="true" variant="outline" size="icon" {{ attachment.type }}
class="ml-auto"> </DialogTitle>
<Download /> <Button
as="a"
:href="attachment?.url"
target="_blank"
:download="true"
variant="outline"
size="icon"
class="ml-auto"
>
<Download/>
</Button> </Button>
<DialogClose :as-child="true"> <DialogClose :as-child="true">
<Button variant="outline" size="icon"> <Button variant="outline" size="icon">
<X /> <X/>
</Button> </Button>
</DialogClose> </DialogClose>
</div> </div>
<div class="flex items-center justify-center overflow-hidden *:max-h-[80vh] *:max-w-[80vw] *:w-full *:h-full *:object-contain"> <div
<slot /> class="flex items-center justify-center overflow-hidden *:max-h-[80vh] *:max-w-[80vw] *:w-full *:h-full *:object-contain"
>
<slot/>
</div> </div>
<DialogDescription class="flex items-center justify-center"> <DialogDescription class="flex items-center justify-center">
<Card v-if="attachment.description" class="max-w-md max-h-48 overflow-auto text-sm"> <Card
v-if="attachment.description"
class="max-w-md max-h-48 overflow-auto text-sm"
>
<p>{{ attachment.description }}</p> <p>{{ attachment.description }}</p>
</Card> </Card>
</DialogDescription> </DialogDescription>

View file

@ -1,13 +1,17 @@
<template> <template>
<Base :attachment="attachment"> <AttachmentBase :attachment="attachment">
<audio :src="attachment.url" :alt="attachment.description ?? undefined" controls /> <audio
</Base> :src="attachment.url"
:alt="attachment.description ?? undefined"
controls
/>
</AttachmentBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Attachment } from "@versia/client/schemas"; import type { Attachment } from "@versia/client/schemas";
import type { z } from "zod"; import type { z } from "zod";
import Base from "./base.vue"; import AttachmentBase from "./attachment-base.vue";
const { attachment } = defineProps<{ const { attachment } = defineProps<{
attachment: z.infer<typeof Attachment>; attachment: z.infer<typeof Attachment>;

View file

@ -1,17 +1,19 @@
<template> <template>
<Base :attachment="attachment" lightbox> <AttachmentBase :attachment="attachment" lightbox>
<div class="flex flex-col items-center justify-center min-h-48 text-sm gap-2"> <div
<File class="size-12" /> class="flex flex-col items-center justify-center min-h-48 text-sm gap-2"
>
<File class="size-12"/>
<span>File attachment</span> <span>File attachment</span>
</div> </div>
</Base> </AttachmentBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Attachment } from "@versia/client/schemas"; import type { Attachment } from "@versia/client/schemas";
import { File } from "lucide-vue-next"; import { File } from "lucide-vue-next";
import type { z } from "zod"; import type { z } from "zod";
import Base from "./base.vue"; import AttachmentBase from "./attachment-base.vue";
const { attachment } = defineProps<{ const { attachment } = defineProps<{
attachment: z.infer<typeof Attachment>; attachment: z.infer<typeof Attachment>;

View file

@ -1,13 +1,13 @@
<template> <template>
<Base :attachment="attachment" lightbox> <AttachmentBase :attachment="attachment" lightbox>
<img :src="attachment.url" :alt="attachment.description ?? undefined" /> <img :src="attachment.url" :alt="attachment.description ?? undefined">
</Base> </AttachmentBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Attachment } from "@versia/client/schemas"; import type { Attachment } from "@versia/client/schemas";
import type { z } from "zod"; import type { z } from "zod";
import Base from "./base.vue"; import AttachmentBase from "./attachment-base.vue";
const { attachment } = defineProps<{ const { attachment } = defineProps<{
attachment: z.infer<typeof Attachment>; attachment: z.infer<typeof Attachment>;

View file

@ -1,13 +1,19 @@
<template> <template>
<Base :attachment="attachment"> <AttachmentBase :attachment="attachment">
<video :src="attachment.url" :alt="attachment.description ?? undefined" controls /> <video
</Base> :src="attachment.url"
:alt="attachment.description ?? undefined"
controls
>
Your browser does not support the video tag.
</video>
</AttachmentBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import type { Attachment } from "@versia/client/schemas"; import type { Attachment } from "@versia/client/schemas";
import type { z } from "zod"; import type { z } from "zod";
import Base from "./base.vue"; import AttachmentBase from "./attachment-base.vue";
const { attachment } = defineProps<{ const { attachment } = defineProps<{
attachment: z.infer<typeof Attachment>; attachment: z.infer<typeof Attachment>;

View file

@ -3,10 +3,18 @@
<p class="text-sm leading-6 wrap-anywhere"> <p class="text-sm leading-6 wrap-anywhere">
{{ contentWarning || m.sour_seemly_bird_hike() }} {{ contentWarning || m.sour_seemly_bird_hike() }}
</p> </p>
<Button @click="hidden = !hidden" variant="outline" size="sm" class="col-span-2"> <Button
@click="hidden = !hidden"
variant="outline"
size="sm"
class="col-span-2"
>
{{ hidden ? m.bald_direct_turtle_win() : {{ hidden ? m.bald_direct_turtle_win() :
m.known_flaky_cockroach_dash() }} {{ characterCount > 0 ? ` (${characterCount} characters` : "" }}{{ m.known_flaky_cockroach_dash() }}
attachmentCount > 0 ? `${characterCount > 0 ? " · " : " ("}${attachmentCount} file(s)` : "" }}{{ (characterCount > 0 || attachmentCount > 0) ? ")" : "" }} {{ characterCount > 0 ? ` (${characterCount} characters` : "" }}
{{
attachmentCount > 0 ? `${characterCount > 0 ? " · " : " ("}${attachmentCount} file(s)` : "" }}
{{ (characterCount > 0 || attachmentCount > 0) ? ")" : "" }}
</Button> </Button>
</div> </div>
</template> </template>

View file

@ -1,14 +1,28 @@
<template> <template>
<ContentWarning v-if="(sensitive || contentWarning) && preferences.show_content_warning" :content-warning="contentWarning" :character-count="characterCount ?? 0" :attachment-count="attachments.length" v-model="hidden" /> <ContentWarning
v-if="(sensitive || contentWarning) && preferences.show_content_warning"
:content-warning="contentWarning"
:character-count="characterCount ?? 0"
:attachment-count="attachments.length"
v-model="hidden"
/>
<OverflowGuard v-if="content" :character-count="characterCount" :class="(hidden && preferences.show_content_warning) && 'hidden'"> <OverflowGuard
v-if="content"
:character-count="characterCount"
:class="(hidden && preferences.show_content_warning) && 'hidden'"
>
<Prose v-html="content" v-render-emojis="emojis"></Prose> <Prose v-html="content" v-render-emojis="emojis"></Prose>
</OverflowGuard> </OverflowGuard>
<Attachments v-if="attachments.length > 0" :attachments="attachments" :class="(hidden && preferences.show_content_warning) && 'hidden'" /> <Attachments
v-if="attachments.length > 0"
:attachments="attachments"
:class="(hidden && preferences.show_content_warning) && 'hidden'"
/>
<div v-if="quote" class="mt-4 rounded border overflow-hidden"> <div v-if="quote" class="mt-4 rounded border overflow-hidden">
<Note :note="quote" :hide-actions="true" :small-layout="true" /> <Note :note="quote" :hide-actions="true" :small-layout="true"/>
</div> </div>
</template> </template>

View file

@ -1,36 +1,61 @@
<template> <template>
<div class="rounded grid grid-cols-[auto_1fr_auto] items-center gap-3"> <div class="rounded grid grid-cols-[auto_1fr_auto] items-center gap-3">
<HoverCard v-model:open="popupOpen" @update:open="() => { <HoverCard
v-model:open="popupOpen"
@update:open="() => {
if (!preferences.popup_avatar_hover) { if (!preferences.popup_avatar_hover) {
popupOpen = false; popupOpen = false;
} }
}" :open-delay="2000"> }"
:open-delay="2000"
>
<HoverCardTrigger :as-child="true"> <HoverCardTrigger :as-child="true">
<NuxtLink :href="urlAsPath" :class="cn('relative size-12', smallLayout && 'size-8')"> <NuxtLink
<Avatar :class="cn('size-12 border border-card', smallLayout && 'size-8')" :src="author.avatar" :href="urlAsPath"
:name="author.display_name" /> :class="cn('relative size-12', smallLayout && 'size-8')"
<Avatar v-if="cornerAvatar" class="size-6 border absolute -bottom-1 -right-1" :src="cornerAvatar" /> >
<Avatar
:class="cn('size-12 border border-card', smallLayout && 'size-8')"
:src="author.avatar"
:name="author.display_name"
/>
<Avatar
v-if="cornerAvatar"
class="size-6 border absolute -bottom-1 -right-1"
:src="cornerAvatar"
/>
</NuxtLink> </NuxtLink>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent class="w-96"> <HoverCardContent class="w-96">
<SmallCard :account="author" /> <SmallCard :account="author"/>
</HoverCardContent> </HoverCardContent>
</HoverCard> </HoverCard>
<Col <Column :class="smallLayout && 'text-sm'">
:class="smallLayout && 'text-sm'"> <Text class="font-semibold" v-render-emojis="author.emojis">
<Text class="font-semibold" v-render-emojis="author.emojis">{{ {{
author.display_name author.display_name
}}</Text> }}
</Text>
<div class="-mt-1"> <div class="-mt-1">
<Address as="span" :username="username" :domain="instance" /> <Address as="span" :username="username" :domain="instance"/>
&middot; &middot;
<Text as="span" muted class="ml-auto tracking-normal" :title="fullTime">{{ timeAgo }}</Text> <Text
as="span"
muted
class="ml-auto tracking-normal"
:title="fullTime"
>
{{ timeAgo }}
</Text>
</div> </div>
</Col> </Column>
<div v-if="!smallLayout"> <div v-if="!smallLayout">
<NuxtLink :href="noteUrlAsPath" class="text-xs text-muted-foreground" <NuxtLink
:title="visibilities[visibility].text"> :href="noteUrlAsPath"
<component :is="visibilities[visibility].icon" class="size-4" /> class="text-xs text-muted-foreground"
:title="visibilities[visibility].text"
>
<component :is="visibilities[visibility].icon" class="size-4"/>
</NuxtLink> </NuxtLink>
</div> </div>
</div> </div>
@ -49,7 +74,7 @@ import { getLocale } from "~~/paraglide/runtime";
import Address from "../profiles/address.vue"; import Address from "../profiles/address.vue";
import Avatar from "../profiles/avatar.vue"; import Avatar from "../profiles/avatar.vue";
import SmallCard from "../profiles/small-card.vue"; import SmallCard from "../profiles/small-card.vue";
import Col from "../typography/layout/col.vue"; import Column from "../typography/layout/col.vue";
import Text from "../typography/text.vue"; import Text from "../typography/text.vue";
import { import {
HoverCard, HoverCard,

View file

@ -79,57 +79,71 @@ const _delete = async () => {
<template> <template>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<slot /> <slot/>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="min-w-56"> <DropdownMenuContent class="min-w-56">
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem v-if="authorIsMe" as="button" @click="emit('edit')"> <DropdownMenuItem
<Pencil /> v-if="authorIsMe"
as="button"
@click="emit('edit')"
>
<Pencil/>
{{ m.front_lime_grizzly_persist() }} {{ m.front_lime_grizzly_persist() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(apiNoteString)"> <DropdownMenuItem as="button" @click="copyText(apiNoteString)">
<Code /> <Code/>
{{ m.yummy_moving_scallop_sail() }} {{ m.yummy_moving_scallop_sail() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(noteId)"> <DropdownMenuItem as="button" @click="copyText(noteId)">
<Hash /> <Hash/>
{{ m.sunny_zany_jellyfish_pop() }} {{ m.sunny_zany_jellyfish_pop() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator/>
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem as="button" @click="copyText(url)"> <DropdownMenuItem as="button" @click="copyText(url)">
<Link /> <Link/>
{{ m.ago_new_pelican_drip() }} {{ m.ago_new_pelican_drip() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" v-if="isRemote && remoteUrl" @click="copyText(remoteUrl)"> <DropdownMenuItem
<Link /> as="button"
v-if="isRemote && remoteUrl"
@click="copyText(remoteUrl)"
>
<Link/>
{{ m.solid_witty_zebra_walk() }} {{ m.solid_witty_zebra_walk() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="remoteUrl"> <DropdownMenuItem
<ExternalLink /> as="a"
v-if="isRemote"
target="_blank"
rel="noopener noreferrer"
:href="remoteUrl"
>
<ExternalLink/>
{{ m.active_trite_lark_inspire() }} {{ m.active_trite_lark_inspire() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="authorIsMe" /> <DropdownMenuSeparator v-if="authorIsMe"/>
<DropdownMenuGroup v-if="authorIsMe"> <DropdownMenuGroup v-if="authorIsMe">
<DropdownMenuItem as="button" :disabled="true"> <DropdownMenuItem as="button" :disabled="true">
<Delete /> <Delete/>
{{ m.real_green_clownfish_pet() }} {{ m.real_green_clownfish_pet() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="_delete"> <DropdownMenuItem as="button" @click="_delete">
<Trash /> <Trash/>
{{ m.tense_quick_cod_favor() }} {{ m.tense_quick_cod_favor() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="authStore.isSignedIn && !authorIsMe" /> <DropdownMenuSeparator v-if="authStore.isSignedIn && !authorIsMe"/>
<DropdownMenuGroup v-if="authStore.isSignedIn && !authorIsMe"> <DropdownMenuGroup v-if="authStore.isSignedIn && !authorIsMe">
<DropdownMenuItem as="button" :disabled="true"> <DropdownMenuItem as="button" :disabled="true">
<Flag /> <Flag/>
{{ m.great_few_jaguar_rise() }} {{ m.great_few_jaguar_rise() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="blockUser(authorId)"> <DropdownMenuItem as="button" @click="blockUser(authorId)">
<Ban /> <Ban/>
{{ m.misty_soft_sparrow_vent() }} {{ m.misty_soft_sparrow_vent() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>

View file

@ -50,7 +50,12 @@
:sensitive="noteToUse.sensitive" :sensitive="noteToUse.sensitive"
:content-warning="noteToUse.spoiler_text" :content-warning="noteToUse.spoiler_text"
/> />
<Reactions v-if="noteToUse.reactions && noteToUse.reactions.length > 0" :reactions="noteToUse.reactions" :emojis="noteToUse.emojis" :status-id="noteToUse.id" /> <Reactions
v-if="noteToUse.reactions && noteToUse.reactions.length > 0"
:reactions="noteToUse.reactions"
:emojis="noteToUse.emojis"
:status-id="noteToUse.id"
/>
</CardContent> </CardContent>
<CardFooter v-if="!hideActions"> <CardFooter v-if="!hideActions">
<Actions <Actions

View file

@ -1,18 +1,29 @@
<template> <template>
<div ref="container" class="overflow-y-hidden relative duration-200" :style="{ <div
ref="container"
class="overflow-y-hidden relative duration-200"
:style="{
maxHeight: collapsed ? '18rem' : `${container?.scrollHeight}px`, maxHeight: collapsed ? '18rem' : `${container?.scrollHeight}px`,
}"> }"
<slot /> >
<div v-if="isOverflowing && collapsed" <slot/>
class="absolute inset-x-0 bottom-0 h-36 bg-gradient-to-t from-black/5 to-transparent rounded-b"></div> <div
<Button v-if="isOverflowing" @click="collapsed = !collapsed" v-if="isOverflowing && collapsed"
class="absolute bottom-2 right-1/2 translate-x-1/2">{{ class="absolute inset-x-0 bottom-0 h-36 bg-gradient-to-t from-black/5 to-transparent rounded-b"
></div>
<Button
v-if="isOverflowing"
@click="collapsed = !collapsed"
class="absolute bottom-2 right-1/2 translate-x-1/2"
>
{{
collapsed collapsed
? `${m.lazy_honest_mammoth_bump()}${formattedCharacterCount ? `${m.dark_spare_goldfish_charm({ ? `${m.lazy_honest_mammoth_bump()}${formattedCharacterCount ? `${m.dark_spare_goldfish_charm({
count: formattedCharacterCount, count: formattedCharacterCount,
})}` : ""}` })}` : ""}`
: m.that_misty_mule_arrive() : m.that_misty_mule_arrive()
}}</Button> }}
</Button>
</div> </div>
</template> </template>

View file

@ -1,9 +1,11 @@
<template> <template>
<div :class="[ <div
:class="[
'prose prose-sm block relative dark:prose-invert duration-200 !max-w-full break-words prose-a:no-underline hover:prose-a:underline', 'prose prose-sm block relative dark:prose-invert duration-200 !max-w-full break-words prose-a:no-underline hover:prose-a:underline',
$style.content, $style.content,
]"> ]"
<slot /> >
<slot/>
</div> </div>
</template> </template>

View file

@ -1,6 +1,12 @@
<template> <template>
<div class="flex flex-row gap-2 flex-wrap"> <div class="flex flex-row gap-2 flex-wrap">
<Reaction v-for="reaction in reactions" :key="reaction.name" :reaction="reaction" :emoji="emojis.find(e => `:${e.shortcode}:` === reaction.name)" :status-id="statusId" /> <Reaction
v-for="reaction in reactions"
:key="reaction.name"
:reaction="reaction"
:emoji="emojis.find(e => `:${e.shortcode}:` === reaction.name)"
:status-id="statusId"
/>
</div> </div>
</template> </template>

View file

@ -1,8 +1,6 @@
<template> <template>
<div class="sticky top-2 z-10 flex items-center justify-center p-2"> <div class="sticky top-2 z-10 flex items-center justify-center p-2">
<Badge variant="secondary"> <Badge variant="secondary">{{ categoryName }}</Badge>
{{ categoryName }}
</Badge>
</div> </div>
</template> </template>

View file

@ -1,8 +1,17 @@
<template> <template>
<div class="p-2 text-sm font-semibold border-0 rounded-none text-center flex flex-row items-center gap-2 truncate"> <div
<img v-if="(emoji as InferredEmoji)?.url" :src="(emoji as InferredEmoji)?.url" class="p-2 text-sm font-semibold border-0 rounded-none text-center flex flex-row items-center gap-2 truncate"
:alt="(emoji as InferredEmoji)?.shortcode" class="h-8 align-middle inline not-prose" /> >
<span v-else-if="(emoji as UnicodeEmoji)?.unicode" class="text-2xl align-middle inline not-prose"> <img
v-if="(emoji as InferredEmoji)?.url"
:src="(emoji as InferredEmoji)?.url"
:alt="(emoji as InferredEmoji)?.shortcode"
class="h-8 align-middle inline not-prose"
>
<span
v-else-if="(emoji as UnicodeEmoji)?.unicode"
class="text-2xl align-middle inline not-prose"
>
{{ (emoji as UnicodeEmoji)?.unicode }} {{ (emoji as UnicodeEmoji)?.unicode }}
</span> </span>
{{ (emoji as InferredEmoji)?.shortcode || (emoji as UnicodeEmoji)?.shortcode }} {{ (emoji as InferredEmoji)?.shortcode || (emoji as UnicodeEmoji)?.shortcode }}

View file

@ -1,9 +1,22 @@
<template> <template>
<Button @focus="() => emit('select', emoji)" @mouseenter="() => emit('select', emoji)" @click="() => emit('pick', emoji)" size="icon" variant="ghost" <Button
class="size-12"> @focus="() => emit('select', emoji)"
<img v-if="(emoji as InferredEmoji).url" :src="(emoji as InferredEmoji).url" @mouseenter="() => emit('select', emoji)"
:alt="(emoji as InferredEmoji).shortcode" class="h-8 align-middle inline not-prose" /> @click="() => emit('pick', emoji)"
<span v-else-if="(emoji as UnicodeEmoji).unicode" class="text-2xl align-middle inline not-prose"> size="icon"
variant="ghost"
class="size-12"
>
<img
v-if="(emoji as InferredEmoji).url"
:src="(emoji as InferredEmoji).url"
:alt="(emoji as InferredEmoji).shortcode"
class="h-8 align-middle inline not-prose"
>
<span
v-else-if="(emoji as UnicodeEmoji).unicode"
class="text-2xl align-middle inline not-prose"
>
{{ (emoji as UnicodeEmoji).unicode }} {{ (emoji as UnicodeEmoji).unicode }}
</span> </span>
</Button> </Button>

View file

@ -1,31 +1,57 @@
<template> <template>
<Popover v-model:open="open"> <Popover v-model:open="open">
<PopoverTrigger as-child> <PopoverTrigger as-child>
<slot /> <slot/>
</PopoverTrigger> </PopoverTrigger>
<PopoverContent class="p-0 w-fit"> <PopoverContent class="p-0 w-fit">
<div class="grid-cols-[minmax(0,1fr)_auto] gap-0 grid divide-x *:h-112 *:overflow-y-auto" <div
orientation="vertical"> class="grid-cols-[minmax(0,1fr)_auto] gap-0 grid divide-x *:h-112 *:overflow-y-auto"
<div class="grid grid-rows-[auto_minmax(0,1fr)_auto] gap-0" ref="emojiContainer"> orientation="vertical"
>
<div
class="grid grid-rows-[auto_minmax(0,1fr)_auto] gap-0"
ref="emojiContainer"
>
<div class="p-2"> <div class="p-2">
<Input placeholder="Search" v-model="filter" /> <Input placeholder="Search" v-model="filter"/>
</div> </div>
<VList :data="virtualizedItems" #default="{ item }" class="relative" :style="{ <VList
:data="virtualizedItems"
#default="{ item }"
class="relative"
:style="{
width: `calc(var(--spacing) * ((12 * ${EMOJI_PER_ROW}) + (${EMOJI_PER_ROW} - 1)) + var(--spacing) * 4)`, width: `calc(var(--spacing) * ((12 * ${EMOJI_PER_ROW}) + (${EMOJI_PER_ROW} - 1)) + var(--spacing) * 4)`,
}"> }"
<CategoryHeader :key="item.headerId" v-if="item.type === 'header'" :category-name="item.name" /> >
<div v-else-if="item.type === 'emoji-row'" :key="item.rowId" class="flex gap-1 p-2"> <CategoryHeader
<Emoji v-for="emoji in item.emojis" :key="getEmojiKey(emoji)" :emoji="emoji" :key="item.headerId"
@select="(e) => selectedEmoji = e" @pick="e => { v-if="item.type === 'header'"
:category-name="item.name"
/>
<div
v-else-if="item.type === 'emoji-row'"
:key="item.rowId"
class="flex gap-1 p-2"
>
<Emoji
v-for="emoji in item.emojis"
:key="getEmojiKey(emoji)"
:emoji="emoji"
@select="(e) => selectedEmoji = e"
@pick="e => {
emit('pick', e); open = false; emit('pick', e); open = false;
}" /> }"
/>
</div> </div>
</VList> </VList>
<EmojiDisplay :emoji="selectedEmoji" :style="{ <EmojiDisplay
:emoji="selectedEmoji"
:style="{
width: `calc(var(--spacing) * ((12 * ${EMOJI_PER_ROW}) + (${EMOJI_PER_ROW} - 1)) + var(--spacing) * 4)`, width: `calc(var(--spacing) * ((12 * ${EMOJI_PER_ROW}) + (${EMOJI_PER_ROW} - 1)) + var(--spacing) * 4)`,
}" /> }"
/>
</div> </div>
<Sidebar :categories="categories" @select="scrollToCategory" /> <Sidebar :categories="categories" @select="scrollToCategory"/>
</div> </div>
</PopoverContent> </PopoverContent>
</Popover> </Popover>

View file

@ -1,8 +1,23 @@
<template> <template>
<div class="grid gap-1 bg-transparent p-2"> <div class="grid gap-1 bg-transparent p-2">
<Button v-for="category in categories" :key="category.name" size="icon" variant="ghost" @click="() => emit('select', category)"> <Button
<component v-if="category.groupId" :is="emojiGroupIconMap[category.groupId]" class="size-6 text-primary" /> v-for="category in categories"
<img v-else-if="category.src" :src="category.src" class="size-6 align-middle inline not-prose" role="presentation" /> :key="category.name"
size="icon"
variant="ghost"
@click="() => emit('select', category)"
>
<component
v-if="category.groupId"
:is="emojiGroupIconMap[category.groupId]"
class="size-6 text-primary"
/>
<img
v-else-if="category.src"
:src="category.src"
class="size-6 align-middle inline not-prose"
role="presentation"
>
</Button> </Button>
</div> </div>
</template> </template>

View file

@ -1,23 +1,38 @@
<template> <template>
<HoverCard @update:open="(open) => open && accounts === null && refreshReactions()"> <HoverCard
@update:open="(open) => open && accounts === null && refreshReactions()"
>
<HoverCardTrigger as-child> <HoverCardTrigger as-child>
<Button @click="reaction.me ? !reaction.remote && unreact() : !reaction.remote && react()" :variant="reaction.me ? 'secondary' : reaction.remote ? 'ghost' : 'outline'" size="sm" class="gap-2"> <Button
<img v-if="emoji" :src="emoji.url" :alt="emoji.shortcode" @click="reaction.me ? !reaction.remote && unreact() : !reaction.remote && react()"
class="h-[1lh] align-middle inline not-prose" /> :variant="reaction.me ? 'secondary' : reaction.remote ? 'ghost' : 'outline'"
<span v-else> size="sm"
{{ reaction.name }} class="gap-2"
</span> >
<img
v-if="emoji"
:src="emoji.url"
:alt="emoji.shortcode"
class="h-[1lh] align-middle inline not-prose"
>
<span v-else> {{ reaction.name }}</span>
{{ formatNumber(reaction.count) }} {{ formatNumber(reaction.count) }}
</Button> </Button>
</HoverCardTrigger> </HoverCardTrigger>
<HoverCardContent class="p-3"> <HoverCardContent class="p-3">
<Spinner v-if="accounts === null" class="border-0" /> <Spinner v-if="accounts === null" class="border-0"/>
<ul v-else class="flex flex-col gap-4"> <ul v-else class="flex flex-col gap-4">
<li <li v-for="account in accounts">
v-for="account in accounts"> <NuxtLink
<NuxtLink :to="`/@${account.acct}`" class="flex items-center gap-2"> :to="`/@${account.acct}`"
<Avatar class="size-6" :key="account.id" :src="account.avatar" class="flex items-center gap-2"
:name="account.display_name || account.username" /> >
<Avatar
class="size-6"
:key="account.id"
:src="account.avatar"
:name="account.display_name || account.username"
/>
<span class="text-sm font-semibold line-clamp-1"> <span class="text-sm font-semibold line-clamp-1">
{{ account.display_name || account.username }} {{ account.display_name || account.username }}
</span> </span>

View file

@ -1,9 +1,13 @@
<template> <template>
<NuxtLink :href="urlAsPath"> <NuxtLink :href="urlAsPath">
<Card class="flex-row px-2 py-1 items-center gap-2 hover:bg-muted duration-100 text-sm"> <Card
<Repeat class="size-4 text-primary" /> class="flex-row px-2 py-1 items-center gap-2 hover:bg-muted duration-100 text-sm"
<Avatar class="size-6 border" :src="avatar" :name="displayName" /> >
<span class="font-semibold" v-render-emojis="emojis">{{ displayName }}</span> <Repeat class="size-4 text-primary"/>
<Avatar class="size-6 border" :src="avatar" :name="displayName"/>
<span class="font-semibold" v-render-emojis="emojis"
>{{ displayName }}</span
>
{{ m.large_vivid_horse_catch() }} {{ m.large_vivid_horse_catch() }}
</Card> </Card>
</NuxtLink> </NuxtLink>

View file

@ -1,29 +1,55 @@
<template> <template>
<div v-if="relationship?.requested_by !== false" class="flex flex-row items-center gap-3 p-4"> <div
v-if="relationship?.requested_by !== false"
class="flex flex-row items-center gap-3 p-4"
>
<NuxtLink :href="followerUrl" class="relative size-10"> <NuxtLink :href="followerUrl" class="relative size-10">
<Avatar class="size-10 border border-border" :src="follower.avatar" :name="follower.display_name" /> <Avatar
class="size-10 border border-border"
:src="follower.avatar"
:name="follower.display_name"
/>
</NuxtLink> </NuxtLink>
<div class="flex flex-col gap-0.5 justify-center flex-1 text-left leading-tight text-sm"> <div
<span class="truncate font-semibold" v-render-emojis="follower.emojis">{{ class="flex flex-col gap-0.5 justify-center flex-1 text-left leading-tight text-sm"
>
<span
class="truncate font-semibold"
v-render-emojis="follower.emojis"
>{{
follower.display_name follower.display_name
}}</span> }}</span
>
<span class="truncate tracking-tight"> <span class="truncate tracking-tight">
<Address :username="username" :domain="domain" /> <Address :username="username" :domain="domain"/>
</span> </span>
</div> </div>
</div> </div>
<div v-if="loading" class="flex p-2 items-center justify-center h-12"> <div v-if="loading" class="flex p-2 items-center justify-center h-12">
<Loader class="size-4 animate-spin" /> <Loader class="size-4 animate-spin"/>
</div> </div>
<div v-else-if="relationship?.requested_by === false" class="flex p-2 items-center justify-center h-12"> <div
<Check class="size-4" /> v-else-if="relationship?.requested_by === false"
class="flex p-2 items-center justify-center h-12"
>
<Check class="size-4"/>
</div> </div>
<div v-else class="grid grid-cols-2 p-2 gap-2"> <div v-else class="grid grid-cols-2 p-2 gap-2">
<Button variant="secondary" size="sm" @click="accept" :title="m.slow_these_kestrel_sail()"> <Button
<Check /> variant="secondary"
size="sm"
@click="accept"
:title="m.slow_these_kestrel_sail()"
>
<Check/>
</Button> </Button>
<Button variant="ghost" size="sm" @click="reject" :title="m.weary_steep_yak_embrace()"> <Button
<X /> variant="ghost"
size="sm"
@click="reject"
:title="m.weary_steep_yak_embrace()"
>
<X/>
</Button> </Button>
</div> </div>
</template> </template>

View file

@ -7,7 +7,7 @@
v-if="notification.account" v-if="notification.account"
class="flex flex-row items-center gap-2 px-2" class="flex flex-row items-center gap-2 px-2"
> >
<component :is="icon" class="size-5 shrink-0" /> <component :is="icon" class="size-5 shrink-0"/>
<Avatar <Avatar
class="size-5 border border-card" class="size-5 border border-card"
:src="notification.account.avatar" :src="notification.account.avatar"
@ -25,7 +25,7 @@
class="ml-auto [&_svg]:data-[state=open]:-rotate-180" class="ml-auto [&_svg]:data-[state=open]:-rotate-180"
:title="open ? 'Collapse' : 'Expand'" :title="open ? 'Collapse' : 'Expand'"
> >
<ChevronDown class="duration-200" /> <ChevronDown class="duration-200"/>
</Button> </Button>
</CollapsibleTrigger> </CollapsibleTrigger>
</CardHeader> </CardHeader>

View file

@ -1,17 +1,43 @@
<template> <template>
<section class="space-y-2"> <section class="space-y-2">
<CardTitle class="text-xs"> <CardTitle class="text-xs">{{ name }}</CardTitle>
{{ name }}
</CardTitle>
<Card class="p-0 gap-0"> <Card class="p-0 gap-0">
<div v-for="preference of preferences" :key="preference"> <div v-for="preference of preferences" :key="preference">
<TextPreferenceVue v-if="(prefs[preference] instanceof TextPreference)" :pref="(prefs[preference] as TextPreference)" :name="preference" /> <TextPreferenceVue
<BooleanPreferenceVue v-else-if="(prefs[preference] instanceof BooleanPreference)" :pref="(prefs[preference] as BooleanPreference)" :name="preference" /> v-if="(prefs[preference] instanceof TextPreference)"
<SelectPreferenceVue v-else-if="(prefs[preference] instanceof SelectPreference)" :pref="(prefs[preference] as SelectPreference<string>)" :name="preference" /> :pref="(prefs[preference] as TextPreference)"
<NumberPreferenceVue v-else-if="(prefs[preference] instanceof NumberPreference)" :pref="(prefs[preference] as NumberPreference)" :name="preference" /> :name="preference"
<MultiSelectPreferenceVue v-else-if="(prefs[preference] instanceof MultiSelectPreference)" :pref="(prefs[preference] as MultiSelectPreference<string>)" :name="preference" /> />
<CodePreferenceVue v-else-if="(prefs[preference] instanceof CodePreference)" :pref="(prefs[preference] as CodePreference)" :name="preference" /> <BooleanPreferenceVue
<UrlPreferenceVue v-else-if="(prefs[preference] instanceof UrlPreference)" :pref="(prefs[preference] as UrlPreference)" :name="preference" /> v-else-if="(prefs[preference] instanceof BooleanPreference)"
:pref="(prefs[preference] as BooleanPreference)"
:name="preference"
/>
<SelectPreferenceVue
v-else-if="(prefs[preference] instanceof SelectPreference)"
:pref="(prefs[preference] as SelectPreference<string>)"
:name="preference"
/>
<NumberPreferenceVue
v-else-if="(prefs[preference] instanceof NumberPreference)"
:pref="(prefs[preference] as NumberPreference)"
:name="preference"
/>
<MultiSelectPreferenceVue
v-else-if="(prefs[preference] instanceof MultiSelectPreference)"
:pref="(prefs[preference] as MultiSelectPreference<string>)"
:name="preference"
/>
<CodePreferenceVue
v-else-if="(prefs[preference] instanceof CodePreference)"
:pref="(prefs[preference] as CodePreference)"
:name="preference"
/>
<UrlPreferenceVue
v-else-if="(prefs[preference] instanceof UrlPreference)"
:pref="(prefs[preference] as UrlPreference)"
:name="preference"
/>
</div> </div>
</Card> </Card>
</section> </section>

View file

@ -1,13 +1,17 @@
<template> <template>
<Card class="grid gap-3 text-sm"> <Card class="grid gap-3 text-sm">
<dl class="grid gap-3"> <dl class="grid gap-3">
<div v-for="[key, value] of data" :key="key" class="flex flex-row items-baseline justify-between gap-4 truncate"> <div
<dt class="text-muted-foreground"> v-for="[key, value] of data"
{{ key }} :key="key"
</dt> class="flex flex-row items-baseline justify-between gap-4 truncate"
<dd class="font-mono" v-if="typeof value === 'string'">{{ value }}</dd> >
<dt class="text-muted-foreground">{{ key }}</dt>
<dd class="font-mono" v-if="typeof value === 'string'">
{{ value }}
</dd>
<dd class="font-mono" v-else> <dd class="font-mono" v-else>
<component :is="value" /> <component :is="value"/>
</dd> </dd>
</div> </div>
</dl> </dl>

View file

@ -77,46 +77,70 @@ useListen("preferences:open", () => {
<template> <template>
<Dialog v-model:open="open" v-if="authStore.isSignedIn"> <Dialog v-model:open="open" v-if="authStore.isSignedIn">
<DialogContent class="md:max-w-5xl w-full h-full p-0 md:max-h-[70dvh] overflow-hidden"> <DialogContent
<Tabs class="md:grid-cols-[auto_minmax(0,1fr)] !grid gap-2 *:p-4 overflow-hidden *:overflow-y-auto *:h-full" orientation="vertical" class="md:max-w-5xl w-full h-full p-0 md:max-h-[70dvh] overflow-hidden"
:default-value="pages[0]"> >
<DialogHeader class="gap-6 grid grid-rows-[auto_minmax(0,1fr)] border-b md:border-b-0 md:border-r min-w-60 text-left"> <Tabs
<div class="grid gap-3 items-center grid-cols-[auto_minmax(0,1fr)]"> class="md:grid-cols-[auto_minmax(0,1fr)] !grid gap-2 *:p-4 overflow-hidden *:overflow-y-auto *:h-full"
<Avatar :name="authStore.account!.display_name || authStore.account!.username" orientation="vertical"
:src="authStore.account!.avatar" /> :default-value="pages[0]"
>
<DialogHeader
class="gap-6 grid grid-rows-[auto_minmax(0,1fr)] border-b md:border-b-0 md:border-r min-w-60 text-left"
>
<div
class="grid gap-3 items-center grid-cols-[auto_minmax(0,1fr)]"
>
<Avatar
:name="authStore.account!.display_name || authStore.account!.username"
:src="authStore.account!.avatar"
/>
<DialogTitle>Preferences</DialogTitle> <DialogTitle>Preferences</DialogTitle>
</div> </div>
<DialogDescription class="sr-only"> <DialogDescription class="sr-only">
Make changes to your preferences here. Make changes to your preferences here.
</DialogDescription> </DialogDescription>
<TabsList class="md:grid md:grid-cols-1 w-full h-fit *:justify-start !justify-start"> <TabsList
<TabsTrigger v-for="page in pages" :key="page" :value="page"> class="md:grid md:grid-cols-1 w-full h-fit *:justify-start !justify-start"
<component :is="icons[page]" class="size-4 mr-2" /> >
<TabsTrigger
v-for="page in pages"
:key="page"
:value="page"
>
<component :is="icons[page]" class="size-4 mr-2"/>
{{ page }} {{ page }}
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
</DialogHeader> </DialogHeader>
<TabsContent v-for="page in pages.filter(p => !extraPages.includes(p))" :key="page" :value="page" <TabsContent
as-child> v-for="page in pages.filter(p => !extraPages.includes(p))"
:key="page"
:value="page"
as-child
>
<Page :title="page"> <Page :title="page">
<Category v-for="category in categories[page]" :key="category" <Category
v-for="category in categories[page]"
:key="category"
:preferences="Object.entries(preferences).filter(([, p]) => p.options.category === `${page}/${category}`).map(([k,]) => k as keyof typeof preferences)" :preferences="Object.entries(preferences).filter(([, p]) => p.options.category === `${page}/${category}`).map(([k,]) => k as keyof typeof preferences)"
:name="category" /> :name="category"
/>
</Page> </Page>
</TabsContent> </TabsContent>
<TabsContent value="Emojis" as-child> <TabsContent value="Emojis" as-child>
<Page title="Emojis"> <Page title="Emojis">
<Emojis /> <Emojis/>
</Page> </Page>
</TabsContent> </TabsContent>
<TabsContent value="Account" as-child> <TabsContent value="Account" as-child>
<Page title="Account"> <Page title="Account">
<Profile /> <Profile/>
</Page> </Page>
</TabsContent> </TabsContent>
<TabsContent value="Developer" as-child> <TabsContent value="Developer" as-child>
<Page title="Developer"> <Page title="Developer">
<Developer /> <Developer/>
</Page> </Page>
</TabsContent> </TabsContent>
<TabsContent value="About" as-child> <TabsContent value="About" as-child>
@ -126,25 +150,53 @@ useListen("preferences:open", () => {
{{ pkg.description }} {{ pkg.description }}
</p> </p>
<Stats /> <Stats/>
</section> </section>
<Separator /> <Separator/>
<section class="space-y-2"> <section class="space-y-2">
<h3 class="text-lg font-semibold tracking-tight">Developers</h3> <h3 class="text-lg font-semibold tracking-tight">
<div class="grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-4"> Developers
<TinyCard v-if="author1" :account="author1" domain="vs.cpluspatch.com" /> </h3>
<TinyCard v-if="author2" :account="author2" domain="social.lysand.org" /> <div
<TinyCard v-if="author3" :account="author3" domain="social.lysand.org" /> class="grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-4"
<TinyCard v-if="author4" :account="author4" domain="v.everypizza.im" /> >
<TinyCard
v-if="author1"
:account="author1"
domain="vs.cpluspatch.com"
/>
<TinyCard
v-if="author2"
:account="author2"
domain="social.lysand.org"
/>
<TinyCard
v-if="author3"
:account="author3"
domain="social.lysand.org"
/>
<TinyCard
v-if="author4"
:account="author4"
domain="v.everypizza.im"
/>
</div> </div>
</section> </section>
<Separator /> <Separator/>
<section class="space-y-2"> <section class="space-y-2">
<h3 class="text-lg font-semibold tracking-tight">Dependencies</h3> <h3 class="text-lg font-semibold tracking-tight">
<ul class="grid lg:grid-cols-2 gap-2 grid-cols-1 items-center justify-center list-disc ml-6"> Dependencies
<li v-for="[dep, version] in Object.entries(pkg.dependencies)" :key="dep"> </h3>
<ul
class="grid lg:grid-cols-2 gap-2 grid-cols-1 items-center justify-center list-disc ml-6"
>
<li
v-for="[dep, version] in Object.entries(pkg.dependencies)"
:key="dep"
>
<code <code
class="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs font-semibold"> class="rounded bg-muted px-[0.3rem] py-[0.2rem] font-mono text-xs font-semibold"
>
{{ dep }}@{{ version }} {{ dep }}@{{ version }}
</code> </code>
</li> </li>

View file

@ -1,11 +1,11 @@
<template> <template>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<slot /> <slot/>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="min-w-48"> <DropdownMenuContent class="min-w-48">
<DropdownMenuItem @click="deleteAll" :disabled="!canEdit"> <DropdownMenuItem @click="deleteAll" :disabled="!canEdit">
<Delete /> <Delete/>
{{ m.tense_quick_cod_favor() }} {{ m.tense_quick_cod_favor() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View file

@ -1,13 +1,18 @@
<template> <template>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button variant="ghost" size="icon" title="Open menu" class="size-8 p-0"> <Button
<MoreHorizontal class="size-4" /> variant="ghost"
size="icon"
title="Open menu"
class="size-8 p-0"
>
<MoreHorizontal class="size-4"/>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="min-w-48"> <DropdownMenuContent class="min-w-48">
<DropdownMenuItem @click="editName"> <DropdownMenuItem @click="editName">
<TextCursorInput /> <TextCursorInput/>
{{ m.cuddly_such_swallow_hush() }} {{ m.cuddly_such_swallow_hush() }}
</DropdownMenuItem> </DropdownMenuItem>
<!-- <DropdownMenuItem @click="editCaption"> <!-- <DropdownMenuItem @click="editCaption">
@ -16,7 +21,7 @@
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuSeparator /> --> <DropdownMenuSeparator /> -->
<DropdownMenuItem @click="_delete"> <DropdownMenuItem @click="_delete">
<Delete /> <Delete/>
{{ m.tense_quick_cod_favor() }} {{ m.tense_quick_cod_favor() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View file

@ -1,7 +1,7 @@
<template> <template>
<div v-if="authStore.emojis.length > 0" class="grow"> <div v-if="authStore.emojis.length > 0" class="grow">
<Table :emojis="authStore.emojis" :can-upload="canUpload" /> <Table :emojis="authStore.emojis" :can-upload="canUpload"/>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -282,27 +282,34 @@ const table = useVueTable({
<template> <template>
<div class="w-full"> <div class="w-full">
<div class="flex gap-2 items-center py-4"> <div class="flex gap-2 items-center py-4">
<Input class="max-w-52 mr-auto" placeholder="Filter emojis..." <Input
class="max-w-52 mr-auto"
placeholder="Filter emojis..."
:model-value="(table.getColumn('shortcode')?.getFilterValue() as string)" :model-value="(table.getColumn('shortcode')?.getFilterValue() as string)"
@update:model-value="table.getColumn('shortcode')?.setFilterValue($event)" /> @update:model-value="table.getColumn('shortcode')?.setFilterValue($event)"
/>
<Uploader v-if="props.canUpload"> <Uploader v-if="props.canUpload">
<Button variant="outline" size="icon" title="Upload emoji"> <Button variant="outline" size="icon" title="Upload emoji">
<Plus class="size-4" /> <Plus class="size-4"/>
</Button> </Button>
</Uploader> </Uploader>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button variant="outline"> <Button variant="outline">
Columns Columns
<ChevronDown class="ml-2 size-4" /> <ChevronDown class="ml-2 size-4"/>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuCheckboxItem <DropdownMenuCheckboxItem
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())" :key="column.id" v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
class="capitalize" :model-value="column.getIsVisible()" @update:model-value="(value) => { :key="column.id"
class="capitalize"
:model-value="column.getIsVisible()"
@update:model-value="(value) => {
column.toggleVisibility(!!value) column.toggleVisibility(!!value)
}"> }"
>
{{ column.id }} {{ column.id }}
</DropdownMenuCheckboxItem> </DropdownMenuCheckboxItem>
</DropdownMenuContent> </DropdownMenuContent>
@ -311,19 +318,40 @@ const table = useVueTable({
<div class="rounded-md border"> <div class="rounded-md border">
<Table> <Table>
<TableHeader> <TableHeader>
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id"> <TableRow
<TableHead v-for="header in headerGroup.headers" :key="header.id" class=""> v-for="headerGroup in table.getHeaderGroups()"
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header" :key="headerGroup.id"
:props="header.getContext()" /> >
<TableHead
v-for="header in headerGroup.headers"
:key="header.id"
class=""
>
<FlexRender
v-if="!header.isPlaceholder"
:render="header.column.columnDef.header"
:props="header.getContext()"
/>
</TableHead> </TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
<template v-if="table.getRowModel().rows?.length"> <template v-if="table.getRowModel().rows?.length">
<template v-for="row in table.getRowModel().rows" :key="row.id"> <template
<TableRow :data-state="row.getIsSelected() && 'selected'"> v-for="row in table.getRowModel().rows"
<TableCell v-for="cell in row.getVisibleCells()" :key="cell.id"> :key="row.id"
<FlexRender :render="cell.column.columnDef.cell" :props="cell.getContext()" /> >
<TableRow
:data-state="row.getIsSelected() && 'selected'"
>
<TableCell
v-for="cell in row.getVisibleCells()"
:key="cell.id"
>
<FlexRender
:render="cell.column.columnDef.cell"
:props="cell.getContext()"
/>
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow v-if="row.getIsExpanded()"> <TableRow v-if="row.getIsExpanded()">
@ -335,7 +363,10 @@ const table = useVueTable({
</template> </template>
<TableRow v-else> <TableRow v-else>
<TableCell :colspan="columns.length" class="h-24 text-center"> <TableCell
:colspan="columns.length"
class="h-24 text-center"
>
No results. No results.
</TableCell> </TableCell>
</TableRow> </TableRow>
@ -345,15 +376,24 @@ const table = useVueTable({
<div class="flex items-center justify-end space-x-2 py-4"> <div class="flex items-center justify-end space-x-2 py-4">
<div class="flex-1 text-sm text-muted-foreground"> <div class="flex-1 text-sm text-muted-foreground">
{{ table.getFilteredSelectedRowModel().rows.length }} of {{ table.getFilteredSelectedRowModel().rows.length }}of
{{ table.getFilteredRowModel().rows.length }} row(s) selected. {{ table.getFilteredRowModel().rows.length }}row(s) selected.
</div> </div>
<div class="space-x-2"> <div class="space-x-2">
<Button variant="outline" size="sm" :disabled="!table.getCanPreviousPage()" <Button
@click="table.previousPage()"> variant="outline"
size="sm"
:disabled="!table.getCanPreviousPage()"
@click="table.previousPage()"
>
Previous Previous
</Button> </Button>
<Button variant="outline" size="sm" :disabled="!table.getCanNextPage()" @click="table.nextPage()"> <Button
variant="outline"
size="sm"
:disabled="!table.getCanNextPage()"
@click="table.nextPage()"
>
Next Next
</Button> </Button>
</div> </div>

View file

@ -1,12 +1,10 @@
<template> <template>
<Dialog v-model:open="open"> <Dialog v-model:open="open">
<DialogTrigger> <DialogTrigger>
<slot /> <slot/>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogTitle> <DialogTitle>{{ m.whole_icy_puffin_smile() }}</DialogTitle>
{{ m.whole_icy_puffin_smile() }}
</DialogTitle>
<DialogDescription class="sr-only"> <DialogDescription class="sr-only">
{{ m.frail_great_marten_pet() }} {{ m.frail_great_marten_pet() }}
</DialogDescription> </DialogDescription>
@ -20,28 +18,28 @@
class="h-full object-cover" class="h-full object-cover"
:src="createObjectURL(values.image as File)" :src="createObjectURL(values.image as File)"
:alt="values.alt" :alt="values.alt"
/> >
</div> </div>
<div class="bg-zinc-700"> <div class="bg-zinc-700">
<img <img
class="h-full object-cover" class="h-full object-cover"
:src="createObjectURL(values.image as File)" :src="createObjectURL(values.image as File)"
:alt="values.alt" :alt="values.alt"
/> >
</div> </div>
<div class="bg-zinc-400"> <div class="bg-zinc-400">
<img <img
class="h-full object-cover" class="h-full object-cover"
:src="createObjectURL(values.image as File)" :src="createObjectURL(values.image as File)"
:alt="values.alt" :alt="values.alt"
/> >
</div> </div>
<div class="bg-foreground"> <div class="bg-foreground">
<img <img
class="h-full object-cover" class="h-full object-cover"
:src="createObjectURL(values.image as File)" :src="createObjectURL(values.image as File)"
:alt="values.alt" :alt="values.alt"
/> >
</div> </div>
</div> </div>
@ -68,15 +66,13 @@
<FormDescription> <FormDescription>
{{ m.lime_late_millipede_urge() }} {{ m.lime_late_millipede_urge() }}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage/>
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField }" name="shortcode"> <FormField v-slot="{ componentField }" name="shortcode">
<FormItem> <FormItem>
<FormLabel> <FormLabel>{{ m.happy_mild_fox_gleam() }}</FormLabel>
{{ m.happy_mild_fox_gleam() }}
</FormLabel>
<FormControl> <FormControl>
<Input <Input
v-bind="componentField" v-bind="componentField"
@ -86,7 +82,7 @@
<FormDescription> <FormDescription>
{{ m.glad_day_kestrel_amaze() }} {{ m.glad_day_kestrel_amaze() }}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage/>
</FormItem> </FormItem>
</FormField> </FormField>
@ -101,7 +97,7 @@
:disabled="isSubmitting" :disabled="isSubmitting"
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage/>
</FormItem> </FormItem>
</FormField> </FormField>
@ -120,7 +116,7 @@
<FormDescription> <FormDescription>
{{ m.weird_fun_jurgen_arise() }} {{ m.weird_fun_jurgen_arise() }}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage/>
</FormItem> </FormItem>
</FormField> </FormField>
@ -130,7 +126,10 @@
name="global" name="global"
as-child as-child
> >
<FormSwitch :title="m.pink_sharp_carp_work()" :description="m.dark_pretty_hyena_link()"> <FormSwitch
:title="m.pink_sharp_carp_work()"
:description="m.dark_pretty_hyena_link()"
>
<Switch <Switch
:model-value="value" :model-value="value"
@update:model-value="handleChange" @update:model-value="handleChange"

View file

@ -1,5 +1,5 @@
<template> <template>
<Dialog /> <Dialog/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,10 +1,8 @@
<template> <template>
<section class="gap-4 flex flex-col"> <section class="gap-4 flex flex-col">
<h2 class="text-xl font-bold tracking-tight"> <h2 class="text-xl font-bold tracking-tight">{{ title }}</h2>
{{ title }}
</h2>
<slot /> <slot/>
</section> </section>
</template> </template>

View file

@ -1,10 +1,20 @@
<template> <template>
<form class="grid gap-6" @submit="save"> <form class="grid gap-6" @submit="save">
<Transition name="slide-up"> <Transition name="slide-up">
<Alert v-if="dirty" class="absolute bottom-2 z-10 inset-x-2 w-[calc(100%-1rem)] grid-cols-[calc(var(--spacing)*4)_1fr_auto]!"> <Alert
<SaveOff class="size-4" /> v-if="dirty"
class="absolute bottom-2 z-10 inset-x-2 w-[calc(100%-1rem)] grid-cols-[calc(var(--spacing)*4)_1fr_auto]!"
>
<SaveOff class="size-4"/>
<AlertTitle>Unsaved changes</AlertTitle> <AlertTitle>Unsaved changes</AlertTitle>
<Button variant="secondary" class="w-full row-span-2" type="button" :disabled="submitting">Apply</Button> <Button
variant="secondary"
class="w-full row-span-2"
type="button"
:disabled="submitting"
>
Apply
</Button>
<AlertDescription> <AlertDescription>
Click "apply" to save your changes. Click "apply" to save your changes.
</AlertDescription> </AlertDescription>
@ -12,55 +22,101 @@
</Transition> </Transition>
<FormField v-slot="{ handleChange, handleBlur }" name="banner"> <FormField v-slot="{ handleChange, handleBlur }" name="banner">
<TextInput :title="m.bright_late_osprey_renew()" :description="m.great_level_lamb_sway()"> <TextInput
<Input type="file" accept="image/*" @change="handleChange" @blur="handleBlur" /> :title="m.bright_late_osprey_renew()"
:description="m.great_level_lamb_sway()"
>
<Input
type="file"
accept="image/*"
@change="handleChange"
@blur="handleBlur"
/>
</TextInput> </TextInput>
</FormField> </FormField>
<FormField v-slot="{ setValue }" name="avatar"> <FormField v-slot="{ setValue }" name="avatar">
<TextInput :title="m.safe_icy_bulldog_quell()"> <TextInput :title="m.safe_icy_bulldog_quell()">
<ImageUploader v-model:image="authStore.account!.avatar" @submit-file="(file) => setValue(file)" <ImageUploader
@submit-url="(url) => setValue(url)" /> v-model:image="authStore.account!.avatar"
@submit-file="(file) => setValue(file)"
@submit-url="(url) => setValue(url)"
/>
</TextInput> </TextInput>
</FormField> </FormField>
<FormField v-slot="{ componentField }" name="name"> <FormField v-slot="{ componentField }" name="name">
<TextInput :title="m.mild_known_mallard_jolt()" :description="m.lime_dry_skunk_loop()"> <TextInput
<Input v-bind="componentField" /> :title="m.mild_known_mallard_jolt()"
:description="m.lime_dry_skunk_loop()"
>
<Input v-bind="componentField"/>
</TextInput> </TextInput>
</FormField> </FormField>
<FormField v-slot="{ componentField }" name="username"> <FormField v-slot="{ componentField }" name="username">
<TextInput :title="m.neat_silly_dog_prosper()" :description="m.petty_plane_tadpole_earn()"> <TextInput
<Input v-bind="componentField" /> :title="m.neat_silly_dog_prosper()"
:description="m.petty_plane_tadpole_earn()"
>
<Input v-bind="componentField"/>
</TextInput> </TextInput>
</FormField> </FormField>
<FormField v-slot="{ componentField }" name="bio"> <FormField v-slot="{ componentField }" name="bio">
<TextInput :title="m.next_caring_ladybug_hack()" :description="m.stale_just_anaconda_earn()"> <TextInput
<Textarea rows="10" v-bind="componentField" /> :title="m.next_caring_ladybug_hack()"
:description="m.stale_just_anaconda_earn()"
>
<Textarea rows="10" v-bind="componentField"/>
</TextInput> </TextInput>
</FormField> </FormField>
<FormField v-slot="{ value, handleChange }" name="fields"> <FormField v-slot="{ value, handleChange }" name="fields">
<Fields :title="m.aqua_mealy_toucan_pride()" :value="value" @update:value="handleChange" /> <Fields
:title="m.aqua_mealy_toucan_pride()"
:value="value"
@update:value="handleChange"
/>
</FormField> </FormField>
<FormField v-slot="{ value, handleChange }" name="bot" as-child> <FormField v-slot="{ value, handleChange }" name="bot" as-child>
<SwitchInput :title="m.gaudy_each_opossum_play()" :description="m.grassy_acidic_gadfly_cure()"> <SwitchInput
<Switch :model-value="value" @update:model-value="handleChange" /> :title="m.gaudy_each_opossum_play()"
:description="m.grassy_acidic_gadfly_cure()"
>
<Switch
:model-value="value"
@update:model-value="handleChange"
/>
</SwitchInput> </SwitchInput>
</FormField> </FormField>
<FormField v-slot="{ value, handleChange }" name="locked" as-child> <FormField v-slot="{ value, handleChange }" name="locked" as-child>
<SwitchInput :title="m.dirty_moving_shark_emerge()" :description="m.bright_fun_mouse_boil()"> <SwitchInput
<Switch :model-value="value" @update:model-value="handleChange" /> :title="m.dirty_moving_shark_emerge()"
:description="m.bright_fun_mouse_boil()"
>
<Switch
:model-value="value"
@update:model-value="handleChange"
/>
</SwitchInput> </SwitchInput>
</FormField> </FormField>
<FormField v-slot="{ value, handleChange }" name="discoverable" as-child> <FormField
<SwitchInput :title="m.red_vivid_cuckoo_spark()" :description="m.plain_zany_donkey_dart()"> v-slot="{ value, handleChange }"
<Switch :model-value="value" @update:model-value="handleChange" /> name="discoverable"
as-child
>
<SwitchInput
:title="m.red_vivid_cuckoo_spark()"
:description="m.plain_zany_donkey_dart()"
>
<Switch
:model-value="value"
@update:model-value="handleChange"
/>
</SwitchInput> </SwitchInput>
</FormField> </FormField>
</form> </form>

View file

@ -2,30 +2,64 @@
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{{ title }} {{ title }}
<Button type="button" variant="secondary" size="icon" class="ml-auto" @click="addField()" :title="m.front_north_eel_gulp()"> <Button
<Plus /> type="button"
variant="secondary"
size="icon"
class="ml-auto"
@click="addField()"
:title="m.front_north_eel_gulp()"
>
<Plus/>
</Button> </Button>
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<VueDraggable class="grid gap-4" v-model="list" :animation="200" handle=".drag-handle"> <VueDraggable
<div v-for="(field, index) in list" :key="field.id" class="grid gap-4"
class="grid items-center grid-cols-[auto_repeat(3,minmax(0,1fr))_auto] gap-2"> v-model="list"
<Button as="span" variant="ghost" size="icon" class="drag-handle cursor-grab"> :animation="200"
<GripVertical /> handle=".drag-handle"
>
<div
v-for="(field, index) in list"
:key="field.id"
class="grid items-center grid-cols-[auto_repeat(3,minmax(0,1fr))_auto] gap-2"
>
<Button
as="span"
variant="ghost"
size="icon"
class="drag-handle cursor-grab"
>
<GripVertical/>
</Button> </Button>
<Input :model-value="field.name" placeholder="Name" @update:model-value=" <Input
:model-value="field.name"
placeholder="Name"
@update:model-value="
(e) => updateKey(index, String(e)) (e) => updateKey(index, String(e))
" /> "
<Input :model-value="field.value" placeholder="Value" class="col-span-2" @update:model-value=" />
<Input
:model-value="field.value"
placeholder="Value"
class="col-span-2"
@update:model-value="
(e) => updateValue(index, String(e)) (e) => updateValue(index, String(e))
" /> "
<Button type="button" variant="secondary" size="icon" @click="removeField(index)"> />
<Trash /> <Button
type="button"
variant="secondary"
size="icon"
@click="removeField(index)"
>
<Trash/>
</Button> </Button>
</div> </div>
</VueDraggable> </VueDraggable>
<FormMessage /> <FormMessage/>
</FormControl> </FormControl>
</FormItem> </FormItem>
</template> </template>

View file

@ -6,18 +6,16 @@
variant="ghost" variant="ghost"
class="h-fit w-fit p-0 m-0 relative group border overflow-hidden" class="h-fit w-fit p-0 m-0 relative group border overflow-hidden"
> >
<Avatar class="size-32" :src="image" :name="displayName" /> <Avatar class="size-32" :src="image" :name="displayName"/>
<div <div
class="absolute inset-0 bg-background/80 flex group-hover:opacity-100 opacity-0 duration-200 items-center justify-center" class="absolute inset-0 bg-background/80 flex group-hover:opacity-100 opacity-0 duration-200 items-center justify-center"
> >
<Upload /> <Upload/>
</div> </div>
</Button> </Button>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogTitle> <DialogTitle>{{ m.due_hour_husky_prosper() }}</DialogTitle>
{{ m.due_hour_husky_prosper() }}
</DialogTitle>
<DialogDescription class="sr-only"> <DialogDescription class="sr-only">
{{ m.suave_broad_albatross_drop() }} {{ m.suave_broad_albatross_drop() }}
</DialogDescription> </DialogDescription>
@ -58,7 +56,7 @@
<FormDescription> <FormDescription>
{{ m.lime_late_millipede_urge() }} {{ m.lime_late_millipede_urge() }}
</FormDescription> </FormDescription>
<FormMessage /> <FormMessage/>
</FormItem> </FormItem>
</FormField> </FormField>
</TabsContent> </TabsContent>
@ -83,12 +81,14 @@
placeholder="peter.griffin@fox.com" placeholder="peter.griffin@fox.com"
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage/>
<div v-if="value" class="grid gap-4 !mt-4"> <div v-if="value" class="grid gap-4 !mt-4">
<Label>{{ <Label>
{{
m.witty_honest_wallaby_support() m.witty_honest_wallaby_support()
}}</Label> }}
<Avatar class="size-32" :src="gravatarUrl" /> </Label>
<Avatar class="size-32" :src="gravatarUrl"/>
</div> </div>
</FormItem> </FormItem>
</FormField> </FormField>
@ -109,12 +109,14 @@
placeholder="https://mysite.com/avatar.webp" placeholder="https://mysite.com/avatar.webp"
/> />
</FormControl> </FormControl>
<FormMessage /> <FormMessage/>
<div v-if="value" class="grid gap-4 !mt-4"> <div v-if="value" class="grid gap-4 !mt-4">
<Label>{{ <Label>
{{
m.witty_honest_wallaby_support() m.witty_honest_wallaby_support()
}}</Label> }}
<Avatar class="size-32" :src="value" /> </Label>
<Avatar class="size-32" :src="value"/>
</div> </div>
</FormItem> </FormItem>
</FormField> </FormField>

View file

@ -1,13 +1,17 @@
<template> <template>
<Card class="grid gap-3 text-sm max-w-sm"> <Card class="grid gap-3 text-sm max-w-sm">
<dl class="grid gap-3"> <dl class="grid gap-3">
<div v-for="[key, value] of data" :key="key" class="flex flex-row items-baseline justify-between gap-4 truncate"> <div
<dt class="text-muted-foreground"> v-for="[key, value] of data"
{{ key }} :key="key"
</dt> class="flex flex-row items-baseline justify-between gap-4 truncate"
<dd class="font-mono" v-if="typeof value === 'string'">{{ value }}</dd> >
<dt class="text-muted-foreground">{{ key }}</dt>
<dd class="font-mono" v-if="typeof value === 'string'">
{{ value }}
</dd>
<dd class="font-mono" v-else> <dd class="font-mono" v-else>
<component :is="value" /> <component :is="value"/>
</dd> </dd>
</div> </div>
</dl> </dl>

View file

@ -1,14 +1,14 @@
<template> <template>
<Base :pref="pref" :name="name" v-slot="{ setValue, value }"> <TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
<Switch @update:model-value="setValue" :model-value="value" /> <Switch @update:model-value="setValue" :model-value="value"/>
</Base> </TypeBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Switch } from "~/components/ui/switch"; import { Switch } from "~/components/ui/switch";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { BooleanPreference } from "../types"; import type { BooleanPreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: BooleanPreference; pref: BooleanPreference;

View file

@ -1,19 +1,21 @@
<template> <template>
<Collapsible as-child> <Collapsible as-child>
<Base :name="name" :pref="pref"> <TypeBase :name="name" :pref="pref">
<template #default> <template #default>
<CollapsibleTrigger as-child> <CollapsibleTrigger as-child>
<Button variant="outline"> <Button variant="outline">Open code</Button>
Open code </CollapsibleTrigger>
</Button> </template>
</CollapsibleTrigger> <template #extra="{ setValue, value }">
</template> <CollapsibleContent class="col-span-2 mt-2">
<template #extra="{ setValue, value }"> <Textarea
<CollapsibleContent class="col-span-2 mt-2"> :rows="10"
<Textarea :rows="10" :model-value="value" @update:model-value="setValue" /> :model-value="value"
</CollapsibleContent> @update:model-value="setValue"
</template> />
</Base> </CollapsibleContent>
</template>
</TypeBase>
</Collapsible> </Collapsible>
</template> </template>
@ -27,7 +29,7 @@ import {
import { Textarea } from "~/components/ui/textarea"; import { Textarea } from "~/components/ui/textarea";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { CodePreference } from "../types"; import type { CodePreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: CodePreference; pref: CodePreference;

View file

@ -1,25 +1,27 @@
<template> <template>
<Base :pref="pref" :name="name" v-slot="{ setValue, value }"> <TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<Button variant="outline"> <Button variant="outline">Pick</Button>
Pick </DropdownMenuTrigger>
</Button> <DropdownMenuContent class="w-56">
</DropdownMenuTrigger> <DropdownMenuCheckboxItem
<DropdownMenuContent class="w-56"> v-for="[option, title] in Object.entries(pref.options.options)"
<DropdownMenuCheckboxItem v-for="[option, title] in Object.entries(pref.options.options)" :key="option" :key="option"
:model-value="value.includes(option)" @update:model-value="checked => { :model-value="value.includes(option)"
@update:model-value="checked => {
if (checked) { if (checked) {
setValue([...value, option]); setValue([...value, option]);
} else { } else {
setValue(value.filter((v: any) => v !== option)); setValue(value.filter((v: any) => v !== option));
} }
}"> }"
{{ title }} >
</DropdownMenuCheckboxItem> {{ title }}
</DropdownMenuContent> </DropdownMenuCheckboxItem>
</DropdownMenu> </DropdownMenuContent>
</Base> </DropdownMenu>
</TypeBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -32,7 +34,7 @@ import {
} from "~/components/ui/dropdown-menu"; } from "~/components/ui/dropdown-menu";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { MultiSelectPreference } from "../types"; import type { MultiSelectPreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: MultiSelectPreference<string>; pref: MultiSelectPreference<string>;

View file

@ -1,13 +1,19 @@
<template> <template>
<Base :pref="pref" :name="name" v-slot="{ setValue, value }"> <TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
<NumberField :model-value="value" @update:model-value="setValue" :min="pref.options.min" :max="pref.options.max" :step="pref.options.integer ? 1 : pref.options.step"> <NumberField
:model-value="value"
@update:model-value="setValue"
:min="pref.options.min"
:max="pref.options.max"
:step="pref.options.integer ? 1 : pref.options.step"
>
<NumberFieldContent> <NumberFieldContent>
<NumberFieldDecrement /> <NumberFieldDecrement/>
<NumberFieldInput /> <NumberFieldInput/>
<NumberFieldIncrement /> <NumberFieldIncrement/>
</NumberFieldContent> </NumberFieldContent>
</NumberField> </NumberField>
</Base> </TypeBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -20,7 +26,7 @@ import {
} from "~/components/ui/number-field"; } from "~/components/ui/number-field";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { NumberPreference } from "../types"; import type { NumberPreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: NumberPreference; pref: NumberPreference;

View file

@ -1,18 +1,21 @@
<template> <template>
<Base :pref="pref" :name="name" v-slot="{ setValue, value }"> <TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
<Select :model-value="value" @update:model-value="setValue"> <Select :model-value="value" @update:model-value="setValue">
<SelectTrigger> <SelectTrigger>
<SelectValue placeholder="Select an option" /> <SelectValue placeholder="Select an option"/>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectGroup> <SelectGroup>
<SelectItem v-for="[val, title] in Object.entries(pref.options.options)" :value="val"> <SelectItem
v-for="[val, title] in Object.entries(pref.options.options)"
:value="val"
>
{{ title }} {{ title }}
</SelectItem> </SelectItem>
</SelectGroup> </SelectGroup>
</SelectContent> </SelectContent>
</Select> </Select>
</Base> </TypeBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -26,7 +29,7 @@ import {
} from "~/components/ui/select"; } from "~/components/ui/select";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { SelectPreference } from "../types"; import type { SelectPreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: SelectPreference<string>; pref: SelectPreference<string>;

View file

@ -1,14 +1,18 @@
<template> <template>
<Base :pref="pref" :name="name" v-slot="{ setValue, value }"> <TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
<Input placeholder="Content here..." :model-value="value" @update:model-value="setValue" /> <Input
</Base> placeholder="Content here..."
:model-value="value"
@update:model-value="setValue"
/>
</TypeBase>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { TextPreference } from "../types"; import type { TextPreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: TextPreference; pref: TextPreference;

View file

@ -1,14 +1,22 @@
<template> <template>
<div class="grid grid-cols-[minmax(0,1fr)_auto] gap-2 hover:bg-muted/40 duration-75 p-4"> <div
class="grid grid-cols-[minmax(0,1fr)_auto] gap-2 hover:bg-muted/40 duration-75 p-4"
>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<h3 class="text-sm font-semibold tracking-tight">{{ pref.options.name }}</h3> <h3 class="text-sm font-semibold tracking-tight">
<small v-if="pref.options.description" class="text-xs font-medium leading-none text-muted-foreground">{{ {{ pref.options.name }}
pref.options.description }}</small> </h3>
<small
v-if="pref.options.description"
class="text-xs font-medium leading-none text-muted-foreground"
>{{
pref.options.description }}</small
>
</div> </div>
<div class="flex items-center justify-end"> <div class="flex items-center justify-end">
<slot :value="value" :set-value="setValue" /> <slot :value="value" :set-value="setValue"/>
</div> </div>
<slot name="extra" :value="value" :set-value="setValue" /> <slot name="extra" :value="value" :set-value="setValue"/>
</div> </div>
</template> </template>

View file

@ -1,19 +1,21 @@
<template> <template>
<Collapsible as-child> <Collapsible as-child>
<Base :pref="pref" :name="name"> <TypeBase :pref="pref" :name="name">
<template #default> <template #default>
<CollapsibleTrigger as-child> <CollapsibleTrigger as-child>
<Button variant="outline"> <Button variant="outline">Edit URL</Button>
Edit URL </CollapsibleTrigger>
</Button> </template>
</CollapsibleTrigger> <template #extra="{ setValue, value }">
</template> <CollapsibleContent class="col-span-2 mt-2">
<template #extra="{ setValue, value }"> <UrlInput
<CollapsibleContent class="col-span-2 mt-2"> placeholder="Type URL or domain here..."
<UrlInput placeholder="Type URL or domain here..." :model-value="value" @update:model-value="setValue" /> :model-value="value"
</CollapsibleContent> @update:model-value="setValue"
</template> />
</Base> </CollapsibleContent>
</template>
</TypeBase>
</Collapsible> </Collapsible>
</template> </template>
@ -27,7 +29,7 @@ import {
import { Input, UrlInput } from "~/components/ui/input"; import { Input, UrlInput } from "~/components/ui/input";
import type { preferences as prefs } from "../preferences"; import type { preferences as prefs } from "../preferences";
import type { TextPreference } from "../types"; import type { TextPreference } from "../types";
import Base from "./base.vue"; import TypeBase from "./type-base.vue";
const { pref, name } = defineProps<{ const { pref, name } = defineProps<{
pref: TextPreference; pref: TextPreference;

View file

@ -1,9 +1,7 @@
<template> <template>
<Avatar :class="['rounded-md bg-secondary']"> <Avatar :class="['rounded-md bg-secondary']">
<AvatarFallback v-if="name"> <AvatarFallback v-if="name">{{ getInitials(name) }}</AvatarFallback>
{{ getInitials(name) }} <AvatarImage v-if="src" :src="src" :alt="`${name}'s avatar`"/>
</AvatarFallback>
<AvatarImage v-if="src" :src="src" :alt="`${name}'s avatar`" />
</Avatar> </Avatar>
</template> </template>

View file

@ -1,60 +1,72 @@
<template> <template>
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger as-child> <DropdownMenuTrigger as-child>
<slot /> <slot/>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent class="min-w-56"> <DropdownMenuContent class="min-w-56">
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem as="button" @click="copyText(account.username)"> <DropdownMenuItem
<AtSign /> as="button"
@click="copyText(account.username)"
>
<AtSign/>
{{ m.cool_dark_tapir_belong() }} {{ m.cool_dark_tapir_belong() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(JSON.stringify(account, null, 4))"> <DropdownMenuItem
<Code /> as="button"
@click="copyText(JSON.stringify(account, null, 4))"
>
<Code/>
{{ m.yummy_moving_scallop_sail() }} {{ m.yummy_moving_scallop_sail() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(account.id)"> <DropdownMenuItem as="button" @click="copyText(account.id)">
<Hash /> <Hash/>
{{ m.sunny_zany_jellyfish_pop() }} {{ m.sunny_zany_jellyfish_pop() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator /> <DropdownMenuSeparator/>
<DropdownMenuGroup> <DropdownMenuGroup>
<DropdownMenuItem as="button" @click="copyText(url)"> <DropdownMenuItem as="button" @click="copyText(url)">
<Link /> <Link/>
{{ m.ago_new_pelican_drip() }} {{ m.ago_new_pelican_drip() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="copyText(account.url)"> <DropdownMenuItem as="button" @click="copyText(account.url)">
<Link /> <Link/>
{{ m.solid_witty_zebra_walk() }} {{ m.solid_witty_zebra_walk() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="account.url"> <DropdownMenuItem
<ExternalLink /> as="a"
v-if="isRemote"
target="_blank"
rel="noopener noreferrer"
:href="account.url"
>
<ExternalLink/>
{{ m.active_trite_lark_inspire() }} {{ m.active_trite_lark_inspire() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe" /> <DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe"/>
<DropdownMenuGroup v-if="authStore.isSignedIn && !isMe"> <DropdownMenuGroup v-if="authStore.isSignedIn && !isMe">
<DropdownMenuItem as="button" @click="muteUser(account.id)"> <DropdownMenuItem as="button" @click="muteUser(account.id)">
<VolumeX /> <VolumeX/>
{{ m.spare_wild_mole_intend() }} {{ m.spare_wild_mole_intend() }}
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem as="button" @click="blockUser(account.id)"> <DropdownMenuItem as="button" @click="blockUser(account.id)">
<Ban /> <Ban/>
{{ m.misty_soft_sparrow_vent() }} {{ m.misty_soft_sparrow_vent() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="isRemote" /> <DropdownMenuSeparator v-if="isRemote"/>
<DropdownMenuGroup v-if="isRemote"> <DropdownMenuGroup v-if="isRemote">
<DropdownMenuItem as="button" @click="refresh"> <DropdownMenuItem as="button" @click="refresh">
<RefreshCw /> <RefreshCw/>
{{ m.slow_chunky_chipmunk_hush() }} {{ m.slow_chunky_chipmunk_hush() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>
<DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe" /> <DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe"/>
<DropdownMenuGroup v-if="authStore.isSignedIn && !isMe"> <DropdownMenuGroup v-if="authStore.isSignedIn && !isMe">
<DropdownMenuItem as="button" :disabled="true"> <DropdownMenuItem as="button" :disabled="true">
<Flag /> <Flag/>
{{ m.great_few_jaguar_rise() }} {{ m.great_few_jaguar_rise() }}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuGroup> </DropdownMenuGroup>

View file

@ -2,8 +2,8 @@
<Tooltip> <Tooltip>
<TooltipTrigger :as-child="true"> <TooltipTrigger :as-child="true">
<Badge variant="default" class="gap-1"> <Badge variant="default" class="gap-1">
<BadgeCheck v-if="verified" /> <BadgeCheck v-if="verified"/>
<img v-else-if="icon" :src="icon" alt="" class="size-4 rounded" /> <img v-else-if="icon" :src="icon" alt="" class="size-4 rounded">
{{ name }} {{ name }}
</Badge> </Badge>
</TooltipTrigger> </TooltipTrigger>

View file

@ -1,5 +1,7 @@
<template> <template>
<Row class="gap-2" wrap <Row
class="gap-2"
wrap
v-if="isDeveloper || account.bot || roles.length > 0" v-if="isDeveloper || account.bot || roles.length > 0"
> >
<ProfileBadge <ProfileBadge

View file

@ -1,10 +1,16 @@
<template> <template>
<Col class="gap-y-4"> <Column class="gap-y-4">
<Col v-for="field in fields" :key="field.name" class="gap-1 break-words"> <Column
<HeadingSmall v-render-emojis="emojis">{{ field.name }}</HeadingSmall> v-for="field in fields"
<Html v-html="field.value" v-render-emojis="emojis" /> :key="field.name"
</Col> class="gap-1 break-words"
</Col> >
<HeadingSmall v-render-emojis="emojis">
{{ field.name }}
</HeadingSmall>
<Html v-html="field.value" v-render-emojis="emojis"/>
</Column>
</Column>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -12,7 +18,7 @@ import type { CustomEmoji, Field } from "@versia/client/schemas";
import type { z } from "zod"; import type { z } from "zod";
import HeadingSmall from "~/components/typography/headings/small.vue"; import HeadingSmall from "~/components/typography/headings/small.vue";
import Html from "../typography/html.vue"; import Html from "../typography/html.vue";
import Col from "../typography/layout/col.vue"; import Column from "../typography/layout/col.vue";
defineProps<{ defineProps<{
fields: z.infer<typeof Field>[]; fields: z.infer<typeof Field>[];

View file

@ -6,7 +6,7 @@
:src="header" :src="header"
alt="" alt=""
class="object-cover w-full h-full" class="object-cover w-full h-full"
/> >
<!-- Shadow overlay at the bottom --> <!-- Shadow overlay at the bottom -->
<div <div
class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40" class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40"
@ -15,11 +15,7 @@
<div <div
class="absolute bottom-0 translate-y-1/3 left-4 flex flex-row items-start gap-2" class="absolute bottom-0 translate-y-1/3 left-4 flex flex-row items-start gap-2"
> >
<Avatar <Avatar class="size-32 border" :src="avatar" :name="displayName"/>
class="size-32 border"
:src="avatar"
:name="displayName"
/>
</div> </div>
</CardHeader> </CardHeader>
</template> </template>

View file

@ -1,7 +1,11 @@
<template> <template>
<Button variant="secondary" :disabled="isLoading || relationship?.requested" v-if="!isMe && authStore.isSignedIn" <Button
@click="relationship?.following ? unfollow() : follow()"> variant="secondary"
<Loader v-if="isLoading" class="animate-spin" /> :disabled="isLoading || relationship?.requested"
v-if="!isMe && authStore.isSignedIn"
@click="relationship?.following ? unfollow() : follow()"
>
<Loader v-if="isLoading" class="animate-spin"/>
<span v-else> <span v-else>
{{ {{
relationship?.following relationship?.following

View file

@ -1,24 +1,24 @@
<template> <template>
<Row class="gap-2 w-full justify-around"> <Row class="gap-2 w-full justify-around">
<Col centered> <Column centered>
<Bold>{{ noteCount }}</Bold> <Bold>{{ noteCount }}</Bold>
<Small muted>{{ m.real_gray_stork_seek() }}</Small> <Small muted>{{ m.real_gray_stork_seek() }}</Small>
</Col> </Column>
<Col centered> <Column centered>
<Bold>{{ followerCount }}</Bold> <Bold>{{ followerCount }}</Bold>
<Small muted>{{ m.teal_helpful_parakeet_hike() }}</Small> <Small muted>{{ m.teal_helpful_parakeet_hike() }}</Small>
</Col> </Column>
<Col centered> <Column centered>
<Bold>{{ followingCount }}</Bold> <Bold>{{ followingCount }}</Bold>
<Small muted>{{ m.aloof_royal_samuel_startle() }}</Small> <Small muted>{{ m.aloof_royal_samuel_startle() }}</Small>
</Col> </Column>
</Row> </Row>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as m from "~~/paraglide/messages.js"; import * as m from "~~/paraglide/messages.js";
import Bold from "../typography/bold.vue"; import Bold from "../typography/bold.vue";
import Col from "../typography/layout/col.vue"; import Column from "../typography/layout/col.vue";
import Row from "../typography/layout/row.vue"; import Row from "../typography/layout/row.vue";
import Small from "../typography/small.vue"; import Small from "../typography/small.vue";

View file

@ -1,33 +1,45 @@
<template> <template>
<Card class="gap-4"> <Card class="gap-4">
<ProfileHeader :header="account.header" :avatar="account.avatar" :display-name="account.display_name" /> <ProfileHeader
:header="account.header"
:avatar="account.avatar"
:display-name="account.display_name"
/>
<Row class="justify-end gap-2"> <Row class="justify-end gap-2">
<ProfileRelationshipActions :account="account" /> <ProfileRelationshipActions :account="account"/>
<ProfileActions :account="account"> <ProfileActions :account="account">
<Button variant="secondary" size="icon"> <Button variant="secondary" size="icon">
<Ellipsis /> <Ellipsis/>
</Button> </Button>
</ProfileActions> </ProfileActions>
</Row> </Row>
<Col class="justify-center"> <Column class="justify-center">
<Text class="font-bold" v-render-emojis="account.emojis"> <Text class="font-bold" v-render-emojis="account.emojis">
{{ account.display_name }} {{ account.display_name }}
</Text> </Text>
<Address :username="username" :domain="domain" /> <Address :username="username" :domain="domain"/>
</Col> </Column>
<ProfileBadges :account="account" /> <ProfileBadges :account="account"/>
<Html v-html="account.note" v-render-emojis="account.emojis" /> <Html v-html="account.note" v-render-emojis="account.emojis"/>
<Separator /> <Separator/>
<ProfileFields v-if="account.fields.length > 0" :fields="account.fields" :emojis="account.emojis" /> <ProfileFields
<Separator v-if="account.fields.length > 0" /> v-if="account.fields.length > 0"
:fields="account.fields"
:emojis="account.emojis"
/>
<Separator v-if="account.fields.length > 0"/>
<Row> <Row>
<HeadingSmall class="flex items-center gap-1"> <HeadingSmall class="flex items-center gap-1">
<CalendarDays class="size-4" /> {{ formattedCreationDate }} <CalendarDays class="size-4"/>
{{ formattedCreationDate }}
</HeadingSmall> </HeadingSmall>
</Row> </Row>
<Separator /> <Separator/>
<ProfileStats :follower-count="account.followers_count" :following-count="account.following_count" <ProfileStats
:note-count="account.statuses_count" /> :follower-count="account.followers_count"
:following-count="account.following_count"
:note-count="account.statuses_count"
/>
</Card> </Card>
</template> </template>
@ -41,7 +53,7 @@ import { Separator } from "~/components/ui/separator";
import { getLocale } from "~~/paraglide/runtime"; import { getLocale } from "~~/paraglide/runtime";
import HeadingSmall from "../typography/headings/small.vue"; import HeadingSmall from "../typography/headings/small.vue";
import Html from "../typography/html.vue"; import Html from "../typography/html.vue";
import Col from "../typography/layout/col.vue"; import Column from "../typography/layout/col.vue";
import Row from "../typography/layout/row.vue"; import Row from "../typography/layout/row.vue";
import Text from "../typography/text.vue"; import Text from "../typography/text.vue";
import Address from "./address.vue"; import Address from "./address.vue";

View file

@ -5,7 +5,7 @@
:src="account.header" :src="account.header"
alt="" alt=""
class="object-cover w-full h-full" class="object-cover w-full h-full"
/> >
<!-- Shadow overlay at the bottom --> <!-- Shadow overlay at the bottom -->
<div <div
class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40" class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40"
@ -26,14 +26,14 @@
<Text class="font-bold" v-render-emojis="account.emojis"> <Text class="font-bold" v-render-emojis="account.emojis">
{{ account.display_name }} {{ account.display_name }}
</Text> </Text>
<Address :username="username" :domain="domain" /> <Address :username="username" :domain="domain"/>
</div> </div>
<Html <Html
v-html="account.note" v-html="account.note"
v-render-emojis="account.emojis" v-render-emojis="account.emojis"
class="mt-4 max-h-72 overflow-y-auto" class="mt-4 max-h-72 overflow-y-auto"
/> />
<Separator v-if="account.fields.length > 0" class="mt-4" /> <Separator v-if="account.fields.length > 0" class="mt-4"/>
<ProfileFields <ProfileFields
v-if="account.fields.length > 0" v-if="account.fields.length > 0"
:fields="account.fields" :fields="account.fields"

View file

@ -3,12 +3,16 @@
class="flex-row gap-2 p-2 truncate items-center" class="flex-row gap-2 p-2 truncate items-center"
:class="naked ? 'p-0 bg-transparent ring-0 border-none shadow-none' : ''" :class="naked ? 'p-0 bg-transparent ring-0 border-none shadow-none' : ''"
> >
<Avatar :src="account.avatar" :name="account.display_name" class="size-10" /> <Avatar
:src="account.avatar"
:name="account.display_name"
class="size-10"
/>
<CardContent class="leading-tight"> <CardContent class="leading-tight">
<Text class="font-semibold" v-render-emojis="account.emojis"> <Text class="font-semibold" v-render-emojis="account.emojis">
{{ account.display_name }} {{ account.display_name }}
</Text> </Text>
<Address :username="account.username" :domain="domain" /> <Address :username="account.username" :domain="domain"/>
</CardContent> </CardContent>
</Card> </Card>
</template> </template>

View file

@ -1,7 +1,7 @@
<template> <template>
<Dialog> <Dialog>
<DialogTrigger as-child> <DialogTrigger as-child>
<slot /> <slot/>
</DialogTrigger> </DialogTrigger>
<DialogContent> <DialogContent>
<DialogHeader> <DialogHeader>
@ -11,16 +11,31 @@
</DialogDescription> </DialogDescription>
</DialogHeader> </DialogHeader>
<div v-if="authStore.identities.length > 0" class="grid gap-4 py-2"> <div v-if="authStore.identities.length > 0" class="grid gap-4 py-2">
<div v-for="identity of authStore.identities" :key="identity.account.id" <div
class="grid grid-cols-[1fr_auto] has-[>[data-switch]]:grid-cols-[1fr_auto_auto] gap-2"> v-for="identity of authStore.identities"
<TinyCard :account="identity.account" :domain="identity.instance.domain" naked /> :key="identity.account.id"
<Button data-switch v-if="authStore.identity?.id !== identity.id" class="grid grid-cols-[1fr_auto] has-[>[data-switch]]:grid-cols-[1fr_auto_auto] gap-2"
@click="authStore.setActiveIdentity(identity.id)" variant="outline"> >
<TinyCard
:account="identity.account"
:domain="identity.instance.domain"
naked
/>
<Button
data-switch
v-if="authStore.identity?.id !== identity.id"
@click="authStore.setActiveIdentity(identity.id)"
variant="outline"
>
Switch Switch
</Button> </Button>
<Button @click="signOutAction(identity.id)" variant="outline" size="icon" <Button
:title="m.sharp_big_mallard_reap()"> @click="signOutAction(identity.id)"
<LogOut /> variant="outline"
size="icon"
:title="m.sharp_big_mallard_reap()"
>
<LogOut/>
</Button> </Button>
</div> </div>
</div> </div>
@ -31,11 +46,11 @@
</div> </div>
<DialogFooter> <DialogFooter>
<Button :as="NuxtLink" href="/register" variant="outline"> <Button :as="NuxtLink" href="/register" variant="outline">
<UserPlus /> <UserPlus/>
{{ m.honest_few_baboon_pop() }} {{ m.honest_few_baboon_pop() }}
</Button> </Button>
<Button @click="signInAction"> <Button @click="signInAction">
<LogIn /> <LogIn/>
{{ m.sunny_pink_hyena_walk() }} {{ m.sunny_pink_hyena_walk() }}
</Button> </Button>
</DialogFooter> </DialogFooter>

View file

@ -26,32 +26,54 @@ const authStore = useAuthStore();
<SidebarMenu class="gap-3"> <SidebarMenu class="gap-3">
<SidebarMenuItem> <SidebarMenuItem>
<AccountManager> <AccountManager>
<SidebarMenuButton v-if="authStore.account && authStore.instance" size="lg"> <SidebarMenuButton
<TinyCard :account="authStore.account" :domain="authStore.instance.domain" naked /> v-if="authStore.account && authStore.instance"
<ChevronsUpDown class="ml-auto size-4" /> size="lg"
>
<TinyCard
:account="authStore.account"
:domain="authStore.instance.domain"
naked
/>
<ChevronsUpDown class="ml-auto size-4"/>
</SidebarMenuButton> </SidebarMenuButton>
<SidebarMenuButton v-else> <SidebarMenuButton v-else>
<UserPlus /> <UserPlus/>
{{ m.sunny_pink_hyena_walk() }} {{ m.sunny_pink_hyena_walk() }}
<ChevronsUpDown class="ml-auto size-4" /> <ChevronsUpDown class="ml-auto size-4"/>
</SidebarMenuButton> </SidebarMenuButton>
</AccountManager> </AccountManager>
</SidebarMenuItem> </SidebarMenuItem>
<SidebarMenuItem class="flex flex-col gap-2"> <SidebarMenuItem class="flex flex-col gap-2">
<Button v-if="authStore.isSignedIn" variant="default" size="lg" class="w-full group-data-[collapsible=icon]:px-4" <Button
@click="useEvent('composer:open')"> v-if="authStore.isSignedIn"
<Pen /> variant="default"
size="lg"
class="w-full group-data-[collapsible=icon]:px-4"
@click="useEvent('composer:open')"
>
<Pen/>
<span class="group-data-[collapsible=icon]:hidden"> <span class="group-data-[collapsible=icon]:hidden">
{{ m.salty_aloof_turkey_nudge() }} {{ m.salty_aloof_turkey_nudge() }}
</span> </span>
</Button> </Button>
<Button v-if="authStore.isSignedIn" size="lg" variant="secondary" @click="useEvent('preferences:open')"> <Button
<Cog /> v-if="authStore.isSignedIn"
size="lg"
variant="secondary"
@click="useEvent('preferences:open')"
>
<Cog/>
Preferences Preferences
</Button> </Button>
<Button v-if="$pwa?.needRefresh" variant="destructive" size="lg" <Button
class="w-full group-data-[collapsible=icon]:px-4" @click="$pwa?.updateServiceWorker(true)"> v-if="$pwa?.needRefresh"
<DownloadCloud /> variant="destructive"
size="lg"
class="w-full group-data-[collapsible=icon]:px-4"
@click="$pwa?.updateServiceWorker(true)"
>
<DownloadCloud/>
<span class="group-data-[collapsible=icon]:hidden"> <span class="group-data-[collapsible=icon]:hidden">
{{ m.quaint_low_felix_pave() }} {{ m.quaint_low_felix_pave() }}
</span> </span>

View file

@ -14,7 +14,10 @@ const authStore = useAuthStore();
<SidebarMenu> <SidebarMenu>
<SidebarMenuItem> <SidebarMenuItem>
<NuxtLink href="/"> <NuxtLink href="/">
<InstanceSmallCard v-if="authStore.instance" :instance="authStore.instance" /> <InstanceSmallCard
v-if="authStore.instance"
:instance="authStore.instance"
/>
</NuxtLink> </NuxtLink>
</SidebarMenuItem> </SidebarMenuItem>
</SidebarMenu> </SidebarMenu>

View file

@ -1,11 +1,13 @@
<template> <template>
<Sidebar collapsible="offcanvas"> <Sidebar collapsible="offcanvas">
<InstanceHeader /> <InstanceHeader/>
<SidebarContent> <SidebarContent>
<SidebarGroup> <SidebarGroup>
<SidebarGroupLabel>{{ <SidebarGroupLabel>
{{
m.trite_real_sawfish_drum() m.trite_real_sawfish_drum()
}}</SidebarGroupLabel> }}
</SidebarGroupLabel>
<NavItems <NavItems
:items=" :items="
sidebarConfig.other.filter((i) => sidebarConfig.other.filter((i) =>
@ -15,8 +17,8 @@
/> />
</SidebarGroup> </SidebarGroup>
</SidebarContent> </SidebarContent>
<FooterActions /> <FooterActions/>
<SidebarRail /> <SidebarRail/>
</Sidebar> </Sidebar>
</template> </template>

View file

@ -32,7 +32,7 @@ defineProps<{
<SidebarMenuItem> <SidebarMenuItem>
<CollapsibleTrigger as-child> <CollapsibleTrigger as-child>
<SidebarMenuButton :tooltip="item.title"> <SidebarMenuButton :tooltip="item.title">
<component :is="item.icon" /> <component :is="item.icon"/>
{{ item.title }} {{ item.title }}
<ChevronRight <ChevronRight
class="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180" class="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180"

View file

@ -16,7 +16,7 @@ defineProps<{
<SidebarMenuItem v-for="item in items" :key="item.title"> <SidebarMenuItem v-for="item in items" :key="item.title">
<SidebarMenuButton as-child> <SidebarMenuButton as-child>
<NuxtLink :href="item.url"> <NuxtLink :href="item.url">
<component :is="item.icon" /> <component :is="item.icon"/>
<span>{{ item.title }}</span> <span>{{ item.title }}</span>
</NuxtLink> </NuxtLink>
</SidebarMenuButton> </SidebarMenuButton>

View file

@ -6,7 +6,7 @@
style="--sidebar-width: 24rem; --sidebar-width-mobile: 18rem" style="--sidebar-width: 24rem; --sidebar-width-mobile: 18rem"
> >
<SidebarContent class="overflow-y-auto *:p-2 *:gap-2"> <SidebarContent class="overflow-y-auto *:p-2 *:gap-2">
<NotificationsTimeline /> <NotificationsTimeline/>
</SidebarContent> </SidebarContent>
</Sidebar> </Sidebar>
</template> </template>

View file

@ -14,15 +14,18 @@ const authStore = useAuthStore();
</script> </script>
<template> <template>
<LeftSidebar /> <LeftSidebar/>
<main class="grow h-dvh overflow-y-auto"> <main class="grow h-dvh overflow-y-auto">
<header <header
v-if="showTimelines" v-if="showTimelines"
class="flex h-16 items-center bg-background/80 backdrop-blur-2xl sticky top-0 inset-x-0 z-10 p-4" class="flex h-16 items-center bg-background/80 backdrop-blur-2xl sticky top-0 inset-x-0 z-10 p-4"
> >
<Timelines /> <Timelines/>
</header> </header>
<slot /> <slot/>
</main> </main>
<RightSidebar v-if="authStore.isSignedIn" v-show="preferences.display_notifications_sidebar" /> <RightSidebar
v-if="authStore.isSignedIn"
v-show="preferences.display_notifications_sidebar"
/>
</template> </template>

View file

@ -1,7 +1,15 @@
<template> <template>
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd" <Timeline
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem" type="status"
:update-item="updateItem" /> :items="items"
:is-loading="isLoading"
:has-reached-end="hasReachedEnd"
:error="error"
:load-next="loadNext"
:load-prev="loadPrev"
:remove-item="removeItem"
:update-item="updateItem"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,7 +1,15 @@
<template> <template>
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd" <Timeline
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem" type="status"
:update-item="updateItem" /> :items="items"
:is-loading="isLoading"
:has-reached-end="hasReachedEnd"
:error="error"
:load-next="loadNext"
:load-prev="loadPrev"
:remove-item="removeItem"
:update-item="updateItem"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,7 +1,15 @@
<template> <template>
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd" <Timeline
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem" type="status"
:update-item="updateItem" /> :items="items"
:is-loading="isLoading"
:has-reached-end="hasReachedEnd"
:error="error"
:load-next="loadNext"
:load-prev="loadPrev"
:remove-item="removeItem"
:update-item="updateItem"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,7 +1,15 @@
<template> <template>
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd" <Timeline
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem" type="status"
:update-item="updateItem" /> :items="items"
:is-loading="isLoading"
:has-reached-end="hasReachedEnd"
:error="error"
:load-next="loadNext"
:load-prev="loadPrev"
:remove-item="removeItem"
:update-item="updateItem"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,7 +1,15 @@
<template> <template>
<Timeline type="notification" :items="items" :is-loading="isLoading" <Timeline
:has-reached-end="hasReachedEnd" :error="error" :load-next="loadNext" :load-prev="loadPrev" type="notification"
:remove-item="removeItem" :update-item="updateItem" /> :items="items"
:is-loading="isLoading"
:has-reached-end="hasReachedEnd"
:error="error"
:load-next="loadNext"
:load-prev="loadPrev"
:remove-item="removeItem"
:update-item="updateItem"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,7 +1,15 @@
<template> <template>
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd" <Timeline
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem" type="status"
:update-item="updateItem" /> :items="items"
:is-loading="isLoading"
:has-reached-end="hasReachedEnd"
:error="error"
:load-next="loadNext"
:load-prev="loadPrev"
:remove-item="removeItem"
:update-item="updateItem"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,6 +1,11 @@
<template> <template>
<component :is="itemComponent" :note="type === 'status' ? item : undefined" :notification="type === 'notification' ? item : (undefined as any)" @update="$emit('update', $event)" <component
@delete="$emit('delete', item?.id)" /> :is="itemComponent"
:note="type === 'status' ? item : undefined"
:notification="type === 'notification' ? item : (undefined as any)"
@update="$emit('update', $event)"
@delete="$emit('delete', item?.id)"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -1,5 +1,5 @@
<template> <template>
<slot /> <slot/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View file

@ -2,27 +2,25 @@
<div <div
role="status" role="status"
class="flex flex-col gap-4 items-center *:max-w-2xl *:w-full p-4" class="flex flex-col gap-4 items-center *:max-w-2xl *:w-full p-4"
> >
<TimelineItem <TimelineItem
:type="type" :type="type"
v-for="item in items" v-for="item in items"
:key="item.id" :key="item.id"
:item="item" :item="item"
@update="updateItem" @update="updateItem"
@delete="removeItem" @delete="removeItem"
/> />
<Spinner v-if="isLoading" /> <Spinner v-if="isLoading"/>
<div v-if="error" class="timeline-error"> <div v-if="error" class="timeline-error">{{ error.message }}</div>
{{ error.message }}
</div>
<!-- If there are some posts, but the user scrolled to the end --> <!-- If there are some posts, but the user scrolled to the end -->
<ReachedEnd v-if="hasReachedEnd && items.length > 0" /> <ReachedEnd v-if="hasReachedEnd && items.length > 0"/>
<!-- If there are no posts at all --> <!-- If there are no posts at all -->
<NoPosts v-else-if="hasReachedEnd && items.length === 0" /> <NoPosts v-else-if="hasReachedEnd && items.length === 0"/>
<div v-else-if="!preferences.infinite_scroll" class="py-10 px-4"> <div v-else-if="!preferences.infinite_scroll" class="py-10 px-4">
<Button <Button

Some files were not shown because too many files have changed in this diff Show more