This commit is contained in:
2026-07-31 23:21:51 +02:00
parent f6b4b36df8
commit f14f2cfd98
294 changed files with 5880 additions and 0 deletions
+30
View File
@@ -0,0 +1,30 @@
import type { Field, TextField } from 'payload';
/**
* Normalizes a string into a URL slug. slugify handles diacritics out of the
* box (Polish included: "Strona główna" → "strona-glowna").
*/
export declare function toSlug(input: string): string;
type BuildSlugFieldOptions = {
/** Source field to derive the slug from. Default: 'title'. */
from?: string;
/** Whether the slug is localized (per-locale). Default: true. */
localized?: boolean;
/** Field name for the slug. Default: 'slug'. */
name?: string;
/** Override or extend the generated field config. */
overrides?: Partial<TextField>;
/** Whether the slug is required. Default: true. */
required?: boolean;
};
/**
* Builds a slug field that auto-generates from a source field (default 'title')
* only when left empty — an editor's manual slug is never overwritten (mode 4a).
*
* Localized-safe: on a localized field Payload runs the hook per locale, so
* `value` is the slug for the active locale and `siblingData[from]` is the
* source in that same locale. Editing the doc in 'pl' fills slug.pl from
* title.pl; editing in 'en' fills slug.en from title.en — giving genuinely
* per-locale slugs (strona-glowna / homepage) that the language switcher needs.
*/
export declare function buildSlugField(options?: BuildSlugFieldOptions): Field;
export {};
+55
View File
@@ -0,0 +1,55 @@
import slugify from 'slugify';
/**
* Normalizes a string into a URL slug. slugify handles diacritics out of the
* box (Polish included: "Strona główna" → "strona-glowna").
*/ export function toSlug(input) {
return slugify(input, {
lower: true,
strict: true,
trim: true
});
}
/**
* Builds a slug field that auto-generates from a source field (default 'title')
* only when left empty — an editor's manual slug is never overwritten (mode 4a).
*
* Localized-safe: on a localized field Payload runs the hook per locale, so
* `value` is the slug for the active locale and `siblingData[from]` is the
* source in that same locale. Editing the doc in 'pl' fills slug.pl from
* title.pl; editing in 'en' fills slug.en from title.en — giving genuinely
* per-locale slugs (strona-glowna / homepage) that the language switcher needs.
*/ export function buildSlugField(options = {}) {
const { name = 'slug', from = 'title', localized = true, overrides = {}, required = true } = options;
const autoGenerate = ({ originalDoc, siblingData, value })=>{
// Respect a manually entered slug — only generate when empty (mode 4a).
if (typeof value === 'string' && value.trim()) {
return toSlug(value) // still normalize what the editor typed
;
}
// Derive from the source field in the current locale.
const source = siblingData?.[from] ?? originalDoc?.[from];
if (typeof source === 'string' && source.trim()) {
return toSlug(source);
}
return value;
};
return {
name,
type: 'text',
admin: {
description: 'Auto-generated from the title when left empty. You can override it.',
...overrides.admin
},
hooks: {
beforeValidate: [
autoGenerate
]
},
index: true,
localized,
required,
...overrides
};
}
//# sourceMappingURL=buildSlugField.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/slug/buildSlugField.ts"],"sourcesContent":["import type { Field, FieldHook, TextField } from 'payload'\n\nimport slugify from 'slugify'\n\n/**\n * Normalizes a string into a URL slug. slugify handles diacritics out of the\n * box (Polish included: \"Strona główna\" → \"strona-glowna\").\n */\nexport function toSlug(input: string): string {\n return slugify(input, {\n lower: true,\n strict: true, // drop characters that aren't url-safe\n trim: true,\n })\n}\n\ntype BuildSlugFieldOptions = {\n /** Source field to derive the slug from. Default: 'title'. */\n from?: string\n /** Whether the slug is localized (per-locale). Default: true. */\n localized?: boolean\n /** Field name for the slug. Default: 'slug'. */\n name?: string\n /** Override or extend the generated field config. */\n overrides?: Partial<TextField>\n /** Whether the slug is required. Default: true. */\n required?: boolean\n}\n\n/**\n * Builds a slug field that auto-generates from a source field (default 'title')\n * only when left empty — an editor's manual slug is never overwritten (mode 4a).\n *\n * Localized-safe: on a localized field Payload runs the hook per locale, so\n * `value` is the slug for the active locale and `siblingData[from]` is the\n * source in that same locale. Editing the doc in 'pl' fills slug.pl from\n * title.pl; editing in 'en' fills slug.en from title.en — giving genuinely\n * per-locale slugs (strona-glowna / homepage) that the language switcher needs.\n */\nexport function buildSlugField(options: BuildSlugFieldOptions = {}): Field {\n const {\n name = 'slug',\n from = 'title',\n localized = true,\n overrides = {},\n required = true,\n } = options\n\n const autoGenerate: FieldHook = ({ originalDoc, siblingData, value }) => {\n // Respect a manually entered slug — only generate when empty (mode 4a).\n if (typeof value === 'string' && value.trim()) {\n return toSlug(value) // still normalize what the editor typed\n }\n\n // Derive from the source field in the current locale.\n const source =\n (siblingData as Record<string, unknown>)?.[from] ??\n (originalDoc as Record<string, unknown>)?.[from]\n\n if (typeof source === 'string' && source.trim()) {\n return toSlug(source)\n }\n\n return value\n }\n\n return {\n name,\n type: 'text',\n admin: {\n description: 'Auto-generated from the title when left empty. You can override it.',\n ...overrides.admin,\n },\n hooks: {\n beforeValidate: [autoGenerate],\n },\n index: true,\n localized,\n required,\n ...overrides,\n } as TextField\n}\n"],"names":["slugify","toSlug","input","lower","strict","trim","buildSlugField","options","name","from","localized","overrides","required","autoGenerate","originalDoc","siblingData","value","source","type","admin","description","hooks","beforeValidate","index"],"mappings":"AAEA,OAAOA,aAAa,UAAS;AAE7B;;;CAGC,GACD,OAAO,SAASC,OAAOC,KAAa;IAClC,OAAOF,QAAQE,OAAO;QACpBC,OAAO;QACPC,QAAQ;QACRC,MAAM;IACR;AACF;AAeA;;;;;;;;;CASC,GACD,OAAO,SAASC,eAAeC,UAAiC,CAAC,CAAC;IAChE,MAAM,EACJC,OAAO,MAAM,EACbC,OAAO,OAAO,EACdC,YAAY,IAAI,EAChBC,YAAY,CAAC,CAAC,EACdC,WAAW,IAAI,EAChB,GAAGL;IAEJ,MAAMM,eAA0B,CAAC,EAAEC,WAAW,EAAEC,WAAW,EAAEC,KAAK,EAAE;QAClE,wEAAwE;QACxE,IAAI,OAAOA,UAAU,YAAYA,MAAMX,IAAI,IAAI;YAC7C,OAAOJ,OAAOe,OAAO,wCAAwC;;QAC/D;QAEA,sDAAsD;QACtD,MAAMC,SACJ,AAACF,aAAyC,CAACN,KAAK,IAC/CK,aAAyC,CAACL,KAAK;QAElD,IAAI,OAAOQ,WAAW,YAAYA,OAAOZ,IAAI,IAAI;YAC/C,OAAOJ,OAAOgB;QAChB;QAEA,OAAOD;IACT;IAEA,OAAO;QACLR;QACAU,MAAM;QACNC,OAAO;YACLC,aAAa;YACb,GAAGT,UAAUQ,KAAK;QACpB;QACAE,OAAO;YACLC,gBAAgB;gBAACT;aAAa;QAChC;QACAU,OAAO;QACPb;QACAE;QACA,GAAGD,SAAS;IACd;AACF"}
+1
View File
@@ -0,0 +1 @@
export { buildSlugField, toSlug } from './buildSlugField.js';
+3
View File
@@ -0,0 +1,3 @@
export { buildSlugField, toSlug } from './buildSlugField.js';
//# sourceMappingURL=index.js.map
+1
View File
@@ -0,0 +1 @@
{"version":3,"sources":["../../../src/modules/slug/index.ts"],"sourcesContent":["export { buildSlugField, toSlug } from './buildSlugField.js'\n"],"names":["buildSlugField","toSlug"],"mappings":"AAAA,SAASA,cAAc,EAAEC,MAAM,QAAQ,sBAAqB"}