feat: auto slug
This commit is contained in:
parent
d6e70824d0
commit
7438f39786
6 changed files with 92 additions and 2 deletions
42
src/admin/components/SlugField/SlugField.tsx
Normal file
42
src/admin/components/SlugField/SlugField.tsx
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import type { TextFieldClientComponent } from 'payload'
|
||||||
|
import { TextField, useField, useFormFields } from '@payloadcms/ui'
|
||||||
|
import { useEffect, useRef } from 'react'
|
||||||
|
|
||||||
|
const toSlug = (value: string): string =>
|
||||||
|
value
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(/ä/g, 'ae')
|
||||||
|
.replace(/ö/g, 'oe')
|
||||||
|
.replace(/ü/g, 'ue')
|
||||||
|
.replace(/ß/g, 'ss')
|
||||||
|
.replace(/[^\w\s-]/g, '')
|
||||||
|
.replace(/[\s_]+/g, '-')
|
||||||
|
.replace(/-+/g, '-')
|
||||||
|
.replace(/^-+|-+$/g, '')
|
||||||
|
|
||||||
|
export const SlugField: TextFieldClientComponent = (props) => {
|
||||||
|
const { value, setValue } = useField<string>({ path: props.path })
|
||||||
|
const prevAutoSlug = useRef<string>('')
|
||||||
|
const sourceField =
|
||||||
|
((props as Record<string, unknown>).sourceField as string) || 'name'
|
||||||
|
|
||||||
|
const sourceValue = useFormFields(([fields]) => {
|
||||||
|
const field = fields[sourceField]
|
||||||
|
return (field?.value as string) ?? ''
|
||||||
|
})
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newSlug = toSlug(sourceValue)
|
||||||
|
// Only auto-fill if slug is empty or matches the previous auto-slug
|
||||||
|
if (!value || value === prevAutoSlug.current) {
|
||||||
|
setValue(newSlug)
|
||||||
|
}
|
||||||
|
prevAutoSlug.current = newSlug
|
||||||
|
}, [sourceValue]) // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
|
return <TextField {...props} />
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SlugField
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { SlugField as SlugField_d8007c5f8420db846c29d4e7711b3d75 } from '@/admin/components/SlugField/SlugField'
|
||||||
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
import { RscEntryLexicalCell as RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||||
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
import { RscEntryLexicalField as RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||||
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
import { LexicalDiffComponent as LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e } from '@payloadcms/richtext-lexical/rsc'
|
||||||
|
|
@ -17,6 +18,7 @@ import { GcsClientUploadHandler as GcsClientUploadHandler_06e62ca02c7c441053a9b6
|
||||||
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
import { CollectionCards as CollectionCards_f9c02e79a4aed9a3924487c0cd4cafb1 } from '@payloadcms/next/rsc'
|
||||||
|
|
||||||
export const importMap = {
|
export const importMap = {
|
||||||
|
"@/admin/components/SlugField/SlugField#SlugField": SlugField_d8007c5f8420db846c29d4e7711b3d75,
|
||||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalCell": RscEntryLexicalCell_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||||
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
"@payloadcms/richtext-lexical/rsc#RscEntryLexicalField": RscEntryLexicalField_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||||
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
"@payloadcms/richtext-lexical/rsc#LexicalDiffComponent": LexicalDiffComponent_44fe37237e0ebf4470c9990d8cb7b07e,
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,20 @@ export const Groups: CollectionConfig = {
|
||||||
de: 'URL slug',
|
de: 'URL slug',
|
||||||
},
|
},
|
||||||
required: true,
|
required: true,
|
||||||
unique: true
|
unique: true,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
'Slug der Gruppe (z.B. "ministrant" → Gruppenseite ist zu finden unter /gruppe/ministrant)',
|
||||||
|
components: {
|
||||||
|
Field: '@/admin/components/SlugField/SlugField#SlugField',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [
|
||||||
|
({ value }) =>
|
||||||
|
typeof value === 'string' ? value.replace(/^\/+/, '') : value,
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'shortDescription',
|
name: 'shortDescription',
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,20 @@ export const Pages: CollectionConfig = {
|
||||||
},
|
},
|
||||||
admin: {
|
admin: {
|
||||||
description: 'URL-Pfad der Seite (z.B. "meine-seite" → /meine-seite)',
|
description: 'URL-Pfad der Seite (z.B. "meine-seite" → /meine-seite)',
|
||||||
|
components: {
|
||||||
|
Field: {
|
||||||
|
path: '@/admin/components/SlugField/SlugField#SlugField',
|
||||||
|
clientProps: {
|
||||||
|
sourceField: 'title',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [
|
||||||
|
({ value }) =>
|
||||||
|
typeof value === 'string' ? value.replace(/^\/+/, '') : value,
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,20 @@ export const Parish: CollectionConfig = {
|
||||||
de: 'URL slug',
|
de: 'URL slug',
|
||||||
},
|
},
|
||||||
type: 'text',
|
type: 'text',
|
||||||
required: true
|
required: true,
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
'Slug der Gemeinde (z.B. "st-clara" → Gemeindeseite ist zu finden unter /gemeinde/st-clara)',
|
||||||
|
components: {
|
||||||
|
Field: '@/admin/components/SlugField/SlugField#SlugField',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
hooks: {
|
||||||
|
beforeValidate: [
|
||||||
|
({ value }) =>
|
||||||
|
typeof value === 'string' ? value.replace(/^\/+/, '') : value,
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'churches',
|
name: 'churches',
|
||||||
|
|
|
||||||
|
|
@ -169,6 +169,9 @@ export interface UserAuthOperations {
|
||||||
export interface Parish {
|
export interface Parish {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
/**
|
||||||
|
* Slug der Gemeinde (z.B. "st-clara" → Gemeindeseite ist zu finden unter /gemeinde/st-clara)
|
||||||
|
*/
|
||||||
slug: string;
|
slug: string;
|
||||||
churches: (string | Church)[];
|
churches: (string | Church)[];
|
||||||
contactPersons?:
|
contactPersons?:
|
||||||
|
|
@ -546,6 +549,9 @@ export interface Group {
|
||||||
id: string;
|
id: string;
|
||||||
photo?: (string | null) | Media;
|
photo?: (string | null) | Media;
|
||||||
name: string;
|
name: string;
|
||||||
|
/**
|
||||||
|
* Slug der Gruppe (z.B. "ministrant" → Gruppenseite ist zu finden unter /gruppe/ministrant)
|
||||||
|
*/
|
||||||
slug: string;
|
slug: string;
|
||||||
shortDescription: string;
|
shortDescription: string;
|
||||||
text?: {
|
text?: {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue