mirror of
https://github.com/versia-pub/frontend.git
synced 2025-12-06 08:28:20 +01:00
feat: ✨ Implement emoji uploads
This commit is contained in:
parent
17441bfd47
commit
93a3d233d0
|
|
@ -72,9 +72,15 @@ const { emoji } = defineProps<{
|
||||||
|
|
||||||
const permissions = usePermissions();
|
const permissions = usePermissions();
|
||||||
const canEdit =
|
const canEdit =
|
||||||
!emoji.global || permissions.value.includes(RolePermission.ManageEmojis);
|
(!emoji.global &&
|
||||||
|
permissions.value.includes(RolePermission.ManageOwnEmojis)) ||
|
||||||
|
permissions.value.includes(RolePermission.ManageEmojis);
|
||||||
|
|
||||||
const editName = async () => {
|
const editName = async () => {
|
||||||
|
if (!identity.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const result = await confirmModalService.confirm({
|
const result = await confirmModalService.confirm({
|
||||||
title: m.slimy_awful_florian_sail(),
|
title: m.slimy_awful_florian_sail(),
|
||||||
defaultValue: emoji.shortcode,
|
defaultValue: emoji.shortcode,
|
||||||
|
|
@ -85,12 +91,16 @@ const editName = async () => {
|
||||||
if (result.confirmed) {
|
if (result.confirmed) {
|
||||||
const id = toast.loading(m.teary_tame_gull_bless());
|
const id = toast.loading(m.teary_tame_gull_bless());
|
||||||
try {
|
try {
|
||||||
await client.value.updateEmoji(emoji.id, {
|
const { data } = await client.value.updateEmoji(emoji.id, {
|
||||||
shortcode: result.value,
|
shortcode: result.value,
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.dismiss(id);
|
toast.dismiss(id);
|
||||||
toast.success(m.gaudy_lime_bison_adore());
|
toast.success(m.gaudy_lime_bison_adore());
|
||||||
|
|
||||||
|
identity.value.emojis = identity.value.emojis.map((e) =>
|
||||||
|
e.id === emoji.id ? data : e,
|
||||||
|
);
|
||||||
} catch {
|
} catch {
|
||||||
toast.dismiss(id);
|
toast.dismiss(id);
|
||||||
}
|
}
|
||||||
|
|
@ -98,13 +108,29 @@ const editName = async () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _delete = async () => {
|
const _delete = async () => {
|
||||||
const id = toast.loading(m.weary_away_liger_zip());
|
if (!identity.value) {
|
||||||
try {
|
return;
|
||||||
await client.value.deleteEmoji(emoji.id);
|
}
|
||||||
toast.dismiss(id);
|
|
||||||
toast.success(m.crisp_whole_canary_tear());
|
const { confirmed } = await confirmModalService.confirm({
|
||||||
} catch {
|
title: m.tense_quick_cod_favor(),
|
||||||
toast.dismiss(id);
|
message: m.honest_factual_carp_aspire(),
|
||||||
|
confirmText: m.tense_quick_cod_favor(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
const id = toast.loading(m.weary_away_liger_zip());
|
||||||
|
try {
|
||||||
|
await client.value.deleteEmoji(emoji.id);
|
||||||
|
toast.dismiss(id);
|
||||||
|
toast.success(m.crisp_whole_canary_tear());
|
||||||
|
|
||||||
|
identity.value.emojis = identity.value.emojis.filter(
|
||||||
|
(e) => e.id !== emoji.id,
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
toast.dismiss(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
245
components/preferences/emojis/uploader.vue
Normal file
245
components/preferences/emojis/uploader.vue
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
<template>
|
||||||
|
<Dialog v-model:open="open">
|
||||||
|
<DialogTrigger>
|
||||||
|
<slot />
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogTitle>
|
||||||
|
{{ m.whole_icy_puffin_smile() }}
|
||||||
|
</DialogTitle>
|
||||||
|
<DialogDescription class="sr-only">
|
||||||
|
{{ m.frail_great_marten_pet() }}
|
||||||
|
</DialogDescription>
|
||||||
|
<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 class="bg-background">
|
||||||
|
<img 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>
|
||||||
|
|
||||||
|
<FormField v-slot="{ handleChange, handleBlur }" name="image">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{{ m.active_direct_bear_compose() }}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input type="file" accept="image/*" @change="(e: any) => {
|
||||||
|
handleChange(e);
|
||||||
|
|
||||||
|
if (!values.shortcode) {
|
||||||
|
setFieldValue('shortcode', e.target.files[0].name.replace(/\.[^/.]+$/, ''));
|
||||||
|
}
|
||||||
|
}" @blur="handleBlur" :disabled="isSubmitting" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{{ m.lime_late_millipede_urge() }}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="shortcode">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{{ m.happy_mild_fox_gleam() }}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input v-bind="componentField" :disabled="isSubmitting" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{{ m.glad_day_kestrel_amaze() }}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="category">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{{ m.short_cute_jackdaw_comfort() }}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input v-bind="componentField" :disabled="isSubmitting" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField }" name="alt">
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{{ m.watery_left_shrimp_bless() }}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Textarea rows="2" v-bind="componentField" :disabled="isSubmitting" />
|
||||||
|
</FormControl>
|
||||||
|
<FormDescription>
|
||||||
|
{{ m.weird_fun_jurgen_arise() }}
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<FormField v-slot="{ componentField, value, handleChange }" 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">
|
||||||
|
<FormLabel :as="CardTitle">
|
||||||
|
{{ m.pink_sharp_carp_work() }}
|
||||||
|
</FormLabel>
|
||||||
|
<CardDescription>
|
||||||
|
{{ m.dark_pretty_hyena_link() }}
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<FormControl>
|
||||||
|
<Switch :checked="value" @update:checked="handleChange" v-bind="componentField" :disabled="isSubmitting" />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
</FormField>
|
||||||
|
|
||||||
|
<DialogFooter>
|
||||||
|
<DialogClose :as-child="true">
|
||||||
|
<Button variant="outline" :disabled="isSubmitting">
|
||||||
|
{{ m.soft_bold_ant_attend() }}
|
||||||
|
</Button>
|
||||||
|
</DialogClose>
|
||||||
|
<Button type="submit" variant="default" :disabled="isSubmitting">
|
||||||
|
{{ m.flat_safe_haddock_gaze() }}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { toTypedSchema } from "@vee-validate/zod";
|
||||||
|
import { RolePermission } from "@versia/client/types";
|
||||||
|
import { useForm } from "vee-validate";
|
||||||
|
import { toast } from "vue-sonner";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { Button } from "~/components/ui/button";
|
||||||
|
import { Card, CardTitle } from "~/components/ui/card";
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogClose,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "~/components/ui/dialog";
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
FormDescription,
|
||||||
|
FormField,
|
||||||
|
FormItem,
|
||||||
|
FormLabel,
|
||||||
|
FormMessage,
|
||||||
|
} from "~/components/ui/form";
|
||||||
|
import * as m from "~/paraglide/messages.js";
|
||||||
|
|
||||||
|
const open = ref(false);
|
||||||
|
const permissions = usePermissions();
|
||||||
|
const hasEmojiAdmin = permissions.value.includes(RolePermission.ManageEmojis);
|
||||||
|
const createObjectURL = URL.createObjectURL;
|
||||||
|
|
||||||
|
const formSchema = toTypedSchema(
|
||||||
|
z.object({
|
||||||
|
image: z
|
||||||
|
.instanceof(File, {
|
||||||
|
message: m.sound_topical_gopher_offer(),
|
||||||
|
})
|
||||||
|
.refine(
|
||||||
|
(v) =>
|
||||||
|
v.size <=
|
||||||
|
// @ts-expect-error Types aren't updated with this new value yet
|
||||||
|
(identity.value?.instance.configuration.emojis
|
||||||
|
.emoji_size_limit ?? 0),
|
||||||
|
m.orange_weird_parakeet_hug({
|
||||||
|
// @ts-expect-error Types aren't updated with this new value yet
|
||||||
|
count: identity.value?.instance.configuration.emojis
|
||||||
|
.emoji_size_limit,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
shortcode: z
|
||||||
|
.string()
|
||||||
|
.min(1)
|
||||||
|
.max(
|
||||||
|
// @ts-expect-error Types aren't updated with this new value yet
|
||||||
|
identity.value?.instance.configuration.emojis
|
||||||
|
.max_emoji_shortcode_characters,
|
||||||
|
m.solid_inclusive_owl_hug({
|
||||||
|
// @ts-expect-error Types aren't updated with this new value yet
|
||||||
|
count: identity.value?.instance.configuration.emojis
|
||||||
|
.max_emoji_shortcode_characters,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.regex(emojiValidator),
|
||||||
|
global: z.boolean().default(false),
|
||||||
|
category: z
|
||||||
|
.string()
|
||||||
|
.max(
|
||||||
|
64,
|
||||||
|
m.home_cool_orangutan_hug({
|
||||||
|
count: 64,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
alt: z
|
||||||
|
.string()
|
||||||
|
.max(
|
||||||
|
// @ts-expect-error Types aren't updated with this new value yet
|
||||||
|
identity.value?.instance.configuration.emojis
|
||||||
|
.max_emoji_description_characters,
|
||||||
|
m.key_ago_hound_emerge({
|
||||||
|
// @ts-expect-error Types aren't updated with this new value yet
|
||||||
|
count: identity.value?.instance.configuration.emojis
|
||||||
|
.max_emoji_description_characters,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const { isSubmitting, handleSubmit, values, setFieldValue } = useForm({
|
||||||
|
validationSchema: formSchema,
|
||||||
|
});
|
||||||
|
|
||||||
|
const submit = handleSubmit(async (values) => {
|
||||||
|
if (!identity.value) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = toast.loading(m.factual_gray_mouse_believe());
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await client.value.uploadEmoji(
|
||||||
|
values.shortcode,
|
||||||
|
values.image,
|
||||||
|
{
|
||||||
|
alt: values.alt,
|
||||||
|
category: values.category,
|
||||||
|
global: values.global,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
toast.dismiss(id);
|
||||||
|
toast.success(m.cool_trite_gull_quiz());
|
||||||
|
|
||||||
|
identity.value.emojis = [...identity.value.emojis, data];
|
||||||
|
open.value = false;
|
||||||
|
} catch {
|
||||||
|
toast.dismiss(id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -239,7 +239,7 @@
|
||||||
"lucky_suave_myna_adore": "Ask the captain to add some deck decorations.",
|
"lucky_suave_myna_adore": "Ask the captain to add some deck decorations.",
|
||||||
"actual_steep_llama_rest": "No decorations in the hold.",
|
"actual_steep_llama_rest": "No decorations in the hold.",
|
||||||
"mild_many_dolphin_mend": "Deck decoration preferences",
|
"mild_many_dolphin_mend": "Deck decoration preferences",
|
||||||
"lucky_ago_rat_pinch": "Uncategorized booty",
|
"lucky_ago_rat_pinch": "Uncategorized",
|
||||||
"empty_awful_lark_dart": "Sailor not found in the ship's log.",
|
"empty_awful_lark_dart": "Sailor not found in the ship's log.",
|
||||||
"clean_even_mayfly_tap": "Check for sea monster tracks or try again later.",
|
"clean_even_mayfly_tap": "Check for sea monster tracks or try again later.",
|
||||||
"vexed_each_falcon_enjoy": "Blimey!",
|
"vexed_each_falcon_enjoy": "Blimey!",
|
||||||
|
|
@ -317,5 +317,25 @@
|
||||||
"keen_aware_goldfish_thrive": "King's English",
|
"keen_aware_goldfish_thrive": "King's English",
|
||||||
"vivid_mellow_sawfish_approve": "Fancy French",
|
"vivid_mellow_sawfish_approve": "Fancy French",
|
||||||
"gray_clean_shark_comfort": "The following URI parameters be required:",
|
"gray_clean_shark_comfort": "The following URI parameters be required:",
|
||||||
"grand_spry_goldfish_embrace": "Yer URI parameters be invalid"
|
"grand_spry_goldfish_embrace": "Yer URI parameters be invalid",
|
||||||
|
"honest_factual_carp_aspire": "Be yer certain yer want to send this deck decoration to Davy Jones' locker?",
|
||||||
|
"flat_safe_haddock_gaze": "Return",
|
||||||
|
"orange_weird_parakeet_hug": "Decoration must be inferior to {count} bytes",
|
||||||
|
"solid_inclusive_owl_hug": "Code must have less than {count} markings",
|
||||||
|
"key_ago_hound_emerge": "Map must have less than {count} markings",
|
||||||
|
"pink_sharp_carp_work": "Global deck decoration",
|
||||||
|
"dark_pretty_hyena_link": "Can be employed by every sailor, not just yer",
|
||||||
|
"home_cool_orangutan_hug": "Category must be not greater than {count} markings",
|
||||||
|
"sound_topical_gopher_offer": "Required",
|
||||||
|
"watery_left_shrimp_bless": "Description",
|
||||||
|
"weird_fun_jurgen_arise": "Useful for people with screen readers, or poor network conditions.",
|
||||||
|
"short_cute_jackdaw_comfort": "Category",
|
||||||
|
"happy_mild_fox_gleam": "Shortcode",
|
||||||
|
"active_direct_bear_compose": "Image",
|
||||||
|
"lime_late_millipede_urge": "Recommended size: 128x128px. Every image type is allowed.",
|
||||||
|
"factual_gray_mouse_believe": "Uploading emoji...",
|
||||||
|
"cool_trite_gull_quiz": "Emoji uploaded!",
|
||||||
|
"kind_deft_myna_hint": "Failed to upload emoji.",
|
||||||
|
"frail_great_marten_pet": "Upload a new emoji to the server.",
|
||||||
|
"whole_icy_puffin_smile": "Upload Emoji"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,5 +318,25 @@
|
||||||
"vivid_mellow_sawfish_approve": "French",
|
"vivid_mellow_sawfish_approve": "French",
|
||||||
"these_awful_ape_reside": "Pirate",
|
"these_awful_ape_reside": "Pirate",
|
||||||
"gray_clean_shark_comfort": "The following rizzy parameters are required:",
|
"gray_clean_shark_comfort": "The following rizzy parameters are required:",
|
||||||
"grand_spry_goldfish_embrace": "Invalid URI parameters"
|
"grand_spry_goldfish_embrace": "Invalid URI parameters",
|
||||||
|
"honest_factual_carp_aspire": "Are you sure you want to ax this pepe?",
|
||||||
|
"flat_safe_haddock_gaze": "Upload",
|
||||||
|
"orange_weird_parakeet_hug": "Image must be less than {count} bytes",
|
||||||
|
"solid_inclusive_owl_hug": "Shortcode must be less than {count} characters",
|
||||||
|
"key_ago_hound_emerge": "Description must be less than {count} characters",
|
||||||
|
"pink_sharp_carp_work": "Global emoji",
|
||||||
|
"dark_pretty_hyena_link": "Can be used by every NPC, not just you",
|
||||||
|
"home_cool_orangutan_hug": "Category must be less than {count} characters",
|
||||||
|
"sound_topical_gopher_offer": "Required",
|
||||||
|
"watery_left_shrimp_bless": "Description",
|
||||||
|
"weird_fun_jurgen_arise": "Useful for people with screen readers, or poor network conditions.",
|
||||||
|
"short_cute_jackdaw_comfort": "Category",
|
||||||
|
"happy_mild_fox_gleam": "Shortcode",
|
||||||
|
"active_direct_bear_compose": "Image",
|
||||||
|
"lime_late_millipede_urge": "Recommended size: 128x128px. Every image type is allowed.",
|
||||||
|
"factual_gray_mouse_believe": "Uploading emoji...",
|
||||||
|
"cool_trite_gull_quiz": "Emoji uploaded!",
|
||||||
|
"kind_deft_myna_hint": "Failed to upload emoji.",
|
||||||
|
"frail_great_marten_pet": "Upload a new emoji to the server.",
|
||||||
|
"whole_icy_puffin_smile": "Upload Emoji"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -318,5 +318,26 @@
|
||||||
"vivid_mellow_sawfish_approve": "French",
|
"vivid_mellow_sawfish_approve": "French",
|
||||||
"these_awful_ape_reside": "Pirate",
|
"these_awful_ape_reside": "Pirate",
|
||||||
"gray_clean_shark_comfort": "The following URI parameters are required:",
|
"gray_clean_shark_comfort": "The following URI parameters are required:",
|
||||||
"grand_spry_goldfish_embrace": "Invalid URI parameters"
|
"grand_spry_goldfish_embrace": "Invalid URI parameters",
|
||||||
|
"honest_factual_carp_aspire": "Are you sure you want to delete this emoji?",
|
||||||
|
"flat_safe_haddock_gaze": "Upload",
|
||||||
|
"orange_weird_parakeet_hug": "Image must be less than {count} bytes",
|
||||||
|
"solid_inclusive_owl_hug": "Shortcode must be less than {count} characters",
|
||||||
|
"key_ago_hound_emerge": "Description must be less than {count} characters",
|
||||||
|
"pink_sharp_carp_work": "Global emoji",
|
||||||
|
"dark_pretty_hyena_link": "Can be used by every user, not just you",
|
||||||
|
"home_cool_orangutan_hug": "Category must be less than {count} characters",
|
||||||
|
"sound_topical_gopher_offer": "Required",
|
||||||
|
"watery_left_shrimp_bless": "Description",
|
||||||
|
"weird_fun_jurgen_arise": "Useful for people with screen readers, or poor network conditions.",
|
||||||
|
"short_cute_jackdaw_comfort": "Category",
|
||||||
|
"happy_mild_fox_gleam": "Shortcode",
|
||||||
|
"glad_day_kestrel_amaze": "Should be short.",
|
||||||
|
"active_direct_bear_compose": "Image",
|
||||||
|
"lime_late_millipede_urge": "Recommended size: 128x128px. Every image type is allowed.",
|
||||||
|
"factual_gray_mouse_believe": "Uploading emoji...",
|
||||||
|
"cool_trite_gull_quiz": "Emoji uploaded!",
|
||||||
|
"kind_deft_myna_hint": "Failed to upload emoji.",
|
||||||
|
"frail_great_marten_pet": "Upload a new emoji to the server.",
|
||||||
|
"whole_icy_puffin_smile": "Upload Emoji"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -299,7 +299,27 @@
|
||||||
"keen_aware_goldfish_thrive": "Anglais",
|
"keen_aware_goldfish_thrive": "Anglais",
|
||||||
"vivid_mellow_sawfish_approve": "Français",
|
"vivid_mellow_sawfish_approve": "Français",
|
||||||
"these_awful_ape_reside": "Pirate",
|
"these_awful_ape_reside": "Pirate",
|
||||||
"dirty_inclusive_meerkat_nudge": "Annuler",
|
|
||||||
"gray_clean_shark_comfort": "Les paramètres de l'URI suivants sont obligatoires:",
|
"gray_clean_shark_comfort": "Les paramètres de l'URI suivants sont obligatoires:",
|
||||||
"grand_spry_goldfish_embrace": "Paramètres URI non valides"
|
"grand_spry_goldfish_embrace": "Paramètres URI non valides",
|
||||||
|
"honest_factual_carp_aspire": "Etes-vous sûr de vouloir supprimer cet emoji ?",
|
||||||
|
"flat_safe_haddock_gaze": "Ajouter",
|
||||||
|
"orange_weird_parakeet_hug": "L'image doit être inférieure à {count} octets",
|
||||||
|
"solid_inclusive_owl_hug": "Le nom doit contenir moins de {count} caractères",
|
||||||
|
"key_ago_hound_emerge": "La description doit comporter moins de {count} caractères",
|
||||||
|
"pink_sharp_carp_work": "Émoji global",
|
||||||
|
"dark_pretty_hyena_link": "Peut être utilisé par tous les utilisateurs, pas juste vous",
|
||||||
|
"home_cool_orangutan_hug": "La catégorie doit comporter moins de {count} caractères",
|
||||||
|
"sound_topical_gopher_offer": "Requis",
|
||||||
|
"watery_left_shrimp_bless": "Description",
|
||||||
|
"weird_fun_jurgen_arise": "Utile pour les personnes disposant de lecteurs d'écran ou de mauvaises conditions de réseau.",
|
||||||
|
"short_cute_jackdaw_comfort": "Catégorie",
|
||||||
|
"happy_mild_fox_gleam": "Nom",
|
||||||
|
"active_direct_bear_compose": "Image",
|
||||||
|
"lime_late_millipede_urge": "Taille recommandée : 128x128px. Tous les types d'images sont autorisés.",
|
||||||
|
"dirty_inclusive_meerkat_nudge": "Annuler",
|
||||||
|
"factual_gray_mouse_believe": "Ajout de l'emoji...",
|
||||||
|
"cool_trite_gull_quiz": "Emoji ajouté!",
|
||||||
|
"kind_deft_myna_hint": "Échec de l'ajout de l'emoji.",
|
||||||
|
"frail_great_marten_pet": "Ajoutez un nouvel emoji sur le serveur.",
|
||||||
|
"whole_icy_puffin_smile": "Ajouter un Emoji"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
<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.tasty_late_termite_sew() }}
|
{{ m.tasty_late_termite_sew() }}
|
||||||
</h1>
|
</h1>
|
||||||
<Button class="ml-auto" v-if="profileEditor?.dirty" @click="profileEditor.submitForm">Save</Button>
|
<Button v-if="profileEditor?.dirty" @click="profileEditor.submitForm">Save</Button>
|
||||||
</div>
|
</div>
|
||||||
<div class="grid xl:grid-cols-[1fr,auto] gap-4 *:max-h-[80vh]">
|
<div class="grid xl:grid-cols-[1fr,auto] gap-4 *:max-h-[80vh]">
|
||||||
<ProfileEditor ref="profileEditor" />
|
<ProfileEditor ref="profileEditor" />
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
<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">
|
||||||
<h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize">
|
<div :class="cn('grid gap-2', canUpload && 'grid-cols-[1fr,auto]')">
|
||||||
{{ m.suave_smart_mantis_climb() }}
|
<h1 class="scroll-m-20 text-3xl font-extrabold tracking-tight lg:text-4xl capitalize">
|
||||||
</h1>
|
{{ m.suave_smart_mantis_climb() }}
|
||||||
|
</h1>
|
||||||
|
<Uploader v-if="canUpload">
|
||||||
|
<Button variant="default">
|
||||||
|
<Upload /> Upload
|
||||||
|
</Button>
|
||||||
|
</Uploader>
|
||||||
|
</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" />
|
||||||
|
|
@ -21,9 +28,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import type { Emoji } from "@versia/client/types";
|
import { cn } from "@/lib/utils";
|
||||||
import { Search } from "lucide-vue-next";
|
import { type Emoji, RolePermission } from "@versia/client/types";
|
||||||
|
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 {
|
import {
|
||||||
Card,
|
Card,
|
||||||
CardDescription,
|
CardDescription,
|
||||||
|
|
@ -47,6 +56,13 @@ definePageMeta({
|
||||||
requiresAuth: true,
|
requiresAuth: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const permissions = usePermissions();
|
||||||
|
const canUpload = computed(
|
||||||
|
() =>
|
||||||
|
permissions.value.includes(RolePermission.ManageOwnEmojis) ||
|
||||||
|
permissions.value.includes(RolePermission.ManageEmojis),
|
||||||
|
);
|
||||||
|
|
||||||
const emojis = computed(
|
const emojis = computed(
|
||||||
() =>
|
() =>
|
||||||
identity.value?.emojis?.filter((emoji) =>
|
identity.value?.emojis?.filter((emoji) =>
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import {
|
import {
|
||||||
caseInsensitive,
|
caseInsensitive,
|
||||||
char,
|
char,
|
||||||
|
charIn,
|
||||||
createRegExp,
|
createRegExp,
|
||||||
digit,
|
digit,
|
||||||
exactly,
|
exactly,
|
||||||
|
|
@ -12,7 +13,7 @@ import {
|
||||||
|
|
||||||
export const emojiValidator = createRegExp(
|
export const emojiValidator = createRegExp(
|
||||||
// A-Z a-z 0-9 _ -
|
// A-Z a-z 0-9 _ -
|
||||||
oneOrMore(letter.or(digit).or(exactly("_")).or(exactly("-"))),
|
oneOrMore(letter.or(digit).or(charIn("_-"))),
|
||||||
[caseInsensitive, global],
|
[caseInsensitive, global],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue