@pro-laico/payload-fonts 0.0.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/collections/font.d.ts.map +1 -1
- package/dist/collections/font.js +7 -2
- package/dist/collections/font.js.map +1 -1
- package/dist/collections/fontOptimized.d.ts.map +1 -1
- package/dist/collections/fontOptimized.js +6 -0
- package/dist/collections/fontOptimized.js.map +1 -1
- package/dist/collections/fontOriginal.d.ts.map +1 -1
- package/dist/collections/fontOriginal.js +5 -0
- package/dist/collections/fontOriginal.js.map +1 -1
- package/dist/hooks/optimizeFromOriginals.d.ts.map +1 -1
- package/dist/hooks/optimizeFromOriginals.js +7 -2
- package/dist/hooks/optimizeFromOriginals.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"font.d.ts","sourceRoot":"","sources":["../../src/collections/font.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,gBAAgB,EAGtB,MAAM,SAAS,CAAA;AAIhB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,KAAK,gBAAgB,EAAuB,MAAM,iBAAiB,CAAA;AAO5E,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,sDAAsD,CAAA;AASzF,MAAM,WAAW,2BAA2B;IAC1C,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAA;CAC9B;
|
|
1
|
+
{"version":3,"file":"font.d.ts","sourceRoot":"","sources":["../../src/collections/font.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,gBAAgB,EAGtB,MAAM,SAAS,CAAA;AAIhB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAA;AACpD,OAAO,EAAE,KAAK,gBAAgB,EAAuB,MAAM,iBAAiB,CAAA;AAO5E,0FAA0F;AAC1F,eAAO,MAAM,sBAAsB,sDAAsD,CAAA;AASzF,MAAM,WAAW,2BAA2B;IAC1C,4FAA4F;IAC5F,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,iFAAiF;IACjF,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,kFAAkF;IAClF,QAAQ,CAAC,EAAE,gBAAgB,EAAE,CAAA;CAC9B;AA6FD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,oBAAoB,GAAI,OAAM,2BAAgC,KAAG,gBAgH7E,CAAA"}
|
package/dist/collections/font.js
CHANGED
|
@@ -54,8 +54,13 @@ const hasWeights = (data)=>Array.isArray(data?.weights) && data.weights.some((w)
|
|
|
54
54
|
req
|
|
55
55
|
});
|
|
56
56
|
doc.servedFiles = totalDocs;
|
|
57
|
-
} catch
|
|
58
|
-
|
|
57
|
+
} catch (err) {
|
|
58
|
+
// A count failure shouldn't break the read — leave servedFiles unset, but say so: silence
|
|
59
|
+
// here made a broken optimized collection indistinguishable from "nothing served yet".
|
|
60
|
+
req.payload.logger.warn({
|
|
61
|
+
msg: `[payload-fonts] could not count served files for typeface ${id}`,
|
|
62
|
+
err
|
|
63
|
+
});
|
|
59
64
|
}
|
|
60
65
|
return doc;
|
|
61
66
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/font.ts"],"sourcesContent":["import {\n APIError,\n type ArrayField,\n type CollectionAfterReadHook,\n type CollectionBeforeValidateHook,\n type CollectionConfig,\n type CollectionSlug,\n type Field,\n} from 'payload'\n\nimport { authd } from '../access/authd'\nimport { cleanupFontAssetsHook, optimizeFromOriginalsHook, originalIdsFromDoc } from '../hooks/optimizeFromOriginals'\nimport type { Charset } from '../hooks/optimizeFont'\nimport { type FontFamilyConfig, resolveFontFamilies } from '../lib/families'\nimport { FONT_OPTIMIZED_SLUG } from './fontOptimized'\nimport { FONT_ORIGINAL_SLUG } from './fontOriginal'\n\n/** Standard `next/font/local` weight steps offered by each weight row. */\nconst WEIGHT_OPTIONS = ['100', '200', '300', '400', '500', '600', '700', '800', '900']\n\n/** Admin component subpath (Payload import map) for the create-only original uploader. */\nexport const FontOriginalUploadPath = '@pro-laico/payload-fonts/admin/FontOriginalUpload'\n/**\n * A FRESH admin config that renders the `fontOriginal` slot create-only (no \"Choose from\n * existing\"), so every slot uploads a fresh original — never shared between typefaces. A\n * factory (not a shared object) because Payload mutates field configs in place during\n * sanitization; sharing one reference across slots corrupts it.\n */\nconst createOnlyUpload = () => ({ components: { Field: { path: FontOriginalUploadPath } } })\n\nexport interface CreateFontCollectionOptions {\n /** Characters the subsetter keeps, or a preset ('latin' | 'latin-ext'). Default 'latin'. */\n charset?: Charset\n /** Slug of the archival original upload collection. Default 'fontOriginal'. */\n originalSlug?: string\n /** Slug of the optimized (served) upload collection. Default 'fontOptimized'. */\n optimizedSlug?: string\n /** The options offered by the `family` field. Default sans/serif/mono/display. */\n families?: FontFamilyConfig[]\n}\n\ntype VariableGroup = { upright?: unknown; italic?: unknown }\nconst hasVariable = (data: Record<string, unknown> | undefined): boolean => {\n const v = (data?.variable ?? {}) as VariableGroup\n return Boolean(v.upright || v.italic)\n}\nconst hasWeights = (data: Record<string, unknown> | undefined): boolean =>\n Array.isArray(data?.weights) && (data.weights as Array<{ file?: unknown }>).some((w) => w?.file)\n\n/**\n * `afterRead`: report how many served `fontOptimized` files this typeface produced, so an editor can\n * see at a glance whether optimization succeeded — `0` means nothing was served (a swap/re-save is\n * due, or the upload failed to subset). Skipped on list reads (`findMany`) so it's one count query\n * on the edit view, not one per row. Populates the virtual `servedFiles` sidebar field.\n */\nconst servedFilesHook =\n (optimizedSlug: string): CollectionAfterReadHook =>\n async ({ doc, findMany, req }) => {\n if (findMany || !req?.payload) return doc\n const id = (doc as { id?: string | number }).id\n if (id == null) return doc\n try {\n const { totalDocs } = await req.payload.count({\n collection: optimizedSlug as CollectionSlug,\n where: { font: { equals: id } },\n overrideAccess: true,\n req,\n })\n ;(doc as Record<string, unknown>).servedFiles = totalDocs\n } catch {\n // a count failure shouldn't break the read — just leave it unset\n }\n return doc\n }\n\n/**\n * `beforeValidate`: a typeface needs at least one file, and can't mix a variable font with\n * specific weights (you compose from one or the other). Runs on create and on any update that\n * touches these fields, so an unrelated partial edit is left alone.\n */\nconst requireFontFiles: CollectionBeforeValidateHook = ({ data, operation }) => {\n const touches = operation === 'create' || (data != null && ('variable' in data || 'weights' in data))\n if (!touches) return data\n if (hasVariable(data) && hasWeights(data)) {\n throw new APIError('Use either a variable font or specific weight files, not both.', 400, null, true)\n }\n if (!hasVariable(data) && !hasWeights(data)) {\n throw new APIError('Add at least one font file before saving.', 400, null, true)\n }\n return data\n}\n\n/**\n * `beforeValidate`: enforce one `fontOriginal` per typeface — reject a save that references an\n * original already used by ANOTHER typeface. The create-only upload slots make sharing\n * impossible from the admin UI, but this is the data-layer guarantee (covers the REST API,\n * imports, seeds, and a future Payload upgrade that might un-hide \"Choose from existing\"). It's\n * what makes the direct asset cleanup in {@link cleanupFontAssetsHook} /\n * {@link optimizeFromOriginalsHook} safe: a de-referenced or deleted original is never still in\n * use elsewhere.\n */\nconst makeRejectSharedOriginals =\n (fontSlug: string): CollectionBeforeValidateHook =>\n async ({ data, originalDoc, req }) => {\n if (!data || !req?.payload) return data\n const ids = originalIdsFromDoc(data as Record<string, unknown>)\n if (ids.length === 0) return data\n const selfId = (originalDoc as { id?: string | number } | undefined)?.id ?? (data as { id?: string | number }).id\n const refs = [{ 'variable.upright': { in: ids } }, { 'variable.italic': { in: ids } }, { 'weights.file': { in: ids } }]\n const where = selfId != null ? { and: [{ id: { not_equals: selfId } }, { or: refs }] } : { or: refs }\n const res = await req.payload.find({\n collection: fontSlug as CollectionSlug,\n where: where as never,\n depth: 0,\n limit: 1,\n overrideAccess: true,\n req,\n })\n if (res.totalDocs > 0) {\n const other = (res.docs[0] as { title?: string }).title || 'another typeface'\n throw new APIError(\n `That font file is already used by ${other}. Each typeface needs its own upload — add a fresh copy for this slot.`,\n 400,\n null,\n true,\n )\n }\n return data\n }\n\n/**\n * The `Font` collection — ONE document per **typeface** (e.g. \"Inter\"). The four family slots\n * (sans/serif/mono/display) select one of these each.\n *\n * It is NOT itself an upload collection. Editors drop font files into standard Payload `upload`\n * fields backed by `fontOriginal` (raw archive) — either the `variable` group (a single\n * variable file per upright/italic) OR the `weights` array (one file per weight/style). On save,\n * {@link optimizeFromOriginalsHook} subsets each referenced original to a served\n * `fontOptimized` WOFF2. The slots are Payload's own upload field, thinly wrapped to be\n * create-only (no \"Choose from existing\"), so each original belongs to exactly one typeface —\n * keeping asset cleanup safe and race-free.\n */\nexport const createFontCollection = (opts: CreateFontCollectionOptions = {}): CollectionConfig => {\n const fontSlug = 'font'\n const originalSlug = (opts.originalSlug || FONT_ORIGINAL_SLUG) as CollectionSlug\n const optimizedSlug = opts.optimizedSlug || FONT_OPTIMIZED_SLUG\n const families = resolveFontFamilies(opts.families)\n\n const fields: Field[] = [\n { name: 'title', type: 'text', required: true, label: 'Typeface name' },\n {\n name: 'family',\n type: 'radio',\n required: true,\n label: 'Preferred Family',\n interfaceName: 'GenericFontFamily',\n options: families.map((r) => ({ label: r.label, value: r.key })),\n },\n // Editor-facing status: how many web-ready files this typeface produced. Virtual (computed on\n // read by `servedFilesHook`, never stored); shown only on an existing doc so a `0` after saving\n // flags a failed/empty optimization instead of it failing silently.\n {\n name: 'servedFiles',\n type: 'number',\n virtual: true,\n admin: {\n readOnly: true,\n position: 'sidebar',\n description:\n 'Web-ready files generated from your uploads. 0 means nothing was served yet — re-save; if it stays 0, the upload may have failed to optimize (check server logs).',\n condition: (data) => Boolean((data as { id?: unknown })?.id),\n },\n },\n {\n name: 'variable',\n type: 'group',\n label: 'Variable font',\n admin: {\n description: 'One file covering many weights. Use this OR specific weights below — not both.',\n // Hidden once specific weights are added; you compose from one path or the other.\n condition: (data) => !hasWeights(data as Record<string, unknown>),\n },\n fields: [\n {\n type: 'row',\n fields: [\n { name: 'upright', type: 'upload', relationTo: originalSlug, label: 'Upright (normal)', admin: createOnlyUpload() },\n { name: 'italic', type: 'upload', relationTo: originalSlug, label: 'Italic', admin: createOnlyUpload() },\n ],\n },\n ],\n },\n {\n name: 'weights',\n type: 'array',\n label: 'Weights',\n labels: { singular: 'Weight file', plural: 'Weight files' },\n admin: {\n description: 'One file per weight/style. Add only the weights you need.',\n condition: (data) => !hasVariable(data as Record<string, unknown>),\n },\n // Each weight + style must be unique so the generated next/font src array has no conflicts.\n validate: ((rows: Array<{ weight?: string; style?: string }> | null | undefined) => {\n const seen = new Set<string>()\n for (const r of Array.isArray(rows) ? rows : []) {\n const key = `${r?.weight ?? ''}|${r?.style ?? ''}`\n if (seen.has(key)) return `Two files share weight ${r?.weight ?? '?'} ${r?.style ?? 'normal'}. Each weight + style must be unique.`\n seen.add(key)\n }\n return true\n }) as ArrayField['validate'],\n fields: [\n {\n type: 'row',\n fields: [\n { name: 'weight', type: 'select', required: true, defaultValue: '400', options: WEIGHT_OPTIONS },\n { name: 'style', type: 'radio', required: true, defaultValue: 'normal', options: ['normal', 'italic'] },\n ],\n },\n { name: 'file', type: 'upload', relationTo: originalSlug, required: true, admin: createOnlyUpload() },\n ],\n },\n ]\n\n return {\n slug: fontSlug,\n access: { create: authd, delete: authd, read: authd, update: authd },\n admin: {\n group: 'Assets',\n useAsTitle: 'title',\n enableListViewSelectAPI: true,\n defaultColumns: ['title', 'family'],\n description:\n 'Upload typefaces here to add them to your library. Uploading alone doesn’t put a font on your site — activate it by picking it in Font Set.',\n },\n timestamps: true,\n // When a font is populated as a relationship target (e.g. the `fontSet` global at depth),\n // return only its identifying metadata and NOT the `variable` / `weights` upload slots — which\n // would otherwise drag the private `fontOriginal` blobs through every populated row.\n defaultPopulate: { title: true, family: true },\n fields,\n hooks: {\n // Guard the referenced originals: at least one file, and no original shared across typefaces.\n beforeValidate: [requireFontFiles, makeRejectSharedOriginals(fontSlug)],\n // On save, (re)build the served WOFF2 files from the referenced originals (and clean up\n // any original a swapped/removed slot de-referenced).\n afterChange: [optimizeFromOriginalsHook({ charset: opts.charset, originalSlug, optimizedSlug })],\n // On read (edit view only), surface the served-file count so a failed optimize isn't silent.\n afterRead: [servedFilesHook(optimizedSlug)],\n // Before delete, cascade to the served files + the archived originals (beforeDelete so the\n // `fontOptimized.font` relationship is still intact — see the hook).\n beforeDelete: [cleanupFontAssetsHook({ originalSlug, optimizedSlug })],\n },\n }\n}\n"],"names":["APIError","authd","cleanupFontAssetsHook","optimizeFromOriginalsHook","originalIdsFromDoc","resolveFontFamilies","FONT_OPTIMIZED_SLUG","FONT_ORIGINAL_SLUG","WEIGHT_OPTIONS","FontOriginalUploadPath","createOnlyUpload","components","Field","path","hasVariable","data","v","variable","Boolean","upright","italic","hasWeights","Array","isArray","weights","some","w","file","servedFilesHook","optimizedSlug","doc","findMany","req","payload","id","totalDocs","count","collection","where","font","equals","overrideAccess","servedFiles","requireFontFiles","operation","touches","makeRejectSharedOriginals","fontSlug","originalDoc","ids","length","selfId","refs","in","and","not_equals","or","res","find","depth","limit","other","docs","title","createFontCollection","opts","originalSlug","families","fields","name","type","required","label","interfaceName","options","map","r","value","key","virtual","admin","readOnly","position","description","condition","relationTo","labels","singular","plural","validate","rows","seen","Set","weight","style","has","add","defaultValue","slug","access","create","delete","read","update","group","useAsTitle","enableListViewSelectAPI","defaultColumns","timestamps","defaultPopulate","family","hooks","beforeValidate","afterChange","charset","afterRead","beforeDelete"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/collections/font.ts"],"sourcesContent":["import {\n APIError,\n type ArrayField,\n type CollectionAfterReadHook,\n type CollectionBeforeValidateHook,\n type CollectionConfig,\n type CollectionSlug,\n type Field,\n} from 'payload'\n\nimport { authd } from '../access/authd'\nimport { cleanupFontAssetsHook, optimizeFromOriginalsHook, originalIdsFromDoc } from '../hooks/optimizeFromOriginals'\nimport type { Charset } from '../hooks/optimizeFont'\nimport { type FontFamilyConfig, resolveFontFamilies } from '../lib/families'\nimport { FONT_OPTIMIZED_SLUG } from './fontOptimized'\nimport { FONT_ORIGINAL_SLUG } from './fontOriginal'\n\n/** Standard `next/font/local` weight steps offered by each weight row. */\nconst WEIGHT_OPTIONS = ['100', '200', '300', '400', '500', '600', '700', '800', '900']\n\n/** Admin component subpath (Payload import map) for the create-only original uploader. */\nexport const FontOriginalUploadPath = '@pro-laico/payload-fonts/admin/FontOriginalUpload'\n/**\n * A FRESH admin config that renders the `fontOriginal` slot create-only (no \"Choose from\n * existing\"), so every slot uploads a fresh original — never shared between typefaces. A\n * factory (not a shared object) because Payload mutates field configs in place during\n * sanitization; sharing one reference across slots corrupts it.\n */\nconst createOnlyUpload = () => ({ components: { Field: { path: FontOriginalUploadPath } } })\n\nexport interface CreateFontCollectionOptions {\n /** Characters the subsetter keeps, or a preset ('latin' | 'latin-ext'). Default 'latin'. */\n charset?: Charset\n /** Slug of the archival original upload collection. Default 'fontOriginal'. */\n originalSlug?: string\n /** Slug of the optimized (served) upload collection. Default 'fontOptimized'. */\n optimizedSlug?: string\n /** The options offered by the `family` field. Default sans/serif/mono/display. */\n families?: FontFamilyConfig[]\n}\n\ntype VariableGroup = { upright?: unknown; italic?: unknown }\nconst hasVariable = (data: Record<string, unknown> | undefined): boolean => {\n const v = (data?.variable ?? {}) as VariableGroup\n return Boolean(v.upright || v.italic)\n}\nconst hasWeights = (data: Record<string, unknown> | undefined): boolean =>\n Array.isArray(data?.weights) && (data.weights as Array<{ file?: unknown }>).some((w) => w?.file)\n\n/**\n * `afterRead`: report how many served `fontOptimized` files this typeface produced, so an editor can\n * see at a glance whether optimization succeeded — `0` means nothing was served (a swap/re-save is\n * due, or the upload failed to subset). Skipped on list reads (`findMany`) so it's one count query\n * on the edit view, not one per row. Populates the virtual `servedFiles` sidebar field.\n */\nconst servedFilesHook =\n (optimizedSlug: string): CollectionAfterReadHook =>\n async ({ doc, findMany, req }) => {\n if (findMany || !req?.payload) return doc\n const id = (doc as { id?: string | number }).id\n if (id == null) return doc\n try {\n const { totalDocs } = await req.payload.count({\n collection: optimizedSlug as CollectionSlug,\n where: { font: { equals: id } },\n overrideAccess: true,\n req,\n })\n ;(doc as Record<string, unknown>).servedFiles = totalDocs\n } catch (err) {\n // A count failure shouldn't break the read — leave servedFiles unset, but say so: silence\n // here made a broken optimized collection indistinguishable from \"nothing served yet\".\n req.payload.logger.warn({ msg: `[payload-fonts] could not count served files for typeface ${id}`, err })\n }\n return doc\n }\n\n/**\n * `beforeValidate`: a typeface needs at least one file, and can't mix a variable font with\n * specific weights (you compose from one or the other). Runs on create and on any update that\n * touches these fields, so an unrelated partial edit is left alone.\n */\nconst requireFontFiles: CollectionBeforeValidateHook = ({ data, operation }) => {\n const touches = operation === 'create' || (data != null && ('variable' in data || 'weights' in data))\n if (!touches) return data\n if (hasVariable(data) && hasWeights(data)) {\n throw new APIError('Use either a variable font or specific weight files, not both.', 400, null, true)\n }\n if (!hasVariable(data) && !hasWeights(data)) {\n throw new APIError('Add at least one font file before saving.', 400, null, true)\n }\n return data\n}\n\n/**\n * `beforeValidate`: enforce one `fontOriginal` per typeface — reject a save that references an\n * original already used by ANOTHER typeface. The create-only upload slots make sharing\n * impossible from the admin UI, but this is the data-layer guarantee (covers the REST API,\n * imports, seeds, and a future Payload upgrade that might un-hide \"Choose from existing\"). It's\n * what makes the direct asset cleanup in {@link cleanupFontAssetsHook} /\n * {@link optimizeFromOriginalsHook} safe: a de-referenced or deleted original is never still in\n * use elsewhere.\n */\nconst makeRejectSharedOriginals =\n (fontSlug: string): CollectionBeforeValidateHook =>\n async ({ data, originalDoc, req }) => {\n if (!data || !req?.payload) return data\n const ids = originalIdsFromDoc(data as Record<string, unknown>)\n if (ids.length === 0) return data\n const selfId = (originalDoc as { id?: string | number } | undefined)?.id ?? (data as { id?: string | number }).id\n const refs = [{ 'variable.upright': { in: ids } }, { 'variable.italic': { in: ids } }, { 'weights.file': { in: ids } }]\n const where = selfId != null ? { and: [{ id: { not_equals: selfId } }, { or: refs }] } : { or: refs }\n const res = await req.payload.find({\n collection: fontSlug as CollectionSlug,\n where: where as never,\n depth: 0,\n limit: 1,\n overrideAccess: true,\n req,\n })\n if (res.totalDocs > 0) {\n const other = (res.docs[0] as { title?: string }).title || 'another typeface'\n throw new APIError(\n `That font file is already used by ${other}. Each typeface needs its own upload — add a fresh copy for this slot.`,\n 400,\n null,\n true,\n )\n }\n return data\n }\n\n/**\n * The `Font` collection — ONE document per **typeface** (e.g. \"Inter\"). The four family slots\n * (sans/serif/mono/display) select one of these each.\n *\n * It is NOT itself an upload collection. Editors drop font files into standard Payload `upload`\n * fields backed by `fontOriginal` (raw archive) — either the `variable` group (a single\n * variable file per upright/italic) OR the `weights` array (one file per weight/style). On save,\n * {@link optimizeFromOriginalsHook} subsets each referenced original to a served\n * `fontOptimized` WOFF2. The slots are Payload's own upload field, thinly wrapped to be\n * create-only (no \"Choose from existing\"), so each original belongs to exactly one typeface —\n * keeping asset cleanup safe and race-free.\n */\nexport const createFontCollection = (opts: CreateFontCollectionOptions = {}): CollectionConfig => {\n const fontSlug = 'font'\n const originalSlug = (opts.originalSlug || FONT_ORIGINAL_SLUG) as CollectionSlug\n const optimizedSlug = opts.optimizedSlug || FONT_OPTIMIZED_SLUG\n const families = resolveFontFamilies(opts.families)\n\n const fields: Field[] = [\n { name: 'title', type: 'text', required: true, label: 'Typeface name' },\n {\n name: 'family',\n type: 'radio',\n required: true,\n label: 'Preferred Family',\n interfaceName: 'GenericFontFamily',\n options: families.map((r) => ({ label: r.label, value: r.key })),\n },\n // Editor-facing status: how many web-ready files this typeface produced. Virtual (computed on\n // read by `servedFilesHook`, never stored); shown only on an existing doc so a `0` after saving\n // flags a failed/empty optimization instead of it failing silently.\n {\n name: 'servedFiles',\n type: 'number',\n virtual: true,\n admin: {\n readOnly: true,\n position: 'sidebar',\n description:\n 'Web-ready files generated from your uploads. 0 means nothing was served yet — re-save; if it stays 0, the upload may have failed to optimize (check server logs).',\n condition: (data) => Boolean((data as { id?: unknown })?.id),\n },\n },\n {\n name: 'variable',\n type: 'group',\n label: 'Variable font',\n admin: {\n description: 'One file covering many weights. Use this OR specific weights below — not both.',\n // Hidden once specific weights are added; you compose from one path or the other.\n condition: (data) => !hasWeights(data as Record<string, unknown>),\n },\n fields: [\n {\n type: 'row',\n fields: [\n { name: 'upright', type: 'upload', relationTo: originalSlug, label: 'Upright (normal)', admin: createOnlyUpload() },\n { name: 'italic', type: 'upload', relationTo: originalSlug, label: 'Italic', admin: createOnlyUpload() },\n ],\n },\n ],\n },\n {\n name: 'weights',\n type: 'array',\n label: 'Weights',\n labels: { singular: 'Weight file', plural: 'Weight files' },\n admin: {\n description: 'One file per weight/style. Add only the weights you need.',\n condition: (data) => !hasVariable(data as Record<string, unknown>),\n },\n // Each weight + style must be unique so the generated next/font src array has no conflicts.\n validate: ((rows: Array<{ weight?: string; style?: string }> | null | undefined) => {\n const seen = new Set<string>()\n for (const r of Array.isArray(rows) ? rows : []) {\n const key = `${r?.weight ?? ''}|${r?.style ?? ''}`\n if (seen.has(key)) return `Two files share weight ${r?.weight ?? '?'} ${r?.style ?? 'normal'}. Each weight + style must be unique.`\n seen.add(key)\n }\n return true\n }) as ArrayField['validate'],\n fields: [\n {\n type: 'row',\n fields: [\n { name: 'weight', type: 'select', required: true, defaultValue: '400', options: WEIGHT_OPTIONS },\n { name: 'style', type: 'radio', required: true, defaultValue: 'normal', options: ['normal', 'italic'] },\n ],\n },\n { name: 'file', type: 'upload', relationTo: originalSlug, required: true, admin: createOnlyUpload() },\n ],\n },\n ]\n\n return {\n slug: fontSlug,\n access: { create: authd, delete: authd, read: authd, update: authd },\n admin: {\n group: 'Assets',\n useAsTitle: 'title',\n enableListViewSelectAPI: true,\n defaultColumns: ['title', 'family'],\n description:\n 'Upload typefaces here to add them to your library. Uploading alone doesn’t put a font on your site — activate it by picking it in Font Set.',\n },\n timestamps: true,\n // When a font is populated as a relationship target (e.g. the `fontSet` global at depth),\n // return only its identifying metadata and NOT the `variable` / `weights` upload slots — which\n // would otherwise drag the private `fontOriginal` blobs through every populated row.\n defaultPopulate: { title: true, family: true },\n fields,\n hooks: {\n // Guard the referenced originals: at least one file, and no original shared across typefaces.\n beforeValidate: [requireFontFiles, makeRejectSharedOriginals(fontSlug)],\n // On save, (re)build the served WOFF2 files from the referenced originals (and clean up\n // any original a swapped/removed slot de-referenced).\n afterChange: [optimizeFromOriginalsHook({ charset: opts.charset, originalSlug, optimizedSlug })],\n // On read (edit view only), surface the served-file count so a failed optimize isn't silent.\n afterRead: [servedFilesHook(optimizedSlug)],\n // Before delete, cascade to the served files + the archived originals (beforeDelete so the\n // `fontOptimized.font` relationship is still intact — see the hook).\n beforeDelete: [cleanupFontAssetsHook({ originalSlug, optimizedSlug })],\n },\n }\n}\n"],"names":["APIError","authd","cleanupFontAssetsHook","optimizeFromOriginalsHook","originalIdsFromDoc","resolveFontFamilies","FONT_OPTIMIZED_SLUG","FONT_ORIGINAL_SLUG","WEIGHT_OPTIONS","FontOriginalUploadPath","createOnlyUpload","components","Field","path","hasVariable","data","v","variable","Boolean","upright","italic","hasWeights","Array","isArray","weights","some","w","file","servedFilesHook","optimizedSlug","doc","findMany","req","payload","id","totalDocs","count","collection","where","font","equals","overrideAccess","servedFiles","err","logger","warn","msg","requireFontFiles","operation","touches","makeRejectSharedOriginals","fontSlug","originalDoc","ids","length","selfId","refs","in","and","not_equals","or","res","find","depth","limit","other","docs","title","createFontCollection","opts","originalSlug","families","fields","name","type","required","label","interfaceName","options","map","r","value","key","virtual","admin","readOnly","position","description","condition","relationTo","labels","singular","plural","validate","rows","seen","Set","weight","style","has","add","defaultValue","slug","access","create","delete","read","update","group","useAsTitle","enableListViewSelectAPI","defaultColumns","timestamps","defaultPopulate","family","hooks","beforeValidate","afterChange","charset","afterRead","beforeDelete"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontOptimized.d.ts","sourceRoot":"","sources":["../../src/collections/fontOptimized.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,SAAS,CAAA;AAK/D,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,kBAAkB,CAAA;AAElD,MAAM,WAAW,oCAAoC;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAM,oCAAyC,KAAG,
|
|
1
|
+
{"version":3,"file":"fontOptimized.d.ts","sourceRoot":"","sources":["../../src/collections/fontOptimized.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAkB,MAAM,SAAS,CAAA;AAK/D,oEAAoE;AACpE,eAAO,MAAM,mBAAmB,kBAAkB,CAAA;AAElD,MAAM,WAAW,oCAAoC;IACnD,8EAA8E;IAC9E,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,6FAA6F;IAC7F,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,6BAA6B,GAAI,OAAM,oCAAyC,KAAG,gBAiC/F,CAAA"}
|
|
@@ -24,6 +24,12 @@ import { FONT_ORIGINAL_SLUG } from "./fontOriginal.js";
|
|
|
24
24
|
read: ()=>true,
|
|
25
25
|
update: authd
|
|
26
26
|
},
|
|
27
|
+
// Derived output written by the Font save hook — opt out of @pro-laico/payload-revalidate's
|
|
28
|
+
// auto-attached hooks. Served fonts are consumed at BUILD time (the next/font export
|
|
29
|
+
// endpoint), so runtime cache tags can't refresh them; a rebuild is the invalidation path.
|
|
30
|
+
custom: {
|
|
31
|
+
revalidate: false
|
|
32
|
+
},
|
|
27
33
|
admin: {
|
|
28
34
|
group: 'Assets',
|
|
29
35
|
hidden: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/fontOptimized.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug } from 'payload'\n\nimport { authd } from '../access/authd'\nimport { FONT_ORIGINAL_SLUG } from './fontOriginal'\n\n/** Slug of the optimized (served) weight-file upload collection. */\nexport const FONT_OPTIMIZED_SLUG = 'fontOptimized'\n\nexport interface CreateFontOptimizedCollectionOptions {\n /** Slug of the `Font` typeface collection these belong to. Default 'font'. */\n fontSlug?: string\n /** Slug of the archival original collection each is derived from. Default 'fontOriginal'. */\n originalSlug?: string\n}\n\n/**\n * The optimized, subsetted WOFF2 files the site actually serves — one upload document per\n * weight/style (or variable file). They are DERIVED, not uploaded by hand: the `Font`\n * collection's save hook reads the referenced `fontOriginal` files, subsets each to WOFF2, and\n * creates one of these linked back to its source original and its owning typeface.\n *\n * Hidden from admin nav (editors only ever touch `Font`), and always server-side stored — the\n * save hook writes the bytes, so there's nothing to client-upload.\n */\nexport const createFontOptimizedCollection = (opts: CreateFontOptimizedCollectionOptions = {}): CollectionConfig => {\n const fontSlug = opts.fontSlug || 'font'\n const originalSlug = opts.originalSlug || FONT_ORIGINAL_SLUG\n return {\n slug: FONT_OPTIMIZED_SLUG,\n // The optimized files ARE public web fonts — the build-time export endpoint (no user\n // session) downloads them to self-host via next/font. Public read lets that read succeed\n // even when they're on cloud storage served through Payload's access-controlled file route.\n // Writes stay gated; the raw originals stay private.\n access: { create: authd, delete: authd, read: () => true, update: authd },\n admin: { group: 'Assets', hidden: true, useAsTitle: 'filename', defaultColumns: ['filename', 'weight', 'style', 'isVariable'] },\n upload: { mimeTypes: ['font/woff2'] },\n fields: [\n // The owning typeface — the export endpoint and the reconcile hook query by it.\n { name: 'font', type: 'relationship', relationTo: fontSlug as CollectionSlug, admin: { readOnly: true } },\n // The source original this was subsetted from — the reconcile key (one optimized per original).\n { name: 'original', type: 'relationship', relationTo: originalSlug as CollectionSlug, admin: { readOnly: true } },\n // CSS font-weight: a single step ('400') or a variable range ('100 900'); served verbatim to next/font.\n { name: 'weight', type: 'text', admin: { readOnly: true } },\n { name: 'style', type: 'radio', options: ['normal', 'italic'], admin: { readOnly: true } },\n { name: 'isVariable', type: 'checkbox', admin: { readOnly: true } },\n // This upright variable file ALSO carries italics via its axes (`ital`, or a negative\n // `slnt`) — the serving layers emit a second, italic @font-face from the same file, unless\n // the typeface has an explicit italic file.\n { name: 'italCapable', type: 'checkbox', admin: { readOnly: true } },\n // For slnt-based italics: the positive CSS `oblique` angle (deg) matching the axis extreme.\n { name: 'obliqueAngle', type: 'number', admin: { readOnly: true, condition: (data) => Boolean(data?.italCapable) } },\n ],\n }\n}\n"],"names":["authd","FONT_ORIGINAL_SLUG","FONT_OPTIMIZED_SLUG","createFontOptimizedCollection","opts","fontSlug","originalSlug","slug","access","create","delete","read","update","admin","group","hidden","useAsTitle","defaultColumns","upload","mimeTypes","fields","name","type","relationTo","readOnly","options","condition","data","Boolean","italCapable"],"mappings":"AAEA,SAASA,KAAK,QAAQ,qBAAiB;AACvC,SAASC,kBAAkB,QAAQ,oBAAgB;AAEnD,kEAAkE,GAClE,OAAO,MAAMC,sBAAsB,gBAAe;AASlD;;;;;;;;CAQC,GACD,OAAO,MAAMC,gCAAgC,CAACC,OAA6C,CAAC,CAAC;IAC3F,MAAMC,WAAWD,KAAKC,QAAQ,IAAI;IAClC,MAAMC,eAAeF,KAAKE,YAAY,IAAIL;IAC1C,OAAO;QACLM,MAAML;QACN,qFAAqF;QACrF,yFAAyF;QACzF,4FAA4F;QAC5F,qDAAqD;QACrDM,QAAQ;YAAEC,QAAQT;YAAOU,QAAQV;YAAOW,MAAM,IAAM;YAAMC,QAAQZ;QAAM;
|
|
1
|
+
{"version":3,"sources":["../../src/collections/fontOptimized.ts"],"sourcesContent":["import type { CollectionConfig, CollectionSlug } from 'payload'\n\nimport { authd } from '../access/authd'\nimport { FONT_ORIGINAL_SLUG } from './fontOriginal'\n\n/** Slug of the optimized (served) weight-file upload collection. */\nexport const FONT_OPTIMIZED_SLUG = 'fontOptimized'\n\nexport interface CreateFontOptimizedCollectionOptions {\n /** Slug of the `Font` typeface collection these belong to. Default 'font'. */\n fontSlug?: string\n /** Slug of the archival original collection each is derived from. Default 'fontOriginal'. */\n originalSlug?: string\n}\n\n/**\n * The optimized, subsetted WOFF2 files the site actually serves — one upload document per\n * weight/style (or variable file). They are DERIVED, not uploaded by hand: the `Font`\n * collection's save hook reads the referenced `fontOriginal` files, subsets each to WOFF2, and\n * creates one of these linked back to its source original and its owning typeface.\n *\n * Hidden from admin nav (editors only ever touch `Font`), and always server-side stored — the\n * save hook writes the bytes, so there's nothing to client-upload.\n */\nexport const createFontOptimizedCollection = (opts: CreateFontOptimizedCollectionOptions = {}): CollectionConfig => {\n const fontSlug = opts.fontSlug || 'font'\n const originalSlug = opts.originalSlug || FONT_ORIGINAL_SLUG\n return {\n slug: FONT_OPTIMIZED_SLUG,\n // The optimized files ARE public web fonts — the build-time export endpoint (no user\n // session) downloads them to self-host via next/font. Public read lets that read succeed\n // even when they're on cloud storage served through Payload's access-controlled file route.\n // Writes stay gated; the raw originals stay private.\n access: { create: authd, delete: authd, read: () => true, update: authd },\n // Derived output written by the Font save hook — opt out of @pro-laico/payload-revalidate's\n // auto-attached hooks. Served fonts are consumed at BUILD time (the next/font export\n // endpoint), so runtime cache tags can't refresh them; a rebuild is the invalidation path.\n custom: { revalidate: false },\n admin: { group: 'Assets', hidden: true, useAsTitle: 'filename', defaultColumns: ['filename', 'weight', 'style', 'isVariable'] },\n upload: { mimeTypes: ['font/woff2'] },\n fields: [\n // The owning typeface — the export endpoint and the reconcile hook query by it.\n { name: 'font', type: 'relationship', relationTo: fontSlug as CollectionSlug, admin: { readOnly: true } },\n // The source original this was subsetted from — the reconcile key (one optimized per original).\n { name: 'original', type: 'relationship', relationTo: originalSlug as CollectionSlug, admin: { readOnly: true } },\n // CSS font-weight: a single step ('400') or a variable range ('100 900'); served verbatim to next/font.\n { name: 'weight', type: 'text', admin: { readOnly: true } },\n { name: 'style', type: 'radio', options: ['normal', 'italic'], admin: { readOnly: true } },\n { name: 'isVariable', type: 'checkbox', admin: { readOnly: true } },\n // This upright variable file ALSO carries italics via its axes (`ital`, or a negative\n // `slnt`) — the serving layers emit a second, italic @font-face from the same file, unless\n // the typeface has an explicit italic file.\n { name: 'italCapable', type: 'checkbox', admin: { readOnly: true } },\n // For slnt-based italics: the positive CSS `oblique` angle (deg) matching the axis extreme.\n { name: 'obliqueAngle', type: 'number', admin: { readOnly: true, condition: (data) => Boolean(data?.italCapable) } },\n ],\n }\n}\n"],"names":["authd","FONT_ORIGINAL_SLUG","FONT_OPTIMIZED_SLUG","createFontOptimizedCollection","opts","fontSlug","originalSlug","slug","access","create","delete","read","update","custom","revalidate","admin","group","hidden","useAsTitle","defaultColumns","upload","mimeTypes","fields","name","type","relationTo","readOnly","options","condition","data","Boolean","italCapable"],"mappings":"AAEA,SAASA,KAAK,QAAQ,qBAAiB;AACvC,SAASC,kBAAkB,QAAQ,oBAAgB;AAEnD,kEAAkE,GAClE,OAAO,MAAMC,sBAAsB,gBAAe;AASlD;;;;;;;;CAQC,GACD,OAAO,MAAMC,gCAAgC,CAACC,OAA6C,CAAC,CAAC;IAC3F,MAAMC,WAAWD,KAAKC,QAAQ,IAAI;IAClC,MAAMC,eAAeF,KAAKE,YAAY,IAAIL;IAC1C,OAAO;QACLM,MAAML;QACN,qFAAqF;QACrF,yFAAyF;QACzF,4FAA4F;QAC5F,qDAAqD;QACrDM,QAAQ;YAAEC,QAAQT;YAAOU,QAAQV;YAAOW,MAAM,IAAM;YAAMC,QAAQZ;QAAM;QACxE,4FAA4F;QAC5F,qFAAqF;QACrF,2FAA2F;QAC3Fa,QAAQ;YAAEC,YAAY;QAAM;QAC5BC,OAAO;YAAEC,OAAO;YAAUC,QAAQ;YAAMC,YAAY;YAAYC,gBAAgB;gBAAC;gBAAY;gBAAU;gBAAS;aAAa;QAAC;QAC9HC,QAAQ;YAAEC,WAAW;gBAAC;aAAa;QAAC;QACpCC,QAAQ;YACN,gFAAgF;YAChF;gBAAEC,MAAM;gBAAQC,MAAM;gBAAgBC,YAAYpB;gBAA4BU,OAAO;oBAAEW,UAAU;gBAAK;YAAE;YACxG,gGAAgG;YAChG;gBAAEH,MAAM;gBAAYC,MAAM;gBAAgBC,YAAYnB;gBAAgCS,OAAO;oBAAEW,UAAU;gBAAK;YAAE;YAChH,wGAAwG;YACxG;gBAAEH,MAAM;gBAAUC,MAAM;gBAAQT,OAAO;oBAAEW,UAAU;gBAAK;YAAE;YAC1D;gBAAEH,MAAM;gBAASC,MAAM;gBAASG,SAAS;oBAAC;oBAAU;iBAAS;gBAAEZ,OAAO;oBAAEW,UAAU;gBAAK;YAAE;YACzF;gBAAEH,MAAM;gBAAcC,MAAM;gBAAYT,OAAO;oBAAEW,UAAU;gBAAK;YAAE;YAClE,sFAAsF;YACtF,2FAA2F;YAC3F,4CAA4C;YAC5C;gBAAEH,MAAM;gBAAeC,MAAM;gBAAYT,OAAO;oBAAEW,UAAU;gBAAK;YAAE;YACnE,4FAA4F;YAC5F;gBAAEH,MAAM;gBAAgBC,MAAM;gBAAUT,OAAO;oBAAEW,UAAU;oBAAME,WAAW,CAACC,OAASC,QAAQD,MAAME;gBAAa;YAAE;SACpH;IACH;AACF,EAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fontOriginal.d.ts","sourceRoot":"","sources":["../../src/collections/fontOriginal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,iBAAiB,CAAA;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,UAW3B,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,GAAI,WAA+B;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,KAAG,
|
|
1
|
+
{"version":3,"file":"fontOriginal.d.ts","sourceRoot":"","sources":["../../src/collections/fontOriginal.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAI/C,qEAAqE;AACrE,eAAO,MAAM,kBAAkB,iBAAiB,CAAA;AAEhD;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,UAW3B,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,4BAA4B,GAAI,WAA+B;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,KAAG,gBASnG,CAAA"}
|
|
@@ -35,6 +35,11 @@ import { authd } from "../access/authd.js";
|
|
|
35
35
|
read: authd,
|
|
36
36
|
update: authd
|
|
37
37
|
},
|
|
38
|
+
// Derived archive written by the Font save hook — opt out of @pro-laico/payload-revalidate's
|
|
39
|
+
// auto-attached hooks (nothing on the frontend reads it, and derived writes would only add noise).
|
|
40
|
+
custom: {
|
|
41
|
+
revalidate: false
|
|
42
|
+
},
|
|
38
43
|
admin: {
|
|
39
44
|
group: 'Assets',
|
|
40
45
|
hidden: true,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/collections/fontOriginal.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport { authd } from '../access/authd'\n\n/** Default slug for the archival font-original upload collection. */\nexport const FONT_ORIGINAL_SLUG = 'fontOriginal'\n\n/**\n * Accepted upload mime types for the four web-font formats. OTF/TTF arrive under several\n * different mime strings depending on the OS/browser, so the whitelist covers the common\n * sfnt variants — otherwise a valid `.otf` can be rejected as \"MIME Type invalid\". Shared by\n * the `Font` collection and this archival one (which stores the untouched original under its\n * own original mime).\n */\nexport const FONT_MIME_TYPES = [\n 'font/ttf',\n 'font/otf',\n 'font/woff',\n 'font/woff2',\n 'font/sfnt',\n 'application/font-sfnt',\n 'application/vnd.ms-opentype',\n 'application/x-font-ttf',\n 'application/x-font-otf',\n 'application/x-font-truetype',\n]\n\n/**\n * Upload collection holding the raw, untouched font files editors drop into the `Font`\n * typeface's `upload` fields. It's the archive of truth: the `Font` save hook reads these and\n * subsets each to a served `fontOptimized` WOFF2, so the (lossy, subsetted) output can be\n * re-derived with a different charset later.\n *\n * It carries NO hooks — uploading here is a plain store, which is exactly what lets it be a\n * client-upload (direct-to-Blob) collection in production. Hidden from admin nav; editors only\n * ever interact with it through the `Font` fields.\n */\nexport const createFontOriginalCollection = ({ slug = FONT_ORIGINAL_SLUG }: { slug?: string } = {}): CollectionConfig => ({\n slug,\n access: { create: authd, delete: authd, read: authd, update: authd },\n admin: { group: 'Assets', hidden: true, useAsTitle: 'filename' },\n upload: { mimeTypes: FONT_MIME_TYPES },\n fields: [],\n})\n"],"names":["authd","FONT_ORIGINAL_SLUG","FONT_MIME_TYPES","createFontOriginalCollection","slug","access","create","delete","read","update","admin","group","hidden","useAsTitle","upload","mimeTypes","fields"],"mappings":"AAEA,SAASA,KAAK,QAAQ,qBAAiB;AAEvC,mEAAmE,GACnE,OAAO,MAAMC,qBAAqB,eAAc;AAEhD;;;;;;CAMC,GACD,OAAO,MAAMC,kBAAkB;IAC7B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED;;;;;;;;;CASC,GACD,OAAO,MAAMC,+BAA+B,CAAC,EAAEC,OAAOH,kBAAkB,EAAqB,GAAG,CAAC,CAAC,GAAwB,CAAA;QACxHG;QACAC,QAAQ;YAAEC,QAAQN;YAAOO,QAAQP;YAAOQ,MAAMR;YAAOS,QAAQT;QAAM;
|
|
1
|
+
{"version":3,"sources":["../../src/collections/fontOriginal.ts"],"sourcesContent":["import type { CollectionConfig } from 'payload'\n\nimport { authd } from '../access/authd'\n\n/** Default slug for the archival font-original upload collection. */\nexport const FONT_ORIGINAL_SLUG = 'fontOriginal'\n\n/**\n * Accepted upload mime types for the four web-font formats. OTF/TTF arrive under several\n * different mime strings depending on the OS/browser, so the whitelist covers the common\n * sfnt variants — otherwise a valid `.otf` can be rejected as \"MIME Type invalid\". Shared by\n * the `Font` collection and this archival one (which stores the untouched original under its\n * own original mime).\n */\nexport const FONT_MIME_TYPES = [\n 'font/ttf',\n 'font/otf',\n 'font/woff',\n 'font/woff2',\n 'font/sfnt',\n 'application/font-sfnt',\n 'application/vnd.ms-opentype',\n 'application/x-font-ttf',\n 'application/x-font-otf',\n 'application/x-font-truetype',\n]\n\n/**\n * Upload collection holding the raw, untouched font files editors drop into the `Font`\n * typeface's `upload` fields. It's the archive of truth: the `Font` save hook reads these and\n * subsets each to a served `fontOptimized` WOFF2, so the (lossy, subsetted) output can be\n * re-derived with a different charset later.\n *\n * It carries NO hooks — uploading here is a plain store, which is exactly what lets it be a\n * client-upload (direct-to-Blob) collection in production. Hidden from admin nav; editors only\n * ever interact with it through the `Font` fields.\n */\nexport const createFontOriginalCollection = ({ slug = FONT_ORIGINAL_SLUG }: { slug?: string } = {}): CollectionConfig => ({\n slug,\n access: { create: authd, delete: authd, read: authd, update: authd },\n // Derived archive written by the Font save hook — opt out of @pro-laico/payload-revalidate's\n // auto-attached hooks (nothing on the frontend reads it, and derived writes would only add noise).\n custom: { revalidate: false },\n admin: { group: 'Assets', hidden: true, useAsTitle: 'filename' },\n upload: { mimeTypes: FONT_MIME_TYPES },\n fields: [],\n})\n"],"names":["authd","FONT_ORIGINAL_SLUG","FONT_MIME_TYPES","createFontOriginalCollection","slug","access","create","delete","read","update","custom","revalidate","admin","group","hidden","useAsTitle","upload","mimeTypes","fields"],"mappings":"AAEA,SAASA,KAAK,QAAQ,qBAAiB;AAEvC,mEAAmE,GACnE,OAAO,MAAMC,qBAAqB,eAAc;AAEhD;;;;;;CAMC,GACD,OAAO,MAAMC,kBAAkB;IAC7B;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;CACD,CAAA;AAED;;;;;;;;;CASC,GACD,OAAO,MAAMC,+BAA+B,CAAC,EAAEC,OAAOH,kBAAkB,EAAqB,GAAG,CAAC,CAAC,GAAwB,CAAA;QACxHG;QACAC,QAAQ;YAAEC,QAAQN;YAAOO,QAAQP;YAAOQ,MAAMR;YAAOS,QAAQT;QAAM;QACnE,6FAA6F;QAC7F,mGAAmG;QACnGU,QAAQ;YAAEC,YAAY;QAAM;QAC5BC,OAAO;YAAEC,OAAO;YAAUC,QAAQ;YAAMC,YAAY;QAAW;QAC/DC,QAAQ;YAAEC,WAAWf;QAAgB;QACrCgB,QAAQ,EAAE;IACZ,CAAA,EAAE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"optimizeFromOriginals.d.ts","sourceRoot":"","sources":["../../src/hooks/optimizeFromOriginals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,0BAA0B,EAAkB,MAAM,SAAS,CAAA;AAIpG,OAAO,EAAE,KAAK,OAAO,EAA2E,MAAM,gBAAgB,CAAA;AAatH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAYvF,CAAA;AAgCD,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAM,4BAAiC,KAAG,
|
|
1
|
+
{"version":3,"file":"optimizeFromOriginals.d.ts","sourceRoot":"","sources":["../../src/hooks/optimizeFromOriginals.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,0BAA0B,EAAkB,MAAM,SAAS,CAAA;AAIpG,OAAO,EAAE,KAAK,OAAO,EAA2E,MAAM,gBAAgB,CAAA;AAatH;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB,GAAI,MAAM,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAG,KAAK,CAAC,MAAM,GAAG,MAAM,CAYvF,CAAA;AAgCD,MAAM,WAAW,4BAA4B;IAC3C,qFAAqF;IACrF,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,0EAA0E;IAC1E,aAAa,CAAC,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,yBAAyB,GAAI,OAAM,4BAAiC,KAAG,yBA8InF,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,GAAI,OAAM;IAAE,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAO,KAAG,0BA+CpG,CAAA"}
|
|
@@ -110,6 +110,7 @@ let warnedSubsetterLoad = false;
|
|
|
110
110
|
if (oid != null) byOriginal.set(oid, d);
|
|
111
111
|
}
|
|
112
112
|
const desiredIds = new Set(desired.map((d)=>d.originalId));
|
|
113
|
+
const fontTitle = typeof data.title === 'string' && data.title ? data.title : String(fontId);
|
|
113
114
|
// Create new, or sync metadata on a kept one whose row changed.
|
|
114
115
|
for (const d of desired){
|
|
115
116
|
const current = byOriginal.get(d.originalId);
|
|
@@ -129,6 +130,9 @@ let warnedSubsetterLoad = false;
|
|
|
129
130
|
}
|
|
130
131
|
continue;
|
|
131
132
|
}
|
|
133
|
+
// Upgraded to include the filename once the original doc is fetched — a bare id tells an
|
|
134
|
+
// operator nothing about WHICH font file broke.
|
|
135
|
+
let originalLabel = `original ${d.originalId}`;
|
|
132
136
|
try {
|
|
133
137
|
const original = await req.payload.findByID({
|
|
134
138
|
collection: originalSlug,
|
|
@@ -137,11 +141,12 @@ let warnedSubsetterLoad = false;
|
|
|
137
141
|
overrideAccess: true,
|
|
138
142
|
req
|
|
139
143
|
});
|
|
144
|
+
if (original.filename) originalLabel = `original ${d.originalId} ('${original.filename}')`;
|
|
140
145
|
const bytes = await readUploadBytes(req.payload, originalSlug, original, {
|
|
141
146
|
headers: fwdHeaders
|
|
142
147
|
});
|
|
143
148
|
if (!bytes) {
|
|
144
|
-
req.payload.logger.warn(`
|
|
149
|
+
req.payload.logger.warn(`[payload-fonts] typeface '${fontTitle}': could not read ${originalLabel} — the stored file is missing or unreadable, so that weight will NOT be served.`);
|
|
145
150
|
continue;
|
|
146
151
|
}
|
|
147
152
|
const meta = await detectMetadata(bytes);
|
|
@@ -184,7 +189,7 @@ let warnedSubsetterLoad = false;
|
|
|
184
189
|
req.payload.logger.error("[payload-fonts] The font subsetter failed to LOAD (subset-font / harfbuzz wasm / fontkit) — uploaded fonts are NOT being subsetted, so no web fonts will be served. In Next.js the wasm/native deps were bundled: add `serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']` to your next.config. Docs: https://payload-plugins.prolaico.com/docs/plugins/payload-fonts");
|
|
185
190
|
}
|
|
186
191
|
req.payload.logger.warn({
|
|
187
|
-
msg: `
|
|
192
|
+
msg: `[payload-fonts] typeface '${fontTitle}': optimization failed for ${originalLabel} — the file may be corrupt or not a real font; that weight will NOT be served.`,
|
|
188
193
|
err
|
|
189
194
|
});
|
|
190
195
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/hooks/optimizeFromOriginals.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionBeforeDeleteHook, CollectionSlug } from 'payload'\n\nimport { refId } from '../lib/refs'\nimport { readUploadBytes } from '../lib/uploadBytes'\nimport { type Charset, detectMetadata, isSubsetterLoadError, resolveCharsetText, subsetToWoff2 } from './optimizeFont'\n\ntype Ref = string | number | { id?: string | number } | null | undefined\n\n/** True when a delete failed because the doc is already gone — the goal state, not a problem.\n * Happens routinely when another path deleted it first (e.g. a seed run clears `fontOriginal`\n * directly, then clearing `font` fires this cascade at the same ids). */\nconst isNotFound = (err: unknown): boolean => (err as { status?: number })?.status === 404 || (err instanceof Error && err.name === 'NotFound')\n\n// Surface the bundled-subsetter load failure (see isSubsetterLoadError) as ONE loud, actionable\n// log instead of only the generic per-font warning.\nlet warnedSubsetterLoad = false\n\n/**\n * Every `fontOriginal` id a typeface doc references across all of its slots. Each original\n * belongs to exactly one typeface — enforced by the create-only upload slots (UI) and the\n * `rejectSharedOriginals` guard (data layer) — so cleanup can delete a de-referenced or\n * deleted original outright, with no shared-original / concurrent-delete hazard.\n */\nexport const originalIdsFromDoc = (data: Record<string, unknown>): Array<string | number> => {\n const ids: Array<string | number> = []\n const variable = (data.variable ?? {}) as { upright?: Ref; italic?: Ref }\n for (const r of [variable.upright, variable.italic]) {\n const id = refId(r)\n if (id != null) ids.push(id)\n }\n for (const row of (Array.isArray(data.weights) ? data.weights : []) as Array<{ file?: Ref }>) {\n const id = refId(row.file)\n if (id != null) ids.push(id)\n }\n return ids\n}\n\n/** A weight/style file the typeface should have an optimized WOFF2 for. */\ninterface Desired {\n originalId: string | number\n style: 'normal' | 'italic'\n isVariable: boolean\n /** Static rows carry an explicit weight; variable files derive a range from the binary. */\n weight?: string\n}\n\n/** Read the font doc's slots into the set of originals to optimize. */\nconst desiredFromDoc = (data: Record<string, unknown>): Desired[] => {\n const desired: Desired[] = []\n const variable = (data.variable ?? {}) as { upright?: Ref; italic?: Ref }\n const uprightId = refId(variable.upright)\n const italicId = refId(variable.italic)\n if (uprightId != null) desired.push({ originalId: uprightId, style: 'normal', isVariable: true })\n if (italicId != null) desired.push({ originalId: italicId, style: 'italic', isVariable: true })\n // Variable and weights are mutually exclusive (enforced on the collection); only read the\n // weights array when no variable file is present.\n if (uprightId == null && italicId == null) {\n const weights = Array.isArray(data.weights) ? data.weights : []\n for (const row of weights as Array<{ weight?: string; style?: string; file?: Ref }>) {\n const fid = refId(row.file)\n if (fid != null)\n desired.push({ originalId: fid, style: row.style === 'italic' ? 'italic' : 'normal', isVariable: false, weight: row.weight })\n }\n }\n return desired\n}\n\nexport interface OptimizeFromOriginalsOptions {\n /** Characters to keep, or a preset name ('latin' | 'latin-ext'). Default 'latin'. */\n charset?: Charset\n /** Slug of the archival original collection. Default 'fontOriginal'. */\n originalSlug?: string\n /** Slug of the optimized (served) collection. Default 'fontOptimized'. */\n optimizedSlug?: string\n}\n\n/**\n * `afterChange` for the `Font` typeface: reconcile its served `fontOptimized` files\n * against the `fontOriginal` files referenced in its slots.\n *\n * Keyed by source-original id: a newly-referenced original is fetched, subsetted to WOFF2\n * (variable weight range read from the `wght` axis; static weight/style from the row) and\n * created; an original that's gone has its optimized deleted; an unchanged original is kept\n * (only its weight/style metadata is synced if the row changed). Best-effort — a single bad\n * font logs a warning and is skipped, never failing the typeface save. Touches only\n * `fontOptimized`, so it can't re-trigger this hook.\n */\nexport const optimizeFromOriginalsHook = (opts: OptimizeFromOriginalsOptions = {}): CollectionAfterChangeHook => {\n const charsetText = resolveCharsetText(opts.charset)\n const originalSlug = (opts.originalSlug || 'fontOriginal') as CollectionSlug\n const optimizedSlug = (opts.optimizedSlug || 'fontOptimized') as CollectionSlug\n\n return async ({ doc, operation, previousDoc, req }) => {\n const data = doc as Record<string, unknown>\n const fontId = data.id as string | number\n const desired = desiredFromDoc(data)\n\n // Forward the request's auth so reading the original succeeds when it lives on cloud\n // storage served through Payload's own access-controlled file route — an unauthenticated\n // fetch 403s. `origin`/`referer` are required too: Payload's CSRF protection rejects a\n // cookie-authenticated request whose Origin isn't allow-listed.\n const fwdHeaders: Record<string, string> = {}\n for (const h of ['cookie', 'authorization', 'origin', 'referer']) {\n const v = req.headers?.get(h)\n if (v) fwdHeaders[h] = v\n }\n\n try {\n const existing = await req.payload.find({\n collection: optimizedSlug,\n where: { font: { equals: fontId } },\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n req,\n })\n const byOriginal = new Map<string | number, Record<string, unknown>>()\n for (const d of existing.docs as unknown as Array<Record<string, unknown>>) {\n const oid = refId(d.original as Ref)\n if (oid != null) byOriginal.set(oid, d)\n }\n const desiredIds = new Set(desired.map((d) => d.originalId))\n\n // Create new, or sync metadata on a kept one whose row changed.\n for (const d of desired) {\n const current = byOriginal.get(d.originalId)\n if (current) {\n const weightDrift = !d.isVariable && d.weight && String(current.weight ?? '') !== d.weight\n if (weightDrift || String(current.style ?? '') !== d.style) {\n await req.payload.update({\n collection: optimizedSlug,\n id: current.id as string | number,\n data: { weight: d.weight ?? current.weight, style: d.style } as never,\n overrideAccess: true,\n req,\n })\n }\n continue\n }\n try {\n const original = (await req.payload.findByID({\n collection: originalSlug,\n id: d.originalId,\n depth: 0,\n overrideAccess: true,\n req,\n })) as {\n filename?: string | null\n url?: string | null\n }\n const bytes = await readUploadBytes(req.payload, originalSlug, original, { headers: fwdHeaders })\n if (!bytes) {\n req.payload.logger.warn(`Font optimize: could not read original ${d.originalId}`)\n continue\n }\n const meta = await detectMetadata(bytes)\n const woff2 = await subsetToWoff2(bytes, charsetText)\n // Variable: the intrinsic axis range wins. Static: the row's weight wins, detection fills a blank.\n const weight = d.isVariable ? (meta?.weight ?? d.weight) : (d.weight ?? meta?.weight)\n // An upright variable file whose axes also cover italics (ital / negative slnt) is\n // flagged so the serving layers can emit an italic face from the same file.\n const italCapable = Boolean(d.style === 'normal' && meta?.italCapable)\n const baseName = (original.filename || `font-${d.originalId}`).replace(/\\.[^.]+$/, '')\n // Shallow-cloned req: createLocalReq mutates `.file` on the req it's given; the clone\n // keeps that off the parent while sharing the transaction.\n await req.payload.create({\n collection: optimizedSlug,\n req: { ...req },\n overrideAccess: true,\n data: {\n font: fontId,\n original: d.originalId,\n weight,\n style: d.style,\n isVariable: meta?.isVariable ?? d.isVariable,\n italCapable,\n ...(italCapable && meta?.obliqueAngle ? { obliqueAngle: meta.obliqueAngle } : {}),\n } as never,\n file: { data: woff2, name: `${baseName}.woff2`, mimetype: 'font/woff2', size: woff2.length },\n })\n } catch (err) {\n if (isSubsetterLoadError(err) && !warnedSubsetterLoad) {\n warnedSubsetterLoad = true\n req.payload.logger.error(\n \"[payload-fonts] The font subsetter failed to LOAD (subset-font / harfbuzz wasm / fontkit) — uploaded fonts are NOT being subsetted, so no web fonts will be served. In Next.js the wasm/native deps were bundled: add `serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']` to your next.config. Docs: https://payload-plugins.prolaico.com/docs/plugins/payload-fonts\",\n )\n }\n req.payload.logger.warn({ msg: `Font optimization failed for original ${d.originalId}`, err })\n }\n }\n\n // Delete optimized files whose source original is no longer referenced.\n for (const [oid, d] of byOriginal) {\n if (desiredIds.has(oid)) continue\n try {\n await req.payload.delete({ collection: optimizedSlug, id: d.id as string | number, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: 'Could not delete stale optimized font', err })\n }\n }\n\n // Delete originals this update de-referenced (a swapped/removed slot file). Originals\n // aren't shared (create-only slots), so this is always safe.\n if (operation === 'update' && previousDoc) {\n const stillReferenced = new Set(originalIdsFromDoc(data))\n for (const oid of originalIdsFromDoc(previousDoc as Record<string, unknown>)) {\n if (stillReferenced.has(oid)) continue\n try {\n await req.payload.delete({ collection: originalSlug, id: oid, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: `Could not delete de-referenced font original ${oid}`, err })\n }\n }\n }\n } catch (err) {\n req.payload.logger.warn({ msg: 'Font optimize reconcile failed', err })\n }\n return doc\n }\n}\n\n/**\n * `beforeDelete` for the `Font` typeface: cascade-delete its served `fontOptimized` files and\n * the `fontOriginal` files its slots referenced, so nothing orphans in storage. Best-effort.\n *\n * It runs `beforeDelete` (not after) on purpose: deleting the `font` doc triggers Payload's\n * dangling-reference cleanup, which nulls `fontOptimized.font` — so by `afterDelete` the served\n * files can no longer be found by their owning typeface. Here the relationship is still intact,\n * so we resolve them by `font` first (and read the originals off the doc's own upload slots).\n */\nexport const cleanupFontAssetsHook = (opts: { originalSlug?: string; optimizedSlug?: string } = {}): CollectionBeforeDeleteHook => {\n const originalSlug = (opts.originalSlug || 'fontOriginal') as CollectionSlug\n const optimizedSlug = (opts.optimizedSlug || 'fontOptimized') as CollectionSlug\n\n return async ({ collection, id, req }) => {\n // Load the doc so we can read its `fontOriginal` slot ids (the font's own upload fields).\n let data: Record<string, unknown> | undefined\n try {\n data = (await req.payload.findByID({\n collection: collection.slug as CollectionSlug,\n id,\n depth: 0,\n overrideAccess: true,\n req,\n })) as unknown as Record<string, unknown>\n } catch {\n // gone already / not found — fall through, optimized cleanup below still runs by `font`\n }\n\n // Delete the served files, found by owning typeface while the relationship still exists.\n try {\n const optimized = await req.payload.find({\n collection: optimizedSlug,\n where: { font: { equals: id } },\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n req,\n })\n for (const d of optimized.docs as Array<{ id: string | number }>) {\n await req.payload.delete({ collection: optimizedSlug, id: d.id, overrideAccess: true, req })\n }\n } catch (err) {\n req.payload.logger.warn({ msg: 'Could not delete optimized fonts', err })\n }\n\n // Delete the originals this typeface referenced (create-only slots → never shared).\n if (data) {\n for (const oid of originalIdsFromDoc(data)) {\n try {\n await req.payload.delete({ collection: originalSlug, id: oid, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: 'Could not delete font original', err })\n }\n }\n }\n }\n}\n"],"names":["refId","readUploadBytes","detectMetadata","isSubsetterLoadError","resolveCharsetText","subsetToWoff2","isNotFound","err","status","Error","name","warnedSubsetterLoad","originalIdsFromDoc","data","ids","variable","r","upright","italic","id","push","row","Array","isArray","weights","file","desiredFromDoc","desired","uprightId","italicId","originalId","style","isVariable","fid","weight","optimizeFromOriginalsHook","opts","charsetText","charset","originalSlug","optimizedSlug","doc","operation","previousDoc","req","fontId","fwdHeaders","h","v","headers","get","existing","payload","find","collection","where","font","equals","depth","limit","overrideAccess","byOriginal","Map","d","docs","oid","original","set","desiredIds","Set","map","current","weightDrift","String","update","findByID","bytes","logger","warn","meta","woff2","italCapable","Boolean","baseName","filename","replace","create","obliqueAngle","mimetype","size","length","error","msg","has","delete","stillReferenced","cleanupFontAssetsHook","slug","optimized"],"mappings":"AAEA,SAASA,KAAK,QAAQ,iBAAa;AACnC,SAASC,eAAe,QAAQ,wBAAoB;AACpD,SAAuBC,cAAc,EAAEC,oBAAoB,EAAEC,kBAAkB,EAAEC,aAAa,QAAQ,oBAAgB;AAItH;;wEAEwE,GACxE,MAAMC,aAAa,CAACC,MAA0B,AAACA,KAA6BC,WAAW,OAAQD,eAAeE,SAASF,IAAIG,IAAI,KAAK;AAEpI,gGAAgG;AAChG,oDAAoD;AACpD,IAAIC,sBAAsB;AAE1B;;;;;CAKC,GACD,OAAO,MAAMC,qBAAqB,CAACC;IACjC,MAAMC,MAA8B,EAAE;IACtC,MAAMC,WAAYF,KAAKE,QAAQ,IAAI,CAAC;IACpC,KAAK,MAAMC,KAAK;QAACD,SAASE,OAAO;QAAEF,SAASG,MAAM;KAAC,CAAE;QACnD,MAAMC,KAAKnB,MAAMgB;QACjB,IAAIG,MAAM,MAAML,IAAIM,IAAI,CAACD;IAC3B;IACA,KAAK,MAAME,OAAQC,MAAMC,OAAO,CAACV,KAAKW,OAAO,IAAIX,KAAKW,OAAO,GAAG,EAAE,CAA4B;QAC5F,MAAML,KAAKnB,MAAMqB,IAAII,IAAI;QACzB,IAAIN,MAAM,MAAML,IAAIM,IAAI,CAACD;IAC3B;IACA,OAAOL;AACT,EAAC;AAWD,qEAAqE,GACrE,MAAMY,iBAAiB,CAACb;IACtB,MAAMc,UAAqB,EAAE;IAC7B,MAAMZ,WAAYF,KAAKE,QAAQ,IAAI,CAAC;IACpC,MAAMa,YAAY5B,MAAMe,SAASE,OAAO;IACxC,MAAMY,WAAW7B,MAAMe,SAASG,MAAM;IACtC,IAAIU,aAAa,MAAMD,QAAQP,IAAI,CAAC;QAAEU,YAAYF;QAAWG,OAAO;QAAUC,YAAY;IAAK;IAC/F,IAAIH,YAAY,MAAMF,QAAQP,IAAI,CAAC;QAAEU,YAAYD;QAAUE,OAAO;QAAUC,YAAY;IAAK;IAC7F,0FAA0F;IAC1F,kDAAkD;IAClD,IAAIJ,aAAa,QAAQC,YAAY,MAAM;QACzC,MAAML,UAAUF,MAAMC,OAAO,CAACV,KAAKW,OAAO,IAAIX,KAAKW,OAAO,GAAG,EAAE;QAC/D,KAAK,MAAMH,OAAOG,QAAmE;YACnF,MAAMS,MAAMjC,MAAMqB,IAAII,IAAI;YAC1B,IAAIQ,OAAO,MACTN,QAAQP,IAAI,CAAC;gBAAEU,YAAYG;gBAAKF,OAAOV,IAAIU,KAAK,KAAK,WAAW,WAAW;gBAAUC,YAAY;gBAAOE,QAAQb,IAAIa,MAAM;YAAC;QAC/H;IACF;IACA,OAAOP;AACT;AAWA;;;;;;;;;;CAUC,GACD,OAAO,MAAMQ,4BAA4B,CAACC,OAAqC,CAAC,CAAC;IAC/E,MAAMC,cAAcjC,mBAAmBgC,KAAKE,OAAO;IACnD,MAAMC,eAAgBH,KAAKG,YAAY,IAAI;IAC3C,MAAMC,gBAAiBJ,KAAKI,aAAa,IAAI;IAE7C,OAAO,OAAO,EAAEC,GAAG,EAAEC,SAAS,EAAEC,WAAW,EAAEC,GAAG,EAAE;QAChD,MAAM/B,OAAO4B;QACb,MAAMI,SAAShC,KAAKM,EAAE;QACtB,MAAMQ,UAAUD,eAAeb;QAE/B,qFAAqF;QACrF,yFAAyF;QACzF,uFAAuF;QACvF,gEAAgE;QAChE,MAAMiC,aAAqC,CAAC;QAC5C,KAAK,MAAMC,KAAK;YAAC;YAAU;YAAiB;YAAU;SAAU,CAAE;YAChE,MAAMC,IAAIJ,IAAIK,OAAO,EAAEC,IAAIH;YAC3B,IAAIC,GAAGF,UAAU,CAACC,EAAE,GAAGC;QACzB;QAEA,IAAI;YACF,MAAMG,WAAW,MAAMP,IAAIQ,OAAO,CAACC,IAAI,CAAC;gBACtCC,YAAYd;gBACZe,OAAO;oBAAEC,MAAM;wBAAEC,QAAQZ;oBAAO;gBAAE;gBAClCa,OAAO;gBACPC,OAAO;gBACPC,gBAAgB;gBAChBhB;YACF;YACA,MAAMiB,aAAa,IAAIC;YACvB,KAAK,MAAMC,KAAKZ,SAASa,IAAI,CAA+C;gBAC1E,MAAMC,MAAMjE,MAAM+D,EAAEG,QAAQ;gBAC5B,IAAID,OAAO,MAAMJ,WAAWM,GAAG,CAACF,KAAKF;YACvC;YACA,MAAMK,aAAa,IAAIC,IAAI1C,QAAQ2C,GAAG,CAAC,CAACP,IAAMA,EAAEjC,UAAU;YAE1D,gEAAgE;YAChE,KAAK,MAAMiC,KAAKpC,QAAS;gBACvB,MAAM4C,UAAUV,WAAWX,GAAG,CAACa,EAAEjC,UAAU;gBAC3C,IAAIyC,SAAS;oBACX,MAAMC,cAAc,CAACT,EAAE/B,UAAU,IAAI+B,EAAE7B,MAAM,IAAIuC,OAAOF,QAAQrC,MAAM,IAAI,QAAQ6B,EAAE7B,MAAM;oBAC1F,IAAIsC,eAAeC,OAAOF,QAAQxC,KAAK,IAAI,QAAQgC,EAAEhC,KAAK,EAAE;wBAC1D,MAAMa,IAAIQ,OAAO,CAACsB,MAAM,CAAC;4BACvBpB,YAAYd;4BACZrB,IAAIoD,QAAQpD,EAAE;4BACdN,MAAM;gCAAEqB,QAAQ6B,EAAE7B,MAAM,IAAIqC,QAAQrC,MAAM;gCAAEH,OAAOgC,EAAEhC,KAAK;4BAAC;4BAC3D6B,gBAAgB;4BAChBhB;wBACF;oBACF;oBACA;gBACF;gBACA,IAAI;oBACF,MAAMsB,WAAY,MAAMtB,IAAIQ,OAAO,CAACuB,QAAQ,CAAC;wBAC3CrB,YAAYf;wBACZpB,IAAI4C,EAAEjC,UAAU;wBAChB4B,OAAO;wBACPE,gBAAgB;wBAChBhB;oBACF;oBAIA,MAAMgC,QAAQ,MAAM3E,gBAAgB2C,IAAIQ,OAAO,EAAEb,cAAc2B,UAAU;wBAAEjB,SAASH;oBAAW;oBAC/F,IAAI,CAAC8B,OAAO;wBACVhC,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC,CAAC,uCAAuC,EAAEf,EAAEjC,UAAU,EAAE;wBAChF;oBACF;oBACA,MAAMiD,OAAO,MAAM7E,eAAe0E;oBAClC,MAAMI,QAAQ,MAAM3E,cAAcuE,OAAOvC;oBACzC,mGAAmG;oBACnG,MAAMH,SAAS6B,EAAE/B,UAAU,GAAI+C,MAAM7C,UAAU6B,EAAE7B,MAAM,GAAK6B,EAAE7B,MAAM,IAAI6C,MAAM7C;oBAC9E,mFAAmF;oBACnF,4EAA4E;oBAC5E,MAAM+C,cAAcC,QAAQnB,EAAEhC,KAAK,KAAK,YAAYgD,MAAME;oBAC1D,MAAME,WAAW,AAACjB,CAAAA,SAASkB,QAAQ,IAAI,CAAC,KAAK,EAAErB,EAAEjC,UAAU,EAAE,AAAD,EAAGuD,OAAO,CAAC,YAAY;oBACnF,sFAAsF;oBACtF,2DAA2D;oBAC3D,MAAMzC,IAAIQ,OAAO,CAACkC,MAAM,CAAC;wBACvBhC,YAAYd;wBACZI,KAAK;4BAAE,GAAGA,GAAG;wBAAC;wBACdgB,gBAAgB;wBAChB/C,MAAM;4BACJ2C,MAAMX;4BACNqB,UAAUH,EAAEjC,UAAU;4BACtBI;4BACAH,OAAOgC,EAAEhC,KAAK;4BACdC,YAAY+C,MAAM/C,cAAc+B,EAAE/B,UAAU;4BAC5CiD;4BACA,GAAIA,eAAeF,MAAMQ,eAAe;gCAAEA,cAAcR,KAAKQ,YAAY;4BAAC,IAAI,CAAC,CAAC;wBAClF;wBACA9D,MAAM;4BAAEZ,MAAMmE;4BAAOtE,MAAM,GAAGyE,SAAS,MAAM,CAAC;4BAAEK,UAAU;4BAAcC,MAAMT,MAAMU,MAAM;wBAAC;oBAC7F;gBACF,EAAE,OAAOnF,KAAK;oBACZ,IAAIJ,qBAAqBI,QAAQ,CAACI,qBAAqB;wBACrDA,sBAAsB;wBACtBiC,IAAIQ,OAAO,CAACyB,MAAM,CAACc,KAAK,CACtB;oBAEJ;oBACA/C,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC;wBAAEc,KAAK,CAAC,sCAAsC,EAAE7B,EAAEjC,UAAU,EAAE;wBAAEvB;oBAAI;gBAC9F;YACF;YAEA,wEAAwE;YACxE,KAAK,MAAM,CAAC0D,KAAKF,EAAE,IAAIF,WAAY;gBACjC,IAAIO,WAAWyB,GAAG,CAAC5B,MAAM;gBACzB,IAAI;oBACF,MAAMrB,IAAIQ,OAAO,CAAC0C,MAAM,CAAC;wBAAExC,YAAYd;wBAAerB,IAAI4C,EAAE5C,EAAE;wBAAqByC,gBAAgB;wBAAMhB;oBAAI;gBAC/G,EAAE,OAAOrC,KAAK;oBACZ,IAAI,CAACD,WAAWC,MAAMqC,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC;wBAAEc,KAAK;wBAAyCrF;oBAAI;gBACpG;YACF;YAEA,sFAAsF;YACtF,6DAA6D;YAC7D,IAAImC,cAAc,YAAYC,aAAa;gBACzC,MAAMoD,kBAAkB,IAAI1B,IAAIzD,mBAAmBC;gBACnD,KAAK,MAAMoD,OAAOrD,mBAAmB+B,aAAyC;oBAC5E,IAAIoD,gBAAgBF,GAAG,CAAC5B,MAAM;oBAC9B,IAAI;wBACF,MAAMrB,IAAIQ,OAAO,CAAC0C,MAAM,CAAC;4BAAExC,YAAYf;4BAAcpB,IAAI8C;4BAAKL,gBAAgB;4BAAMhB;wBAAI;oBAC1F,EAAE,OAAOrC,KAAK;wBACZ,IAAI,CAACD,WAAWC,MAAMqC,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC;4BAAEc,KAAK,CAAC,6CAA6C,EAAE3B,KAAK;4BAAE1D;wBAAI;oBAClH;gBACF;YACF;QACF,EAAE,OAAOA,KAAK;YACZqC,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC;gBAAEc,KAAK;gBAAkCrF;YAAI;QACvE;QACA,OAAOkC;IACT;AACF,EAAC;AAED;;;;;;;;CAQC,GACD,OAAO,MAAMuD,wBAAwB,CAAC5D,OAA0D,CAAC,CAAC;IAChG,MAAMG,eAAgBH,KAAKG,YAAY,IAAI;IAC3C,MAAMC,gBAAiBJ,KAAKI,aAAa,IAAI;IAE7C,OAAO,OAAO,EAAEc,UAAU,EAAEnC,EAAE,EAAEyB,GAAG,EAAE;QACnC,0FAA0F;QAC1F,IAAI/B;QACJ,IAAI;YACFA,OAAQ,MAAM+B,IAAIQ,OAAO,CAACuB,QAAQ,CAAC;gBACjCrB,YAAYA,WAAW2C,IAAI;gBAC3B9E;gBACAuC,OAAO;gBACPE,gBAAgB;gBAChBhB;YACF;QACF,EAAE,OAAM;QACN,wFAAwF;QAC1F;QAEA,yFAAyF;QACzF,IAAI;YACF,MAAMsD,YAAY,MAAMtD,IAAIQ,OAAO,CAACC,IAAI,CAAC;gBACvCC,YAAYd;gBACZe,OAAO;oBAAEC,MAAM;wBAAEC,QAAQtC;oBAAG;gBAAE;gBAC9BuC,OAAO;gBACPC,OAAO;gBACPC,gBAAgB;gBAChBhB;YACF;YACA,KAAK,MAAMmB,KAAKmC,UAAUlC,IAAI,CAAoC;gBAChE,MAAMpB,IAAIQ,OAAO,CAAC0C,MAAM,CAAC;oBAAExC,YAAYd;oBAAerB,IAAI4C,EAAE5C,EAAE;oBAAEyC,gBAAgB;oBAAMhB;gBAAI;YAC5F;QACF,EAAE,OAAOrC,KAAK;YACZqC,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC;gBAAEc,KAAK;gBAAoCrF;YAAI;QACzE;QAEA,oFAAoF;QACpF,IAAIM,MAAM;YACR,KAAK,MAAMoD,OAAOrD,mBAAmBC,MAAO;gBAC1C,IAAI;oBACF,MAAM+B,IAAIQ,OAAO,CAAC0C,MAAM,CAAC;wBAAExC,YAAYf;wBAAcpB,IAAI8C;wBAAKL,gBAAgB;wBAAMhB;oBAAI;gBAC1F,EAAE,OAAOrC,KAAK;oBACZ,IAAI,CAACD,WAAWC,MAAMqC,IAAIQ,OAAO,CAACyB,MAAM,CAACC,IAAI,CAAC;wBAAEc,KAAK;wBAAkCrF;oBAAI;gBAC7F;YACF;QACF;IACF;AACF,EAAC"}
|
|
1
|
+
{"version":3,"sources":["../../src/hooks/optimizeFromOriginals.ts"],"sourcesContent":["import type { CollectionAfterChangeHook, CollectionBeforeDeleteHook, CollectionSlug } from 'payload'\n\nimport { refId } from '../lib/refs'\nimport { readUploadBytes } from '../lib/uploadBytes'\nimport { type Charset, detectMetadata, isSubsetterLoadError, resolveCharsetText, subsetToWoff2 } from './optimizeFont'\n\ntype Ref = string | number | { id?: string | number } | null | undefined\n\n/** True when a delete failed because the doc is already gone — the goal state, not a problem.\n * Happens routinely when another path deleted it first (e.g. a seed run clears `fontOriginal`\n * directly, then clearing `font` fires this cascade at the same ids). */\nconst isNotFound = (err: unknown): boolean => (err as { status?: number })?.status === 404 || (err instanceof Error && err.name === 'NotFound')\n\n// Surface the bundled-subsetter load failure (see isSubsetterLoadError) as ONE loud, actionable\n// log instead of only the generic per-font warning.\nlet warnedSubsetterLoad = false\n\n/**\n * Every `fontOriginal` id a typeface doc references across all of its slots. Each original\n * belongs to exactly one typeface — enforced by the create-only upload slots (UI) and the\n * `rejectSharedOriginals` guard (data layer) — so cleanup can delete a de-referenced or\n * deleted original outright, with no shared-original / concurrent-delete hazard.\n */\nexport const originalIdsFromDoc = (data: Record<string, unknown>): Array<string | number> => {\n const ids: Array<string | number> = []\n const variable = (data.variable ?? {}) as { upright?: Ref; italic?: Ref }\n for (const r of [variable.upright, variable.italic]) {\n const id = refId(r)\n if (id != null) ids.push(id)\n }\n for (const row of (Array.isArray(data.weights) ? data.weights : []) as Array<{ file?: Ref }>) {\n const id = refId(row.file)\n if (id != null) ids.push(id)\n }\n return ids\n}\n\n/** A weight/style file the typeface should have an optimized WOFF2 for. */\ninterface Desired {\n originalId: string | number\n style: 'normal' | 'italic'\n isVariable: boolean\n /** Static rows carry an explicit weight; variable files derive a range from the binary. */\n weight?: string\n}\n\n/** Read the font doc's slots into the set of originals to optimize. */\nconst desiredFromDoc = (data: Record<string, unknown>): Desired[] => {\n const desired: Desired[] = []\n const variable = (data.variable ?? {}) as { upright?: Ref; italic?: Ref }\n const uprightId = refId(variable.upright)\n const italicId = refId(variable.italic)\n if (uprightId != null) desired.push({ originalId: uprightId, style: 'normal', isVariable: true })\n if (italicId != null) desired.push({ originalId: italicId, style: 'italic', isVariable: true })\n // Variable and weights are mutually exclusive (enforced on the collection); only read the\n // weights array when no variable file is present.\n if (uprightId == null && italicId == null) {\n const weights = Array.isArray(data.weights) ? data.weights : []\n for (const row of weights as Array<{ weight?: string; style?: string; file?: Ref }>) {\n const fid = refId(row.file)\n if (fid != null)\n desired.push({ originalId: fid, style: row.style === 'italic' ? 'italic' : 'normal', isVariable: false, weight: row.weight })\n }\n }\n return desired\n}\n\nexport interface OptimizeFromOriginalsOptions {\n /** Characters to keep, or a preset name ('latin' | 'latin-ext'). Default 'latin'. */\n charset?: Charset\n /** Slug of the archival original collection. Default 'fontOriginal'. */\n originalSlug?: string\n /** Slug of the optimized (served) collection. Default 'fontOptimized'. */\n optimizedSlug?: string\n}\n\n/**\n * `afterChange` for the `Font` typeface: reconcile its served `fontOptimized` files\n * against the `fontOriginal` files referenced in its slots.\n *\n * Keyed by source-original id: a newly-referenced original is fetched, subsetted to WOFF2\n * (variable weight range read from the `wght` axis; static weight/style from the row) and\n * created; an original that's gone has its optimized deleted; an unchanged original is kept\n * (only its weight/style metadata is synced if the row changed). Best-effort — a single bad\n * font logs a warning and is skipped, never failing the typeface save. Touches only\n * `fontOptimized`, so it can't re-trigger this hook.\n */\nexport const optimizeFromOriginalsHook = (opts: OptimizeFromOriginalsOptions = {}): CollectionAfterChangeHook => {\n const charsetText = resolveCharsetText(opts.charset)\n const originalSlug = (opts.originalSlug || 'fontOriginal') as CollectionSlug\n const optimizedSlug = (opts.optimizedSlug || 'fontOptimized') as CollectionSlug\n\n return async ({ doc, operation, previousDoc, req }) => {\n const data = doc as Record<string, unknown>\n const fontId = data.id as string | number\n const desired = desiredFromDoc(data)\n\n // Forward the request's auth so reading the original succeeds when it lives on cloud\n // storage served through Payload's own access-controlled file route — an unauthenticated\n // fetch 403s. `origin`/`referer` are required too: Payload's CSRF protection rejects a\n // cookie-authenticated request whose Origin isn't allow-listed.\n const fwdHeaders: Record<string, string> = {}\n for (const h of ['cookie', 'authorization', 'origin', 'referer']) {\n const v = req.headers?.get(h)\n if (v) fwdHeaders[h] = v\n }\n\n try {\n const existing = await req.payload.find({\n collection: optimizedSlug,\n where: { font: { equals: fontId } },\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n req,\n })\n const byOriginal = new Map<string | number, Record<string, unknown>>()\n for (const d of existing.docs as unknown as Array<Record<string, unknown>>) {\n const oid = refId(d.original as Ref)\n if (oid != null) byOriginal.set(oid, d)\n }\n const desiredIds = new Set(desired.map((d) => d.originalId))\n const fontTitle = typeof data.title === 'string' && data.title ? data.title : String(fontId)\n\n // Create new, or sync metadata on a kept one whose row changed.\n for (const d of desired) {\n const current = byOriginal.get(d.originalId)\n if (current) {\n const weightDrift = !d.isVariable && d.weight && String(current.weight ?? '') !== d.weight\n if (weightDrift || String(current.style ?? '') !== d.style) {\n await req.payload.update({\n collection: optimizedSlug,\n id: current.id as string | number,\n data: { weight: d.weight ?? current.weight, style: d.style } as never,\n overrideAccess: true,\n req,\n })\n }\n continue\n }\n // Upgraded to include the filename once the original doc is fetched — a bare id tells an\n // operator nothing about WHICH font file broke.\n let originalLabel = `original ${d.originalId}`\n try {\n const original = (await req.payload.findByID({\n collection: originalSlug,\n id: d.originalId,\n depth: 0,\n overrideAccess: true,\n req,\n })) as {\n filename?: string | null\n url?: string | null\n }\n if (original.filename) originalLabel = `original ${d.originalId} ('${original.filename}')`\n const bytes = await readUploadBytes(req.payload, originalSlug, original, { headers: fwdHeaders })\n if (!bytes) {\n req.payload.logger.warn(\n `[payload-fonts] typeface '${fontTitle}': could not read ${originalLabel} — the stored file is missing or unreadable, so that weight will NOT be served.`,\n )\n continue\n }\n const meta = await detectMetadata(bytes)\n const woff2 = await subsetToWoff2(bytes, charsetText)\n // Variable: the intrinsic axis range wins. Static: the row's weight wins, detection fills a blank.\n const weight = d.isVariable ? (meta?.weight ?? d.weight) : (d.weight ?? meta?.weight)\n // An upright variable file whose axes also cover italics (ital / negative slnt) is\n // flagged so the serving layers can emit an italic face from the same file.\n const italCapable = Boolean(d.style === 'normal' && meta?.italCapable)\n const baseName = (original.filename || `font-${d.originalId}`).replace(/\\.[^.]+$/, '')\n // Shallow-cloned req: createLocalReq mutates `.file` on the req it's given; the clone\n // keeps that off the parent while sharing the transaction.\n await req.payload.create({\n collection: optimizedSlug,\n req: { ...req },\n overrideAccess: true,\n data: {\n font: fontId,\n original: d.originalId,\n weight,\n style: d.style,\n isVariable: meta?.isVariable ?? d.isVariable,\n italCapable,\n ...(italCapable && meta?.obliqueAngle ? { obliqueAngle: meta.obliqueAngle } : {}),\n } as never,\n file: { data: woff2, name: `${baseName}.woff2`, mimetype: 'font/woff2', size: woff2.length },\n })\n } catch (err) {\n if (isSubsetterLoadError(err) && !warnedSubsetterLoad) {\n warnedSubsetterLoad = true\n req.payload.logger.error(\n \"[payload-fonts] The font subsetter failed to LOAD (subset-font / harfbuzz wasm / fontkit) — uploaded fonts are NOT being subsetted, so no web fonts will be served. In Next.js the wasm/native deps were bundled: add `serverExternalPackages: ['subset-font', 'harfbuzzjs', 'fontkit']` to your next.config. Docs: https://payload-plugins.prolaico.com/docs/plugins/payload-fonts\",\n )\n }\n req.payload.logger.warn({\n msg: `[payload-fonts] typeface '${fontTitle}': optimization failed for ${originalLabel} — the file may be corrupt or not a real font; that weight will NOT be served.`,\n err,\n })\n }\n }\n\n // Delete optimized files whose source original is no longer referenced.\n for (const [oid, d] of byOriginal) {\n if (desiredIds.has(oid)) continue\n try {\n await req.payload.delete({ collection: optimizedSlug, id: d.id as string | number, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: 'Could not delete stale optimized font', err })\n }\n }\n\n // Delete originals this update de-referenced (a swapped/removed slot file). Originals\n // aren't shared (create-only slots), so this is always safe.\n if (operation === 'update' && previousDoc) {\n const stillReferenced = new Set(originalIdsFromDoc(data))\n for (const oid of originalIdsFromDoc(previousDoc as Record<string, unknown>)) {\n if (stillReferenced.has(oid)) continue\n try {\n await req.payload.delete({ collection: originalSlug, id: oid, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: `Could not delete de-referenced font original ${oid}`, err })\n }\n }\n }\n } catch (err) {\n req.payload.logger.warn({ msg: 'Font optimize reconcile failed', err })\n }\n return doc\n }\n}\n\n/**\n * `beforeDelete` for the `Font` typeface: cascade-delete its served `fontOptimized` files and\n * the `fontOriginal` files its slots referenced, so nothing orphans in storage. Best-effort.\n *\n * It runs `beforeDelete` (not after) on purpose: deleting the `font` doc triggers Payload's\n * dangling-reference cleanup, which nulls `fontOptimized.font` — so by `afterDelete` the served\n * files can no longer be found by their owning typeface. Here the relationship is still intact,\n * so we resolve them by `font` first (and read the originals off the doc's own upload slots).\n */\nexport const cleanupFontAssetsHook = (opts: { originalSlug?: string; optimizedSlug?: string } = {}): CollectionBeforeDeleteHook => {\n const originalSlug = (opts.originalSlug || 'fontOriginal') as CollectionSlug\n const optimizedSlug = (opts.optimizedSlug || 'fontOptimized') as CollectionSlug\n\n return async ({ collection, id, req }) => {\n // Load the doc so we can read its `fontOriginal` slot ids (the font's own upload fields).\n let data: Record<string, unknown> | undefined\n try {\n data = (await req.payload.findByID({\n collection: collection.slug as CollectionSlug,\n id,\n depth: 0,\n overrideAccess: true,\n req,\n })) as unknown as Record<string, unknown>\n } catch {\n // gone already / not found — fall through, optimized cleanup below still runs by `font`\n }\n\n // Delete the served files, found by owning typeface while the relationship still exists.\n try {\n const optimized = await req.payload.find({\n collection: optimizedSlug,\n where: { font: { equals: id } },\n depth: 0,\n limit: 1000,\n overrideAccess: true,\n req,\n })\n for (const d of optimized.docs as Array<{ id: string | number }>) {\n await req.payload.delete({ collection: optimizedSlug, id: d.id, overrideAccess: true, req })\n }\n } catch (err) {\n req.payload.logger.warn({ msg: 'Could not delete optimized fonts', err })\n }\n\n // Delete the originals this typeface referenced (create-only slots → never shared).\n if (data) {\n for (const oid of originalIdsFromDoc(data)) {\n try {\n await req.payload.delete({ collection: originalSlug, id: oid, overrideAccess: true, req })\n } catch (err) {\n if (!isNotFound(err)) req.payload.logger.warn({ msg: 'Could not delete font original', err })\n }\n }\n }\n }\n}\n"],"names":["refId","readUploadBytes","detectMetadata","isSubsetterLoadError","resolveCharsetText","subsetToWoff2","isNotFound","err","status","Error","name","warnedSubsetterLoad","originalIdsFromDoc","data","ids","variable","r","upright","italic","id","push","row","Array","isArray","weights","file","desiredFromDoc","desired","uprightId","italicId","originalId","style","isVariable","fid","weight","optimizeFromOriginalsHook","opts","charsetText","charset","originalSlug","optimizedSlug","doc","operation","previousDoc","req","fontId","fwdHeaders","h","v","headers","get","existing","payload","find","collection","where","font","equals","depth","limit","overrideAccess","byOriginal","Map","d","docs","oid","original","set","desiredIds","Set","map","fontTitle","title","String","current","weightDrift","update","originalLabel","findByID","filename","bytes","logger","warn","meta","woff2","italCapable","Boolean","baseName","replace","create","obliqueAngle","mimetype","size","length","error","msg","has","delete","stillReferenced","cleanupFontAssetsHook","slug","optimized"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pro-laico/payload-fonts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Custom fonts for Payload CMS: a Font typeface collection that subsets uploaded files to served WOFF2s, an optional fontSet global, an export endpoint + CLI that writes the active fonts to disk for next/font/local, and declarative seeding.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|