chore: ⬆️ Upgrade dependencies

This commit is contained in:
Jesse Wierzbinski 2025-03-27 22:20:04 +01:00
parent 3e28801709
commit 7649ecfb80
No known key found for this signature in database
14 changed files with 947 additions and 627 deletions

14
app.vue
View file

@ -18,13 +18,14 @@ import "~/styles/index.css";
import { convert } from "html-to-text"; import { convert } from "html-to-text";
import ConfirmationModal from "./components/modals/confirm.vue"; import ConfirmationModal from "./components/modals/confirm.vue";
import { Toaster } from "./components/ui/sonner"; import { Toaster } from "./components/ui/sonner";
import { defineGetLocale } from "./paraglide/runtime"; import { overwriteGetLocale } from "./paraglide/runtime";
import { type EnumSetting, SettingIds } from "./settings"; import { type EnumSetting, SettingIds } from "./settings";
import { TooltipProvider } from "./components/ui/tooltip";
// Sin // Sin
//import "~/styles/mcdonalds.css"; //import "~/styles/mcdonalds.css";
const lang = useLanguage(); const lang = useLanguage();
defineGetLocale(() => lang.value); overwriteGetLocale(() => lang.value);
const code = useRequestURL().searchParams.get("code"); const code = useRequestURL().searchParams.get("code");
const origin = useRequestURL().searchParams.get("origin"); const origin = useRequestURL().searchParams.get("origin");
@ -57,7 +58,7 @@ useSeoMeta({
ogImage: computed(() => instance.value?.banner?.url), ogImage: computed(() => instance.value?.banner?.url),
twitterTitle: computed(() => instance.value?.title ?? ""), twitterTitle: computed(() => instance.value?.title ?? ""),
twitterDescription: computed(() => twitterDescription: computed(() =>
convert(description.value?.content ?? ""), convert(description.value?.content ?? "")
), ),
twitterImage: computed(() => instance.value?.banner?.url), twitterImage: computed(() => instance.value?.banner?.url),
description: computed(() => convert(description.value?.content ?? "")), description: computed(() => convert(description.value?.content ?? "")),
@ -75,7 +76,7 @@ useHead({
if (code && origin && appData.value && route.path !== "/oauth/code") { if (code && origin && appData.value && route.path !== "/oauth/code") {
const newOrigin = new URL( const newOrigin = new URL(
URL.canParse(origin) ? origin : `https://${origin}`, URL.canParse(origin) ? origin : `https://${origin}`
); );
signInWithCode(code, appData.value, newOrigin); signInWithCode(code, appData.value, newOrigin);
@ -83,7 +84,7 @@ if (code && origin && appData.value && route.path !== "/oauth/code") {
if (origin && !code) { if (origin && !code) {
const newOrigin = new URL( const newOrigin = new URL(
URL.canParse(origin) ? origin : `https://${origin}`, URL.canParse(origin) ? origin : `https://${origin}`
); );
signIn(appData, newOrigin); signIn(appData, newOrigin);
@ -104,6 +105,7 @@ body {
html.theme-changing * { html.theme-changing * {
/* Stroke and fill aren't animatable */ /* Stroke and fill aren't animatable */
transition: background-color 1s ease, border 1s ease, color 1s ease, box-shadow 1s ease !important; transition: background-color 1s ease, border 1s ease, color 1s ease,
box-shadow 1s ease !important;
} }
</style> </style>

822
bun.lock

File diff suppressed because it is too large Load diff

View file

@ -3,15 +3,28 @@
<Note :note="relation.note" :hide-actions="true" :small-layout="true" /> <Note :note="relation.note" :hide-actions="true" :small-layout="true" />
</div> </div>
<Input v-model:model-value="state.contentWarning" v-if="state.sensitive" <Input
placeholder="Put your content warning here" /> v-model:model-value="state.contentWarning"
v-if="state.sensitive"
placeholder="Put your content warning here"
/>
<EditorContent v-model:content="state.content" :placeholder="chosenSplash" <EditorContent
v-model:content="state.content"
:placeholder="chosenSplash"
class="*:!border-none *:!ring-0 *:!outline-none *:rounded-none p-0 *:max-h-[50dvh] *:overflow-y-auto *:min-h-48 *:!ring-offset-0 *:h-full" class="*:!border-none *:!ring-0 *:!outline-none *:rounded-none p-0 *:max-h-[50dvh] *:overflow-y-auto *:min-h-48 *:!ring-offset-0 *:h-full"
:disabled="sending" :mode="state.contentType === 'text/html' ? 'rich' : 'plain'" /> :disabled="sending"
:mode="state.contentType === 'text/html' ? 'rich' : 'plain'"
/>
<div class="w-full flex flex-row gap-2 overflow-x-auto *:shrink-0 pb-2"> <div class="w-full flex flex-row gap-2 overflow-x-auto *:shrink-0 pb-2">
<input type="file" ref="fileInput" @change="uploadFileFromEvent" class="hidden" multiple /> <input
type="file"
ref="fileInput"
@change="uploadFileFromEvent"
class="hidden"
multiple
/>
<Files v-model:files="state.files" /> <Files v-model:files="state.files" />
</div> </div>
@ -28,11 +41,15 @@
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger as="div"> <TooltipTrigger as="div">
<Toggle variant="default" size="sm" :pressed="state.contentType === 'text/html'" @update:pressed="(i) => <Toggle
(state.contentType = i variant="default"
? 'text/html' size="sm"
: 'text/plain') :pressed="state.contentType === 'text/html'"
"> @update:pressed="
(i) =>
(state.contentType = i ? 'text/html' : 'text/plain')
"
>
<LetterText class="!size-5" /> <LetterText class="!size-5" />
</Toggle> </Toggle>
</TooltipTrigger> </TooltipTrigger>
@ -41,14 +58,27 @@
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Select v-model:model-value="state.visibility"> <Select v-model:model-value="state.visibility">
<SelectTrigger :as-child="true" :disabled="relation?.type === 'edit'"> <SelectTrigger
:as-child="true"
:disabled="relation?.type === 'edit'"
>
<Button variant="ghost" size="icon"> <Button variant="ghost" size="icon">
<component :is="visibilities[state.visibility].icon" class="!size-5" /> <component
:is="visibilities[state.visibility].icon"
class="!size-5"
/>
</Button> </Button>
</SelectTrigger> </SelectTrigger>
<SelectContent> <SelectContent>
<SelectItem v-for="(v, k) in visibilities" :key="k" @click="state.visibility = k" :value="k"> <SelectItem
<div class="flex flex-row gap-4 items-center w-full justify-between"> v-for="(v, k) in visibilities"
:key="k"
@click="state.visibility = k"
:value="k"
>
<div
class="flex flex-row gap-4 items-center w-full justify-between"
>
<div class="flex flex-col gap-1"> <div class="flex flex-col gap-1">
<span class="font-semibold">{{ v.name }}</span> <span class="font-semibold">{{ v.name }}</span>
<span>{{ v.text }}</span> <span>{{ v.text }}</span>
@ -80,7 +110,11 @@
</Tooltip> </Tooltip>
<Tooltip> <Tooltip>
<TooltipTrigger as="div"> <TooltipTrigger as="div">
<Toggle variant="default" size="sm" v-model:pressed="state.sensitive"> <Toggle
variant="default"
size="sm"
v-model:pressed="state.sensitive"
>
<TriangleAlert class="!size-5" /> <TriangleAlert class="!size-5" />
</Toggle> </Toggle>
</TooltipTrigger> </TooltipTrigger>
@ -88,7 +122,13 @@
<p>{{ m.frail_broad_mallard_dart() }}</p> <p>{{ m.frail_broad_mallard_dart() }}</p>
</TooltipContent> </TooltipContent>
</Tooltip> </Tooltip>
<Button type="submit" size="lg" class="ml-auto" :disabled="sending" @click="submit"> <Button
type="submit"
size="lg"
class="ml-auto"
:disabled="sending"
@click="submit"
>
<Loader v-if="sending" class="!size-5 animate-spin" /> <Loader v-if="sending" class="!size-5 animate-spin" />
{{ {{
relation?.type === "edit" relation?.type === "edit"
@ -123,7 +163,9 @@ import EditorContent from "../editor/content.vue";
import { Button } from "../ui/button"; import { Button } from "../ui/button";
import { Input } from "../ui/input"; import { Input } from "../ui/input";
import { Toggle } from "../ui/toggle"; import { Toggle } from "../ui/toggle";
import { DialogFooter } from "../ui/dialog";
import Files from "./files.vue"; import Files from "./files.vue";
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
const { Control_Enter, Command_Enter } = useMagicKeys(); const { Control_Enter, Command_Enter } = useMagicKeys();
const ctrlEnterSend = useSetting(SettingIds.CtrlEnterToSend); const ctrlEnterSend = useSetting(SettingIds.CtrlEnterToSend);
@ -179,7 +221,7 @@ const state = reactive({
contentType: "text/html" as "text/html" | "text/plain", contentType: "text/html" as "text/html" | "text/plain",
visibility: (relation?.type === "edit" visibility: (relation?.type === "edit"
? relation.note.visibility ? relation.note.visibility
: (defaultVisibility.value.value ?? "public")) as Status["visibility"], : defaultVisibility.value.value ?? "public") as Status["visibility"],
files: (relation?.type === "edit" files: (relation?.type === "edit"
? relation.note.media_attachments.map((a) => ({ ? relation.note.media_attachments.map((a) => ({
apiId: a.id, apiId: a.id,

View file

@ -1,5 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { Dialog, DialogContent, DialogTitle } from "@/components/ui/dialog"; import {
Dialog,
DialogContent,
DialogDescription,
DialogTitle,
} from "@/components/ui/dialog";
import type { Status, StatusSource } from "@versia/client/types"; import type { Status, StatusSource } from "@versia/client/types";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import * as m from "~/paraglide/messages.js"; import * as m from "~/paraglide/messages.js";
@ -55,26 +60,43 @@ const relation = ref(
type: "reply" | "quote" | "edit"; type: "reply" | "quote" | "edit";
note: Status; note: Status;
source?: StatusSource; source?: StatusSource;
} | null, } | null
); );
</script> </script>
<template> <template>
<Dialog v-model:open="open" @update:open="o => { <Dialog
if (!o) { v-model:open="open"
relation = null; // Unfocus the active element @update:open="
activeElement?.blur(); (o) => {
} if (!o) {
}"> relation = null; // Unfocus the active element
<DialogContent :hide-close="true" activeElement?.blur();
class="sm:max-w-xl max-w-full w-[calc(100%-2*0.5rem)] grid-rows-[minmax(0,1fr)_auto] max-h-[90dvh] p-5 pt-6 top-2 sm:top-1/2 translate-y-0 sm:-translate-y-1/2 rounded"> }
}
"
>
<DialogContent
:hide-close="true"
class="sm:max-w-xl max-w-full w-[calc(100%-2*0.5rem)] grid-rows-[minmax(0,1fr)_auto] max-h-[90dvh] p-5 pt-6 top-2 sm:top-1/2 translate-y-0 sm:-translate-y-1/2 rounded"
>
<DialogTitle class="sr-only"> <DialogTitle class="sr-only">
{{ relation?.type === "reply" ? m.loved_busy_mantis_slide() : relation?.type === "quote" ? "Quote" : {{
m.chunky_dull_marlin_trip() }} relation?.type === "reply"
? m.loved_busy_mantis_slide()
: relation?.type === "quote"
? "Quote"
: m.chunky_dull_marlin_trip()
}}
</DialogTitle> </DialogTitle>
<DialogDescription class="sr-only"> <DialogDescription class="sr-only">
{{ relation?.type === "reply" ? m.tired_grassy_vulture_forgive() : relation?.type === "quote" ? {{
m.livid_livid_nils_snip() : m.brief_cool_capybara_fear() }} relation?.type === "reply"
? m.tired_grassy_vulture_forgive()
: relation?.type === "quote"
? m.livid_livid_nils_snip()
: m.brief_cool_capybara_fear()
}}
</DialogDescription> </DialogDescription>
<Composer :relation="relation ?? undefined" /> <Composer :relation="relation ?? undefined" />
</DialogContent> </DialogContent>

View file

@ -31,7 +31,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import type { Status } from "@versia/client/types"; import type { Status } from "@versia/client/types";
import { Card, CardFooter, CardHeader } from "../ui/card"; import { Card, CardContent, CardFooter, CardHeader } from "../ui/card";
import Actions from "./actions.vue"; import Actions from "./actions.vue";
import Content from "./content.vue"; import Content from "./content.vue";
import Header from "./header.vue"; import Header from "./header.vue";

View file

@ -1,7 +1,15 @@
<template> <template>
<DropdownMenu> <DropdownMenu>
<Card <Card
:class="cn('grid hover:cursor-pointer gap-4 items-center p-4', canEdit ? 'grid-cols-[auto,1fr,auto]' : 'grid-cols-[auto,1fr]')"> :class="
cn(
'grid hover:cursor-pointer gap-4 items-center p-4',
canEdit
? 'grid-cols-[auto,1fr,auto]'
: 'grid-cols-[auto,1fr]'
)
"
>
<Avatar shape="square"> <Avatar shape="square">
<AvatarImage :src="emoji.url" /> <AvatarImage :src="emoji.url" />
</Avatar> </Avatar>
@ -10,7 +18,11 @@
{{ emoji.shortcode }} {{ emoji.shortcode }}
</CardTitle> </CardTitle>
<CardDescription> <CardDescription>
{{ emoji.global ? m.real_tame_moose_greet() : m.witty_heroic_trout_cry() }} {{
emoji.global
? m.real_tame_moose_greet()
: m.witty_heroic_trout_cry()
}}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardFooter class="p-0" v-if="canEdit"> <CardFooter class="p-0" v-if="canEdit">
@ -45,12 +57,13 @@ import { type Emoji, RolePermission } from "@versia/client/types";
import { Delete, Ellipsis, TextCursorInput } from "lucide-vue-next"; import { Delete, Ellipsis, TextCursorInput } from "lucide-vue-next";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import { confirmModalService } from "~/components/modals/composable"; import { confirmModalService } from "~/components/modals/composable";
import { Avatar } from "~/components/ui/avatar"; import { Avatar, AvatarImage } from "~/components/ui/avatar";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { import {
Card, Card,
CardDescription, CardDescription,
CardFooter, CardFooter,
CardHeader,
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import { import {
@ -94,7 +107,7 @@ const editName = async () => {
toast.success(m.gaudy_lime_bison_adore()); toast.success(m.gaudy_lime_bison_adore());
identity.value.emojis = identity.value.emojis.map((e) => identity.value.emojis = identity.value.emojis.map((e) =>
e.id === emoji.id ? data : e, e.id === emoji.id ? data : e
); );
} catch { } catch {
toast.dismiss(id); toast.dismiss(id);
@ -121,7 +134,7 @@ const _delete = async () => {
toast.success(m.crisp_whole_canary_tear()); toast.success(m.crisp_whole_canary_tear());
identity.value.emojis = identity.value.emojis.filter( identity.value.emojis = identity.value.emojis.filter(
(e) => e.id !== emoji.id, (e) => e.id !== emoji.id
); );
} catch { } catch {
toast.dismiss(id); toast.dismiss(id);

View file

@ -11,18 +11,37 @@
{{ m.frail_great_marten_pet() }} {{ m.frail_great_marten_pet() }}
</DialogDescription> </DialogDescription>
<form class="p-4 grid gap-6" @submit="submit"> <form class="p-4 grid gap-6" @submit="submit">
<div v-if="values.image" class="flex items-center justify-around *:size-20 *:p-2 *:rounded *:border *:shadow"> <div
v-if="values.image"
class="flex items-center justify-around *:size-20 *:p-2 *:rounded *:border *:shadow"
>
<div class="bg-background"> <div class="bg-background">
<img class="h-full object-cover" :src="createObjectURL(values.image as File)" :alt="values.alt" /> <img
class="h-full object-cover"
:src="createObjectURL(values.image as File)"
:alt="values.alt"
/>
</div> </div>
<div class="bg-zinc-700"> <div class="bg-zinc-700">
<img class="h-full object-cover" :src="createObjectURL(values.image as File)" :alt="values.alt" /> <img
class="h-full object-cover"
:src="createObjectURL(values.image as File)"
:alt="values.alt"
/>
</div> </div>
<div class="bg-zinc-400"> <div class="bg-zinc-400">
<img class="h-full object-cover" :src="createObjectURL(values.image as File)" :alt="values.alt" /> <img
class="h-full object-cover"
:src="createObjectURL(values.image as File)"
:alt="values.alt"
/>
</div> </div>
<div class="bg-foreground"> <div class="bg-foreground">
<img class="h-full object-cover" :src="createObjectURL(values.image as File)" :alt="values.alt" /> <img
class="h-full object-cover"
:src="createObjectURL(values.image as File)"
:alt="values.alt"
/>
</div> </div>
</div> </div>
@ -32,13 +51,19 @@
{{ m.active_direct_bear_compose() }} {{ m.active_direct_bear_compose() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input type="file" accept="image/*" @change="(e: any) => { <Input
type="file"
accept="image/*"
@change="(e: any) => {
handleChange(e); handleChange(e);
if (!values.shortcode) { if (!values.shortcode) {
setFieldValue('shortcode', e.target.files[0].name.replace(/\.[^/.]+$/, '')); setFieldValue('shortcode', e.target.files[0].name.replace(/\.[^/.]+$/, ''));
} }
}" @blur="handleBlur" :disabled="isSubmitting" /> }"
@blur="handleBlur"
:disabled="isSubmitting"
/>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{{ m.lime_late_millipede_urge() }} {{ m.lime_late_millipede_urge() }}
@ -53,7 +78,10 @@
{{ m.happy_mild_fox_gleam() }} {{ m.happy_mild_fox_gleam() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input v-bind="componentField" :disabled="isSubmitting" /> <Input
v-bind="componentField"
:disabled="isSubmitting"
/>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{{ m.glad_day_kestrel_amaze() }} {{ m.glad_day_kestrel_amaze() }}
@ -68,7 +96,10 @@
{{ m.short_cute_jackdaw_comfort() }} {{ m.short_cute_jackdaw_comfort() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input v-bind="componentField" :disabled="isSubmitting" /> <Input
v-bind="componentField"
:disabled="isSubmitting"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -80,7 +111,11 @@
{{ m.watery_left_shrimp_bless() }} {{ m.watery_left_shrimp_bless() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Textarea rows="2" v-bind="componentField" :disabled="isSubmitting" /> <Textarea
rows="2"
v-bind="componentField"
:disabled="isSubmitting"
/>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{{ m.weird_fun_jurgen_arise() }} {{ m.weird_fun_jurgen_arise() }}
@ -89,9 +124,15 @@
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField, value, handleChange }" v-if="hasEmojiAdmin" name="global" <FormField
:as="Card"> v-slot="{ componentField, value, handleChange }"
<FormItem class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"> v-if="hasEmojiAdmin"
name="global"
:as="Card"
>
<FormItem
class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"
>
<CardHeader class="space-y-0.5 p-0"> <CardHeader class="space-y-0.5 p-0">
<FormLabel :as="CardTitle"> <FormLabel :as="CardTitle">
{{ m.pink_sharp_carp_work() }} {{ m.pink_sharp_carp_work() }}
@ -101,7 +142,12 @@
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<FormControl> <FormControl>
<Switch :checked="value" @update:checked="handleChange" v-bind="componentField" :disabled="isSubmitting" /> <Switch
:checked="value"
@update:checked="handleChange"
v-bind="componentField"
:disabled="isSubmitting"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -113,7 +159,11 @@
{{ m.soft_bold_ant_attend() }} {{ m.soft_bold_ant_attend() }}
</Button> </Button>
</DialogClose> </DialogClose>
<Button type="submit" variant="default" :disabled="isSubmitting"> <Button
type="submit"
variant="default"
:disabled="isSubmitting"
>
{{ m.flat_safe_haddock_gaze() }} {{ m.flat_safe_haddock_gaze() }}
</Button> </Button>
</DialogFooter> </DialogFooter>
@ -129,7 +179,12 @@ import { useForm } from "vee-validate";
import { toast } from "vue-sonner"; import { toast } from "vue-sonner";
import { z } from "zod"; import { z } from "zod";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardTitle } from "~/components/ui/card"; import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { import {
Dialog, Dialog,
DialogClose, DialogClose,
@ -147,6 +202,9 @@ import {
FormLabel, FormLabel,
FormMessage, FormMessage,
} from "~/components/ui/form"; } from "~/components/ui/form";
import { Input } from "~/components/ui/input";
import { Switch } from "~/components/ui/switch";
import { Textarea } from "~/components/ui/textarea";
import * as m from "~/paraglide/messages.js"; import * as m from "~/paraglide/messages.js";
const open = ref(false); const open = ref(false);
@ -169,7 +227,7 @@ const formSchema = toTypedSchema(
count: count:
identity.value?.instance.configuration.emojis identity.value?.instance.configuration.emojis
.emoji_size_limit ?? Number.POSITIVE_INFINITY, .emoji_size_limit ?? Number.POSITIVE_INFINITY,
}), })
), ),
shortcode: z shortcode: z
.string() .string()
@ -182,7 +240,7 @@ const formSchema = toTypedSchema(
identity.value?.instance.configuration.emojis identity.value?.instance.configuration.emojis
.max_emoji_shortcode_characters ?? .max_emoji_shortcode_characters ??
Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
}), })
) )
.regex(emojiValidator), .regex(emojiValidator),
global: z.boolean().default(false), global: z.boolean().default(false),
@ -192,7 +250,7 @@ const formSchema = toTypedSchema(
64, 64,
m.home_cool_orangutan_hug({ m.home_cool_orangutan_hug({
count: 64, count: 64,
}), })
) )
.optional(), .optional(),
alt: z alt: z
@ -206,10 +264,10 @@ const formSchema = toTypedSchema(
identity.value?.instance.configuration.emojis identity.value?.instance.configuration.emojis
.max_emoji_description_characters ?? .max_emoji_description_characters ??
Number.POSITIVE_INFINITY, Number.POSITIVE_INFINITY,
}), })
) )
.optional(), .optional(),
}), })
); );
const { isSubmitting, handleSubmit, values, setFieldValue } = useForm({ const { isSubmitting, handleSubmit, values, setFieldValue } = useForm({
validationSchema: formSchema, validationSchema: formSchema,
@ -230,7 +288,7 @@ const submit = handleSubmit(async (values) => {
alt: values.alt, alt: values.alt,
category: values.category, category: values.category,
global: values.global, global: values.global,
}, }
); );
toast.dismiss(id); toast.dismiss(id);

View file

@ -7,7 +7,12 @@
{{ m.bright_late_osprey_renew() }} {{ m.bright_late_osprey_renew() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input type="file" accept="image/*" @change="handleChange" @blur="handleBlur" /> <Input
type="file"
accept="image/*"
@change="handleChange"
@blur="handleBlur"
/>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{{ m.great_level_lamb_sway() }} {{ m.great_level_lamb_sway() }}
@ -22,7 +27,11 @@
{{ m.safe_icy_bulldog_quell() }} {{ m.safe_icy_bulldog_quell() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<ImageUploader v-model:image="identity.account.avatar" @submit-file="file => setValue(file)" @submit-url="url => setValue(url)" /> <ImageUploader
v-model:image="identity.account.avatar"
@submit-file="(file) => setValue(file)"
@submit-url="(url) => setValue(url)"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
@ -80,21 +89,61 @@
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<div class="grid gap-4"> <div class="grid gap-4">
<div v-for="(field, index) in value" :key="index" <div
class="grid items-center grid-cols-[auto,repeat(3,minmax(0,1fr))] gap-2"> v-for="(field, index) in value"
<Button variant="destructive" size="icon" :key="index"
@click="handleChange([...value.slice(0, index), ...value.slice(index + 1)])"> class="grid items-center grid-cols-[auto,repeat(3,minmax(0,1fr))] gap-2"
>
<Button
variant="destructive"
size="icon"
@click="
handleChange([
...value.slice(0, index),
...value.slice(index + 1),
])
"
>
<Trash /> <Trash />
</Button> </Button>
<Input v-model="field.name" placeholder="Name" @update:model-value="e => { <Input
handleChange([...value.slice(0, index), { name: e, value: field.value }, ...value.slice(index + 1)]); v-model="field.name"
}" /> placeholder="Name"
<Input v-model="field.value" placeholder="Value" class="col-span-2" @update:model-value="e => { @update:model-value="
handleChange([...value.slice(0, index), { name: field.name, value: e }, ...value.slice(index + 1)]); (e) => {
}" /> handleChange([
...value.slice(0, index),
{ name: e, value: field.value },
...value.slice(index + 1),
]);
}
"
/>
<Input
v-model="field.value"
placeholder="Value"
class="col-span-2"
@update:model-value="
(e) => {
handleChange([
...value.slice(0, index),
{ name: field.name, value: e },
...value.slice(index + 1),
]);
}
"
/>
</div> </div>
<Button type="button" variant="secondary" <Button
@click="handleChange([...value, { name: '', value: '' }])"> type="button"
variant="secondary"
@click="
handleChange([
...value,
{ name: '', value: '' },
])
"
>
{{ m.front_north_eel_gulp() }} {{ m.front_north_eel_gulp() }}
</Button> </Button>
</div> </div>
@ -103,8 +152,14 @@
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField, value, handleChange }" name="bot" :as="Card"> <FormField
<FormItem class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"> v-slot="{ componentField, value, handleChange }"
name="bot"
:as="Card"
>
<FormItem
class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"
>
<CardHeader class="space-y-0.5 p-0"> <CardHeader class="space-y-0.5 p-0">
<FormLabel :as="CardTitle"> <FormLabel :as="CardTitle">
{{ m.gaudy_each_opossum_play() }} {{ m.gaudy_each_opossum_play() }}
@ -114,14 +169,24 @@
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<FormControl> <FormControl>
<Switch :checked="value" @update:checked="handleChange" v-bind="componentField" /> <Switch
:checked="value"
@update:checked="handleChange"
v-bind="componentField"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField, value, handleChange }" name="locked" :as="Card"> <FormField
<FormItem class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"> v-slot="{ componentField, value, handleChange }"
name="locked"
:as="Card"
>
<FormItem
class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"
>
<CardHeader class="space-y-0.5 p-0"> <CardHeader class="space-y-0.5 p-0">
<FormLabel :as="CardTitle"> <FormLabel :as="CardTitle">
{{ m.dirty_moving_shark_emerge() }} {{ m.dirty_moving_shark_emerge() }}
@ -131,14 +196,24 @@
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<FormControl> <FormControl>
<Switch :checked="value" @update:checked="handleChange" v-bind="componentField" /> <Switch
:checked="value"
@update:checked="handleChange"
v-bind="componentField"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField, value, handleChange }" name="discoverable" :as="Card"> <FormField
<FormItem class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"> v-slot="{ componentField, value, handleChange }"
name="discoverable"
:as="Card"
>
<FormItem
class="grid grid-cols-[1fr,auto] items-center p-6 gap-2"
>
<CardHeader class="space-y-0.5 p-0"> <CardHeader class="space-y-0.5 p-0">
<FormLabel :as="CardTitle"> <FormLabel :as="CardTitle">
{{ m.red_vivid_cuckoo_spark() }} {{ m.red_vivid_cuckoo_spark() }}
@ -148,14 +223,21 @@
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<FormControl> <FormControl>
<Switch :checked="value" @update:checked="handleChange" v-bind="componentField" /> <Switch
:checked="value"
@update:checked="handleChange"
v-bind="componentField"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
</form> </form>
</Card> </Card>
<Profile :account="account" class="max-w-lg overflow-auto hidden xl:block" /> <Profile
:account="account"
class="max-w-lg overflow-auto hidden xl:block"
/>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
@ -167,7 +249,12 @@ import { toast } from "vue-sonner";
import { z } from "zod"; import { z } from "zod";
import Profile from "~/components/profiles/profile.vue"; import Profile from "~/components/profiles/profile.vue";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardTitle } from "~/components/ui/card"; import {
Card,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { import {
FormControl, FormControl,
FormDescription, FormDescription,
@ -200,7 +287,7 @@ const formSchema = toTypedSchema(
m.civil_icy_ant_mend({ m.civil_icy_ant_mend({
size: identity.value?.instance.configuration.accounts size: identity.value?.instance.configuration.accounts
.header_size_limit, .header_size_limit,
}), })
) )
.optional(), .optional(),
avatar: z avatar: z
@ -213,7 +300,7 @@ const formSchema = toTypedSchema(
m.zippy_caring_raven_edit({ m.zippy_caring_raven_edit({
size: identity.value?.instance.configuration.accounts size: identity.value?.instance.configuration.accounts
.avatar_size_limit, .avatar_size_limit,
}), })
) )
.or(z.string().url()) .or(z.string().url())
.optional(), .optional(),
@ -221,26 +308,26 @@ const formSchema = toTypedSchema(
.string() .string()
.max( .max(
identity.value.instance.configuration.accounts identity.value.instance.configuration.accounts
.max_displayname_characters, .max_displayname_characters
), ),
username: z username: z
.string() .string()
.regex(/^[a-z0-9_-]+$/, m.still_upper_otter_dine()) .regex(/^[a-z0-9_-]+$/, m.still_upper_otter_dine())
.max( .max(
identity.value.instance.configuration.accounts identity.value.instance.configuration.accounts
.max_username_characters, .max_username_characters
), ),
bio: z bio: z
.string() .string()
.max( .max(
identity.value.instance.configuration.accounts identity.value.instance.configuration.accounts
.max_note_characters, .max_note_characters
), ),
bot: z.boolean(), bot: z.boolean(),
locked: z.boolean(), locked: z.boolean(),
discoverable: z.boolean(), discoverable: z.boolean(),
fields: z.array(z.object({ name: z.string(), value: z.string() })), fields: z.array(z.object({ name: z.string(), value: z.string() })),
}), })
); );
const form = useForm({ const form = useForm({
@ -280,8 +367,8 @@ const handleSubmit = form.handleSubmit(async (values) => {
// Can't compare two arrays directly in JS, so we need to check if all fields are the same // Can't compare two arrays directly in JS, so we need to check if all fields are the same
fields_attributes: values.fields.every((field) => fields_attributes: values.fields.every((field) =>
account.value.source?.fields?.some( account.value.source?.fields?.some(
(f) => f.name === field.name && f.value === field.value, (f) => f.name === field.name && f.value === field.value
), )
) )
? undefined ? undefined
: values.fields, : values.fields,
@ -300,8 +387,8 @@ const handleSubmit = form.handleSubmit(async (values) => {
try { try {
const { data } = await client.value.updateCredentials( const { data } = await client.value.updateCredentials(
Object.fromEntries( Object.fromEntries(
Object.entries(changedData).filter(([, v]) => v !== undefined), Object.entries(changedData).filter(([, v]) => v !== undefined)
), )
); );
toast.dismiss(id); toast.dismiss(id);

View file

@ -1,10 +1,15 @@
<template> <template>
<Dialog v-model:open="open"> <Dialog v-model:open="open">
<DialogTrigger :as-child="true"> <DialogTrigger :as-child="true">
<Button v-bind="$attrs" variant="ghost" class="h-fit w-fit p-0 m-0 relative group border overflow-hidden"> <Button
v-bind="$attrs"
variant="ghost"
class="h-fit w-fit p-0 m-0 relative group border overflow-hidden"
>
<Avatar size="lg" :src="image" :name="displayName" /> <Avatar size="lg" :src="image" :name="displayName" />
<div <div
class="absolute inset-0 bg-background/80 flex group-hover:opacity-100 opacity-0 duration-200 items-center justify-center"> class="absolute inset-0 bg-background/80 flex group-hover:opacity-100 opacity-0 duration-200 items-center justify-center"
>
<Upload /> <Upload />
</div> </div>
</Button> </Button>
@ -17,7 +22,10 @@
{{ m.suave_broad_albatross_drop() }} {{ m.suave_broad_albatross_drop() }}
</DialogDescription> </DialogDescription>
<form class="p-4 grid gap-6" @submit="submit"> <form class="p-4 grid gap-6" @submit="submit">
<Tabs default-value="upload" class="mt-2 data-[component=tabpanel]:*:mt-6"> <Tabs
default-value="upload"
class="mt-2 data-[component=tabpanel]:*:mt-6"
>
<TabsList class="w-full *:w-full"> <TabsList class="w-full *:w-full">
<TabsTrigger value="upload"> <TabsTrigger value="upload">
{{ m.flat_safe_haddock_gaze() }} {{ m.flat_safe_haddock_gaze() }}
@ -30,14 +38,22 @@
</TabsTrigger> </TabsTrigger>
</TabsList> </TabsList>
<TabsContent value="upload"> <TabsContent value="upload">
<FormField v-slot="{ handleChange, handleBlur }" name="image"> <FormField
v-slot="{ handleChange, handleBlur }"
name="image"
>
<FormItem> <FormItem>
<FormLabel class="sr-only"> <FormLabel class="sr-only">
{{ m.flat_safe_haddock_gaze() }} {{ m.flat_safe_haddock_gaze() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input type="file" accept="image/*" @change="handleChange" @blur="handleBlur" <Input
:disabled="isSubmitting" /> type="file"
accept="image/*"
@change="handleChange"
@blur="handleBlur"
:disabled="isSubmitting"
/>
</FormControl> </FormControl>
<FormDescription> <FormDescription>
{{ m.lime_late_millipede_urge() }} {{ m.lime_late_millipede_urge() }}
@ -47,38 +63,57 @@
</FormField> </FormField>
</TabsContent> </TabsContent>
<TabsContent value="gravatar"> <TabsContent value="gravatar">
<FormField v-slot="{ componentField, errors, value }" name="email" @update:model-value="async (value) => { <FormField
gravatarUrl = await emailToGravatar(value) v-slot="{ componentField, value }"
}"> name="email"
@update:model-value="
async (value) => {
gravatarUrl = await emailToGravatar(value);
}
"
>
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{{ m.lower_formal_kudu_lift() }} {{ m.lower_formal_kudu_lift() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input v-bind="componentField" :disabled="isSubmitting" <Input
placeholder="peter.griffin@fox.com" /> v-bind="componentField"
:disabled="isSubmitting"
placeholder="peter.griffin@fox.com"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<div v-if="value" class="grid gap-4 !mt-4"> <div v-if="value" class="grid gap-4 !mt-4">
<Label>{{ m.witty_honest_wallaby_support() }}</Label> <Label>{{
m.witty_honest_wallaby_support()
}}</Label>
<Avatar size="lg" :src="gravatarUrl" /> <Avatar size="lg" :src="gravatarUrl" />
</div> </div>
</FormItem> </FormItem>
</FormField> </FormField>
</TabsContent> </TabsContent>
<TabsContent value="url"> <TabsContent value="url">
<FormField v-slot="{ componentField, errors, value }" name="url"> <FormField
v-slot="{ componentField, value }"
name="url"
>
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{{ m.proud_next_elk_beam() }} {{ m.proud_next_elk_beam() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input v-bind="componentField" :disabled="isSubmitting" <Input
placeholder="https://mysite.com/avatar.webp" /> v-bind="componentField"
:disabled="isSubmitting"
placeholder="https://mysite.com/avatar.webp"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
<div v-if="value" class="grid gap-4 !mt-4"> <div v-if="value" class="grid gap-4 !mt-4">
<Label>{{ m.witty_honest_wallaby_support() }}</Label> <Label>{{
m.witty_honest_wallaby_support()
}}</Label>
<Avatar size="lg" :src="value" /> <Avatar size="lg" :src="value" />
</div> </div>
</FormItem> </FormItem>
@ -91,7 +126,11 @@
{{ m.soft_bold_ant_attend() }} {{ m.soft_bold_ant_attend() }}
</Button> </Button>
</DialogClose> </DialogClose>
<Button type="submit" variant="default" :disabled="isSubmitting"> <Button
type="submit"
variant="default"
:disabled="isSubmitting"
>
{{ m.teary_antsy_panda_aid() }} {{ m.teary_antsy_panda_aid() }}
</Button> </Button>
</DialogFooter> </DialogFooter>
@ -106,6 +145,7 @@ import { Upload } from "lucide-vue-next";
import { useForm } from "vee-validate"; import { useForm } from "vee-validate";
import { z } from "zod"; import { z } from "zod";
import Avatar from "~/components/profiles/avatar.vue"; import Avatar from "~/components/profiles/avatar.vue";
import { Button } from "~/components/ui/button";
import { import {
Dialog, Dialog,
DialogClose, DialogClose,
@ -113,6 +153,7 @@ import {
DialogDescription, DialogDescription,
DialogFooter, DialogFooter,
DialogTitle, DialogTitle,
DialogTrigger,
} from "~/components/ui/dialog"; } from "~/components/ui/dialog";
import { import {
FormControl, FormControl,
@ -123,6 +164,7 @@ import {
FormMessage, FormMessage,
} from "~/components/ui/form"; } from "~/components/ui/form";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import { Label } from "~/components/ui/label";
import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "~/components/ui/tabs";
import * as m from "~/paraglide/messages.js"; import * as m from "~/paraglide/messages.js";
@ -151,25 +193,25 @@ const schema = toTypedSchema(
(v) => v.size <= (maxSize ?? Number.MAX_SAFE_INTEGER), (v) => v.size <= (maxSize ?? Number.MAX_SAFE_INTEGER),
m.zippy_caring_raven_edit({ m.zippy_caring_raven_edit({
size: maxSize ?? Number.MAX_SAFE_INTEGER, size: maxSize ?? Number.MAX_SAFE_INTEGER,
}), })
), ),
}) })
.or( .or(
z.object({ z.object({
url: z.string().url(), url: z.string().url(),
}), })
) )
.or( .or(
z.object({ z.object({
email: z.string().email(), email: z.string().email(),
}), })
), )
); );
const emailToGravatar = async (email: string) => { const emailToGravatar = async (email: string) => {
const sha256 = await crypto.subtle.digest( const sha256 = await crypto.subtle.digest(
"SHA-256", "SHA-256",
new TextEncoder().encode(email), new TextEncoder().encode(email)
); );
return `https://www.gravatar.com/avatar/${Array.from(new Uint8Array(sha256)) return `https://www.gravatar.com/avatar/${Array.from(new Uint8Array(sha256))
@ -192,7 +234,7 @@ const submit = handleSubmit(async (values) => {
} else if ((values as { email: string }).email) { } else if ((values as { email: string }).email) {
emit( emit(
"submitUrl", "submitUrl",
await emailToGravatar((values as { email: string }).email), await emailToGravatar((values as { email: string }).email)
); );
} }

View file

@ -30,7 +30,7 @@
"check": "bunx tsc -p ." "check": "bunx tsc -p ."
}, },
"dependencies": { "dependencies": {
"@nuxt/fonts": "^0.10.3", "@nuxt/fonts": "^0.11.0",
"@nuxtjs/color-mode": "3.5.2", "@nuxtjs/color-mode": "3.5.2",
"@tailwindcss/typography": "^0.5.16", "@tailwindcss/typography": "^0.5.16",
"@tiptap/extension-highlight": "^2.11.5", "@tiptap/extension-highlight": "^2.11.5",
@ -50,25 +50,25 @@
"@vee-validate/zod": "^4.15.0", "@vee-validate/zod": "^4.15.0",
"@versia/client": "0.1.5", "@versia/client": "0.1.5",
"@videojs-player/vue": "^1.0.0", "@videojs-player/vue": "^1.0.0",
"@vite-pwa/nuxt": "^0.10.6", "@vite-pwa/nuxt": "^0.10.8",
"@vueuse/core": "^12.6.1", "@vueuse/core": "^13.0.0",
"@vueuse/nuxt": "^12.6.1", "@vueuse/nuxt": "^13.0.0",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"embla-carousel-vue": "^8.5.2", "embla-carousel-vue": "^8.5.2",
"fuzzysort": "^3.1.0", "fuzzysort": "^3.1.0",
"html-to-text": "^9.0.5", "html-to-text": "^9.0.5",
"lucide-vue-next": "^0.475.0", "lucide-vue-next": "^0.484.0",
"magic-regexp": "^0.8.0", "magic-regexp": "^0.8.0",
"mitt": "^3.0.1", "mitt": "^3.0.1",
"nanoid": "^5.0.9", "nanoid": "^5.1.5",
"nuxt": "^3.15.4", "nuxt": "^3.16.1",
"nuxt-security": "^2.1.5", "nuxt-security": "^2.2.0",
"radix-vue": "^1.9.14", "radix-vue": "^1.9.17",
"shadcn-nuxt": "0.11.3", "shadcn-nuxt": "1.0.3",
"tailwind-merge": "^3.0.1", "tailwind-merge": "^3.0.2",
"tailwindcss-animate": "^1.0.7", "tailwindcss-animate": "^1.0.7",
"vaul-vue": "^0.2.1", "vaul-vue": "^0.4.1",
"vee-validate": "^4.15.0", "vee-validate": "^4.15.0",
"vue": "^3.5.13", "vue": "^3.5.13",
"vue-router": "^4.5.0", "vue-router": "^4.5.0",
@ -79,15 +79,15 @@
"@biomejs/biome": "^1.9.4", "@biomejs/biome": "^1.9.4",
"@iconify-json/fluent-emoji": "^1.2.3", "@iconify-json/fluent-emoji": "^1.2.3",
"@iconify-json/fluent-emoji-flat": "^1.2.3", "@iconify-json/fluent-emoji-flat": "^1.2.3",
"@iconify-json/noto": "^1.2.2", "@iconify-json/noto": "^1.2.3",
"@iconify-json/twemoji": "^1.2.2", "@iconify-json/twemoji": "^1.2.2",
"@iconify/utils": "^2.3.0", "@iconify/utils": "^2.3.0",
"@inlang/paraglide-js": "2.0.0-beta.17", "@inlang/paraglide-js": "2.0.6",
"@nuxtjs/tailwindcss": "^6.13.1", "@nuxtjs/tailwindcss": "^6.13.2",
"@tailwindcss/forms": "^0.5.10", "@tailwindcss/forms": "^0.5.10",
"@types/html-to-text": "^9.0.4", "@types/html-to-text": "^9.0.4",
"typescript": "^5.7.3", "typescript": "^5.8.2",
"vue-tsc": "^2.2.0" "vue-tsc": "^2.2.8"
}, },
"trustedDependencies": [ "trustedDependencies": [
"@biomejs/biome", "@biomejs/biome",

View file

@ -1,15 +1,24 @@
<template> <template>
<div class="h-svh flex items-center justify-center px-4 bg-center bg-no-repeat bg-cover" :style="{ <div
backgroundImage: 'url(/images/banner.webp)' class="h-svh flex items-center justify-center px-4 bg-center bg-no-repeat bg-cover"
}"> :style="{
backgroundImage: 'url(/images/banner.webp)',
}"
>
<Card class="w-full max-w-md"> <Card class="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>{{ m.aware_awful_crow_spur() }}</CardTitle> <CardTitle>{{ m.aware_awful_crow_spur() }}</CardTitle>
<CardDescription>{{ m.mushy_soft_lizard_propel() }}<br />{{ m.short_arable_leopard_zap() }} <CardDescription
>{{ m.mushy_soft_lizard_propel() }}<br />{{
m.short_arable_leopard_zap()
}}
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent class="grid"> <CardContent class="grid">
<pre class="rounded bg-muted px-4 py-2 border text-center w-full font-mono text-sm font-semibold select-all">{{ code }}</pre> <pre
class="rounded bg-muted px-4 py-2 border text-center w-full font-mono text-sm font-semibold select-all"
>{{ code }}</pre
>
</CardContent> </CardContent>
</Card> </Card>
</div> </div>

View file

@ -1,41 +1,83 @@
<template> <template>
<div class="flex h-svh items-center justify-center px-6 py-12 lg:px-8 bg-center bg-no-repeat bg-cover" :style="{ <div
backgroundImage: 'url(/images/banner.webp)' class="flex h-svh items-center justify-center px-6 py-12 lg:px-8 bg-center bg-no-repeat bg-cover"
}"> :style="{
<Card class="w-full max-w-md" as="form" method="POST" :action="url.pathname.replace('/oauth/consent', '/oauth/authorize')"> backgroundImage: 'url(/images/banner.webp)',
<input type="hidden" v-for="([key, value]) in url.searchParams" :key="key" :name="key" :value="value" /> }"
>
<Card
class="w-full max-w-md"
as="form"
method="POST"
:action="url.pathname.replace('/oauth/consent', '/oauth/authorize')"
>
<input
type="hidden"
v-for="[key, value] in url.searchParams"
:key="key"
:name="key"
:value="value"
/>
<CardHeader> <CardHeader>
<CardTitle as="h1" class="text-2xl break-words">{{ m.fresh_broad_cockroach_radiate({ <CardTitle as="h1" class="text-2xl break-words">{{
application: application ?? "", m.fresh_broad_cockroach_radiate({
}) }}</CardTitle> application: application ?? "",
})
}}</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<Card> <Card>
<CardContent class="flex flex-col px-4 py-2"> <CardContent class="flex flex-col px-4 py-2">
<CardTitle as="h2" class="text-lg">{{ application }}</CardTitle> <CardTitle as="h2" class="text-lg">{{
<a v-if="website" :href="website" target="_blank" rel="noopener noreferrer" class="underline">{{ website }}</a> application
}}</CardTitle>
<a
v-if="website"
:href="website"
target="_blank"
rel="noopener noreferrer"
class="underline"
>{{ website }}</a
>
</CardContent> </CardContent>
</Card> </Card>
<ul class="list-none my-6 [&>li]:mt-2"> <ul class="list-none my-6 [&>li]:mt-2">
<li v-for="text in getScopeText(scopes)" :key="text[1]" class="flex flex-row gap-1 items-center"> <li
v-for="text in getScopeText(scopes)"
:key="text[1]"
class="flex flex-row gap-1 items-center"
>
<Check class="size-4" /> <Check class="size-4" />
<h2 class="text-sm"> <h2 class="text-sm">
<strong class="font-bold">{{ text[0] }}</strong> {{ text[1] }} <strong class="font-bold">{{ text[0] }}</strong>
{{ text[1] }}
</h2> </h2>
</li> </li>
</ul> </ul>
<div class="flex-col flex gap-y-1 text-sm"> <div class="flex-col flex gap-y-1 text-sm">
<p v-html="m.gross_antsy_kangaroo_succeed({ <p
application: application ?? '', v-html="
})"></p> m.gross_antsy_kangaroo_succeed({
<p v-html="m.hour_close_giraffe_mop({ application: application ?? '',
application: application ?? '', })
})"></p> "
></p>
<p
v-html="
m.hour_close_giraffe_mop({
application: application ?? '',
})
"
></p>
</div> </div>
</CardContent> </CardContent>
<CardFooter class="grid gap-2"> <CardFooter class="grid gap-2">
<Button variant="default" type="submit">{{ m.last_spare_polecat_reside() }}</Button> <Button variant="default" type="submit">{{
<Button :as="NuxtLink" href="/" variant="secondary">{{ m.soft_bold_ant_attend() }}</Button> m.last_spare_polecat_reside()
}}</Button>
<Button :as="NuxtLink" href="/" variant="secondary">{{
m.soft_bold_ant_attend()
}}</Button>
</CardFooter> </CardFooter>
</Card> </Card>
</div> </div>
@ -44,7 +86,13 @@
<script setup lang="ts"> <script setup lang="ts">
import { Check } from "lucide-vue-next"; import { Check } from "lucide-vue-next";
import { Button } from "~/components/ui/button"; import { Button } from "~/components/ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "~/components/ui/card"; import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import * as m from "~/paraglide/messages.js"; import * as m from "~/paraglide/messages.js";
import { NuxtLink } from "#components"; import { NuxtLink } from "#components";

View file

@ -1,7 +1,10 @@
<template> <template>
<div class="flex h-svh items-center justify-center px-6 py-12 lg:px-8 bg-center bg-no-repeat bg-cover" :style="{ <div
backgroundImage: 'url(/images/banner.webp)' class="flex h-svh items-center justify-center px-6 py-12 lg:px-8 bg-center bg-no-repeat bg-cover"
}"> :style="{
backgroundImage: 'url(/images/banner.webp)',
}"
>
<Card v-if="params.success" class="w-full max-w-md"> <Card v-if="params.success" class="w-full max-w-md">
<CardHeader> <CardHeader>
<CardTitle>{{ m.late_mean_capybara_fade() }}</CardTitle> <CardTitle>{{ m.late_mean_capybara_fade() }}</CardTitle>
@ -16,23 +19,39 @@
</CardFooter> </CardFooter>
</Card> </Card>
<Card v-else class="w-full max-w-md"> <Card v-else class="w-full max-w-md">
<form method="POST" action="/api/auth/reset" @submit="form.submitForm"> <form
method="POST"
action="/api/auth/reset"
@submit="form.submitForm"
>
<CardHeader> <CardHeader>
<Alert v-if="params.login_reset" variant="default" class="mb-4"> <Alert
v-if="params.login_reset"
variant="default"
class="mb-4"
>
<AlertCircle class="size-4" /> <AlertCircle class="size-4" />
<AlertTitle>{{ m.east_loud_lobster_explore() }}</AlertTitle> <AlertTitle>{{
m.east_loud_lobster_explore()
}}</AlertTitle>
<AlertDescription> <AlertDescription>
{{ m.good_plane_gazelle_glow() }} {{ m.good_plane_gazelle_glow() }}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<Alert v-if="params.error" variant="destructive" class="mb-4"> <Alert
v-if="params.error"
variant="destructive"
class="mb-4"
>
<AlertCircle class="size-4" /> <AlertCircle class="size-4" />
<AlertTitle>{{ params.error }}</AlertTitle> <AlertTitle>{{ params.error }}</AlertTitle>
<AlertDescription> <AlertDescription>
{{ params.error_description }} {{ params.error_description }}
</AlertDescription> </AlertDescription>
</Alert> </Alert>
<CardTitle as="h1">{{ m.tired_green_sloth_evoke() }}</CardTitle> <CardTitle as="h1">{{
m.tired_green_sloth_evoke()
}}</CardTitle>
<CardDescription> <CardDescription>
{{ m.solid_slow_platypus_talk() }} {{ m.solid_slow_platypus_talk() }}
</CardDescription> </CardDescription>
@ -51,27 +70,42 @@
{{ m.true_male_gadfly_stab() }} {{ m.true_male_gadfly_stab() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="hunter2" type="password" auto-capitalize="none" auto-correct="off" <Input
v-bind="componentField" /> placeholder="hunter2"
type="password"
auto-capitalize="none"
auto-correct="off"
v-bind="componentField"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
<FormField v-slot="{ componentField }" name="password-confirm"> <FormField
v-slot="{ componentField }"
name="password-confirm"
>
<FormItem> <FormItem>
<FormLabel> <FormLabel>
{{ m.awful_cozy_jannes_rise() }} {{ m.awful_cozy_jannes_rise() }}
</FormLabel> </FormLabel>
<FormControl> <FormControl>
<Input placeholder="hunter2" type="password" auto-capitalize="none" auto-correct="off" <Input
v-bind="componentField" /> placeholder="hunter2"
type="password"
auto-capitalize="none"
auto-correct="off"
v-bind="componentField"
/>
</FormControl> </FormControl>
<FormMessage /> <FormMessage />
</FormItem> </FormItem>
</FormField> </FormField>
</CardContent> </CardContent>
<CardFooter class="grid gap-2"> <CardFooter class="grid gap-2">
<Button variant="default" type="submit">{{ m.noisy_round_skate_yell() }}</Button> <Button variant="default" type="submit">{{
m.noisy_round_skate_yell()
}}</Button>
</CardFooter> </CardFooter>
</form> </form>
</Card> </Card>
@ -88,6 +122,7 @@ import { Button } from "~/components/ui/button";
import { import {
Card, Card,
CardContent, CardContent,
CardDescription,
CardFooter, CardFooter,
CardHeader, CardHeader,
CardTitle, CardTitle,
@ -147,7 +182,7 @@ const formSchema = toTypedSchema(
}); });
} }
return {}; return {};
}), })
); );
const params = useUrlSearchParams(); const params = useUrlSearchParams();
@ -158,4 +193,4 @@ const form = useForm({
token: (params.token as string) ?? undefined, token: (params.token as string) ?? undefined,
}, },
}); });
</script> </script>

View file

@ -1,21 +1,28 @@
<template> <template>
<div class="md:px-8 px-4 py-2 max-w-7xl mx-auto w-full space-y-6"> <div class="md:px-8 px-4 py-2 max-w-7xl mx-auto w-full space-y-6">
<div :class="cn('grid gap-2', canUpload && 'grid-cols-[1fr,auto]')"> <div :class="cn('grid gap-2', canUpload && 'grid-cols-[1fr,auto]')">
<h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize"> <h1
class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize"
>
{{ m.suave_smart_mantis_climb() }} {{ m.suave_smart_mantis_climb() }}
</h1> </h1>
<Uploader v-if="canUpload"> <Uploader v-if="canUpload">
<Button variant="default"> <Button variant="default"> <Upload /> Upload </Button>
<Upload /> Upload
</Button>
</Uploader> </Uploader>
</div> </div>
<div v-if="emojis.length > 0" class="max-w-sm w-full relative"> <div v-if="emojis.length > 0" class="max-w-sm w-full relative">
<Input v-model="search" placeholder="Search" class="pl-8" /> <Input v-model="search" placeholder="Search" class="pl-8" />
<Search class="absolute size-4 top-1/2 left-2.5 transform -translate-y-1/2" /> <Search
class="absolute size-4 top-1/2 left-2.5 transform -translate-y-1/2"
/>
</div> </div>
<Category v-if="emojis.length > 0" v-for="([name, emojis]) in categories" :key="name" :emojis="emojis" <Category
:name="name" /> v-if="emojis.length > 0"
v-for="[name, emojis] in categories"
:key="name"
:emojis="emojis"
:name="name"
/>
<Card v-else class="shadow-none bg-transparent border-none p-4"> <Card v-else class="shadow-none bg-transparent border-none p-4">
<CardHeader class="text-center gap-y-4"> <CardHeader class="text-center gap-y-4">
<CardTitle>{{ m.actual_steep_llama_rest() }}</CardTitle> <CardTitle>{{ m.actual_steep_llama_rest() }}</CardTitle>
@ -33,6 +40,7 @@ import { type Emoji, RolePermission } from "@versia/client/types";
import { Search, Upload } from "lucide-vue-next"; import { Search, Upload } from "lucide-vue-next";
import Category from "~/components/preferences/emojis/category.vue"; import Category from "~/components/preferences/emojis/category.vue";
import Uploader from "~/components/preferences/emojis/uploader.vue"; import Uploader from "~/components/preferences/emojis/uploader.vue";
import { Button } from "~/components/ui/button";
import { import {
Card, Card,
CardDescription, CardDescription,
@ -60,14 +68,14 @@ const permissions = usePermissions();
const canUpload = computed( const canUpload = computed(
() => () =>
permissions.value.includes(RolePermission.ManageOwnEmojis) || permissions.value.includes(RolePermission.ManageOwnEmojis) ||
permissions.value.includes(RolePermission.ManageEmojis), permissions.value.includes(RolePermission.ManageEmojis)
); );
const emojis = computed( const emojis = computed(
() => () =>
identity.value?.emojis?.filter((emoji) => identity.value?.emojis?.filter((emoji) =>
emoji.shortcode.toLowerCase().includes(search.value.toLowerCase()), emoji.shortcode.toLowerCase().includes(search.value.toLowerCase())
) ?? [], ) ?? []
); );
const search = ref(""); const search = ref("");
@ -95,4 +103,4 @@ const categories = computed(() => {
} }
return categories; return categories;
}); });
</script> </script>