Sink/components/dashboard/links/Editor.vue
ccbikai ac7caf141f fix: correct link copy function and add i18n support
- Updates shortLink copy function to use .value for reactive reference
- Adds i18n translation support in Editor component

The copy function was trying to access the shortLink directly instead of its value, which would fail in Vue's reactive system. This fix ensures proper reactivity handling when copying links.
2025-03-16 13:17:30 +08:00

199 lines
4.5 KiB
Vue

<script setup>
import { DependencyType } from '@/components/ui/auto-form/interface'
import { LinkSchema, nanoid } from '@/schemas/link'
import { toTypedSchema } from '@vee-validate/zod'
import { Shuffle, Sparkles } from 'lucide-vue-next'
import { useForm } from 'vee-validate'
import { toast } from 'vue-sonner'
import { z } from 'zod'
const props = defineProps({
link: {
type: Object,
default: () => ({}),
},
})
const emit = defineEmits(['update:link'])
const { t } = useI18n()
const link = ref(props.link)
const dialogOpen = ref(false)
const isEdit = !!props.link.id
const EditLinkSchema = LinkSchema.pick({
url: true,
slug: true,
}).extend({
optional: LinkSchema.omit({
id: true,
url: true,
slug: true,
createdAt: true,
updatedAt: true,
title: true,
description: true,
image: true,
}).extend({
expiration: z.coerce.date().optional(),
}).optional(),
})
const fieldConfig = {
slug: {
disabled: isEdit,
},
optional: {
comment: {
component: 'textarea',
},
},
}
const dependencies = [
{
sourceField: 'slug',
type: DependencyType.DISABLES,
targetField: 'slug',
when: () => isEdit,
},
]
const form = useForm({
validationSchema: toTypedSchema(EditLinkSchema),
initialValues: {
slug: link.value.slug,
url: link.value.url,
optional: {
comment: link.value.comment,
},
},
validateOnMount: isEdit,
keepValuesOnUnmount: isEdit,
})
function randomSlug() {
form.setFieldValue('slug', nanoid()())
}
const aiSlugPending = ref(false)
async function aiSlug() {
if (!form.values.url)
return
aiSlugPending.value = true
try {
const { slug } = await useAPI('/api/link/ai', {
query: {
url: form.values.url,
},
})
form.setFieldValue('slug', slug)
}
catch (error) {
console.log(error)
}
aiSlugPending.value = false
}
onMounted(() => {
if (link.value.expiration) {
form.setFieldValue('optional.expiration', unix2date(link.value.expiration))
}
})
async function onSubmit(formData) {
const link = {
url: formData.url,
slug: formData.slug,
...(formData.optional || []),
expiration: formData.optional?.expiration ? date2unix(formData.optional?.expiration, 'end') : undefined,
}
const { link: newLink } = await useAPI(isEdit ? '/api/link/edit' : '/api/link/create', {
method: isEdit ? 'PUT' : 'POST',
body: link,
})
dialogOpen.value = false
emit('update:link', newLink, isEdit ? 'edit' : 'create')
if (isEdit) {
toast(t('links.update_success'))
}
else {
toast(t('links.create_success'))
}
}
const { previewMode } = useRuntimeConfig().public
</script>
<template>
<Dialog v-model:open="dialogOpen">
<DialogTrigger as-child>
<slot>
<Button
class="ml-2"
variant="outline"
@click="randomSlug"
>
{{ $t('links.create') }}
</Button>
</slot>
</DialogTrigger>
<DialogContent class="max-w-[95svw] max-h-[95svh] md:max-w-lg grid-rows-[auto_minmax(0,1fr)_auto]">
<DialogHeader>
<DialogTitle>{{ link.id ? $t('links.edit') : $t('links.create') }}</DialogTitle>
</DialogHeader>
<p
v-if="previewMode"
class="text-sm text-muted-foreground"
>
{{ $t('links.preview_mode_tip') }}
</p>
<AutoForm
class="overflow-y-auto px-2 space-y-2"
:schema="EditLinkSchema"
:form="form"
:field-config="fieldConfig"
:dependencies="dependencies"
@submit="onSubmit"
>
<template #slug="slotProps">
<div
v-if="!isEdit"
class="relative"
>
<div class="flex absolute right-0 top-1 space-x-3">
<Shuffle
class="w-4 h-4 cursor-pointer"
@click="randomSlug"
/>
<Sparkles
class="w-4 h-4 cursor-pointer"
:class="{ 'animate-bounce': aiSlugPending }"
@click="aiSlug"
/>
</div>
<AutoFormField
v-bind="slotProps"
/>
</div>
</template>
<DialogFooter>
<DialogClose as-child>
<Button
type="button"
variant="secondary"
class="mt-2 sm:mt-0"
>
{{ $t('common.close') }}
</Button>
</DialogClose>
<Button type="submit">
{{ $t('common.save') }}
</Button>
</DialogFooter>
</AutoForm>
</DialogContent>
</Dialog>
</template>