mirror of
https://github.com/versia-pub/frontend.git
synced 2026-01-26 04:16:02 +01:00
style: 🎨 Format code with Biome
This commit is contained in:
parent
7ff9d2302a
commit
3627ac0ef8
17
app/app.vue
17
app/app.vue
|
|
@ -1,15 +1,13 @@
|
|||
<template>
|
||||
<TooltipProvider>
|
||||
<Component is="style">
|
||||
{{ preferences.custom_css }}
|
||||
</Component>
|
||||
<NuxtPwaAssets />
|
||||
<Component is="style">{{ preferences.custom_css }}</Component>
|
||||
<NuxtPwaAssets/>
|
||||
<NuxtLayout>
|
||||
<NuxtPage />
|
||||
<NuxtPage/>
|
||||
</NuxtLayout>
|
||||
<ConfirmationModal />
|
||||
<ConfirmationModal/>
|
||||
<!-- pointer-events-auto fixes https://github.com/unovue/shadcn-vue/issues/462 -->
|
||||
<Toaster class="pointer-events-auto" />
|
||||
<Toaster class="pointer-events-auto"/>
|
||||
</TooltipProvider>
|
||||
</template>
|
||||
|
||||
|
|
@ -91,7 +89,10 @@ body {
|
|||
|
||||
html.theme-changing * {
|
||||
/* 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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,69 +1,105 @@
|
|||
<template>
|
||||
<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>
|
||||
|
||||
|
||||
<InputGroup class="p-1">
|
||||
<InputGroupAddon v-if="store.sensitive" align="block-start" class="pt-3">
|
||||
<Input v-model:model-value="store.contentWarning" placeholder="Put your content warning here" />
|
||||
<InputGroupAddon
|
||||
v-if="store.sensitive"
|
||||
align="block-start"
|
||||
class="pt-3"
|
||||
>
|
||||
<Input
|
||||
v-model:model-value="store.contentWarning"
|
||||
placeholder="Put your content warning here"
|
||||
/>
|
||||
</InputGroupAddon>
|
||||
|
||||
<EditorContent data-slot="input-group-control" @paste-files="uploadFiles" v-model:content="store.content"
|
||||
v-model:raw-content="store.rawContent" :placeholder="getRandomSplash()"
|
||||
<EditorContent
|
||||
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"
|
||||
: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">
|
||||
<Files v-model:files="store.files" :composer-key="composerKey" />
|
||||
<InputGroupAddon
|
||||
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 align="block-end">
|
||||
<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">
|
||||
<LetterText v-if="store.contentType === 'text/html'" />
|
||||
<Type v-else />
|
||||
<LetterText v-if="store.contentType === 'text/html'"/>
|
||||
<Type v-else/>
|
||||
</InputGroupButton>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="text/plain">
|
||||
Plain text
|
||||
</SelectItem>
|
||||
<SelectItem value="text/html">
|
||||
Rich text
|
||||
</SelectItem>
|
||||
<SelectItem value="text/plain">Plain text</SelectItem>
|
||||
<SelectItem value="text/html">Rich text</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<VisibilityPicker v-model:visibility="store.visibility">
|
||||
<InputGroupButton variant="ghost" size="icon-sm" :disabled="store.relation?.type === 'edit'">
|
||||
<component :is="visibilities[store.visibility].icon" />
|
||||
<InputGroupButton
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
:disabled="store.relation?.type === 'edit'"
|
||||
>
|
||||
<component :is="visibilities[store.visibility].icon"/>
|
||||
</InputGroupButton>
|
||||
</VisibilityPicker>
|
||||
<InputGroupButton variant="ghost" size="icon-sm" @click="fileInput?.click()">
|
||||
<FilePlus2 />
|
||||
<InputGroupButton
|
||||
variant="ghost"
|
||||
size="icon-sm"
|
||||
@click="fileInput?.click()"
|
||||
>
|
||||
<FilePlus2/>
|
||||
</InputGroupButton>
|
||||
<Toggle size="sm" v-model="store.sensitive">
|
||||
<TriangleAlert />
|
||||
<TriangleAlert/>
|
||||
</Toggle>
|
||||
<InputGroupText :class="['ml-auto', charactersLeft < 0 && 'text-destructive']">
|
||||
<InputGroupText
|
||||
:class="['ml-auto', charactersLeft < 0 && 'text-destructive']"
|
||||
>
|
||||
{{ charactersLeft.toLocaleString(getLocale(), {
|
||||
maximumFractionDigits: 2,
|
||||
notation: 'compact',
|
||||
compactDisplay: 'short',
|
||||
}) }}
|
||||
</InputGroupText>
|
||||
<Separator orientation="vertical" class="h-4!" />
|
||||
<InputGroupButton variant="default" size="icon-sm" :disabled="store.sending || !store.canSend"
|
||||
@click="send">
|
||||
<Spinner v-if="store.sending" />
|
||||
<ArrowUp v-else />
|
||||
<Separator orientation="vertical" class="h-4!"/>
|
||||
<InputGroupButton
|
||||
variant="default"
|
||||
size="icon-sm"
|
||||
:disabled="store.sending || !store.canSend"
|
||||
@click="send"
|
||||
>
|
||||
<Spinner v-if="store.sending"/>
|
||||
<ArrowUp v-else/>
|
||||
<span class="sr-only">Send</span>
|
||||
</InputGroupButton>
|
||||
</InputGroupAddon>
|
||||
</InputGroup>
|
||||
|
||||
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple />
|
||||
<input
|
||||
type="file"
|
||||
ref="fileInput"
|
||||
@change="uploadFileFromEvent"
|
||||
class="hidden"
|
||||
multiple
|
||||
>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -101,7 +101,7 @@ const relation = ref(
|
|||
: m.brief_cool_capybara_fear()
|
||||
}}
|
||||
</DialogDescription>
|
||||
<Composer :relation="relation ?? undefined" />
|
||||
<Composer :relation="relation ?? undefined"/>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -5,26 +5,37 @@
|
|||
: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"
|
||||
>
|
||||
<img 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" />
|
||||
<img
|
||||
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
|
||||
v-if="file.file && !(file.uploading || file.updating)"
|
||||
class="absolute bottom-1 right-1"
|
||||
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>
|
||||
<DropdownMenuContent class="min-w-48">
|
||||
<DropdownMenuLabel v-if="file.file">{{ file.file.name }}</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuLabel v-if="file.file">
|
||||
{{ file.file.name }}
|
||||
</DropdownMenuLabel>
|
||||
<DropdownMenuSeparator/>
|
||||
<DropdownMenuItem @click="editCaption">
|
||||
<Captions />
|
||||
<Captions/>
|
||||
Add caption
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSeparator/>
|
||||
<DropdownMenuItem @click="emit('remove')">
|
||||
<Delete />
|
||||
<Delete/>
|
||||
Remove
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
<template>
|
||||
<FilePreview v-for="(file, index) in files" :key="file.apiId" :file="file" @update:file="files[index] = $event" :composer-key="composerKey"
|
||||
@remove="files.splice(index, 1)" />
|
||||
<FilePreview
|
||||
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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,19 @@
|
|||
<template>
|
||||
<Select v-model:model-value="visibility">
|
||||
<SelectTrigger as-child disable-default-classes disable-select-icon>
|
||||
<slot />
|
||||
<slot/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem v-for="(v, k) in visibilities" :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" />
|
||||
<SelectItem
|
||||
v-for="(v, k) in visibilities"
|
||||
: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">
|
||||
<span class="font-semibold">{{ v.name }}</span>
|
||||
<span>{{ v.text }}</span>
|
||||
|
|
|
|||
|
|
@ -69,30 +69,31 @@ watch(active, (value) => {
|
|||
|
||||
<template>
|
||||
<BubbleMenu :editor="editor">
|
||||
<ToggleGroup type="multiple"
|
||||
<ToggleGroup
|
||||
type="multiple"
|
||||
v-model="active"
|
||||
class="bg-popover rounded-md"
|
||||
>
|
||||
<ToggleGroupItem value="bold">
|
||||
<BoldIcon />
|
||||
<BoldIcon/>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="italic">
|
||||
<ItalicIcon />
|
||||
<ItalicIcon/>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="underline">
|
||||
<UnderlineIcon />
|
||||
<UnderlineIcon/>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="code">
|
||||
<CurlyBracesIcon />
|
||||
<CurlyBracesIcon/>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="strike">
|
||||
<StrikethroughIcon />
|
||||
<StrikethroughIcon/>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="subscript">
|
||||
<SubscriptIcon />
|
||||
<SubscriptIcon/>
|
||||
</ToggleGroupItem>
|
||||
<ToggleGroupItem value="superscript">
|
||||
<SuperscriptIcon />
|
||||
<SuperscriptIcon/>
|
||||
</ToggleGroupItem>
|
||||
</ToggleGroup>
|
||||
</BubbleMenu>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<template>
|
||||
<BubbleMenu :editor="editor" />
|
||||
<EditorContent :editor="editor"
|
||||
v-bind="$attrs"
|
||||
:class="$style.content" />
|
||||
<BubbleMenu :editor="editor"/>
|
||||
<EditorContent :editor="editor" v-bind="$attrs" :class="$style.content"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -122,7 +120,7 @@ onUnmounted(() => {
|
|||
@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;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,23 @@
|
|||
<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>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<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">
|
||||
<img class="h-[1lh] align-middle inline hover:scale-110 transition-transform duration-75 ease-in-out" :src="emoji.url" :title="emoji.shortcode" />
|
||||
<CommandItem
|
||||
: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>
|
||||
</CommandItem>
|
||||
</CommandGroup>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,26 @@
|
|||
<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>
|
||||
<CommandEmpty>No results found.</CommandEmpty>
|
||||
<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">
|
||||
<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
|
||||
:value="user.key"
|
||||
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>
|
||||
</CommandGroup>
|
||||
</CommandList>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<Alert>
|
||||
<AlertTitle>{{ m.fine_arable_lemming_fold() }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ m.petty_honest_fish_stir() }}
|
||||
</AlertDescription>
|
||||
<AlertDescription>{{ m.petty_honest_fish_stir() }}</AlertDescription>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<Alert>
|
||||
<AlertTitle>{{ m.empty_awful_lark_dart() }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ m.clean_even_mayfly_tap() }}
|
||||
</AlertDescription>
|
||||
<AlertDescription>{{ m.clean_even_mayfly_tap() }}</AlertDescription>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<Alert>
|
||||
<AlertTitle>{{ m.steep_suave_fish_snap() }}</AlertTitle>
|
||||
<AlertDescription>
|
||||
{{ m.muddy_bland_shark_accept() }}
|
||||
</AlertDescription>
|
||||
<AlertDescription>{{ m.muddy_bland_shark_accept() }}</AlertDescription>
|
||||
</Alert>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -3,23 +3,33 @@ import SquarePattern from "../graphics/square-pattern.vue";
|
|||
</script>
|
||||
|
||||
<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">
|
||||
<SquarePattern />
|
||||
<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"
|
||||
>
|
||||
<SquarePattern/>
|
||||
<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>
|
||||
<p class="mt-6 text-base leading-7 text-gray-400">
|
||||
This website requires JavaScript to function properly. Please enable JavaScript in your browser
|
||||
settings.
|
||||
This website requires JavaScript to function properly. Please
|
||||
enable JavaScript in your browser settings.
|
||||
</p>
|
||||
<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
|
||||
like <a href="https://www.mozilla.org/firefox/new/" class="underline">Firefox</a> or <a
|
||||
href="https://www.google.com/chrome/" class="underline">Chrome</a>.
|
||||
If you are using a browser that does not support JavaScript,
|
||||
please consider using a modern browser like
|
||||
<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 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
|
||||
servers outside of your account's instance.
|
||||
This application does not track you, collect user data, use
|
||||
cookies of any kind or send requests to servers outside of your
|
||||
account's instance.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,18 +1,23 @@
|
|||
<template>
|
||||
<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">
|
||||
<FormLabel class="font-semibold tracking-tight" :as="CardTitle">
|
||||
{{ title }}
|
||||
</FormLabel>
|
||||
<FormDescription class="text-xs leading-none" v-if="description">
|
||||
<FormDescription
|
||||
class="text-xs leading-none"
|
||||
v-if="description"
|
||||
>
|
||||
{{ description }}
|
||||
</FormDescription>
|
||||
</CardHeader>
|
||||
<FormControl>
|
||||
<slot />
|
||||
<slot/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</Card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,15 +1,11 @@
|
|||
<template>
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ title }}
|
||||
</FormLabel>
|
||||
<FormLabel>{{ title }}</FormLabel>
|
||||
<FormControl>
|
||||
<slot />
|
||||
<slot/>
|
||||
</FormControl>
|
||||
<FormDescription v-if="description">
|
||||
{{ description }}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<FormDescription v-if="description">{{ description }}</FormDescription>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<Card class="flex items-center justify-center">
|
||||
<Loader class="size-6 animate-spin" />
|
||||
<Loader class="size-6 animate-spin"/>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,34 @@
|
|||
<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"
|
||||
aria-hidden="true">
|
||||
<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"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<defs>
|
||||
<pattern id="983e3e4c-de6d-4c3f-8d64-b9761d1534cc" width="200" height="200" x="50%" y="-1"
|
||||
patternUnits="userSpaceOnUse">
|
||||
<pattern
|
||||
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>
|
||||
</pattern>
|
||||
</defs><svg x="50%" y="-1" class="overflow-visible fill-primary/[0.03]">
|
||||
<path d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
|
||||
stroke-width="0"></path>
|
||||
</defs>
|
||||
<svg x="50%" y="-1" class="overflow-visible fill-primary/[0.03]">
|
||||
<path
|
||||
d="M-200 0h201v201h-201Z M600 0h201v201h-201Z M-400 600h201v201h-201Z M200 800h201v201h-201Z"
|
||||
stroke-width="0"
|
||||
></path>
|
||||
</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>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,11 @@
|
|||
<template>
|
||||
<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'
|
||||
" :name="instance.title" />
|
||||
"
|
||||
:name="instance.title"
|
||||
/>
|
||||
<div class="grid text-sm leading-tight *:line-clamp-1">
|
||||
<span class="truncate font-semibold">
|
||||
{{
|
||||
|
|
@ -14,7 +17,6 @@
|
|||
instance.versia_version || instance.version
|
||||
}}
|
||||
</span>
|
||||
|
||||
</div>
|
||||
<h1 class="line-clamp-1 text-sm font-semibold col-span-2">
|
||||
{{
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ const inputValue = ref<string>("");
|
|||
<template>
|
||||
<Dialog>
|
||||
<DialogTrigger :as-child="true">
|
||||
<slot />
|
||||
<slot/>
|
||||
</DialogTrigger>
|
||||
<DialogContent class="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
|
|
@ -40,17 +40,35 @@ const inputValue = ref<string>("");
|
|||
</DialogDescription>
|
||||
</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">
|
||||
<Label for="confirmInput" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label>
|
||||
<Input id="confirmInput" v-model="inputValue" class="col-span-3" />
|
||||
<Label for="confirmInput" class="text-right">
|
||||
{{ m.mean_mean_badger_inspire() }}
|
||||
</Label>
|
||||
<Input
|
||||
id="confirmInput"
|
||||
v-model="inputValue"
|
||||
class="col-span-3"
|
||||
/>
|
||||
</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">
|
||||
<Label for="confirmTextarea" class="text-right">{{ m.mean_mean_badger_inspire() }}</Label>
|
||||
<Textarea id="confirmTextarea" v-model="inputValue" class="col-span-3" />
|
||||
<Label for="confirmTextarea" class="text-right">
|
||||
{{ m.mean_mean_badger_inspire() }}
|
||||
</Label>
|
||||
<Textarea
|
||||
id="confirmTextarea"
|
||||
v-model="inputValue"
|
||||
class="col-span-3"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -58,10 +76,12 @@ const inputValue = ref<string>("");
|
|||
<Button variant="outline" @click="() => emit('cancel')">
|
||||
{{ modalOptions.cancelText }}
|
||||
</Button>
|
||||
<Button @click="() => emit('confirm', {
|
||||
<Button
|
||||
@click="() => emit('confirm', {
|
||||
confirmed: true,
|
||||
value: inputValue,
|
||||
})">
|
||||
})"
|
||||
>
|
||||
{{ modalOptions.confirmText }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,11 @@ const isValid = ref(false);
|
|||
</script>
|
||||
|
||||
<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">
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>{{ modalOptions.title }}</AlertDialogTitle>
|
||||
|
|
@ -84,11 +88,23 @@ const isValid = ref(false);
|
|||
</AlertDialogDescription>
|
||||
</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">
|
||||
<AlertDialogCancel :as-child="true">
|
||||
|
|
@ -97,7 +113,10 @@ const isValid = ref(false);
|
|||
</Button>
|
||||
</AlertDialogCancel>
|
||||
<AlertDialogAction :as-child="true">
|
||||
<Button @click="handleConfirm" :disabled="!isValid && modalOptions.inputType === 'url'">
|
||||
<Button
|
||||
@click="handleConfirm"
|
||||
:disabled="!isValid && modalOptions.inputType === 'url'"
|
||||
>
|
||||
{{ modalOptions.confirmText }}
|
||||
</Button>
|
||||
</AlertDialogAction>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<DrawerContent class="flex flex-col gap-2 px-4 mb-4 [&>:nth-child(2)]:mt-4">
|
||||
<slot />
|
||||
<slot/>
|
||||
</DrawerContent>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
>
|
||||
<Button :as="NuxtLink" href="/" variant="ghost" size="icon">
|
||||
<Home />
|
||||
<Home/>
|
||||
</Button>
|
||||
<Button
|
||||
:as="NuxtLink"
|
||||
|
|
@ -11,10 +11,10 @@
|
|||
variant="ghost"
|
||||
size="icon"
|
||||
>
|
||||
<Bell />
|
||||
<Bell/>
|
||||
</Button>
|
||||
<Button variant="ghost" size="icon">
|
||||
<User />
|
||||
<User/>
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
|
|
@ -22,7 +22,7 @@
|
|||
:title="m.salty_aloof_turkey_nudge()"
|
||||
@click="useEvent('composer:open')"
|
||||
>
|
||||
<Pen />
|
||||
<Pen/>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
<template>
|
||||
<Tabs v-model:model-value="current">
|
||||
<TabsList>
|
||||
<TabsTrigger v-for="timeline in timelines.filter(
|
||||
<TabsTrigger
|
||||
v-for="timeline in timelines.filter(
|
||||
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 }}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<Button variant="ghost" class="max-w-14 w-full" size="sm">
|
||||
<component :is="icon" class="size-4" />
|
||||
<slot />
|
||||
<component :is="icon" class="size-4"/>
|
||||
<slot/>
|
||||
</Button>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,58 @@
|
|||
<template>
|
||||
<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) }}
|
||||
</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) }}
|
||||
</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) }}
|
||||
</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">
|
||||
<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>
|
||||
<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')">
|
||||
<ActionButton :icon="Ellipsis" :title="m.busy_merry_cowfish_absorb()" />
|
||||
<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')"
|
||||
>
|
||||
<ActionButton
|
||||
:icon="Ellipsis"
|
||||
:title="m.busy_merry_cowfish_absorb()"
|
||||
/>
|
||||
</Menu>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
<template>
|
||||
<ImageAttachment v-if="attachment.type === 'image'" :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" />
|
||||
<ImageAttachment
|
||||
v-if="attachment.type === 'image'"
|
||||
: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>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,13 @@
|
|||
<template>
|
||||
<!-- [&: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">
|
||||
<Attachment v-for="attachment in attachments" :key="attachment.id" :attachment="attachment" />
|
||||
<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"
|
||||
>
|
||||
<Attachment
|
||||
v-for="attachment in attachments"
|
||||
:key="attachment.id"
|
||||
:attachment="attachment"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,22 @@
|
|||
<template>
|
||||
<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">
|
||||
<slot />
|
||||
<slot/>
|
||||
</DialogTrigger>
|
||||
<slot v-else />
|
||||
<slot v-else/>
|
||||
<!-- Alt text viewer -->
|
||||
<Popover v-if="attachment.description">
|
||||
<div class="absolute top-0 right-0 p-2">
|
||||
<PopoverTrigger :as-child="true">
|
||||
<Button variant="outline" size="icon" title="View alt text">
|
||||
<Captions />
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
title="View alt text"
|
||||
>
|
||||
<Captions/>
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
</div>
|
||||
|
|
@ -19,26 +25,42 @@
|
|||
</PopoverContent>
|
||||
</Popover>
|
||||
</Card>
|
||||
<DialogContent :hide-close="true"
|
||||
class="duration-200 bg-transparent border-none overflow-hidden !animate-none gap-6 w-screen h-screen !max-w-none">
|
||||
<DialogContent
|
||||
: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="flex flex-row gap-2 w-full">
|
||||
<DialogTitle class="sr-only">{{ attachment.type }}</DialogTitle>
|
||||
<Button as="a" :href="attachment?.url" target="_blank" :download="true" variant="outline" size="icon"
|
||||
class="ml-auto">
|
||||
<Download />
|
||||
<DialogTitle class="sr-only">
|
||||
{{ attachment.type }}
|
||||
</DialogTitle>
|
||||
<Button
|
||||
as="a"
|
||||
:href="attachment?.url"
|
||||
target="_blank"
|
||||
:download="true"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
class="ml-auto"
|
||||
>
|
||||
<Download/>
|
||||
</Button>
|
||||
<DialogClose :as-child="true">
|
||||
<Button variant="outline" size="icon">
|
||||
<X />
|
||||
<X/>
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</div>
|
||||
<div class="flex items-center justify-center overflow-hidden *:max-h-[80vh] *:max-w-[80vw] *:w-full *:h-full *:object-contain">
|
||||
<slot />
|
||||
<div
|
||||
class="flex items-center justify-center overflow-hidden *:max-h-[80vh] *:max-w-[80vw] *:w-full *:h-full *:object-contain"
|
||||
>
|
||||
<slot/>
|
||||
</div>
|
||||
<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>
|
||||
</Card>
|
||||
</DialogDescription>
|
||||
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<Base :attachment="attachment">
|
||||
<audio :src="attachment.url" :alt="attachment.description ?? undefined" controls />
|
||||
</Base>
|
||||
<AttachmentBase :attachment="attachment">
|
||||
<audio
|
||||
:src="attachment.url"
|
||||
:alt="attachment.description ?? undefined"
|
||||
controls
|
||||
/>
|
||||
</AttachmentBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Attachment } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import Base from "./base.vue";
|
||||
import AttachmentBase from "./attachment-base.vue";
|
||||
|
||||
const { attachment } = defineProps<{
|
||||
attachment: z.infer<typeof Attachment>;
|
||||
|
|
|
|||
|
|
@ -1,17 +1,19 @@
|
|||
<template>
|
||||
<Base :attachment="attachment" lightbox>
|
||||
<div class="flex flex-col items-center justify-center min-h-48 text-sm gap-2">
|
||||
<File class="size-12" />
|
||||
<AttachmentBase :attachment="attachment" lightbox>
|
||||
<div
|
||||
class="flex flex-col items-center justify-center min-h-48 text-sm gap-2"
|
||||
>
|
||||
<File class="size-12"/>
|
||||
<span>File attachment</span>
|
||||
</div>
|
||||
</Base>
|
||||
</AttachmentBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Attachment } from "@versia/client/schemas";
|
||||
import { File } from "lucide-vue-next";
|
||||
import type { z } from "zod";
|
||||
import Base from "./base.vue";
|
||||
import AttachmentBase from "./attachment-base.vue";
|
||||
|
||||
const { attachment } = defineProps<{
|
||||
attachment: z.infer<typeof Attachment>;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
<template>
|
||||
<Base :attachment="attachment" lightbox>
|
||||
<img :src="attachment.url" :alt="attachment.description ?? undefined" />
|
||||
</Base>
|
||||
<AttachmentBase :attachment="attachment" lightbox>
|
||||
<img :src="attachment.url" :alt="attachment.description ?? undefined">
|
||||
</AttachmentBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Attachment } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import Base from "./base.vue";
|
||||
import AttachmentBase from "./attachment-base.vue";
|
||||
|
||||
const { attachment } = defineProps<{
|
||||
attachment: z.infer<typeof Attachment>;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
<template>
|
||||
<Base :attachment="attachment">
|
||||
<video :src="attachment.url" :alt="attachment.description ?? undefined" controls />
|
||||
</Base>
|
||||
<AttachmentBase :attachment="attachment">
|
||||
<video
|
||||
:src="attachment.url"
|
||||
:alt="attachment.description ?? undefined"
|
||||
controls
|
||||
>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</AttachmentBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Attachment } from "@versia/client/schemas";
|
||||
import type { z } from "zod";
|
||||
import Base from "./base.vue";
|
||||
import AttachmentBase from "./attachment-base.vue";
|
||||
|
||||
const { attachment } = defineProps<{
|
||||
attachment: z.infer<typeof Attachment>;
|
||||
|
|
|
|||
|
|
@ -3,10 +3,18 @@
|
|||
<p class="text-sm leading-6 wrap-anywhere">
|
||||
{{ contentWarning || m.sour_seemly_bird_hike() }}
|
||||
</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() :
|
||||
m.known_flaky_cockroach_dash() }} {{ characterCount > 0 ? ` (${characterCount} characters` : "" }}{{
|
||||
attachmentCount > 0 ? `${characterCount > 0 ? " · " : " ("}${attachmentCount} file(s)` : "" }}{{ (characterCount > 0 || attachmentCount > 0) ? ")" : "" }}
|
||||
m.known_flaky_cockroach_dash() }}
|
||||
{{ characterCount > 0 ? ` (${characterCount} characters` : "" }}
|
||||
{{
|
||||
attachmentCount > 0 ? `${characterCount > 0 ? " · " : " ("}${attachmentCount} file(s)` : "" }}
|
||||
{{ (characterCount > 0 || attachmentCount > 0) ? ")" : "" }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,28 @@
|
|||
<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>
|
||||
</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">
|
||||
<Note :note="quote" :hide-actions="true" :small-layout="true" />
|
||||
<Note :note="quote" :hide-actions="true" :small-layout="true"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,36 +1,61 @@
|
|||
<template>
|
||||
<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) {
|
||||
popupOpen = false;
|
||||
}
|
||||
}" :open-delay="2000">
|
||||
}"
|
||||
:open-delay="2000"
|
||||
>
|
||||
<HoverCardTrigger :as-child="true">
|
||||
<NuxtLink :href="urlAsPath" :class="cn('relative size-12', smallLayout && 'size-8')">
|
||||
<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
|
||||
:href="urlAsPath"
|
||||
:class="cn('relative size-12', smallLayout && 'size-8')"
|
||||
>
|
||||
<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>
|
||||
</HoverCardTrigger>
|
||||
<HoverCardContent class="w-96">
|
||||
<SmallCard :account="author" />
|
||||
<SmallCard :account="author"/>
|
||||
</HoverCardContent>
|
||||
</HoverCard>
|
||||
<Col
|
||||
:class="smallLayout && 'text-sm'">
|
||||
<Text class="font-semibold" v-render-emojis="author.emojis">{{
|
||||
<Column :class="smallLayout && 'text-sm'">
|
||||
<Text class="font-semibold" v-render-emojis="author.emojis">
|
||||
{{
|
||||
author.display_name
|
||||
}}</Text>
|
||||
}}
|
||||
</Text>
|
||||
<div class="-mt-1">
|
||||
<Address as="span" :username="username" :domain="instance" />
|
||||
<Address as="span" :username="username" :domain="instance"/>
|
||||
·
|
||||
<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>
|
||||
</Col>
|
||||
</Column>
|
||||
<div v-if="!smallLayout">
|
||||
<NuxtLink :href="noteUrlAsPath" class="text-xs text-muted-foreground"
|
||||
:title="visibilities[visibility].text">
|
||||
<component :is="visibilities[visibility].icon" class="size-4" />
|
||||
<NuxtLink
|
||||
:href="noteUrlAsPath"
|
||||
class="text-xs text-muted-foreground"
|
||||
:title="visibilities[visibility].text"
|
||||
>
|
||||
<component :is="visibilities[visibility].icon" class="size-4"/>
|
||||
</NuxtLink>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -49,7 +74,7 @@ import { getLocale } from "~~/paraglide/runtime";
|
|||
import Address from "../profiles/address.vue";
|
||||
import Avatar from "../profiles/avatar.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 {
|
||||
HoverCard,
|
||||
|
|
|
|||
|
|
@ -79,57 +79,71 @@ const _delete = async () => {
|
|||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<slot />
|
||||
<slot/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="min-w-56">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem v-if="authorIsMe" as="button" @click="emit('edit')">
|
||||
<Pencil />
|
||||
<DropdownMenuItem
|
||||
v-if="authorIsMe"
|
||||
as="button"
|
||||
@click="emit('edit')"
|
||||
>
|
||||
<Pencil/>
|
||||
{{ m.front_lime_grizzly_persist() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(apiNoteString)">
|
||||
<Code />
|
||||
<Code/>
|
||||
{{ m.yummy_moving_scallop_sail() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(noteId)">
|
||||
<Hash />
|
||||
<Hash/>
|
||||
{{ m.sunny_zany_jellyfish_pop() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSeparator/>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem as="button" @click="copyText(url)">
|
||||
<Link />
|
||||
<Link/>
|
||||
{{ m.ago_new_pelican_drip() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" v-if="isRemote && remoteUrl" @click="copyText(remoteUrl)">
|
||||
<Link />
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
v-if="isRemote && remoteUrl"
|
||||
@click="copyText(remoteUrl)"
|
||||
>
|
||||
<Link/>
|
||||
{{ m.solid_witty_zebra_walk() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="remoteUrl">
|
||||
<ExternalLink />
|
||||
<DropdownMenuItem
|
||||
as="a"
|
||||
v-if="isRemote"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:href="remoteUrl"
|
||||
>
|
||||
<ExternalLink/>
|
||||
{{ m.active_trite_lark_inspire() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="authorIsMe" />
|
||||
<DropdownMenuSeparator v-if="authorIsMe"/>
|
||||
<DropdownMenuGroup v-if="authorIsMe">
|
||||
<DropdownMenuItem as="button" :disabled="true">
|
||||
<Delete />
|
||||
<Delete/>
|
||||
{{ m.real_green_clownfish_pet() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="_delete">
|
||||
<Trash />
|
||||
<Trash/>
|
||||
{{ m.tense_quick_cod_favor() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="authStore.isSignedIn && !authorIsMe" />
|
||||
<DropdownMenuSeparator v-if="authStore.isSignedIn && !authorIsMe"/>
|
||||
<DropdownMenuGroup v-if="authStore.isSignedIn && !authorIsMe">
|
||||
<DropdownMenuItem as="button" :disabled="true">
|
||||
<Flag />
|
||||
<Flag/>
|
||||
{{ m.great_few_jaguar_rise() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="blockUser(authorId)">
|
||||
<Ban />
|
||||
<Ban/>
|
||||
{{ m.misty_soft_sparrow_vent() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
|
|
|||
|
|
@ -50,7 +50,12 @@
|
|||
:sensitive="noteToUse.sensitive"
|
||||
: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>
|
||||
<CardFooter v-if="!hideActions">
|
||||
<Actions
|
||||
|
|
|
|||
|
|
@ -1,18 +1,29 @@
|
|||
<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`,
|
||||
}">
|
||||
<slot />
|
||||
<div v-if="isOverflowing && collapsed"
|
||||
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">{{
|
||||
}"
|
||||
>
|
||||
<slot/>
|
||||
<div
|
||||
v-if="isOverflowing && collapsed"
|
||||
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
|
||||
? `${m.lazy_honest_mammoth_bump()}${formattedCharacterCount ? ` • ${m.dark_spare_goldfish_charm({
|
||||
count: formattedCharacterCount,
|
||||
})}` : ""}`
|
||||
: m.that_misty_mule_arrive()
|
||||
}}</Button>
|
||||
}}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
<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',
|
||||
$style.content,
|
||||
]">
|
||||
<slot />
|
||||
]"
|
||||
>
|
||||
<slot/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<template>
|
||||
<div class="sticky top-2 z-10 flex items-center justify-center p-2">
|
||||
<Badge variant="secondary">
|
||||
{{ categoryName }}
|
||||
</Badge>
|
||||
<Badge variant="secondary">{{ categoryName }}</Badge>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,17 @@
|
|||
<template>
|
||||
<div class="p-2 text-sm font-semibold border-0 rounded-none text-center flex flex-row items-center gap-2 truncate">
|
||||
<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">
|
||||
<div
|
||||
class="p-2 text-sm font-semibold border-0 rounded-none text-center flex flex-row items-center gap-2 truncate"
|
||||
>
|
||||
<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 }}
|
||||
</span>
|
||||
{{ (emoji as InferredEmoji)?.shortcode || (emoji as UnicodeEmoji)?.shortcode }}
|
||||
|
|
|
|||
|
|
@ -1,9 +1,22 @@
|
|||
<template>
|
||||
<Button @focus="() => emit('select', emoji)" @mouseenter="() => emit('select', emoji)" @click="() => emit('pick', emoji)" 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">
|
||||
<Button
|
||||
@focus="() => emit('select', emoji)"
|
||||
@mouseenter="() => emit('select', emoji)"
|
||||
@click="() => emit('pick', emoji)"
|
||||
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 }}
|
||||
</span>
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,31 +1,57 @@
|
|||
<template>
|
||||
<Popover v-model:open="open">
|
||||
<PopoverTrigger as-child>
|
||||
<slot />
|
||||
<slot/>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent class="p-0 w-fit">
|
||||
<div class="grid-cols-[minmax(0,1fr)_auto] gap-0 grid divide-x *:h-112 *:overflow-y-auto"
|
||||
orientation="vertical">
|
||||
<div class="grid grid-rows-[auto_minmax(0,1fr)_auto] gap-0" ref="emojiContainer">
|
||||
<div
|
||||
class="grid-cols-[minmax(0,1fr)_auto] gap-0 grid divide-x *:h-112 *:overflow-y-auto"
|
||||
orientation="vertical"
|
||||
>
|
||||
<div
|
||||
class="grid grid-rows-[auto_minmax(0,1fr)_auto] gap-0"
|
||||
ref="emojiContainer"
|
||||
>
|
||||
<div class="p-2">
|
||||
<Input placeholder="Search" v-model="filter" />
|
||||
<Input placeholder="Search" v-model="filter"/>
|
||||
</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)`,
|
||||
}">
|
||||
<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">
|
||||
<Emoji v-for="emoji in item.emojis" :key="getEmojiKey(emoji)" :emoji="emoji"
|
||||
@select="(e) => selectedEmoji = e" @pick="e => {
|
||||
}"
|
||||
>
|
||||
<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"
|
||||
>
|
||||
<Emoji
|
||||
v-for="emoji in item.emojis"
|
||||
:key="getEmojiKey(emoji)"
|
||||
:emoji="emoji"
|
||||
@select="(e) => selectedEmoji = e"
|
||||
@pick="e => {
|
||||
emit('pick', e); open = false;
|
||||
}" />
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
</VList>
|
||||
<EmojiDisplay :emoji="selectedEmoji" :style="{
|
||||
<EmojiDisplay
|
||||
:emoji="selectedEmoji"
|
||||
:style="{
|
||||
width: `calc(var(--spacing) * ((12 * ${EMOJI_PER_ROW}) + (${EMOJI_PER_ROW} - 1)) + var(--spacing) * 4)`,
|
||||
}" />
|
||||
}"
|
||||
/>
|
||||
</div>
|
||||
<Sidebar :categories="categories" @select="scrollToCategory" />
|
||||
<Sidebar :categories="categories" @select="scrollToCategory"/>
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,23 @@
|
|||
<template>
|
||||
<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)">
|
||||
<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
|
||||
v-for="category in categories"
|
||||
: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>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,23 +1,38 @@
|
|||
<template>
|
||||
<HoverCard @update:open="(open) => open && accounts === null && refreshReactions()">
|
||||
<HoverCard
|
||||
@update:open="(open) => open && accounts === null && refreshReactions()"
|
||||
>
|
||||
<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">
|
||||
<img v-if="emoji" :src="emoji.url" :alt="emoji.shortcode"
|
||||
class="h-[1lh] align-middle inline not-prose" />
|
||||
<span v-else>
|
||||
{{ reaction.name }}
|
||||
</span>
|
||||
<Button
|
||||
@click="reaction.me ? !reaction.remote && unreact() : !reaction.remote && react()"
|
||||
:variant="reaction.me ? 'secondary' : reaction.remote ? 'ghost' : 'outline'"
|
||||
size="sm"
|
||||
class="gap-2"
|
||||
>
|
||||
<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) }}
|
||||
</Button>
|
||||
</HoverCardTrigger>
|
||||
<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">
|
||||
<li
|
||||
v-for="account in accounts">
|
||||
<NuxtLink :to="`/@${account.acct}`" class="flex items-center gap-2">
|
||||
<Avatar class="size-6" :key="account.id" :src="account.avatar"
|
||||
:name="account.display_name || account.username" />
|
||||
<li v-for="account in accounts">
|
||||
<NuxtLink
|
||||
:to="`/@${account.acct}`"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<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">
|
||||
{{ account.display_name || account.username }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,13 @@
|
|||
<template>
|
||||
<NuxtLink :href="urlAsPath">
|
||||
<Card class="flex-row px-2 py-1 items-center gap-2 hover:bg-muted duration-100 text-sm">
|
||||
<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>
|
||||
<Card
|
||||
class="flex-row px-2 py-1 items-center gap-2 hover:bg-muted duration-100 text-sm"
|
||||
>
|
||||
<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() }}
|
||||
</Card>
|
||||
</NuxtLink>
|
||||
|
|
|
|||
|
|
@ -1,29 +1,55 @@
|
|||
<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">
|
||||
<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>
|
||||
<div 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">{{
|
||||
<div
|
||||
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
|
||||
}}</span>
|
||||
}}</span
|
||||
>
|
||||
<span class="truncate tracking-tight">
|
||||
<Address :username="username" :domain="domain" />
|
||||
<Address :username="username" :domain="domain"/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<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 v-else-if="relationship?.requested_by === false" class="flex p-2 items-center justify-center h-12">
|
||||
<Check class="size-4" />
|
||||
<div
|
||||
v-else-if="relationship?.requested_by === false"
|
||||
class="flex p-2 items-center justify-center h-12"
|
||||
>
|
||||
<Check class="size-4"/>
|
||||
</div>
|
||||
<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()">
|
||||
<Check />
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
@click="accept"
|
||||
:title="m.slow_these_kestrel_sail()"
|
||||
>
|
||||
<Check/>
|
||||
</Button>
|
||||
<Button variant="ghost" size="sm" @click="reject" :title="m.weary_steep_yak_embrace()">
|
||||
<X />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
@click="reject"
|
||||
:title="m.weary_steep_yak_embrace()"
|
||||
>
|
||||
<X/>
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
v-if="notification.account"
|
||||
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
|
||||
class="size-5 border border-card"
|
||||
:src="notification.account.avatar"
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
class="ml-auto [&_svg]:data-[state=open]:-rotate-180"
|
||||
:title="open ? 'Collapse' : 'Expand'"
|
||||
>
|
||||
<ChevronDown class="duration-200" />
|
||||
<ChevronDown class="duration-200"/>
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
</CardHeader>
|
||||
|
|
|
|||
|
|
@ -1,17 +1,43 @@
|
|||
<template>
|
||||
<section class="space-y-2">
|
||||
<CardTitle class="text-xs">
|
||||
{{ name }}
|
||||
</CardTitle>
|
||||
<CardTitle class="text-xs">{{ name }}</CardTitle>
|
||||
<Card class="p-0 gap-0">
|
||||
<div v-for="preference of preferences" :key="preference">
|
||||
<TextPreferenceVue v-if="(prefs[preference] instanceof TextPreference)" :pref="(prefs[preference] as TextPreference)" :name="preference" />
|
||||
<BooleanPreferenceVue 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" />
|
||||
<TextPreferenceVue
|
||||
v-if="(prefs[preference] instanceof TextPreference)"
|
||||
:pref="(prefs[preference] as TextPreference)"
|
||||
:name="preference"
|
||||
/>
|
||||
<BooleanPreferenceVue
|
||||
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>
|
||||
</Card>
|
||||
</section>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<Card class="grid gap-3 text-sm">
|
||||
<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">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ key }}
|
||||
</dt>
|
||||
<dd class="font-mono" v-if="typeof value === 'string'">{{ value }}</dd>
|
||||
<div
|
||||
v-for="[key, value] of data"
|
||||
:key="key"
|
||||
class="flex flex-row items-baseline justify-between gap-4 truncate"
|
||||
>
|
||||
<dt class="text-muted-foreground">{{ key }}</dt>
|
||||
<dd class="font-mono" v-if="typeof value === 'string'">
|
||||
{{ value }}
|
||||
</dd>
|
||||
<dd class="font-mono" v-else>
|
||||
<component :is="value" />
|
||||
<component :is="value"/>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
|
|
|||
|
|
@ -77,46 +77,70 @@ useListen("preferences:open", () => {
|
|||
|
||||
<template>
|
||||
<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">
|
||||
<Tabs class="md:grid-cols-[auto_minmax(0,1fr)] !grid gap-2 *:p-4 overflow-hidden *:overflow-y-auto *:h-full" orientation="vertical"
|
||||
: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" />
|
||||
<DialogContent
|
||||
class="md:max-w-5xl w-full h-full p-0 md:max-h-[70dvh] overflow-hidden"
|
||||
>
|
||||
<Tabs
|
||||
class="md:grid-cols-[auto_minmax(0,1fr)] !grid gap-2 *:p-4 overflow-hidden *:overflow-y-auto *:h-full"
|
||||
orientation="vertical"
|
||||
: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>
|
||||
</div>
|
||||
<DialogDescription class="sr-only">
|
||||
Make changes to your preferences here.
|
||||
</DialogDescription>
|
||||
<TabsList class="md:grid md:grid-cols-1 w-full h-fit *:justify-start !justify-start">
|
||||
<TabsTrigger v-for="page in pages" :key="page" :value="page">
|
||||
<component :is="icons[page]" class="size-4 mr-2" />
|
||||
<TabsList
|
||||
class="md:grid md:grid-cols-1 w-full h-fit *:justify-start !justify-start"
|
||||
>
|
||||
<TabsTrigger
|
||||
v-for="page in pages"
|
||||
:key="page"
|
||||
:value="page"
|
||||
>
|
||||
<component :is="icons[page]" class="size-4 mr-2"/>
|
||||
{{ page }}
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
</DialogHeader>
|
||||
<TabsContent v-for="page in pages.filter(p => !extraPages.includes(p))" :key="page" :value="page"
|
||||
as-child>
|
||||
<TabsContent
|
||||
v-for="page in pages.filter(p => !extraPages.includes(p))"
|
||||
:key="page"
|
||||
:value="page"
|
||||
as-child
|
||||
>
|
||||
<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)"
|
||||
:name="category" />
|
||||
:name="category"
|
||||
/>
|
||||
</Page>
|
||||
</TabsContent>
|
||||
<TabsContent value="Emojis" as-child>
|
||||
<Page title="Emojis">
|
||||
<Emojis />
|
||||
<Emojis/>
|
||||
</Page>
|
||||
</TabsContent>
|
||||
<TabsContent value="Account" as-child>
|
||||
<Page title="Account">
|
||||
<Profile />
|
||||
<Profile/>
|
||||
</Page>
|
||||
</TabsContent>
|
||||
<TabsContent value="Developer" as-child>
|
||||
<Page title="Developer">
|
||||
<Developer />
|
||||
<Developer/>
|
||||
</Page>
|
||||
</TabsContent>
|
||||
<TabsContent value="About" as-child>
|
||||
|
|
@ -126,25 +150,53 @@ useListen("preferences:open", () => {
|
|||
{{ pkg.description }}
|
||||
</p>
|
||||
|
||||
<Stats />
|
||||
<Stats/>
|
||||
</section>
|
||||
<Separator />
|
||||
<Separator/>
|
||||
<section class="space-y-2">
|
||||
<h3 class="text-lg font-semibold tracking-tight">Developers</h3>
|
||||
<div class="grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-4">
|
||||
<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" />
|
||||
<h3 class="text-lg font-semibold tracking-tight">
|
||||
Developers
|
||||
</h3>
|
||||
<div
|
||||
class="grid lg:grid-cols-3 md:grid-cols-2 grid-cols-1 gap-4"
|
||||
>
|
||||
<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>
|
||||
</section>
|
||||
<Separator />
|
||||
<Separator/>
|
||||
<section class="space-y-2">
|
||||
<h3 class="text-lg font-semibold tracking-tight">Dependencies</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">
|
||||
<h3 class="text-lg font-semibold tracking-tight">
|
||||
Dependencies
|
||||
</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
|
||||
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 }}
|
||||
</code>
|
||||
</li>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<slot />
|
||||
<slot/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="min-w-48">
|
||||
<DropdownMenuItem @click="deleteAll" :disabled="!canEdit">
|
||||
<Delete />
|
||||
<Delete/>
|
||||
{{ m.tense_quick_cod_favor() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,18 @@
|
|||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="ghost" size="icon" title="Open menu" class="size-8 p-0">
|
||||
<MoreHorizontal class="size-4" />
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
title="Open menu"
|
||||
class="size-8 p-0"
|
||||
>
|
||||
<MoreHorizontal class="size-4"/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="min-w-48">
|
||||
<DropdownMenuItem @click="editName">
|
||||
<TextCursorInput />
|
||||
<TextCursorInput/>
|
||||
{{ m.cuddly_such_swallow_hush() }}
|
||||
</DropdownMenuItem>
|
||||
<!-- <DropdownMenuItem @click="editCaption">
|
||||
|
|
@ -16,7 +21,7 @@
|
|||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator /> -->
|
||||
<DropdownMenuItem @click="_delete">
|
||||
<Delete />
|
||||
<Delete/>
|
||||
{{ m.tense_quick_cod_favor() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div v-if="authStore.emojis.length > 0" class="grow">
|
||||
<Table :emojis="authStore.emojis" :can-upload="canUpload" />
|
||||
<Table :emojis="authStore.emojis" :can-upload="canUpload"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -282,27 +282,34 @@ const table = useVueTable({
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<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)"
|
||||
@update:model-value="table.getColumn('shortcode')?.setFilterValue($event)" />
|
||||
@update:model-value="table.getColumn('shortcode')?.setFilterValue($event)"
|
||||
/>
|
||||
<Uploader v-if="props.canUpload">
|
||||
<Button variant="outline" size="icon" title="Upload emoji">
|
||||
<Plus class="size-4" />
|
||||
<Plus class="size-4"/>
|
||||
</Button>
|
||||
</Uploader>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Columns
|
||||
<ChevronDown class="ml-2 size-4" />
|
||||
<ChevronDown class="ml-2 size-4"/>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuCheckboxItem
|
||||
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())" :key="column.id"
|
||||
class="capitalize" :model-value="column.getIsVisible()" @update:model-value="(value) => {
|
||||
v-for="column in table.getAllColumns().filter((column) => column.getCanHide())"
|
||||
:key="column.id"
|
||||
class="capitalize"
|
||||
:model-value="column.getIsVisible()"
|
||||
@update:model-value="(value) => {
|
||||
column.toggleVisibility(!!value)
|
||||
}">
|
||||
}"
|
||||
>
|
||||
{{ column.id }}
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
|
|
@ -311,19 +318,40 @@ const table = useVueTable({
|
|||
<div class="rounded-md border">
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow v-for="headerGroup in table.getHeaderGroups()" :key="headerGroup.id">
|
||||
<TableHead v-for="header in headerGroup.headers" :key="header.id" class="">
|
||||
<FlexRender v-if="!header.isPlaceholder" :render="header.column.columnDef.header"
|
||||
:props="header.getContext()" />
|
||||
<TableRow
|
||||
v-for="headerGroup in table.getHeaderGroups()"
|
||||
:key="headerGroup.id"
|
||||
>
|
||||
<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>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
<template v-if="table.getRowModel().rows?.length">
|
||||
<template v-for="row in table.getRowModel().rows" :key="row.id">
|
||||
<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()" />
|
||||
<template
|
||||
v-for="row in table.getRowModel().rows"
|
||||
:key="row.id"
|
||||
>
|
||||
<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>
|
||||
</TableRow>
|
||||
<TableRow v-if="row.getIsExpanded()">
|
||||
|
|
@ -335,7 +363,10 @@ const table = useVueTable({
|
|||
</template>
|
||||
|
||||
<TableRow v-else>
|
||||
<TableCell :colspan="columns.length" class="h-24 text-center">
|
||||
<TableCell
|
||||
:colspan="columns.length"
|
||||
class="h-24 text-center"
|
||||
>
|
||||
No results.
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
|
|
@ -345,15 +376,24 @@ const table = useVueTable({
|
|||
|
||||
<div class="flex items-center justify-end space-x-2 py-4">
|
||||
<div class="flex-1 text-sm text-muted-foreground">
|
||||
{{ table.getFilteredSelectedRowModel().rows.length }} of
|
||||
{{ table.getFilteredRowModel().rows.length }} row(s) selected.
|
||||
{{ table.getFilteredSelectedRowModel().rows.length }}of
|
||||
{{ table.getFilteredRowModel().rows.length }}row(s) selected.
|
||||
</div>
|
||||
<div class="space-x-2">
|
||||
<Button variant="outline" size="sm" :disabled="!table.getCanPreviousPage()"
|
||||
@click="table.previousPage()">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!table.getCanPreviousPage()"
|
||||
@click="table.previousPage()"
|
||||
>
|
||||
Previous
|
||||
</Button>
|
||||
<Button variant="outline" size="sm" :disabled="!table.getCanNextPage()" @click="table.nextPage()">
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
:disabled="!table.getCanNextPage()"
|
||||
@click="table.nextPage()"
|
||||
>
|
||||
Next
|
||||
</Button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
<template>
|
||||
<Dialog v-model:open="open">
|
||||
<DialogTrigger>
|
||||
<slot />
|
||||
<slot/>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
{{ m.whole_icy_puffin_smile() }}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{{ m.whole_icy_puffin_smile() }}</DialogTitle>
|
||||
<DialogDescription class="sr-only">
|
||||
{{ m.frail_great_marten_pet() }}
|
||||
</DialogDescription>
|
||||
|
|
@ -20,28 +18,28 @@
|
|||
class="h-full object-cover"
|
||||
:src="createObjectURL(values.image as File)"
|
||||
:alt="values.alt"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="bg-zinc-700">
|
||||
<img
|
||||
class="h-full object-cover"
|
||||
:src="createObjectURL(values.image as File)"
|
||||
:alt="values.alt"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="bg-zinc-400">
|
||||
<img
|
||||
class="h-full object-cover"
|
||||
:src="createObjectURL(values.image as File)"
|
||||
:alt="values.alt"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
<div class="bg-foreground">
|
||||
<img
|
||||
class="h-full object-cover"
|
||||
:src="createObjectURL(values.image as File)"
|
||||
:alt="values.alt"
|
||||
/>
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -68,15 +66,13 @@
|
|||
<FormDescription>
|
||||
{{ m.lime_late_millipede_urge() }}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="shortcode">
|
||||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ m.happy_mild_fox_gleam() }}
|
||||
</FormLabel>
|
||||
<FormLabel>{{ m.happy_mild_fox_gleam() }}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
v-bind="componentField"
|
||||
|
|
@ -86,7 +82,7 @@
|
|||
<FormDescription>
|
||||
{{ m.glad_day_kestrel_amaze() }}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
|
|
@ -101,7 +97,7 @@
|
|||
:disabled="isSubmitting"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
|
|
@ -120,7 +116,7 @@
|
|||
<FormDescription>
|
||||
{{ m.weird_fun_jurgen_arise() }}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
||||
|
|
@ -130,7 +126,10 @@
|
|||
name="global"
|
||||
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
|
||||
:model-value="value"
|
||||
@update:model-value="handleChange"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<Dialog />
|
||||
<Dialog/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,8 @@
|
|||
<template>
|
||||
<section class="gap-4 flex flex-col">
|
||||
<h2 class="text-xl font-bold tracking-tight">
|
||||
{{ title }}
|
||||
</h2>
|
||||
<h2 class="text-xl font-bold tracking-tight">{{ title }}</h2>
|
||||
|
||||
<slot />
|
||||
<slot/>
|
||||
</section>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,10 +1,20 @@
|
|||
<template>
|
||||
<form class="grid gap-6" @submit="save">
|
||||
<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]!">
|
||||
<SaveOff class="size-4" />
|
||||
<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]!"
|
||||
>
|
||||
<SaveOff class="size-4"/>
|
||||
<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>
|
||||
Click "apply" to save your changes.
|
||||
</AlertDescription>
|
||||
|
|
@ -12,55 +22,101 @@
|
|||
</Transition>
|
||||
|
||||
<FormField v-slot="{ handleChange, handleBlur }" name="banner">
|
||||
<TextInput :title="m.bright_late_osprey_renew()" :description="m.great_level_lamb_sway()">
|
||||
<Input type="file" accept="image/*" @change="handleChange" @blur="handleBlur" />
|
||||
<TextInput
|
||||
:title="m.bright_late_osprey_renew()"
|
||||
:description="m.great_level_lamb_sway()"
|
||||
>
|
||||
<Input
|
||||
type="file"
|
||||
accept="image/*"
|
||||
@change="handleChange"
|
||||
@blur="handleBlur"
|
||||
/>
|
||||
</TextInput>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ setValue }" name="avatar">
|
||||
<TextInput :title="m.safe_icy_bulldog_quell()">
|
||||
<ImageUploader v-model:image="authStore.account!.avatar" @submit-file="(file) => setValue(file)"
|
||||
@submit-url="(url) => setValue(url)" />
|
||||
<ImageUploader
|
||||
v-model:image="authStore.account!.avatar"
|
||||
@submit-file="(file) => setValue(file)"
|
||||
@submit-url="(url) => setValue(url)"
|
||||
/>
|
||||
</TextInput>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="name">
|
||||
<TextInput :title="m.mild_known_mallard_jolt()" :description="m.lime_dry_skunk_loop()">
|
||||
<Input v-bind="componentField" />
|
||||
<TextInput
|
||||
:title="m.mild_known_mallard_jolt()"
|
||||
:description="m.lime_dry_skunk_loop()"
|
||||
>
|
||||
<Input v-bind="componentField"/>
|
||||
</TextInput>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="username">
|
||||
<TextInput :title="m.neat_silly_dog_prosper()" :description="m.petty_plane_tadpole_earn()">
|
||||
<Input v-bind="componentField" />
|
||||
<TextInput
|
||||
:title="m.neat_silly_dog_prosper()"
|
||||
:description="m.petty_plane_tadpole_earn()"
|
||||
>
|
||||
<Input v-bind="componentField"/>
|
||||
</TextInput>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ componentField }" name="bio">
|
||||
<TextInput :title="m.next_caring_ladybug_hack()" :description="m.stale_just_anaconda_earn()">
|
||||
<Textarea rows="10" v-bind="componentField" />
|
||||
<TextInput
|
||||
:title="m.next_caring_ladybug_hack()"
|
||||
:description="m.stale_just_anaconda_earn()"
|
||||
>
|
||||
<Textarea rows="10" v-bind="componentField"/>
|
||||
</TextInput>
|
||||
</FormField>
|
||||
|
||||
<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 v-slot="{ value, handleChange }" name="bot" as-child>
|
||||
<SwitchInput :title="m.gaudy_each_opossum_play()" :description="m.grassy_acidic_gadfly_cure()">
|
||||
<Switch :model-value="value" @update:model-value="handleChange" />
|
||||
<SwitchInput
|
||||
:title="m.gaudy_each_opossum_play()"
|
||||
:description="m.grassy_acidic_gadfly_cure()"
|
||||
>
|
||||
<Switch
|
||||
:model-value="value"
|
||||
@update:model-value="handleChange"
|
||||
/>
|
||||
</SwitchInput>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ value, handleChange }" name="locked" as-child>
|
||||
<SwitchInput :title="m.dirty_moving_shark_emerge()" :description="m.bright_fun_mouse_boil()">
|
||||
<Switch :model-value="value" @update:model-value="handleChange" />
|
||||
<SwitchInput
|
||||
:title="m.dirty_moving_shark_emerge()"
|
||||
:description="m.bright_fun_mouse_boil()"
|
||||
>
|
||||
<Switch
|
||||
:model-value="value"
|
||||
@update:model-value="handleChange"
|
||||
/>
|
||||
</SwitchInput>
|
||||
</FormField>
|
||||
|
||||
<FormField v-slot="{ 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" />
|
||||
<FormField
|
||||
v-slot="{ 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>
|
||||
</FormField>
|
||||
</form>
|
||||
|
|
|
|||
|
|
@ -2,30 +2,64 @@
|
|||
<FormItem>
|
||||
<FormLabel>
|
||||
{{ title }}
|
||||
<Button type="button" variant="secondary" size="icon" class="ml-auto" @click="addField()" :title="m.front_north_eel_gulp()">
|
||||
<Plus />
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
class="ml-auto"
|
||||
@click="addField()"
|
||||
:title="m.front_north_eel_gulp()"
|
||||
>
|
||||
<Plus/>
|
||||
</Button>
|
||||
</FormLabel>
|
||||
|
||||
<FormControl>
|
||||
<VueDraggable class="grid gap-4" v-model="list" :animation="200" 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 />
|
||||
<VueDraggable
|
||||
class="grid gap-4"
|
||||
v-model="list"
|
||||
:animation="200"
|
||||
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>
|
||||
<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))
|
||||
" />
|
||||
<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))
|
||||
" />
|
||||
<Button type="button" variant="secondary" size="icon" @click="removeField(index)">
|
||||
<Trash />
|
||||
"
|
||||
/>
|
||||
<Button
|
||||
type="button"
|
||||
variant="secondary"
|
||||
size="icon"
|
||||
@click="removeField(index)"
|
||||
>
|
||||
<Trash/>
|
||||
</Button>
|
||||
</div>
|
||||
</VueDraggable>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -6,18 +6,16 @@
|
|||
variant="ghost"
|
||||
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
|
||||
class="absolute inset-0 bg-background/80 flex group-hover:opacity-100 opacity-0 duration-200 items-center justify-center"
|
||||
>
|
||||
<Upload />
|
||||
<Upload/>
|
||||
</div>
|
||||
</Button>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogTitle>
|
||||
{{ m.due_hour_husky_prosper() }}
|
||||
</DialogTitle>
|
||||
<DialogTitle>{{ m.due_hour_husky_prosper() }}</DialogTitle>
|
||||
<DialogDescription class="sr-only">
|
||||
{{ m.suave_broad_albatross_drop() }}
|
||||
</DialogDescription>
|
||||
|
|
@ -58,7 +56,7 @@
|
|||
<FormDescription>
|
||||
{{ m.lime_late_millipede_urge() }}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
</TabsContent>
|
||||
|
|
@ -83,12 +81,14 @@
|
|||
placeholder="peter.griffin@fox.com"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
<div v-if="value" class="grid gap-4 !mt-4">
|
||||
<Label>{{
|
||||
<Label>
|
||||
{{
|
||||
m.witty_honest_wallaby_support()
|
||||
}}</Label>
|
||||
<Avatar class="size-32" :src="gravatarUrl" />
|
||||
}}
|
||||
</Label>
|
||||
<Avatar class="size-32" :src="gravatarUrl"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
|
@ -109,12 +109,14 @@
|
|||
placeholder="https://mysite.com/avatar.webp"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
<div v-if="value" class="grid gap-4 !mt-4">
|
||||
<Label>{{
|
||||
<Label>
|
||||
{{
|
||||
m.witty_honest_wallaby_support()
|
||||
}}</Label>
|
||||
<Avatar class="size-32" :src="value" />
|
||||
}}
|
||||
</Label>
|
||||
<Avatar class="size-32" :src="value"/>
|
||||
</div>
|
||||
</FormItem>
|
||||
</FormField>
|
||||
|
|
|
|||
|
|
@ -1,13 +1,17 @@
|
|||
<template>
|
||||
<Card class="grid gap-3 text-sm max-w-sm">
|
||||
<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">
|
||||
<dt class="text-muted-foreground">
|
||||
{{ key }}
|
||||
</dt>
|
||||
<dd class="font-mono" v-if="typeof value === 'string'">{{ value }}</dd>
|
||||
<div
|
||||
v-for="[key, value] of data"
|
||||
:key="key"
|
||||
class="flex flex-row items-baseline justify-between gap-4 truncate"
|
||||
>
|
||||
<dt class="text-muted-foreground">{{ key }}</dt>
|
||||
<dd class="font-mono" v-if="typeof value === 'string'">
|
||||
{{ value }}
|
||||
</dd>
|
||||
<dd class="font-mono" v-else>
|
||||
<component :is="value" />
|
||||
<component :is="value"/>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,14 @@
|
|||
<template>
|
||||
<Base :pref="pref" :name="name" v-slot="{ setValue, value }">
|
||||
<Switch @update:model-value="setValue" :model-value="value" />
|
||||
</Base>
|
||||
<TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
|
||||
<Switch @update:model-value="setValue" :model-value="value"/>
|
||||
</TypeBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Switch } from "~/components/ui/switch";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { BooleanPreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: BooleanPreference;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,21 @@
|
|||
<template>
|
||||
<Collapsible as-child>
|
||||
<Base :name="name" :pref="pref">
|
||||
<TypeBase :name="name" :pref="pref">
|
||||
<template #default>
|
||||
<CollapsibleTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Open code
|
||||
</Button>
|
||||
<Button variant="outline">Open code</Button>
|
||||
</CollapsibleTrigger>
|
||||
</template>
|
||||
<template #extra="{ setValue, value }">
|
||||
<CollapsibleContent class="col-span-2 mt-2">
|
||||
<Textarea :rows="10" :model-value="value" @update:model-value="setValue" />
|
||||
<Textarea
|
||||
:rows="10"
|
||||
:model-value="value"
|
||||
@update:model-value="setValue"
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</template>
|
||||
</Base>
|
||||
</TypeBase>
|
||||
</Collapsible>
|
||||
</template>
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ import {
|
|||
import { Textarea } from "~/components/ui/textarea";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { CodePreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: CodePreference;
|
||||
|
|
|
|||
|
|
@ -1,25 +1,27 @@
|
|||
<template>
|
||||
<Base :pref="pref" :name="name" v-slot="{ setValue, value }">
|
||||
<TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Pick
|
||||
</Button>
|
||||
<Button variant="outline">Pick</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="w-56">
|
||||
<DropdownMenuCheckboxItem v-for="[option, title] in Object.entries(pref.options.options)" :key="option"
|
||||
:model-value="value.includes(option)" @update:model-value="checked => {
|
||||
<DropdownMenuCheckboxItem
|
||||
v-for="[option, title] in Object.entries(pref.options.options)"
|
||||
:key="option"
|
||||
:model-value="value.includes(option)"
|
||||
@update:model-value="checked => {
|
||||
if (checked) {
|
||||
setValue([...value, option]);
|
||||
} else {
|
||||
setValue(value.filter((v: any) => v !== option));
|
||||
}
|
||||
}">
|
||||
}"
|
||||
>
|
||||
{{ title }}
|
||||
</DropdownMenuCheckboxItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</Base>
|
||||
</TypeBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -32,7 +34,7 @@ import {
|
|||
} from "~/components/ui/dropdown-menu";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { MultiSelectPreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: MultiSelectPreference<string>;
|
||||
|
|
|
|||
|
|
@ -1,13 +1,19 @@
|
|||
<template>
|
||||
<Base :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">
|
||||
<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"
|
||||
>
|
||||
<NumberFieldContent>
|
||||
<NumberFieldDecrement />
|
||||
<NumberFieldInput />
|
||||
<NumberFieldIncrement />
|
||||
<NumberFieldDecrement/>
|
||||
<NumberFieldInput/>
|
||||
<NumberFieldIncrement/>
|
||||
</NumberFieldContent>
|
||||
</NumberField>
|
||||
</Base>
|
||||
</TypeBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -20,7 +26,7 @@ import {
|
|||
} from "~/components/ui/number-field";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { NumberPreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: NumberPreference;
|
||||
|
|
|
|||
|
|
@ -1,18 +1,21 @@
|
|||
<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">
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Select an option" />
|
||||
<SelectValue placeholder="Select an option"/>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<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 }}
|
||||
</SelectItem>
|
||||
</SelectGroup>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</Base>
|
||||
</TypeBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -26,7 +29,7 @@ import {
|
|||
} from "~/components/ui/select";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { SelectPreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: SelectPreference<string>;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,18 @@
|
|||
<template>
|
||||
<Base :pref="pref" :name="name" v-slot="{ setValue, value }">
|
||||
<Input placeholder="Content here..." :model-value="value" @update:model-value="setValue" />
|
||||
</Base>
|
||||
<TypeBase :pref="pref" :name="name" v-slot="{ setValue, value }">
|
||||
<Input
|
||||
placeholder="Content here..."
|
||||
:model-value="value"
|
||||
@update:model-value="setValue"
|
||||
/>
|
||||
</TypeBase>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { Input } from "~/components/ui/input";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { TextPreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: TextPreference;
|
||||
|
|
|
|||
|
|
@ -1,14 +1,22 @@
|
|||
<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">
|
||||
<h3 class="text-sm font-semibold tracking-tight">{{ pref.options.name }}</h3>
|
||||
<small v-if="pref.options.description" class="text-xs font-medium leading-none text-muted-foreground">{{
|
||||
pref.options.description }}</small>
|
||||
<h3 class="text-sm font-semibold tracking-tight">
|
||||
{{ pref.options.name }}
|
||||
</h3>
|
||||
<small
|
||||
v-if="pref.options.description"
|
||||
class="text-xs font-medium leading-none text-muted-foreground"
|
||||
>{{
|
||||
pref.options.description }}</small
|
||||
>
|
||||
</div>
|
||||
<div class="flex items-center justify-end">
|
||||
<slot :value="value" :set-value="setValue" />
|
||||
<slot :value="value" :set-value="setValue"/>
|
||||
</div>
|
||||
<slot name="extra" :value="value" :set-value="setValue" />
|
||||
<slot name="extra" :value="value" :set-value="setValue"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -1,19 +1,21 @@
|
|||
<template>
|
||||
<Collapsible as-child>
|
||||
<Base :pref="pref" :name="name">
|
||||
<TypeBase :pref="pref" :name="name">
|
||||
<template #default>
|
||||
<CollapsibleTrigger as-child>
|
||||
<Button variant="outline">
|
||||
Edit URL
|
||||
</Button>
|
||||
<Button variant="outline">Edit URL</Button>
|
||||
</CollapsibleTrigger>
|
||||
</template>
|
||||
<template #extra="{ setValue, value }">
|
||||
<CollapsibleContent class="col-span-2 mt-2">
|
||||
<UrlInput placeholder="Type URL or domain here..." :model-value="value" @update:model-value="setValue" />
|
||||
<UrlInput
|
||||
placeholder="Type URL or domain here..."
|
||||
:model-value="value"
|
||||
@update:model-value="setValue"
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</template>
|
||||
</Base>
|
||||
</TypeBase>
|
||||
</Collapsible>
|
||||
</template>
|
||||
|
||||
|
|
@ -27,7 +29,7 @@ import {
|
|||
import { Input, UrlInput } from "~/components/ui/input";
|
||||
import type { preferences as prefs } from "../preferences";
|
||||
import type { TextPreference } from "../types";
|
||||
import Base from "./base.vue";
|
||||
import TypeBase from "./type-base.vue";
|
||||
|
||||
const { pref, name } = defineProps<{
|
||||
pref: TextPreference;
|
||||
|
|
|
|||
|
|
@ -1,9 +1,7 @@
|
|||
<template>
|
||||
<Avatar :class="['rounded-md bg-secondary']">
|
||||
<AvatarFallback v-if="name">
|
||||
{{ getInitials(name) }}
|
||||
</AvatarFallback>
|
||||
<AvatarImage v-if="src" :src="src" :alt="`${name}'s avatar`" />
|
||||
<AvatarFallback v-if="name">{{ getInitials(name) }}</AvatarFallback>
|
||||
<AvatarImage v-if="src" :src="src" :alt="`${name}'s avatar`"/>
|
||||
</Avatar>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -1,60 +1,72 @@
|
|||
<template>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger as-child>
|
||||
<slot />
|
||||
<slot/>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent class="min-w-56">
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem as="button" @click="copyText(account.username)">
|
||||
<AtSign />
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
@click="copyText(account.username)"
|
||||
>
|
||||
<AtSign/>
|
||||
{{ m.cool_dark_tapir_belong() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(JSON.stringify(account, null, 4))">
|
||||
<Code />
|
||||
<DropdownMenuItem
|
||||
as="button"
|
||||
@click="copyText(JSON.stringify(account, null, 4))"
|
||||
>
|
||||
<Code/>
|
||||
{{ m.yummy_moving_scallop_sail() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(account.id)">
|
||||
<Hash />
|
||||
<Hash/>
|
||||
{{ m.sunny_zany_jellyfish_pop() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuSeparator/>
|
||||
<DropdownMenuGroup>
|
||||
<DropdownMenuItem as="button" @click="copyText(url)">
|
||||
<Link />
|
||||
<Link/>
|
||||
{{ m.ago_new_pelican_drip() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="copyText(account.url)">
|
||||
<Link />
|
||||
<Link/>
|
||||
{{ m.solid_witty_zebra_walk() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="a" v-if="isRemote" target="_blank" rel="noopener noreferrer" :href="account.url">
|
||||
<ExternalLink />
|
||||
<DropdownMenuItem
|
||||
as="a"
|
||||
v-if="isRemote"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
:href="account.url"
|
||||
>
|
||||
<ExternalLink/>
|
||||
{{ m.active_trite_lark_inspire() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe" />
|
||||
<DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe"/>
|
||||
<DropdownMenuGroup v-if="authStore.isSignedIn && !isMe">
|
||||
<DropdownMenuItem as="button" @click="muteUser(account.id)">
|
||||
<VolumeX />
|
||||
<VolumeX/>
|
||||
{{ m.spare_wild_mole_intend() }}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuItem as="button" @click="blockUser(account.id)">
|
||||
<Ban />
|
||||
<Ban/>
|
||||
{{ m.misty_soft_sparrow_vent() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="isRemote" />
|
||||
<DropdownMenuSeparator v-if="isRemote"/>
|
||||
<DropdownMenuGroup v-if="isRemote">
|
||||
<DropdownMenuItem as="button" @click="refresh">
|
||||
<RefreshCw />
|
||||
<RefreshCw/>
|
||||
{{ m.slow_chunky_chipmunk_hush() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
<DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe" />
|
||||
<DropdownMenuSeparator v-if="authStore.isSignedIn && !isMe"/>
|
||||
<DropdownMenuGroup v-if="authStore.isSignedIn && !isMe">
|
||||
<DropdownMenuItem as="button" :disabled="true">
|
||||
<Flag />
|
||||
<Flag/>
|
||||
{{ m.great_few_jaguar_rise() }}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuGroup>
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
<Tooltip>
|
||||
<TooltipTrigger :as-child="true">
|
||||
<Badge variant="default" class="gap-1">
|
||||
<BadgeCheck v-if="verified" />
|
||||
<img v-else-if="icon" :src="icon" alt="" class="size-4 rounded" />
|
||||
<BadgeCheck v-if="verified"/>
|
||||
<img v-else-if="icon" :src="icon" alt="" class="size-4 rounded">
|
||||
{{ name }}
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
<template>
|
||||
<Row class="gap-2" wrap
|
||||
<Row
|
||||
class="gap-2"
|
||||
wrap
|
||||
v-if="isDeveloper || account.bot || roles.length > 0"
|
||||
>
|
||||
<ProfileBadge
|
||||
|
|
|
|||
|
|
@ -1,10 +1,16 @@
|
|||
<template>
|
||||
<Col class="gap-y-4">
|
||||
<Col v-for="field in fields" :key="field.name" class="gap-1 break-words">
|
||||
<HeadingSmall v-render-emojis="emojis">{{ field.name }}</HeadingSmall>
|
||||
<Html v-html="field.value" v-render-emojis="emojis" />
|
||||
</Col>
|
||||
</Col>
|
||||
<Column class="gap-y-4">
|
||||
<Column
|
||||
v-for="field in fields"
|
||||
:key="field.name"
|
||||
class="gap-1 break-words"
|
||||
>
|
||||
<HeadingSmall v-render-emojis="emojis">
|
||||
{{ field.name }}
|
||||
</HeadingSmall>
|
||||
<Html v-html="field.value" v-render-emojis="emojis"/>
|
||||
</Column>
|
||||
</Column>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
@ -12,7 +18,7 @@ import type { CustomEmoji, Field } from "@versia/client/schemas";
|
|||
import type { z } from "zod";
|
||||
import HeadingSmall from "~/components/typography/headings/small.vue";
|
||||
import Html from "../typography/html.vue";
|
||||
import Col from "../typography/layout/col.vue";
|
||||
import Column from "../typography/layout/col.vue";
|
||||
|
||||
defineProps<{
|
||||
fields: z.infer<typeof Field>[];
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
:src="header"
|
||||
alt=""
|
||||
class="object-cover w-full h-full"
|
||||
/>
|
||||
>
|
||||
<!-- Shadow overlay at the bottom -->
|
||||
<div
|
||||
class="absolute bottom-0 w-full h-1/3 bg-gradient-to-b from-black/0 to-black/40"
|
||||
|
|
@ -15,11 +15,7 @@
|
|||
<div
|
||||
class="absolute bottom-0 translate-y-1/3 left-4 flex flex-row items-start gap-2"
|
||||
>
|
||||
<Avatar
|
||||
class="size-32 border"
|
||||
:src="avatar"
|
||||
:name="displayName"
|
||||
/>
|
||||
<Avatar class="size-32 border" :src="avatar" :name="displayName"/>
|
||||
</div>
|
||||
</CardHeader>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,11 @@
|
|||
<template>
|
||||
<Button variant="secondary" :disabled="isLoading || relationship?.requested" v-if="!isMe && authStore.isSignedIn"
|
||||
@click="relationship?.following ? unfollow() : follow()">
|
||||
<Loader v-if="isLoading" class="animate-spin" />
|
||||
<Button
|
||||
variant="secondary"
|
||||
:disabled="isLoading || relationship?.requested"
|
||||
v-if="!isMe && authStore.isSignedIn"
|
||||
@click="relationship?.following ? unfollow() : follow()"
|
||||
>
|
||||
<Loader v-if="isLoading" class="animate-spin"/>
|
||||
<span v-else>
|
||||
{{
|
||||
relationship?.following
|
||||
|
|
|
|||
|
|
@ -1,24 +1,24 @@
|
|||
<template>
|
||||
<Row class="gap-2 w-full justify-around">
|
||||
<Col centered>
|
||||
<Column centered>
|
||||
<Bold>{{ noteCount }}</Bold>
|
||||
<Small muted>{{ m.real_gray_stork_seek() }}</Small>
|
||||
</Col>
|
||||
<Col centered>
|
||||
</Column>
|
||||
<Column centered>
|
||||
<Bold>{{ followerCount }}</Bold>
|
||||
<Small muted>{{ m.teal_helpful_parakeet_hike() }}</Small>
|
||||
</Col>
|
||||
<Col centered>
|
||||
</Column>
|
||||
<Column centered>
|
||||
<Bold>{{ followingCount }}</Bold>
|
||||
<Small muted>{{ m.aloof_royal_samuel_startle() }}</Small>
|
||||
</Col>
|
||||
</Column>
|
||||
</Row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import * as m from "~~/paraglide/messages.js";
|
||||
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 Small from "../typography/small.vue";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,33 +1,45 @@
|
|||
<template>
|
||||
<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">
|
||||
<ProfileRelationshipActions :account="account" />
|
||||
<ProfileRelationshipActions :account="account"/>
|
||||
<ProfileActions :account="account">
|
||||
<Button variant="secondary" size="icon">
|
||||
<Ellipsis />
|
||||
<Ellipsis/>
|
||||
</Button>
|
||||
</ProfileActions>
|
||||
</Row>
|
||||
<Col class="justify-center">
|
||||
<Column class="justify-center">
|
||||
<Text class="font-bold" v-render-emojis="account.emojis">
|
||||
{{ account.display_name }}
|
||||
</Text>
|
||||
<Address :username="username" :domain="domain" />
|
||||
</Col>
|
||||
<ProfileBadges :account="account" />
|
||||
<Html v-html="account.note" v-render-emojis="account.emojis" />
|
||||
<Separator />
|
||||
<ProfileFields v-if="account.fields.length > 0" :fields="account.fields" :emojis="account.emojis" />
|
||||
<Separator v-if="account.fields.length > 0" />
|
||||
<Address :username="username" :domain="domain"/>
|
||||
</Column>
|
||||
<ProfileBadges :account="account"/>
|
||||
<Html v-html="account.note" v-render-emojis="account.emojis"/>
|
||||
<Separator/>
|
||||
<ProfileFields
|
||||
v-if="account.fields.length > 0"
|
||||
:fields="account.fields"
|
||||
:emojis="account.emojis"
|
||||
/>
|
||||
<Separator v-if="account.fields.length > 0"/>
|
||||
<Row>
|
||||
<HeadingSmall class="flex items-center gap-1">
|
||||
<CalendarDays class="size-4" /> {{ formattedCreationDate }}
|
||||
<CalendarDays class="size-4"/>
|
||||
{{ formattedCreationDate }}
|
||||
</HeadingSmall>
|
||||
</Row>
|
||||
<Separator />
|
||||
<ProfileStats :follower-count="account.followers_count" :following-count="account.following_count"
|
||||
:note-count="account.statuses_count" />
|
||||
<Separator/>
|
||||
<ProfileStats
|
||||
:follower-count="account.followers_count"
|
||||
:following-count="account.following_count"
|
||||
:note-count="account.statuses_count"
|
||||
/>
|
||||
</Card>
|
||||
</template>
|
||||
|
||||
|
|
@ -41,7 +53,7 @@ import { Separator } from "~/components/ui/separator";
|
|||
import { getLocale } from "~~/paraglide/runtime";
|
||||
import HeadingSmall from "../typography/headings/small.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 Text from "../typography/text.vue";
|
||||
import Address from "./address.vue";
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
:src="account.header"
|
||||
alt=""
|
||||
class="object-cover w-full h-full"
|
||||
/>
|
||||
>
|
||||
<!-- Shadow overlay at the bottom -->
|
||||
<div
|
||||
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">
|
||||
{{ account.display_name }}
|
||||
</Text>
|
||||
<Address :username="username" :domain="domain" />
|
||||
<Address :username="username" :domain="domain"/>
|
||||
</div>
|
||||
<Html
|
||||
v-html="account.note"
|
||||
v-render-emojis="account.emojis"
|
||||
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
|
||||
v-if="account.fields.length > 0"
|
||||
:fields="account.fields"
|
||||
|
|
|
|||
|
|
@ -3,12 +3,16 @@
|
|||
class="flex-row gap-2 p-2 truncate items-center"
|
||||
: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">
|
||||
<Text class="font-semibold" v-render-emojis="account.emojis">
|
||||
{{ account.display_name }}
|
||||
</Text>
|
||||
<Address :username="account.username" :domain="domain" />
|
||||
<Address :username="account.username" :domain="domain"/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<template>
|
||||
<Dialog>
|
||||
<DialogTrigger as-child>
|
||||
<slot />
|
||||
<slot/>
|
||||
</DialogTrigger>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
|
|
@ -11,16 +11,31 @@
|
|||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<div v-if="authStore.identities.length > 0" class="grid gap-4 py-2">
|
||||
<div v-for="identity of authStore.identities" :key="identity.account.id"
|
||||
class="grid grid-cols-[1fr_auto] has-[>[data-switch]]:grid-cols-[1fr_auto_auto] gap-2">
|
||||
<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">
|
||||
<div
|
||||
v-for="identity of authStore.identities"
|
||||
:key="identity.account.id"
|
||||
class="grid grid-cols-[1fr_auto] has-[>[data-switch]]:grid-cols-[1fr_auto_auto] gap-2"
|
||||
>
|
||||
<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
|
||||
</Button>
|
||||
<Button @click="signOutAction(identity.id)" variant="outline" size="icon"
|
||||
:title="m.sharp_big_mallard_reap()">
|
||||
<LogOut />
|
||||
<Button
|
||||
@click="signOutAction(identity.id)"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
:title="m.sharp_big_mallard_reap()"
|
||||
>
|
||||
<LogOut/>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -31,11 +46,11 @@
|
|||
</div>
|
||||
<DialogFooter>
|
||||
<Button :as="NuxtLink" href="/register" variant="outline">
|
||||
<UserPlus />
|
||||
<UserPlus/>
|
||||
{{ m.honest_few_baboon_pop() }}
|
||||
</Button>
|
||||
<Button @click="signInAction">
|
||||
<LogIn />
|
||||
<LogIn/>
|
||||
{{ m.sunny_pink_hyena_walk() }}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
|
|
|
|||
|
|
@ -26,32 +26,54 @@ const authStore = useAuthStore();
|
|||
<SidebarMenu class="gap-3">
|
||||
<SidebarMenuItem>
|
||||
<AccountManager>
|
||||
<SidebarMenuButton v-if="authStore.account && authStore.instance" size="lg">
|
||||
<TinyCard :account="authStore.account" :domain="authStore.instance.domain" naked />
|
||||
<ChevronsUpDown class="ml-auto size-4" />
|
||||
<SidebarMenuButton
|
||||
v-if="authStore.account && authStore.instance"
|
||||
size="lg"
|
||||
>
|
||||
<TinyCard
|
||||
:account="authStore.account"
|
||||
:domain="authStore.instance.domain"
|
||||
naked
|
||||
/>
|
||||
<ChevronsUpDown class="ml-auto size-4"/>
|
||||
</SidebarMenuButton>
|
||||
<SidebarMenuButton v-else>
|
||||
<UserPlus />
|
||||
<UserPlus/>
|
||||
{{ m.sunny_pink_hyena_walk() }}
|
||||
<ChevronsUpDown class="ml-auto size-4" />
|
||||
<ChevronsUpDown class="ml-auto size-4"/>
|
||||
</SidebarMenuButton>
|
||||
</AccountManager>
|
||||
</SidebarMenuItem>
|
||||
<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"
|
||||
@click="useEvent('composer:open')">
|
||||
<Pen />
|
||||
<Button
|
||||
v-if="authStore.isSignedIn"
|
||||
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">
|
||||
{{ m.salty_aloof_turkey_nudge() }}
|
||||
</span>
|
||||
</Button>
|
||||
<Button v-if="authStore.isSignedIn" size="lg" variant="secondary" @click="useEvent('preferences:open')">
|
||||
<Cog />
|
||||
<Button
|
||||
v-if="authStore.isSignedIn"
|
||||
size="lg"
|
||||
variant="secondary"
|
||||
@click="useEvent('preferences:open')"
|
||||
>
|
||||
<Cog/>
|
||||
Preferences
|
||||
</Button>
|
||||
<Button v-if="$pwa?.needRefresh" variant="destructive" size="lg"
|
||||
class="w-full group-data-[collapsible=icon]:px-4" @click="$pwa?.updateServiceWorker(true)">
|
||||
<DownloadCloud />
|
||||
<Button
|
||||
v-if="$pwa?.needRefresh"
|
||||
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">
|
||||
{{ m.quaint_low_felix_pave() }}
|
||||
</span>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,10 @@ const authStore = useAuthStore();
|
|||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<NuxtLink href="/">
|
||||
<InstanceSmallCard v-if="authStore.instance" :instance="authStore.instance" />
|
||||
<InstanceSmallCard
|
||||
v-if="authStore.instance"
|
||||
:instance="authStore.instance"
|
||||
/>
|
||||
</NuxtLink>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
<template>
|
||||
<Sidebar collapsible="offcanvas">
|
||||
<InstanceHeader />
|
||||
<InstanceHeader/>
|
||||
<SidebarContent>
|
||||
<SidebarGroup>
|
||||
<SidebarGroupLabel>{{
|
||||
<SidebarGroupLabel>
|
||||
{{
|
||||
m.trite_real_sawfish_drum()
|
||||
}}</SidebarGroupLabel>
|
||||
}}
|
||||
</SidebarGroupLabel>
|
||||
<NavItems
|
||||
:items="
|
||||
sidebarConfig.other.filter((i) =>
|
||||
|
|
@ -15,8 +17,8 @@
|
|||
/>
|
||||
</SidebarGroup>
|
||||
</SidebarContent>
|
||||
<FooterActions />
|
||||
<SidebarRail />
|
||||
<FooterActions/>
|
||||
<SidebarRail/>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ defineProps<{
|
|||
<SidebarMenuItem>
|
||||
<CollapsibleTrigger as-child>
|
||||
<SidebarMenuButton :tooltip="item.title">
|
||||
<component :is="item.icon" />
|
||||
<component :is="item.icon"/>
|
||||
{{ item.title }}
|
||||
<ChevronRight
|
||||
class="ml-auto transition-transform group-data-[state=open]/collapsible:rotate-180"
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ defineProps<{
|
|||
<SidebarMenuItem v-for="item in items" :key="item.title">
|
||||
<SidebarMenuButton as-child>
|
||||
<NuxtLink :href="item.url">
|
||||
<component :is="item.icon" />
|
||||
<component :is="item.icon"/>
|
||||
<span>{{ item.title }}</span>
|
||||
</NuxtLink>
|
||||
</SidebarMenuButton>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
style="--sidebar-width: 24rem; --sidebar-width-mobile: 18rem"
|
||||
>
|
||||
<SidebarContent class="overflow-y-auto *:p-2 *:gap-2">
|
||||
<NotificationsTimeline />
|
||||
<NotificationsTimeline/>
|
||||
</SidebarContent>
|
||||
</Sidebar>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -14,15 +14,18 @@ const authStore = useAuthStore();
|
|||
</script>
|
||||
|
||||
<template>
|
||||
<LeftSidebar />
|
||||
<LeftSidebar/>
|
||||
<main class="grow h-dvh overflow-y-auto">
|
||||
<header
|
||||
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"
|
||||
>
|
||||
<Timelines />
|
||||
<Timelines/>
|
||||
</header>
|
||||
<slot />
|
||||
<slot/>
|
||||
</main>
|
||||
<RightSidebar v-if="authStore.isSignedIn" v-show="preferences.display_notifications_sidebar" />
|
||||
<RightSidebar
|
||||
v-if="authStore.isSignedIn"
|
||||
v-show="preferences.display_notifications_sidebar"
|
||||
/>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<template>
|
||||
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd"
|
||||
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem"
|
||||
:update-item="updateItem" />
|
||||
<Timeline
|
||||
type="status"
|
||||
:items="items"
|
||||
:is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd"
|
||||
:error="error"
|
||||
:load-next="loadNext"
|
||||
:load-prev="loadPrev"
|
||||
:remove-item="removeItem"
|
||||
:update-item="updateItem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<template>
|
||||
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd"
|
||||
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem"
|
||||
:update-item="updateItem" />
|
||||
<Timeline
|
||||
type="status"
|
||||
:items="items"
|
||||
:is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd"
|
||||
:error="error"
|
||||
:load-next="loadNext"
|
||||
:load-prev="loadPrev"
|
||||
:remove-item="removeItem"
|
||||
:update-item="updateItem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<template>
|
||||
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd"
|
||||
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem"
|
||||
:update-item="updateItem" />
|
||||
<Timeline
|
||||
type="status"
|
||||
:items="items"
|
||||
:is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd"
|
||||
:error="error"
|
||||
:load-next="loadNext"
|
||||
:load-prev="loadPrev"
|
||||
:remove-item="removeItem"
|
||||
:update-item="updateItem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<template>
|
||||
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd"
|
||||
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem"
|
||||
:update-item="updateItem" />
|
||||
<Timeline
|
||||
type="status"
|
||||
:items="items"
|
||||
:is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd"
|
||||
:error="error"
|
||||
:load-next="loadNext"
|
||||
:load-prev="loadPrev"
|
||||
:remove-item="removeItem"
|
||||
:update-item="updateItem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<template>
|
||||
<Timeline type="notification" :items="items" :is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd" :error="error" :load-next="loadNext" :load-prev="loadPrev"
|
||||
:remove-item="removeItem" :update-item="updateItem" />
|
||||
<Timeline
|
||||
type="notification"
|
||||
:items="items"
|
||||
:is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd"
|
||||
:error="error"
|
||||
:load-next="loadNext"
|
||||
:load-prev="loadPrev"
|
||||
:remove-item="removeItem"
|
||||
:update-item="updateItem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,7 +1,15 @@
|
|||
<template>
|
||||
<Timeline type="status" :items="items" :is-loading="isLoading" :has-reached-end="hasReachedEnd"
|
||||
:error="error" :load-next="loadNext" :load-prev="loadPrev" :remove-item="removeItem"
|
||||
:update-item="updateItem" />
|
||||
<Timeline
|
||||
type="status"
|
||||
:items="items"
|
||||
:is-loading="isLoading"
|
||||
:has-reached-end="hasReachedEnd"
|
||||
:error="error"
|
||||
:load-next="loadNext"
|
||||
:load-prev="loadPrev"
|
||||
:remove-item="removeItem"
|
||||
:update-item="updateItem"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,11 @@
|
|||
<template>
|
||||
<component :is="itemComponent" :note="type === 'status' ? item : undefined" :notification="type === 'notification' ? item : (undefined as any)" @update="$emit('update', $event)"
|
||||
@delete="$emit('delete', item?.id)" />
|
||||
<component
|
||||
:is="itemComponent"
|
||||
:note="type === 'status' ? item : undefined"
|
||||
:notification="type === 'notification' ? item : (undefined as any)"
|
||||
@update="$emit('update', $event)"
|
||||
@delete="$emit('delete', item?.id)"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<slot />
|
||||
<slot/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
|
|
|||
|
|
@ -12,17 +12,15 @@
|
|||
@delete="removeItem"
|
||||
/>
|
||||
|
||||
<Spinner v-if="isLoading" />
|
||||
<Spinner v-if="isLoading"/>
|
||||
|
||||
<div v-if="error" class="timeline-error">
|
||||
{{ error.message }}
|
||||
</div>
|
||||
<div v-if="error" class="timeline-error">{{ error.message }}</div>
|
||||
|
||||
<!-- 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 -->
|
||||
<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">
|
||||
<Button
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue