144 lines
3.7 KiB
TypeScript
144 lines
3.7 KiB
TypeScript
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
|
import {Stack, Button, Inline} from '@sanity/ui'
|
|
import {PatchEvent, set, unset} from 'sanity'
|
|
import {SlugInput, type SlugInputProps} from 'sanity'
|
|
import {useClient, useFormValue} from 'sanity'
|
|
|
|
type PersonDoc = {name?: string}
|
|
|
|
function slugify(input: string) {
|
|
const folded = input
|
|
.normalize('NFKD')
|
|
.replace(/\p{M}+/gu, '')
|
|
.replace(/ß/g, 'ss')
|
|
.replace(/ø/g, 'o')
|
|
.replace(/đ/g, 'd')
|
|
.replace(/ł/g, 'l')
|
|
|
|
return folded
|
|
.toLowerCase()
|
|
.trim()
|
|
.replace(/[^\p{L}\p{N}]+/gu, '-')
|
|
.replace(/(^-|-$)+/g, '')
|
|
.slice(0, 96)
|
|
}
|
|
|
|
export function WorkSlugInput(props: SlugInputProps) {
|
|
const client = useClient({apiVersion: '2026-01-01'})
|
|
|
|
const title = (useFormValue(['title']) as string) || ''
|
|
const composerRef = (useFormValue(['composer', '_ref']) as string) || ''
|
|
const arrangerRef = (useFormValue(['arranger', '_ref']) as string) || ''
|
|
|
|
const [composerName, setComposerName] = useState<string>('')
|
|
const lastFetchedRef = useRef<string>('')
|
|
|
|
const [arrangerName, setArrangerName] = useState<string>('')
|
|
const lastFetchedArrangerRef = useRef<string>('')
|
|
|
|
const lastAutoSlugRef = useRef<string>('')
|
|
|
|
const desiredSource = useMemo(() => {
|
|
const parts = [composerName, title, arrangerName ? `arr ${arrangerName}` : null].filter(Boolean)
|
|
return parts.join(' ')
|
|
}, [composerName, title, arrangerName])
|
|
|
|
const generateSlug = useCallback(() => {
|
|
if (!desiredSource) return
|
|
|
|
const nextSlug = slugify(desiredSource)
|
|
lastAutoSlugRef.current = nextSlug
|
|
|
|
props.onChange(PatchEvent.from(nextSlug ? set({_type: 'slug', current: nextSlug}) : unset()))
|
|
}, [desiredSource, props.onChange])
|
|
|
|
useEffect(() => {
|
|
let alive = true
|
|
|
|
async function run() {
|
|
if (!composerRef) {
|
|
setComposerName('')
|
|
lastFetchedRef.current = ''
|
|
return
|
|
}
|
|
if (lastFetchedRef.current === composerRef) return
|
|
|
|
lastFetchedRef.current = composerRef
|
|
|
|
const doc = await client.fetch<PersonDoc>(`*[_type == "composer" && _id == $id][0]{name}`, {
|
|
id: composerRef,
|
|
})
|
|
|
|
if (!alive) return
|
|
setComposerName(doc?.name || '')
|
|
}
|
|
|
|
run()
|
|
|
|
return () => {
|
|
alive = false
|
|
}
|
|
}, [composerRef, client])
|
|
|
|
useEffect(() => {
|
|
let alive = true
|
|
|
|
async function run() {
|
|
if (!arrangerRef) {
|
|
setArrangerName('')
|
|
lastFetchedArrangerRef.current = ''
|
|
return
|
|
}
|
|
if (lastFetchedArrangerRef.current === arrangerRef) return
|
|
|
|
lastFetchedArrangerRef.current = arrangerRef
|
|
|
|
const doc = await client.fetch<PersonDoc>(`*[_type == "composer" && _id == $id][0]{name}`, {
|
|
id: arrangerRef,
|
|
})
|
|
|
|
if (!alive) return
|
|
setArrangerName(doc?.name || '')
|
|
}
|
|
|
|
run()
|
|
|
|
return () => {
|
|
alive = false
|
|
}
|
|
}, [arrangerRef, client])
|
|
|
|
useEffect(() => {
|
|
if (!composerName || !title) return
|
|
|
|
const current = props.value?.current || ''
|
|
const nextSlug = slugify(desiredSource)
|
|
|
|
if (!lastAutoSlugRef.current && current && current === nextSlug) {
|
|
lastAutoSlugRef.current = current
|
|
return
|
|
}
|
|
|
|
if (!current || current === lastAutoSlugRef.current) {
|
|
lastAutoSlugRef.current = nextSlug
|
|
props.onChange(PatchEvent.from(set({_type: 'slug', current: nextSlug})))
|
|
}
|
|
}, [composerName, arrangerName, title, desiredSource, props.value?.current, props.onChange])
|
|
|
|
return (
|
|
<Stack space={3}>
|
|
<SlugInput {...props} />
|
|
|
|
<Stack space={2}>
|
|
<Inline space={2}>
|
|
<Button
|
|
text="Generate"
|
|
mode="default"
|
|
onClick={generateSlug}
|
|
disabled={!composerName || !title}
|
|
/>
|
|
</Inline>
|
|
</Stack>
|
|
</Stack>
|
|
)
|
|
}
|