mirror of
https://github.com/Nezumi-2711/Sink.git
synced 2026-09-22 20:00:55 +00:00
feat: init
This commit is contained in:
@@ -0,0 +1,193 @@
|
||||
<script setup>
|
||||
import { z } from 'zod'
|
||||
import { useForm } from 'vee-validate'
|
||||
import { toTypedSchema } from '@vee-validate/zod'
|
||||
import { Shuffle, Sparkles } from 'lucide-vue-next'
|
||||
import { toast } from 'vue-sonner'
|
||||
import { DependencyType } from '@/components/ui/auto-form/interface'
|
||||
import { LinkSchema, nanoid } from '@/schemas/link'
|
||||
|
||||
const props = defineProps({
|
||||
link: {
|
||||
type: Object,
|
||||
default: () => ({}),
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['update:link'])
|
||||
|
||||
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')
|
||||
isEdit ? toast('Link updated successfully') : toast('Link created successfully')
|
||||
}
|
||||
|
||||
const { previewMode } = useRuntimeConfig().public
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog v-model:open="dialogOpen">
|
||||
<DialogTrigger as-child>
|
||||
<slot>
|
||||
<Button
|
||||
class="ml-2"
|
||||
variant="outline"
|
||||
@click="randomSlug"
|
||||
>
|
||||
Create Link
|
||||
</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 ? 'Edit Link' : 'Create Link' }}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<p
|
||||
v-if="previewMode"
|
||||
class="text-sm text-muted-foreground"
|
||||
>
|
||||
The preview mode link is valid for up to 24 hours.
|
||||
</p>
|
||||
<AutoForm
|
||||
class="px-2 space-y-2 overflow-y-auto"
|
||||
:schema="EditLinkSchema"
|
||||
:form="form"
|
||||
:field-config="fieldConfig"
|
||||
:dependencies="dependencies"
|
||||
@submit="onSubmit"
|
||||
>
|
||||
<template #slug="slotProps">
|
||||
<div
|
||||
v-if="!isEdit"
|
||||
class="relative"
|
||||
>
|
||||
<div class="absolute right-0 flex space-x-3 top-1">
|
||||
<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"
|
||||
>
|
||||
Close
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button type="submit">
|
||||
Save
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</AutoForm>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user