@focus-reactive/payload-plugin-seo 1.4.0 → 1.6.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.
Files changed (149) hide show
  1. package/README.md +331 -124
  2. package/dist/admin.css +97 -1
  3. package/dist/client-config/registry.d.ts +7 -0
  4. package/dist/client-config/registry.d.ts.map +1 -0
  5. package/dist/client-config/registry.js +16 -0
  6. package/dist/client-config/registry.js.map +1 -0
  7. package/dist/components/SeoButton/SeoButtonInner.d.ts +2 -4
  8. package/dist/components/SeoButton/SeoButtonInner.d.ts.map +1 -1
  9. package/dist/components/SeoButton/SeoButtonInner.js +10 -7
  10. package/dist/components/SeoButton/SeoButtonInner.js.map +1 -1
  11. package/dist/components/SeoDrawer/build-analysis-input.d.ts +1 -8
  12. package/dist/components/SeoDrawer/build-analysis-input.d.ts.map +1 -1
  13. package/dist/components/SeoDrawer/build-analysis-input.js +22 -34
  14. package/dist/components/SeoDrawer/build-analysis-input.js.map +1 -1
  15. package/dist/components/SeoDrawer/buildInput.js +1 -1
  16. package/dist/components/SeoDrawer/buildInput.js.map +1 -1
  17. package/dist/components/SeoDrawer/useLiveDocument.d.ts +2 -5
  18. package/dist/components/SeoDrawer/useLiveDocument.d.ts.map +1 -1
  19. package/dist/components/SeoDrawer/useLiveDocument.js +14 -62
  20. package/dist/components/SeoDrawer/useLiveDocument.js.map +1 -1
  21. package/dist/components/SeoField/Meter.d.ts +10 -0
  22. package/dist/components/SeoField/Meter.d.ts.map +1 -0
  23. package/dist/components/SeoField/Meter.js +59 -0
  24. package/dist/components/SeoField/Meter.js.map +1 -0
  25. package/dist/components/SeoField/icons.d.ts +5 -0
  26. package/dist/components/SeoField/icons.d.ts.map +1 -0
  27. package/dist/components/SeoField/icons.js +12 -0
  28. package/dist/components/SeoField/icons.js.map +1 -0
  29. package/dist/components/SeoField/index.d.ts +20 -0
  30. package/dist/components/SeoField/index.d.ts.map +1 -0
  31. package/dist/components/SeoField/index.js +118 -0
  32. package/dist/components/SeoField/index.js.map +1 -0
  33. package/dist/components/SeoField/useGenerate.d.ts +15 -0
  34. package/dist/components/SeoField/useGenerate.d.ts.map +1 -0
  35. package/dist/components/SeoField/useGenerate.js +102 -0
  36. package/dist/components/SeoField/useGenerate.js.map +1 -0
  37. package/dist/constants/generation.d.ts +13 -0
  38. package/dist/constants/generation.d.ts.map +1 -0
  39. package/dist/constants/generation.js +21 -0
  40. package/dist/constants/generation.js.map +1 -0
  41. package/dist/content/index.d.ts +2 -2
  42. package/dist/content/index.d.ts.map +1 -1
  43. package/dist/content/index.js +2 -1
  44. package/dist/content/index.js.map +1 -1
  45. package/dist/content/resolve/resolve-docs.d.ts +3 -0
  46. package/dist/content/resolve/resolve-docs.d.ts.map +1 -0
  47. package/dist/content/resolve/resolve-docs.js +48 -0
  48. package/dist/content/resolve/resolve-docs.js.map +1 -0
  49. package/dist/content/schema/helpers.d.ts +1 -0
  50. package/dist/content/schema/helpers.d.ts.map +1 -1
  51. package/dist/content/schema/helpers.js +4 -0
  52. package/dist/content/schema/helpers.js.map +1 -1
  53. package/dist/engine/helpers/title-progress.d.ts +2 -1
  54. package/dist/engine/helpers/title-progress.d.ts.map +1 -1
  55. package/dist/engine/helpers/title-progress.js +1 -1
  56. package/dist/engine/helpers/title-progress.js.map +1 -1
  57. package/dist/fields/index.d.ts +3 -0
  58. package/dist/fields/index.d.ts.map +1 -0
  59. package/dist/fields/index.js +5 -0
  60. package/dist/fields/index.js.map +1 -0
  61. package/dist/fields/onPublishHook.d.ts +8 -0
  62. package/dist/fields/onPublishHook.d.ts.map +1 -0
  63. package/dist/fields/onPublishHook.js +95 -0
  64. package/dist/fields/onPublishHook.js.map +1 -0
  65. package/dist/fields/seoTextField.d.ts +23 -0
  66. package/dist/fields/seoTextField.d.ts.map +1 -0
  67. package/dist/fields/seoTextField.js +43 -0
  68. package/dist/fields/seoTextField.js.map +1 -0
  69. package/dist/index.d.ts +3 -1
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +3 -1
  72. package/dist/index.js.map +1 -1
  73. package/dist/measure/measure.d.ts +16 -0
  74. package/dist/measure/measure.d.ts.map +1 -0
  75. package/dist/measure/measure.js +38 -0
  76. package/dist/measure/measure.js.map +1 -0
  77. package/dist/plugin.d.ts.map +1 -1
  78. package/dist/plugin.js +18 -3
  79. package/dist/plugin.js.map +1 -1
  80. package/dist/providers/SeoClientConfigProvider.d.ts +8 -0
  81. package/dist/providers/SeoClientConfigProvider.d.ts.map +1 -0
  82. package/dist/providers/SeoClientConfigProvider.js +16 -0
  83. package/dist/providers/SeoClientConfigProvider.js.map +1 -0
  84. package/dist/server/generate/apiKey.d.ts +3 -0
  85. package/dist/server/generate/apiKey.d.ts.map +1 -0
  86. package/dist/server/generate/apiKey.js +11 -0
  87. package/dist/server/generate/apiKey.js.map +1 -0
  88. package/dist/server/generate/endpoint.d.ts +3 -0
  89. package/dist/server/generate/endpoint.d.ts.map +1 -0
  90. package/dist/server/generate/endpoint.js +47 -0
  91. package/dist/server/generate/endpoint.js.map +1 -0
  92. package/dist/server/generate/generateForField.d.ts +18 -0
  93. package/dist/server/generate/generateForField.d.ts.map +1 -0
  94. package/dist/server/generate/generateForField.js +41 -0
  95. package/dist/server/generate/generateForField.js.map +1 -0
  96. package/dist/server/generate/openai.d.ts +9 -0
  97. package/dist/server/generate/openai.d.ts.map +1 -0
  98. package/dist/server/generate/openai.js +43 -0
  99. package/dist/server/generate/openai.js.map +1 -0
  100. package/dist/server/generate/prompts.d.ts +19 -0
  101. package/dist/server/generate/prompts.d.ts.map +1 -0
  102. package/dist/server/generate/prompts.js +32 -0
  103. package/dist/server/generate/prompts.js.map +1 -0
  104. package/dist/server/generate/serverResolveDocs.d.ts +4 -0
  105. package/dist/server/generate/serverResolveDocs.d.ts.map +1 -0
  106. package/dist/server/generate/serverResolveDocs.js +38 -0
  107. package/dist/server/generate/serverResolveDocs.js.map +1 -0
  108. package/dist/types/config.d.ts +59 -23
  109. package/dist/types/config.d.ts.map +1 -1
  110. package/dist/utils/config/overrideAdmin.d.ts.map +1 -1
  111. package/dist/utils/config/overrideAdmin.js +29 -11
  112. package/dist/utils/config/overrideAdmin.js.map +1 -1
  113. package/package.json +13 -1
  114. package/dist/content/extract/context.d.ts +0 -11
  115. package/dist/content/extract/context.d.ts.map +0 -1
  116. package/dist/content/extract/context.js +0 -1
  117. package/dist/content/extract/context.js.map +0 -1
  118. package/dist/content/extract/extract.d.ts +0 -18
  119. package/dist/content/extract/extract.d.ts.map +0 -1
  120. package/dist/content/extract/extract.js +0 -211
  121. package/dist/content/extract/extract.js.map +0 -1
  122. package/dist/content/extract/selection.d.ts +0 -3
  123. package/dist/content/extract/selection.d.ts.map +0 -1
  124. package/dist/content/extract/selection.js +0 -32
  125. package/dist/content/extract/selection.js.map +0 -1
  126. package/dist/content/lexical/transform.d.ts +0 -5
  127. package/dist/content/lexical/transform.d.ts.map +0 -1
  128. package/dist/content/lexical/transform.js +0 -56
  129. package/dist/content/lexical/transform.js.map +0 -1
  130. package/dist/content/resolve/collect-refs.d.ts +0 -8
  131. package/dist/content/resolve/collect-refs.d.ts.map +0 -1
  132. package/dist/content/resolve/collect-refs.js +0 -79
  133. package/dist/content/resolve/collect-refs.js.map +0 -1
  134. package/dist/content/resolve/hydrate.d.ts +0 -7
  135. package/dist/content/resolve/hydrate.d.ts.map +0 -1
  136. package/dist/content/resolve/hydrate.js +0 -123
  137. package/dist/content/resolve/hydrate.js.map +0 -1
  138. package/dist/content/resolve/resolver.d.ts +0 -7
  139. package/dist/content/resolve/resolver.d.ts.map +0 -1
  140. package/dist/content/resolve/resolver.js +0 -65
  141. package/dist/content/resolve/resolver.js.map +0 -1
  142. package/dist/content/resolve/types.d.ts +0 -12
  143. package/dist/content/resolve/types.d.ts.map +0 -1
  144. package/dist/content/resolve/types.js +0 -7
  145. package/dist/content/resolve/types.js.map +0 -1
  146. package/dist/content/walk/walkFields.d.ts +0 -17
  147. package/dist/content/walk/walkFields.d.ts.map +0 -1
  148. package/dist/content/walk/walkFields.js +0 -88
  149. package/dist/content/walk/walkFields.js.map +0 -1
@@ -1,20 +1,17 @@
1
1
  import { serialize } from "../../content/schema/serialize";
2
- import { extractContent } from "../../content/extract/extract";
3
- import { collectRefs } from "../../content/resolve/collect-refs";
4
- import { hydrate } from "../../content/resolve/hydrate";
5
- import { makeExcluded, makeIncluded } from "../../content/extract/selection";
2
+ import {
3
+ compact,
4
+ heading,
5
+ html,
6
+ image,
7
+ link,
8
+ paragraph,
9
+ richText,
10
+ video
11
+ } from "../../content/schema/helpers";
12
+ import { createResolveDocs } from "../../content/resolve/resolve-docs";
6
13
  import { buildInput } from "./buildInput";
7
- function normalizeSelection(content) {
8
- if (content == null)
9
- return { include: [], exclude: [] };
10
- if (typeof content === "string")
11
- return { include: [content], exclude: [] };
12
- const sel = content;
13
- return { include: sel.include ?? [], exclude: sel.exclude ?? [] };
14
- }
15
- function metadataPaths(fields) {
16
- return [fields.seoTitle, fields.metaDescription, fields.slug ?? "slug"].filter((p) => p !== void 0);
17
- }
14
+ const helpers = { heading, paragraph, link, image, video, html, richText, compact };
18
15
  async function buildAnalysisInput(args) {
19
16
  const ir = await extractIntermediateRepresentation(args);
20
17
  const contentHtml = serialize(ir);
@@ -28,26 +25,17 @@ async function buildAnalysisInput(args) {
28
25
  });
29
26
  }
30
27
  async function extractIntermediateRepresentation(args) {
31
- const selection = normalizeSelection(args.fields.content);
32
- const meta = metadataPaths(args.fields);
33
- if (args.fields.content == null)
28
+ if (!args.extractor)
34
29
  return [];
35
- const excluded = makeExcluded(meta, selection.exclude);
36
- const included = makeIncluded(selection.include);
37
- const prune = (path) => excluded(path) || !included(path);
38
- const refs = args.resolveDepth >= 1 ? collectRefs(args.values, args.hostFields, args.ctx, prune) : [];
39
- const resolved = refs.length > 0 ? await args.resolver.resolve(refs, args.payloadLocale, args.resolveDepth - 1) : /* @__PURE__ */ new Map();
40
- const ctx = { ...args.ctx, resolved };
41
- if (args.override)
42
- return await args.override(hydrate(args.values, args.hostFields, ctx, resolved), { locale: args.payloadLocale, apiRoute: args.apiRoute });
43
- return extractContent({
44
- values: args.values,
45
- fields: args.hostFields,
46
- ctx,
47
- selection,
48
- metadataPaths: meta,
49
- depth: args.resolveDepth
50
- });
30
+ const ctx = {
31
+ locale: args.payloadLocale,
32
+ apiRoute: args.apiRoute
33
+ };
34
+ const toolkit = {
35
+ resolveDocs: createResolveDocs(args.apiRoute, args.payloadLocale),
36
+ helpers
37
+ };
38
+ return await args.extractor(args.values, ctx, toolkit);
51
39
  }
52
40
  export {
53
41
  buildAnalysisInput
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/SeoDrawer/build-analysis-input.ts"],"sourcesContent":["import type { ClientField } from \"payload\";\nimport type { ContentNode } from \"../../content/schema/nodes\";\nimport { serialize } from \"../../content/schema/serialize\";\nimport { extractContent } from \"../../content/extract/extract\";\nimport type { ExtractContext } from \"../../content/extract/context\";\nimport { collectRefs } from \"../../content/resolve/collect-refs\";\nimport { hydrate } from \"../../content/resolve/hydrate\";\nimport type { DocResolver } from \"../../content/resolve/resolver\";\nimport type { ResolvedDoc } from \"../../content/resolve/types\";\nimport { makeExcluded, makeIncluded } from \"../../content/extract/selection\";\nimport type { AnalysisInput } from \"../../engine/types/analysis\";\nimport type { ContentExtractor, ContentSelection, SeoFieldPaths } from \"../../types/config\";\nimport { buildInput } from \"./buildInput\";\n\nexport interface BuildAnalysisInputArgs {\n values: Record<string, unknown>;\n locale: string | { code?: string } | null | undefined;\n payloadLocale: string | undefined;\n apiRoute?: string;\n keyphrase: string;\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n hostFields: ClientField[];\n ctx: ExtractContext;\n resolver: DocResolver;\n resolveDepth: number;\n override?: ContentExtractor;\n}\n\nfunction normalizeSelection(content: SeoFieldPaths[\"content\"]): {\n include: string[];\n exclude: string[];\n} {\n if (content == null) return { include: [], exclude: [] };\n if (typeof content === \"string\") return { include: [content], exclude: [] };\n const sel = content as ContentSelection;\n return { include: sel.include ?? [], exclude: sel.exclude ?? [] };\n}\n\nfunction metadataPaths(fields: SeoFieldPaths): string[] {\n return [fields.seoTitle, fields.metaDescription, fields.slug ?? \"slug\"].filter((p): p is string => p !== undefined);\n}\n\nexport async function buildAnalysisInput(args: BuildAnalysisInputArgs): Promise<AnalysisInput> {\n const ir = await extractIntermediateRepresentation(args);\n const contentHtml = serialize(ir);\n\n return buildInput({\n values: args.values,\n contentHtml,\n locale: args.locale,\n keyphrase: args.keyphrase,\n fields: args.fields,\n site: args.site,\n });\n}\n\nasync function extractIntermediateRepresentation(args: BuildAnalysisInputArgs): Promise<ContentNode[]> {\n const selection = normalizeSelection(args.fields.content);\n const meta = metadataPaths(args.fields);\n\n if (args.fields.content == null) return [];\n\n const excluded = makeExcluded(meta, selection.exclude);\n const included = makeIncluded(selection.include);\n const prune = (path: string) => excluded(path) || !included(path);\n\n const refs = args.resolveDepth >= 1 ? collectRefs(args.values, args.hostFields, args.ctx, prune) : [];\n const resolved = refs.length > 0 ? await args.resolver.resolve(refs, args.payloadLocale, args.resolveDepth - 1) : new Map<string, ResolvedDoc>();\n\n const ctx: ExtractContext = { ...args.ctx, resolved };\n\n if (args.override) return await args.override(hydrate(args.values, args.hostFields, ctx, resolved), { locale: args.payloadLocale, apiRoute: args.apiRoute });\n\n return extractContent({\n values: args.values,\n fields: args.hostFields,\n ctx,\n selection,\n metadataPaths: meta,\n depth: args.resolveDepth,\n });\n}\n"],"mappings":"AAEA,SAAS,iBAAiB;AAC1B,SAAS,sBAAsB;AAE/B,SAAS,mBAAmB;AAC5B,SAAS,eAAe;AAGxB,SAAS,cAAc,oBAAoB;AAG3C,SAAS,kBAAkB;AAiB3B,SAAS,mBAAmB,SAG1B;AACA,MAAI,WAAW;AAAM,WAAO,EAAE,SAAS,CAAC,GAAG,SAAS,CAAC,EAAE;AACvD,MAAI,OAAO,YAAY;AAAU,WAAO,EAAE,SAAS,CAAC,OAAO,GAAG,SAAS,CAAC,EAAE;AAC1E,QAAM,MAAM;AACZ,SAAO,EAAE,SAAS,IAAI,WAAW,CAAC,GAAG,SAAS,IAAI,WAAW,CAAC,EAAE;AAClE;AAEA,SAAS,cAAc,QAAiC;AACtD,SAAO,CAAC,OAAO,UAAU,OAAO,iBAAiB,OAAO,QAAQ,MAAM,EAAE,OAAO,CAAC,MAAmB,MAAM,MAAS;AACpH;AAEA,eAAsB,mBAAmB,MAAsD;AAC7F,QAAM,KAAK,MAAM,kCAAkC,IAAI;AACvD,QAAM,cAAc,UAAU,EAAE;AAEhC,SAAO,WAAW;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,EACb,CAAC;AACH;AAEA,eAAe,kCAAkC,MAAsD;AACrG,QAAM,YAAY,mBAAmB,KAAK,OAAO,OAAO;AACxD,QAAM,OAAO,cAAc,KAAK,MAAM;AAEtC,MAAI,KAAK,OAAO,WAAW;AAAM,WAAO,CAAC;AAEzC,QAAM,WAAW,aAAa,MAAM,UAAU,OAAO;AACrD,QAAM,WAAW,aAAa,UAAU,OAAO;AAC/C,QAAM,QAAQ,CAAC,SAAiB,SAAS,IAAI,KAAK,CAAC,SAAS,IAAI;AAEhE,QAAM,OAAO,KAAK,gBAAgB,IAAI,YAAY,KAAK,QAAQ,KAAK,YAAY,KAAK,KAAK,KAAK,IAAI,CAAC;AACpG,QAAM,WAAW,KAAK,SAAS,IAAI,MAAM,KAAK,SAAS,QAAQ,MAAM,KAAK,eAAe,KAAK,eAAe,CAAC,IAAI,oBAAI,IAAyB;AAE/I,QAAM,MAAsB,EAAE,GAAG,KAAK,KAAK,SAAS;AAEpD,MAAI,KAAK;AAAU,WAAO,MAAM,KAAK,SAAS,QAAQ,KAAK,QAAQ,KAAK,YAAY,KAAK,QAAQ,GAAG,EAAE,QAAQ,KAAK,eAAe,UAAU,KAAK,SAAS,CAAC;AAE3J,SAAO,eAAe;AAAA,IACpB,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,IACb;AAAA,IACA;AAAA,IACA,eAAe;AAAA,IACf,OAAO,KAAK;AAAA,EACd,CAAC;AACH;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/SeoDrawer/build-analysis-input.ts"],"sourcesContent":["import type { ContentNode } from \"../../content/schema/nodes\";\nimport { serialize } from \"../../content/schema/serialize\";\nimport {\n compact,\n heading,\n html,\n image,\n link,\n paragraph,\n richText,\n video,\n} from \"../../content/schema/helpers\";\nimport { createResolveDocs } from \"../../content/resolve/resolve-docs\";\nimport type { AnalysisInput } from \"../../engine/types/analysis\";\nimport type {\n ContentExtractor,\n ContentHelpers,\n ExtractContext,\n ExtractToolkit,\n SeoFieldPaths,\n} from \"../../types/config\";\nimport { buildInput } from \"./buildInput\";\n\nconst helpers: ContentHelpers = { heading, paragraph, link, image, video, html, richText, compact };\n\nexport interface BuildAnalysisInputArgs {\n values: Record<string, unknown>;\n locale: string | { code?: string } | null | undefined;\n payloadLocale: string | undefined;\n apiRoute?: string;\n keyphrase: string;\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n extractor?: ContentExtractor;\n}\n\nexport async function buildAnalysisInput(args: BuildAnalysisInputArgs): Promise<AnalysisInput> {\n const ir = await extractIntermediateRepresentation(args);\n const contentHtml = serialize(ir);\n\n return buildInput({\n values: args.values,\n contentHtml,\n locale: args.locale,\n keyphrase: args.keyphrase,\n fields: args.fields,\n site: args.site,\n });\n}\n\nasync function extractIntermediateRepresentation(\n args: BuildAnalysisInputArgs\n): Promise<ContentNode[]> {\n if (!args.extractor) return [];\n\n const ctx: ExtractContext = {\n locale: args.payloadLocale,\n apiRoute: args.apiRoute,\n };\n\n const toolkit: ExtractToolkit = {\n resolveDocs: createResolveDocs(args.apiRoute, args.payloadLocale),\n helpers,\n };\n\n return await args.extractor(args.values, ctx, toolkit);\n}\n"],"mappings":"AACA,SAAS,iBAAiB;AAC1B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,yBAAyB;AASlC,SAAS,kBAAkB;AAE3B,MAAM,UAA0B,EAAE,SAAS,WAAW,MAAM,OAAO,OAAO,MAAM,UAAU,QAAQ;AAalG,eAAsB,mBAAmB,MAAsD;AAC7F,QAAM,KAAK,MAAM,kCAAkC,IAAI;AACvD,QAAM,cAAc,UAAU,EAAE;AAEhC,SAAO,WAAW;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,WAAW,KAAK;AAAA,IAChB,QAAQ,KAAK;AAAA,IACb,MAAM,KAAK;AAAA,EACb,CAAC;AACH;AAEA,eAAe,kCACb,MACwB;AACxB,MAAI,CAAC,KAAK;AAAW,WAAO,CAAC;AAE7B,QAAM,MAAsB;AAAA,IAC1B,QAAQ,KAAK;AAAA,IACb,UAAU,KAAK;AAAA,EACjB;AAEA,QAAM,UAA0B;AAAA,IAC9B,aAAa,kBAAkB,KAAK,UAAU,KAAK,aAAa;AAAA,IAChE;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,UAAU,KAAK,QAAQ,KAAK,OAAO;AACvD;","names":[]}
@@ -32,7 +32,7 @@ function buildInput({
32
32
  seoTitle: Boolean(fields.seoTitle && valueAt(values, fields.seoTitle)),
33
33
  metaDescription: Boolean(fields.metaDescription),
34
34
  slug: Boolean(fields.slug ?? "slug"),
35
- content: Boolean(fields.content)
35
+ content: contentHtml.trim().length > 0
36
36
  }
37
37
  };
38
38
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/SeoDrawer/buildInput.ts"],"sourcesContent":["import type { AnalysisInput } from \"../../engine/types/analysis\";\nimport type { SeoFieldPaths } from \"../../types/config\";\n\nfunction valueAt(values: Record<string, unknown>, path?: string): string {\n if (!path) return \"\";\n\n const v = path\n .split(\".\")\n .reduce<unknown>(\n (acc, k) =>\n acc && typeof acc === \"object\" ? (acc as Record<string, unknown>)[k] : undefined,\n values\n );\n\n return typeof v === \"string\" ? v : \"\";\n}\n\nfunction normalizeLocale(locale: string | { code?: string } | null | undefined): string {\n const code =\n (typeof locale === \"object\" && locale\n ? locale.code\n : locale != null\n ? String(locale)\n : undefined) ?? \"en\";\n\n return code.includes(\"_\") ? code : `${code}_${code.toUpperCase()}`;\n}\n\nexport interface BuildInputArgs {\n values: Record<string, unknown>;\n contentHtml: string;\n locale: string | { code?: string } | null | undefined;\n keyphrase: string;\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n}\n\nexport function buildInput({\n values,\n contentHtml,\n locale,\n keyphrase,\n fields,\n site,\n}: BuildInputArgs): AnalysisInput {\n const title = valueAt(values, fields.seoTitle) || valueAt(values, \"title\");\n\n return {\n title,\n slug: valueAt(values, fields.slug ?? \"slug\"),\n description: valueAt(values, fields.metaDescription),\n contentHtml,\n keyphrase,\n locale: normalizeLocale(locale),\n site,\n has: {\n seoTitle: Boolean(fields.seoTitle && valueAt(values, fields.seoTitle)),\n metaDescription: Boolean(fields.metaDescription),\n slug: Boolean(fields.slug ?? \"slug\"),\n content: Boolean(fields.content),\n },\n };\n}\n"],"mappings":"AAGA,SAAS,QAAQ,QAAiC,MAAuB;AACvE,MAAI,CAAC;AAAM,WAAO;AAElB,QAAM,IAAI,KACP,MAAM,GAAG,EACT;AAAA,IACC,CAAC,KAAK,MACJ,OAAO,OAAO,QAAQ,WAAY,IAAgC,CAAC,IAAI;AAAA,IACzE;AAAA,EACF;AAEF,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,gBAAgB,QAA+D;AACtF,QAAM,QACH,OAAO,WAAW,YAAY,SAC3B,OAAO,OACP,UAAU,OACR,OAAO,MAAM,IACb,WAAc;AAEtB,SAAO,KAAK,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI,IAAI,KAAK,YAAY,CAAC;AAClE;AAWO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,QAAM,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,KAAK,QAAQ,QAAQ,OAAO;AAEzE,SAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,IAC3C,aAAa,QAAQ,QAAQ,OAAO,eAAe;AAAA,IACnD;AAAA,IACA;AAAA,IACA,QAAQ,gBAAgB,MAAM;AAAA,IAC9B;AAAA,IACA,KAAK;AAAA,MACH,UAAU,QAAQ,OAAO,YAAY,QAAQ,QAAQ,OAAO,QAAQ,CAAC;AAAA,MACrE,iBAAiB,QAAQ,OAAO,eAAe;AAAA,MAC/C,MAAM,QAAQ,OAAO,QAAQ,MAAM;AAAA,MACnC,SAAS,QAAQ,OAAO,OAAO;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/SeoDrawer/buildInput.ts"],"sourcesContent":["import type { AnalysisInput } from \"../../engine/types/analysis\";\nimport type { SeoFieldPaths } from \"../../types/config\";\n\nfunction valueAt(values: Record<string, unknown>, path?: string): string {\n if (!path) return \"\";\n\n const v = path\n .split(\".\")\n .reduce<unknown>(\n (acc, k) =>\n acc && typeof acc === \"object\" ? (acc as Record<string, unknown>)[k] : undefined,\n values\n );\n\n return typeof v === \"string\" ? v : \"\";\n}\n\nfunction normalizeLocale(locale: string | { code?: string } | null | undefined): string {\n const code =\n (typeof locale === \"object\" && locale\n ? locale.code\n : locale != null\n ? String(locale)\n : undefined) ?? \"en\";\n\n return code.includes(\"_\") ? code : `${code}_${code.toUpperCase()}`;\n}\n\nexport interface BuildInputArgs {\n values: Record<string, unknown>;\n contentHtml: string;\n locale: string | { code?: string } | null | undefined;\n keyphrase: string;\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n}\n\nexport function buildInput({\n values,\n contentHtml,\n locale,\n keyphrase,\n fields,\n site,\n}: BuildInputArgs): AnalysisInput {\n const title = valueAt(values, fields.seoTitle) || valueAt(values, \"title\");\n\n return {\n title,\n slug: valueAt(values, fields.slug ?? \"slug\"),\n description: valueAt(values, fields.metaDescription),\n contentHtml,\n keyphrase,\n locale: normalizeLocale(locale),\n site,\n has: {\n seoTitle: Boolean(fields.seoTitle && valueAt(values, fields.seoTitle)),\n metaDescription: Boolean(fields.metaDescription),\n slug: Boolean(fields.slug ?? \"slug\"),\n content: contentHtml.trim().length > 0,\n },\n };\n}\n"],"mappings":"AAGA,SAAS,QAAQ,QAAiC,MAAuB;AACvE,MAAI,CAAC;AAAM,WAAO;AAElB,QAAM,IAAI,KACP,MAAM,GAAG,EACT;AAAA,IACC,CAAC,KAAK,MACJ,OAAO,OAAO,QAAQ,WAAY,IAAgC,CAAC,IAAI;AAAA,IACzE;AAAA,EACF;AAEF,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,gBAAgB,QAA+D;AACtF,QAAM,QACH,OAAO,WAAW,YAAY,SAC3B,OAAO,OACP,UAAU,OACR,OAAO,MAAM,IACb,WAAc;AAEtB,SAAO,KAAK,SAAS,GAAG,IAAI,OAAO,GAAG,IAAI,IAAI,KAAK,YAAY,CAAC;AAClE;AAWO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAkC;AAChC,QAAM,QAAQ,QAAQ,QAAQ,OAAO,QAAQ,KAAK,QAAQ,QAAQ,OAAO;AAEzE,SAAO;AAAA,IACL;AAAA,IACA,MAAM,QAAQ,QAAQ,OAAO,QAAQ,MAAM;AAAA,IAC3C,aAAa,QAAQ,QAAQ,OAAO,eAAe;AAAA,IACnD;AAAA,IACA;AAAA,IACA,QAAQ,gBAAgB,MAAM;AAAA,IAC9B;AAAA,IACA,KAAK;AAAA,MACH,UAAU,QAAQ,OAAO,YAAY,QAAQ,QAAQ,OAAO,QAAQ,CAAC;AAAA,MACrE,iBAAiB,QAAQ,OAAO,eAAe;AAAA,MAC/C,MAAM,QAAQ,OAAO,QAAQ,MAAM;AAAA,MACnC,SAAS,YAAY,KAAK,EAAE,SAAS;AAAA,IACvC;AAAA,EACF;AACF;","names":[]}
@@ -9,16 +9,13 @@ export interface LiveDocArgs {
9
9
  };
10
10
  keyphrase: string;
11
11
  enabled?: boolean;
12
- extractContentPath?: string | null;
13
- resolveDepth: number;
14
- slugPaths: Record<string, string>;
12
+ extractContentPath: string;
15
13
  }
16
14
  export interface UseLiveDocumentResult {
17
15
  signature: string;
18
16
  getInput: (opts?: {
19
17
  live?: boolean;
20
18
  }) => Promise<AnalysisInput>;
21
- invalidateMedia: () => void;
22
19
  }
23
- export declare function useLiveDocument({ collectionSlug, fields, site, keyphrase, enabled, extractContentPath, resolveDepth, slugPaths, }: LiveDocArgs): UseLiveDocumentResult;
20
+ export declare function useLiveDocument({ collectionSlug: _collectionSlug, fields, site, keyphrase, enabled, extractContentPath, }: LiveDocArgs): UseLiveDocumentResult;
24
21
  //# sourceMappingURL=useLiveDocument.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"useLiveDocument.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/useLiveDocument.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAOxD,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAChE,eAAe,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,wBAAgB,eAAe,CAAC,EAC9B,cAAc,EACd,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAc,EACd,kBAAkB,EAClB,YAAY,EACZ,SAAS,GACV,EAAE,WAAW,GAAG,qBAAqB,CA8HrC"}
1
+ {"version":3,"file":"useLiveDocument.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/useLiveDocument.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAMxD,MAAM,WAAW,WAAW;IAC1B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;CACjE;AAED,wBAAgB,eAAe,CAAC,EAC9B,cAAc,EAAE,eAAe,EAC/B,MAAM,EACN,IAAI,EACJ,SAAS,EACT,OAAc,EACd,kBAAkB,GACnB,EAAE,WAAW,GAAG,qBAAqB,CAwErC"}
@@ -3,59 +3,29 @@ import { useAllFormFields, useConfig, useDebounce, useLocale } from "@payloadcms
3
3
  import { reduceFieldsToValues } from "payload/shared";
4
4
  import { useCallback, useMemo, useRef } from "react";
5
5
  import { resolveContentExtractor } from "../../content/registry";
6
- import { createDocResolver } from "../../content/resolve/resolver";
7
6
  import { buildAnalysisInput } from "./build-analysis-input";
8
- const warnedPaths = /* @__PURE__ */ new Set();
7
+ const erroredPaths = /* @__PURE__ */ new Set();
9
8
  const DEBOUNCE_MS = 1e3;
10
9
  function useLiveDocument({
11
- collectionSlug,
10
+ collectionSlug: _collectionSlug,
12
11
  fields,
13
12
  site,
14
13
  keyphrase,
15
14
  enabled = true,
16
- extractContentPath,
17
- resolveDepth,
18
- slugPaths
15
+ extractContentPath
19
16
  }) {
20
17
  const [formFields] = useAllFormFields();
21
18
  const locale = useLocale();
22
- const { config, getEntityConfig } = useConfig();
19
+ const { config } = useConfig();
23
20
  const debouncedFields = useDebounce(formFields, DEBOUNCE_MS);
24
21
  const debouncedKeyphrase = useDebounce(keyphrase, DEBOUNCE_MS);
25
22
  const apiRoute = config.routes.api;
26
- const resolver = useMemo(() => createDocResolver(apiRoute), [apiRoute]);
27
- const getFields = useCallback(
28
- (slug) => {
29
- const entity = getEntityConfig({ collectionSlug: slug });
30
- return entity?.fields ?? [];
31
- },
32
- [getEntityConfig]
33
- );
34
- const hostFields = useMemo(
35
- () => getFields(collectionSlug),
36
- [getFields, collectionSlug]
37
- );
38
- const ctx = useMemo(
39
- () => ({
40
- getFields,
41
- isUploadCollection: (slug) => Boolean(config.collections?.find((c) => c.slug === slug)?.upload),
42
- slugPath: (slug) => slugPaths[slug] ?? "slug",
43
- blocksBySlug: { ...config.blocksMap },
44
- resolved: /* @__PURE__ */ new Map(),
45
- baseUrl: site.baseUrl
46
- }),
47
- [getFields, config, slugPaths, site.baseUrl]
48
- );
49
23
  const values = useMemo(
50
24
  () => enabled ? reduceFieldsToValues(debouncedFields, true) : {},
51
25
  [enabled, debouncedFields]
52
26
  );
53
27
  const signature = useMemo(
54
- () => JSON.stringify({
55
- values,
56
- keyphrase: debouncedKeyphrase,
57
- locale: locale?.code ?? null
58
- }),
28
+ () => JSON.stringify({ values, keyphrase: debouncedKeyphrase, locale: locale?.code ?? null }),
59
29
  [values, debouncedKeyphrase, locale]
60
30
  );
61
31
  const liveRef = useRef({
@@ -67,10 +37,6 @@ function useLiveDocument({
67
37
  fields,
68
38
  site,
69
39
  extractContentPath,
70
- hostFields,
71
- ctx,
72
- resolver,
73
- resolveDepth,
74
40
  apiRoute
75
41
  });
76
42
  liveRef.current = {
@@ -82,28 +48,19 @@ function useLiveDocument({
82
48
  fields,
83
49
  site,
84
50
  extractContentPath,
85
- hostFields,
86
- ctx,
87
- resolver,
88
- resolveDepth,
89
51
  apiRoute
90
52
  };
91
53
  const getInput = useCallback(
92
54
  async ({ live = false } = {}) => {
93
55
  const s = liveRef.current;
94
56
  const inputValues = live ? reduceFieldsToValues(s.formFields, true) : s.values;
95
- const override = (() => {
96
- if (!s.extractContentPath)
97
- return void 0;
98
- const fn = resolveContentExtractor(s.extractContentPath);
99
- if (!fn && !warnedPaths.has(s.extractContentPath)) {
100
- warnedPaths.add(s.extractContentPath);
101
- console.warn(
102
- `[payload-plugin-seo] extractContentPath "${s.extractContentPath}" is not registered; falling back to the built-in extractor. Call registerContentExtractors from "@focus-reactive/payload-plugin-seo/content" in an admin-mounted client module.`
103
- );
104
- }
105
- return fn;
106
- })();
57
+ const extractor = resolveContentExtractor(s.extractContentPath);
58
+ if (!extractor && !erroredPaths.has(s.extractContentPath)) {
59
+ erroredPaths.add(s.extractContentPath);
60
+ console.error(
61
+ `[payload-plugin-seo] extractContentPath "${s.extractContentPath}" is not registered; content analysis will be empty. Call registerContentExtractors from "@focus-reactive/payload-plugin-seo/content" in an admin-mounted client module.`
62
+ );
63
+ }
107
64
  return buildAnalysisInput({
108
65
  values: inputValues,
109
66
  locale: s.locale,
@@ -112,17 +69,12 @@ function useLiveDocument({
112
69
  keyphrase: live ? s.keyphrase : s.debouncedKeyphrase,
113
70
  fields: s.fields,
114
71
  site: s.site,
115
- hostFields: s.hostFields,
116
- ctx: s.ctx,
117
- resolver: s.resolver,
118
- resolveDepth: s.resolveDepth,
119
- override
72
+ extractor
120
73
  });
121
74
  },
122
75
  []
123
76
  );
124
- const invalidateMedia = useCallback(() => liveRef.current.resolver.invalidate(), []);
125
- return { signature, getInput, invalidateMedia };
77
+ return { signature, getInput };
126
78
  }
127
79
  export {
128
80
  useLiveDocument
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/SeoDrawer/useLiveDocument.ts"],"sourcesContent":["\"use client\";\n\nimport { useAllFormFields, useConfig, useDebounce, useLocale } from \"@payloadcms/ui\";\nimport type { ClientField } from \"payload\";\nimport { reduceFieldsToValues } from \"payload/shared\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { resolveContentExtractor } from \"../../content/registry\";\nimport { createDocResolver } from \"../../content/resolve/resolver\";\nimport type { ExtractContext } from \"../../content/extract/context\";\nimport type { AnalysisInput } from \"../../engine/types/analysis\";\nimport type { SeoFieldPaths } from \"../../types/config\";\nimport { buildAnalysisInput } from \"./build-analysis-input\";\n\nconst warnedPaths = new Set<string>();\n\nconst DEBOUNCE_MS = 1000;\n\nexport interface LiveDocArgs {\n collectionSlug: string;\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n keyphrase: string;\n enabled?: boolean;\n extractContentPath?: string | null;\n resolveDepth: number;\n slugPaths: Record<string, string>;\n}\n\nexport interface UseLiveDocumentResult {\n signature: string;\n getInput: (opts?: { live?: boolean }) => Promise<AnalysisInput>;\n invalidateMedia: () => void;\n}\n\nexport function useLiveDocument({\n collectionSlug,\n fields,\n site,\n keyphrase,\n enabled = true,\n extractContentPath,\n resolveDepth,\n slugPaths,\n}: LiveDocArgs): UseLiveDocumentResult {\n const [formFields] = useAllFormFields();\n const locale = useLocale();\n const { config, getEntityConfig } = useConfig();\n\n const debouncedFields = useDebounce(formFields, DEBOUNCE_MS);\n const debouncedKeyphrase = useDebounce(keyphrase, DEBOUNCE_MS);\n\n const apiRoute = config.routes.api;\n const resolver = useMemo(() => createDocResolver(apiRoute), [apiRoute]);\n\n const getFields = useCallback(\n (slug: string): ClientField[] => {\n const entity = getEntityConfig({ collectionSlug: slug as never }) as\n | { fields?: ClientField[] }\n | undefined;\n return entity?.fields ?? [];\n },\n [getEntityConfig]\n );\n\n const hostFields = useMemo<ClientField[]>(\n () => getFields(collectionSlug),\n [getFields, collectionSlug]\n );\n\n const ctx = useMemo<ExtractContext>(\n () => ({\n getFields,\n isUploadCollection: (slug) =>\n Boolean(config.collections?.find((c) => c.slug === slug)?.upload),\n slugPath: (slug) => slugPaths[slug] ?? \"slug\",\n blocksBySlug: { ...config.blocksMap },\n resolved: new Map(),\n baseUrl: site.baseUrl,\n }),\n [getFields, config, slugPaths, site.baseUrl]\n );\n\n const values = useMemo<Record<string, unknown>>(\n () => (enabled ? (reduceFieldsToValues(debouncedFields, true) as Record<string, unknown>) : {}),\n [enabled, debouncedFields]\n );\n\n const signature = useMemo(\n () =>\n JSON.stringify({\n values,\n keyphrase: debouncedKeyphrase,\n locale: locale?.code ?? null,\n }),\n [values, debouncedKeyphrase, locale]\n );\n\n const liveRef = useRef({\n formFields,\n values,\n keyphrase,\n debouncedKeyphrase,\n locale,\n fields,\n site,\n extractContentPath,\n hostFields,\n ctx,\n resolver,\n resolveDepth,\n apiRoute,\n });\n liveRef.current = {\n formFields,\n values,\n keyphrase,\n debouncedKeyphrase,\n locale,\n fields,\n site,\n extractContentPath,\n hostFields,\n ctx,\n resolver,\n resolveDepth,\n apiRoute,\n };\n\n const getInput = useCallback(\n async ({ live = false }: { live?: boolean } = {}): Promise<AnalysisInput> => {\n const s = liveRef.current;\n const inputValues = live\n ? (reduceFieldsToValues(s.formFields, true) as Record<string, unknown>)\n : s.values;\n\n const override = (() => {\n if (!s.extractContentPath) return undefined;\n\n const fn = resolveContentExtractor(s.extractContentPath);\n if (!fn && !warnedPaths.has(s.extractContentPath)) {\n warnedPaths.add(s.extractContentPath);\n console.warn(\n `[payload-plugin-seo] extractContentPath \"${s.extractContentPath}\" is not registered; falling back to the built-in extractor. Call registerContentExtractors from \"@focus-reactive/payload-plugin-seo/content\" in an admin-mounted client module.`\n );\n }\n return fn;\n })();\n\n return buildAnalysisInput({\n values: inputValues,\n locale: s.locale,\n payloadLocale: s.locale?.code,\n apiRoute: s.apiRoute,\n keyphrase: live ? s.keyphrase : s.debouncedKeyphrase,\n fields: s.fields,\n site: s.site,\n hostFields: s.hostFields,\n ctx: s.ctx,\n resolver: s.resolver,\n resolveDepth: s.resolveDepth,\n override,\n });\n },\n []\n );\n\n const invalidateMedia = useCallback(() => liveRef.current.resolver.invalidate(), []);\n\n return { signature, getInput, invalidateMedia };\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../../src/components/SeoDrawer/useLiveDocument.ts"],"sourcesContent":["\"use client\";\n\nimport { useAllFormFields, useConfig, useDebounce, useLocale } from \"@payloadcms/ui\";\nimport { reduceFieldsToValues } from \"payload/shared\";\nimport { useCallback, useMemo, useRef } from \"react\";\nimport { resolveContentExtractor } from \"../../content/registry\";\nimport type { AnalysisInput } from \"../../engine/types/analysis\";\nimport type { SeoFieldPaths } from \"../../types/config\";\nimport { buildAnalysisInput } from \"./build-analysis-input\";\n\nconst erroredPaths = new Set<string>();\nconst DEBOUNCE_MS = 1000;\n\nexport interface LiveDocArgs {\n collectionSlug: string;\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n keyphrase: string;\n enabled?: boolean;\n extractContentPath: string;\n}\n\nexport interface UseLiveDocumentResult {\n signature: string;\n getInput: (opts?: { live?: boolean }) => Promise<AnalysisInput>;\n}\n\nexport function useLiveDocument({\n collectionSlug: _collectionSlug,\n fields,\n site,\n keyphrase,\n enabled = true,\n extractContentPath,\n}: LiveDocArgs): UseLiveDocumentResult {\n const [formFields] = useAllFormFields();\n const locale = useLocale();\n const { config } = useConfig();\n\n const debouncedFields = useDebounce(formFields, DEBOUNCE_MS);\n const debouncedKeyphrase = useDebounce(keyphrase, DEBOUNCE_MS);\n const apiRoute = config.routes.api;\n\n const values = useMemo<Record<string, unknown>>(\n () => (enabled ? (reduceFieldsToValues(debouncedFields, true) as Record<string, unknown>) : {}),\n [enabled, debouncedFields]\n );\n\n const signature = useMemo(\n () => JSON.stringify({ values, keyphrase: debouncedKeyphrase, locale: locale?.code ?? null }),\n [values, debouncedKeyphrase, locale]\n );\n\n const liveRef = useRef({\n formFields,\n values,\n keyphrase,\n debouncedKeyphrase,\n locale,\n fields,\n site,\n extractContentPath,\n apiRoute,\n });\n liveRef.current = {\n formFields,\n values,\n keyphrase,\n debouncedKeyphrase,\n locale,\n fields,\n site,\n extractContentPath,\n apiRoute,\n };\n\n const getInput = useCallback(\n async ({ live = false }: { live?: boolean } = {}): Promise<AnalysisInput> => {\n const s = liveRef.current;\n const inputValues = live\n ? (reduceFieldsToValues(s.formFields, true) as Record<string, unknown>)\n : s.values;\n\n const extractor = resolveContentExtractor(s.extractContentPath);\n if (!extractor && !erroredPaths.has(s.extractContentPath)) {\n erroredPaths.add(s.extractContentPath);\n console.error(\n `[payload-plugin-seo] extractContentPath \"${s.extractContentPath}\" is not registered; content analysis will be empty. Call registerContentExtractors from \"@focus-reactive/payload-plugin-seo/content\" in an admin-mounted client module.`\n );\n }\n\n return buildAnalysisInput({\n values: inputValues,\n locale: s.locale,\n payloadLocale: s.locale?.code,\n apiRoute: s.apiRoute,\n keyphrase: live ? s.keyphrase : s.debouncedKeyphrase,\n fields: s.fields,\n site: s.site,\n extractor,\n });\n },\n []\n );\n\n return { signature, getInput };\n}\n"],"mappings":";AAEA,SAAS,kBAAkB,WAAW,aAAa,iBAAiB;AACpE,SAAS,4BAA4B;AACrC,SAAS,aAAa,SAAS,cAAc;AAC7C,SAAS,+BAA+B;AAGxC,SAAS,0BAA0B;AAEnC,MAAM,eAAe,oBAAI,IAAY;AACrC,MAAM,cAAc;AAgBb,SAAS,gBAAgB;AAAA,EAC9B,gBAAgB;AAAA,EAChB;AAAA,EACA;AAAA,EACA;AAAA,EACA,UAAU;AAAA,EACV;AACF,GAAuC;AACrC,QAAM,CAAC,UAAU,IAAI,iBAAiB;AACtC,QAAM,SAAS,UAAU;AACzB,QAAM,EAAE,OAAO,IAAI,UAAU;AAE7B,QAAM,kBAAkB,YAAY,YAAY,WAAW;AAC3D,QAAM,qBAAqB,YAAY,WAAW,WAAW;AAC7D,QAAM,WAAW,OAAO,OAAO;AAE/B,QAAM,SAAS;AAAA,IACb,MAAO,UAAW,qBAAqB,iBAAiB,IAAI,IAAgC,CAAC;AAAA,IAC7F,CAAC,SAAS,eAAe;AAAA,EAC3B;AAEA,QAAM,YAAY;AAAA,IAChB,MAAM,KAAK,UAAU,EAAE,QAAQ,WAAW,oBAAoB,QAAQ,QAAQ,QAAQ,KAAK,CAAC;AAAA,IAC5F,CAAC,QAAQ,oBAAoB,MAAM;AAAA,EACrC;AAEA,QAAM,UAAU,OAAO;AAAA,IACrB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,UAAQ,UAAU;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAW;AAAA,IACf,OAAO,EAAE,OAAO,MAAM,IAAwB,CAAC,MAA8B;AAC3E,YAAM,IAAI,QAAQ;AAClB,YAAM,cAAc,OACf,qBAAqB,EAAE,YAAY,IAAI,IACxC,EAAE;AAEN,YAAM,YAAY,wBAAwB,EAAE,kBAAkB;AAC9D,UAAI,CAAC,aAAa,CAAC,aAAa,IAAI,EAAE,kBAAkB,GAAG;AACzD,qBAAa,IAAI,EAAE,kBAAkB;AACrC,gBAAQ;AAAA,UACN,4CAA4C,EAAE,kBAAkB;AAAA,QAClE;AAAA,MACF;AAEA,aAAO,mBAAmB;AAAA,QACxB,QAAQ;AAAA,QACR,QAAQ,EAAE;AAAA,QACV,eAAe,EAAE,QAAQ;AAAA,QACzB,UAAU,EAAE;AAAA,QACZ,WAAW,OAAO,EAAE,YAAY,EAAE;AAAA,QAClC,QAAQ,EAAE;AAAA,QACV,MAAM,EAAE;AAAA,QACR;AAAA,MACF,CAAC;AAAA,IACH;AAAA,IACA,CAAC;AAAA,EACH;AAEA,SAAO,EAAE,WAAW,SAAS;AAC/B;","names":[]}
@@ -0,0 +1,10 @@
1
+ import type { Measurement } from "../../measure/measure";
2
+ interface MeterProps {
3
+ measurement: Measurement;
4
+ hasText: boolean;
5
+ loading: boolean;
6
+ kindLabel: string;
7
+ }
8
+ export declare function Meter({ measurement, hasText, loading, kindLabel }: MeterProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=Meter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Meter.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/Meter.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAoBzD,UAAU,UAAU;IAClB,WAAW,EAAE,WAAW,CAAC;IACzB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wBAAgB,KAAK,CAAC,EAAE,WAAW,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,UAAU,2CA0C7E"}
@@ -0,0 +1,59 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Pill } from "../../ui/Pill";
4
+ import { cn } from "../../utils/style";
5
+ const STATUS_TO_VARIANT = {
6
+ good: "good",
7
+ short: "warn",
8
+ long: "bad"
9
+ };
10
+ const STATUS_LABEL = {
11
+ good: "Good",
12
+ short: "Too short",
13
+ long: "Too long"
14
+ };
15
+ const FILL_CLASS = {
16
+ good: "bg-seo-good",
17
+ short: "bg-seo-warn",
18
+ long: "bg-seo-bad"
19
+ };
20
+ function Meter({ measurement, hasText, loading, kindLabel }) {
21
+ const { value, min, max, status, unit } = measurement;
22
+ const readout = unit === "px" ? `${value} / ${max} px` : `${value} / ${max}`;
23
+ const pct = (n) => `${Math.min(100, Math.max(0, n / max * 100))}%`;
24
+ return /* @__PURE__ */ jsxs("div", { className: "mt-[11px] flex items-center gap-[12px]", children: [
25
+ /* @__PURE__ */ jsxs("div", { className: "relative h-[6px] flex-1 overflow-hidden rounded-full bg-neutral-100", children: [
26
+ /* @__PURE__ */ jsx(
27
+ "span",
28
+ {
29
+ className: "absolute inset-y-0 border-x border-seo-good/40 bg-seo-good/15",
30
+ style: {
31
+ left: pct(min),
32
+ right: `calc(100% - ${pct(max)})`
33
+ }
34
+ }
35
+ ),
36
+ hasText && !loading && /* @__PURE__ */ jsx(
37
+ "span",
38
+ {
39
+ className: cn("absolute inset-y-0 left-0 rounded-full", FILL_CLASS[status]),
40
+ style: { width: pct(value) }
41
+ }
42
+ ),
43
+ loading && /* @__PURE__ */ jsx("span", { className: "absolute inset-0 animate-pulse bg-neutral-200" })
44
+ ] }),
45
+ /* @__PURE__ */ jsx(
46
+ "span",
47
+ {
48
+ className: "whitespace-nowrap text-[12px] tabular-nums text-neutral-500",
49
+ style: { fontFamily: "ui-monospace, monospace" },
50
+ children: loading ? `\u2014 / ${max}` : readout
51
+ }
52
+ ),
53
+ hasText && !loading ? /* @__PURE__ */ jsx(Pill, { variant: STATUS_TO_VARIANT[status], children: STATUS_LABEL[status] }) : /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: loading ? "Working\u2026" : `No ${kindLabel}` })
54
+ ] });
55
+ }
56
+ export {
57
+ Meter
58
+ };
59
+ //# sourceMappingURL=Meter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/Meter.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Measurement } from \"../../measure/measure\";\nimport { Pill } from \"../../ui/Pill\";\nimport { cn } from \"../../utils/style\";\n\nconst STATUS_TO_VARIANT = {\n good: \"good\",\n short: \"warn\",\n long: \"bad\",\n} as const;\nconst STATUS_LABEL = {\n good: \"Good\",\n short: \"Too short\",\n long: \"Too long\",\n} as const;\nconst FILL_CLASS = {\n good: \"bg-seo-good\",\n short: \"bg-seo-warn\",\n long: \"bg-seo-bad\",\n} as const;\n\ninterface MeterProps {\n measurement: Measurement;\n hasText: boolean;\n loading: boolean;\n kindLabel: string;\n}\n\nexport function Meter({ measurement, hasText, loading, kindLabel }: MeterProps) {\n const { value, min, max, status, unit } = measurement;\n\n const readout = unit === \"px\" ? `${value} / ${max} px` : `${value} / ${max}`;\n\n const pct = (n: number) => `${Math.min(100, Math.max(0, (n / max) * 100))}%`;\n\n return (\n <div className=\"mt-[11px] flex items-center gap-[12px]\">\n <div className=\"relative h-[6px] flex-1 overflow-hidden rounded-full bg-neutral-100\">\n <span\n className=\"absolute inset-y-0 border-x border-seo-good/40 bg-seo-good/15\"\n style={{\n left: pct(min),\n right: `calc(100% - ${pct(max)})`,\n }}\n />\n\n {hasText && !loading && (\n <span\n className={cn(\"absolute inset-y-0 left-0 rounded-full\", FILL_CLASS[status])}\n style={{ width: pct(value) }}\n />\n )}\n\n {loading && <span className=\"absolute inset-0 animate-pulse bg-neutral-200\" />}\n </div>\n\n <span\n className=\"whitespace-nowrap text-[12px] tabular-nums text-neutral-500\"\n style={{ fontFamily: \"ui-monospace, monospace\" }}\n >\n {loading ? `— / ${max}` : readout}\n </span>\n\n {hasText && !loading ? (\n <Pill variant={STATUS_TO_VARIANT[status]}>{STATUS_LABEL[status]}</Pill>\n ) : (\n <Pill variant=\"neutral\">{loading ? \"Working…\" : `No ${kindLabel}`}</Pill>\n )}\n </div>\n );\n}\n"],"mappings":";AAsCM,SACE,KADF;AAnCN,SAAS,YAAY;AACrB,SAAS,UAAU;AAEnB,MAAM,oBAAoB;AAAA,EACxB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AACA,MAAM,eAAe;AAAA,EACnB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AACA,MAAM,aAAa;AAAA,EACjB,MAAM;AAAA,EACN,OAAO;AAAA,EACP,MAAM;AACR;AASO,SAAS,MAAM,EAAE,aAAa,SAAS,SAAS,UAAU,GAAe;AAC9E,QAAM,EAAE,OAAO,KAAK,KAAK,QAAQ,KAAK,IAAI;AAE1C,QAAM,UAAU,SAAS,OAAO,GAAG,KAAK,MAAM,GAAG,QAAQ,GAAG,KAAK,MAAM,GAAG;AAE1E,QAAM,MAAM,CAAC,MAAc,GAAG,KAAK,IAAI,KAAK,KAAK,IAAI,GAAI,IAAI,MAAO,GAAG,CAAC,CAAC;AAEzE,SACE,qBAAC,SAAI,WAAU,0CACb;AAAA,yBAAC,SAAI,WAAU,uEACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO;AAAA,YACL,MAAM,IAAI,GAAG;AAAA,YACb,OAAO,eAAe,IAAI,GAAG,CAAC;AAAA,UAChC;AAAA;AAAA,MACF;AAAA,MAEC,WAAW,CAAC,WACX;AAAA,QAAC;AAAA;AAAA,UACC,WAAW,GAAG,0CAA0C,WAAW,MAAM,CAAC;AAAA,UAC1E,OAAO,EAAE,OAAO,IAAI,KAAK,EAAE;AAAA;AAAA,MAC7B;AAAA,MAGD,WAAW,oBAAC,UAAK,WAAU,iDAAgD;AAAA,OAC9E;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAU;AAAA,QACV,OAAO,EAAE,YAAY,0BAA0B;AAAA,QAE9C,oBAAU,YAAO,GAAG,KAAK;AAAA;AAAA,IAC5B;AAAA,IAEC,WAAW,CAAC,UACX,oBAAC,QAAK,SAAS,kBAAkB,MAAM,GAAI,uBAAa,MAAM,GAAE,IAEhE,oBAAC,QAAK,SAAQ,WAAW,oBAAU,kBAAa,MAAM,SAAS,IAAG;AAAA,KAEtE;AAEJ;","names":[]}
@@ -0,0 +1,5 @@
1
+ export declare const GenerateIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
2
+ export declare const AutoPublishIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
3
+ export declare const ErrorIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
4
+ export declare const SpinnerIcon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
5
+ //# sourceMappingURL=icons.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/icons.tsx"],"names":[],"mappings":"AAEA,eAAO,MAAM,YAAY,2IAAW,CAAC;AACrC,eAAO,MAAM,eAAe,2IAAe,CAAC;AAC5C,eAAO,MAAM,SAAS,2IAAc,CAAC;AACrC,eAAO,MAAM,WAAW,2IAAU,CAAC"}
@@ -0,0 +1,12 @@
1
+ import { AlertCircle, Loader2, Sparkles, WandSparkles } from "lucide-react";
2
+ const GenerateIcon = Sparkles;
3
+ const AutoPublishIcon = WandSparkles;
4
+ const ErrorIcon = AlertCircle;
5
+ const SpinnerIcon = Loader2;
6
+ export {
7
+ AutoPublishIcon,
8
+ ErrorIcon,
9
+ GenerateIcon,
10
+ SpinnerIcon
11
+ };
12
+ //# sourceMappingURL=icons.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/icons.tsx"],"sourcesContent":["import { AlertCircle, Loader2, Sparkles, WandSparkles } from \"lucide-react\";\n\nexport const GenerateIcon = Sparkles;\nexport const AutoPublishIcon = WandSparkles;\nexport const ErrorIcon = AlertCircle;\nexport const SpinnerIcon = Loader2;\n"],"mappings":"AAAA,SAAS,aAAa,SAAS,UAAU,oBAAoB;AAEtD,MAAM,eAAe;AACrB,MAAM,kBAAkB;AACxB,MAAM,YAAY;AAClB,MAAM,cAAc;","names":[]}
@@ -0,0 +1,20 @@
1
+ import type { RangeOverride } from "../../measure/measure";
2
+ import type { SeoFieldKind } from "../../server/generate/prompts";
3
+ interface SeoFieldProps {
4
+ path: string;
5
+ field?: {
6
+ label?: Record<string, string> | string;
7
+ required?: boolean;
8
+ admin?: {
9
+ description?: Record<string, string> | string;
10
+ };
11
+ };
12
+ readOnly?: boolean;
13
+ kind: SeoFieldKind;
14
+ showButton?: boolean;
15
+ generateOnPublish?: boolean;
16
+ range?: RangeOverride;
17
+ }
18
+ export declare function SeoField(props: SeoFieldProps): import("react/jsx-runtime").JSX.Element;
19
+ export default SeoField;
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/index.tsx"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAOlE,UAAU,aAAa;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE;QACN,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC;QACxC,QAAQ,CAAC,EAAE,OAAO,CAAC;QACnB,KAAK,CAAC,EAAE;YAAE,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAA;SAAE,CAAC;KAC3D,CAAC;IACF,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,IAAI,EAAE,YAAY,CAAC;IACnB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAaD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,aAAa,2CAkH5C;AAED,eAAe,QAAQ,CAAC"}
@@ -0,0 +1,118 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { FieldLabel, TextareaInput, TextInput, useField } from "@payloadcms/ui";
4
+ import { getSeoClientConfig } from "../../client-config/registry";
5
+ import { measureDescription, measureTitle } from "../../measure/measure";
6
+ import { Tooltip, TooltipText, TooltipTitle } from "../../ui/Tooltip";
7
+ import { cn } from "../../utils/style";
8
+ import { AutoPublishIcon, ErrorIcon, GenerateIcon, SpinnerIcon } from "./icons";
9
+ import { Meter } from "./Meter";
10
+ import { useGenerate } from "./useGenerate";
11
+ const GEN_BTN_CLASS = "inline-flex items-center gap-[6px] rounded-rs border border-neutral-200 bg-transparent px-[9px] py-[4px] text-[12px] font-semibold text-neutral-800 transition-colors hover:border-neutral-300 hover:bg-neutral-100 disabled:cursor-default disabled:text-neutral-400 cursor-pointer";
12
+ function SeoField(props) {
13
+ const { path, field, readOnly, kind, showButton, generateOnPublish, range } = props;
14
+ const fieldState = useField({ path });
15
+ const { value, setValue, showError } = fieldState;
16
+ const injected = fieldState.customComponents ?? {};
17
+ const text = value ?? "";
18
+ const measurement = kind === "title" ? measureTitle(text, range) : measureDescription(text, range);
19
+ const kindLabel = kind === "title" ? "title" : "description";
20
+ const { generate, status, error } = useGenerate({ kind, measurement, setValue });
21
+ const enabled = getSeoClientConfig().enabled;
22
+ const loading = status === "loading";
23
+ const labelRow = /* @__PURE__ */ jsxs("div", { className: "flex min-h-[26px] items-center justify-between gap-[12px] pb-[5px] [&_.field-label]:pb-0!", children: [
24
+ injected.Label ?? /* @__PURE__ */ jsx(FieldLabel, { label: field?.label, path, required: field?.required }),
25
+ /* @__PURE__ */ jsxs("span", { className: "inline-flex items-center gap-[8px]", children: [
26
+ status === "error" && /* @__PURE__ */ jsx(
27
+ Tooltip,
28
+ {
29
+ align: "end",
30
+ content: /* @__PURE__ */ jsxs(Fragment, { children: [
31
+ /* @__PURE__ */ jsx(TooltipTitle, { children: "Generation failed" }),
32
+ /* @__PURE__ */ jsx(TooltipText, { children: error ?? "Your text is unchanged \u2014 press Generate to try again." })
33
+ ] }),
34
+ side: "bottom",
35
+ children: /* @__PURE__ */ jsx(ErrorIcon, { "aria-label": "Generation failed", className: "size-[14px] text-seo-bad" })
36
+ }
37
+ ),
38
+ generateOnPublish && enabled && /* @__PURE__ */ jsx(
39
+ Tooltip,
40
+ {
41
+ align: "end",
42
+ content: /* @__PURE__ */ jsxs(Fragment, { children: [
43
+ /* @__PURE__ */ jsx(TooltipTitle, { children: "Generated automatically" }),
44
+ /* @__PURE__ */ jsxs(TooltipText, { children: [
45
+ "Left empty, this ",
46
+ kindLabel,
47
+ " is generated from the page content when you publish. Type a value to keep your own."
48
+ ] })
49
+ ] }),
50
+ side: "bottom",
51
+ children: /* @__PURE__ */ jsx(
52
+ AutoPublishIcon,
53
+ {
54
+ "aria-label": "Generated on publish",
55
+ className: "size-[14px] text-neutral-500"
56
+ }
57
+ )
58
+ }
59
+ ),
60
+ showButton && enabled && /* @__PURE__ */ jsxs(
61
+ "button",
62
+ {
63
+ className: GEN_BTN_CLASS,
64
+ disabled: loading || readOnly,
65
+ onClick: generate,
66
+ type: "button",
67
+ children: [
68
+ loading ? /* @__PURE__ */ jsx(SpinnerIcon, { className: "size-[14px] animate-spin" }) : /* @__PURE__ */ jsx(GenerateIcon, { className: "size-[14px]" }),
69
+ loading ? "Generating\u2026" : "Generate"
70
+ ]
71
+ }
72
+ )
73
+ ] })
74
+ ] });
75
+ const commonInputProps = {
76
+ AfterInput: injected.AfterInput,
77
+ BeforeInput: injected.BeforeInput,
78
+ Description: injected.Description,
79
+ Error: injected.Error,
80
+ Label: labelRow,
81
+ description: injected.Description ? void 0 : field?.admin?.description,
82
+ path,
83
+ readOnly: loading || readOnly,
84
+ showError,
85
+ value: text
86
+ };
87
+ return /* @__PURE__ */ jsxs("div", { className: cn("seo-generated-field", showError && "error"), children: [
88
+ kind === "description" ? /* @__PURE__ */ jsx(
89
+ TextareaInput,
90
+ {
91
+ ...commonInputProps,
92
+ onChange: (e) => setValue(e.target.value),
93
+ rows: 3
94
+ }
95
+ ) : /* @__PURE__ */ jsx(
96
+ TextInput,
97
+ {
98
+ ...commonInputProps,
99
+ onChange: (e) => setValue(e.target.value)
100
+ }
101
+ ),
102
+ /* @__PURE__ */ jsx(
103
+ Meter,
104
+ {
105
+ hasText: text.trim().length > 0,
106
+ kindLabel,
107
+ loading,
108
+ measurement
109
+ }
110
+ )
111
+ ] });
112
+ }
113
+ var SeoField_default = SeoField;
114
+ export {
115
+ SeoField,
116
+ SeoField_default as default
117
+ };
118
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { FieldLabel, TextareaInput, TextInput, useField } from \"@payloadcms/ui\";\nimport type { ChangeEvent, ReactNode } from \"react\";\nimport { getSeoClientConfig } from \"../../client-config/registry\";\nimport { measureDescription, measureTitle } from \"../../measure/measure\";\nimport type { RangeOverride } from \"../../measure/measure\";\nimport type { SeoFieldKind } from \"../../server/generate/prompts\";\nimport { Tooltip, TooltipText, TooltipTitle } from \"../../ui/Tooltip\";\nimport { cn } from \"../../utils/style\";\nimport { AutoPublishIcon, ErrorIcon, GenerateIcon, SpinnerIcon } from \"./icons\";\nimport { Meter } from \"./Meter\";\nimport { useGenerate } from \"./useGenerate\";\n\ninterface SeoFieldProps {\n path: string;\n field?: {\n label?: Record<string, string> | string;\n required?: boolean;\n admin?: { description?: Record<string, string> | string };\n };\n readOnly?: boolean;\n kind: SeoFieldKind;\n showButton?: boolean;\n generateOnPublish?: boolean;\n range?: RangeOverride;\n}\n\nconst GEN_BTN_CLASS =\n \"inline-flex items-center gap-[6px] rounded-rs border border-neutral-200 bg-transparent px-[9px] py-[4px] text-[12px] font-semibold text-neutral-800 transition-colors hover:border-neutral-300 hover:bg-neutral-100 disabled:cursor-default disabled:text-neutral-400 cursor-pointer\";\n\ninterface InjectedComponents {\n Label?: ReactNode;\n Description?: ReactNode;\n BeforeInput?: ReactNode;\n AfterInput?: ReactNode;\n Error?: ReactNode;\n}\n\nexport function SeoField(props: SeoFieldProps) {\n const { path, field, readOnly, kind, showButton, generateOnPublish, range } = props;\n const fieldState = useField<string>({ path });\n const { value, setValue, showError } = fieldState;\n const injected: InjectedComponents =\n (fieldState as { customComponents?: InjectedComponents }).customComponents ?? {};\n const text = value ?? \"\";\n const measurement =\n kind === \"title\" ? measureTitle(text, range) : measureDescription(text, range);\n const kindLabel = kind === \"title\" ? \"title\" : \"description\";\n\n const { generate, status, error } = useGenerate({ kind, measurement, setValue });\n const enabled = getSeoClientConfig().enabled;\n const loading = status === \"loading\";\n\n const labelRow = (\n <div className=\"flex min-h-[26px] items-center justify-between gap-[12px] pb-[5px] [&_.field-label]:pb-0!\">\n {injected.Label ?? <FieldLabel label={field?.label} path={path} required={field?.required} />}\n\n <span className=\"inline-flex items-center gap-[8px]\">\n {status === \"error\" && (\n <Tooltip\n align=\"end\"\n content={\n <>\n <TooltipTitle>Generation failed</TooltipTitle>\n <TooltipText>\n {error ?? \"Your text is unchanged — press Generate to try again.\"}\n </TooltipText>\n </>\n }\n side=\"bottom\"\n >\n <ErrorIcon aria-label=\"Generation failed\" className=\"size-[14px] text-seo-bad\" />\n </Tooltip>\n )}\n\n {generateOnPublish && enabled && (\n <Tooltip\n align=\"end\"\n content={\n <>\n <TooltipTitle>Generated automatically</TooltipTitle>\n <TooltipText>\n Left empty, this {kindLabel} is generated from the page content when you publish.\n Type a value to keep your own.\n </TooltipText>\n </>\n }\n side=\"bottom\"\n >\n <AutoPublishIcon\n aria-label=\"Generated on publish\"\n className=\"size-[14px] text-neutral-500\"\n />\n </Tooltip>\n )}\n\n {showButton && enabled && (\n <button\n className={GEN_BTN_CLASS}\n disabled={loading || readOnly}\n onClick={generate}\n type=\"button\"\n >\n {loading ? (\n <SpinnerIcon className=\"size-[14px] animate-spin\" />\n ) : (\n <GenerateIcon className=\"size-[14px]\" />\n )}\n\n {loading ? \"Generating…\" : \"Generate\"}\n </button>\n )}\n </span>\n </div>\n );\n\n const commonInputProps = {\n AfterInput: injected.AfterInput,\n BeforeInput: injected.BeforeInput,\n Description: injected.Description,\n Error: injected.Error,\n Label: labelRow,\n description: injected.Description ? undefined : field?.admin?.description,\n path,\n readOnly: loading || readOnly,\n showError,\n value: text,\n };\n\n return (\n <div className={cn(\"seo-generated-field\", showError && \"error\")}>\n {kind === \"description\" ? (\n <TextareaInput\n {...commonInputProps}\n onChange={(e: ChangeEvent<HTMLTextAreaElement>) => setValue(e.target.value)}\n rows={3}\n />\n ) : (\n <TextInput\n {...commonInputProps}\n onChange={(e: ChangeEvent<HTMLInputElement>) => setValue(e.target.value)}\n />\n )}\n\n <Meter\n hasText={text.trim().length > 0}\n kindLabel={kindLabel}\n loading={loading}\n measurement={measurement}\n />\n </div>\n );\n}\n\nexport default SeoField;\n"],"mappings":"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","names":[]}
@@ -0,0 +1,15 @@
1
+ import type { Measurement } from "../../measure/measure";
2
+ import type { SeoFieldKind } from "../../server/generate/prompts";
3
+ export type GenerateStatus = "idle" | "loading" | "error";
4
+ interface UseGenerateProps {
5
+ kind: SeoFieldKind;
6
+ measurement: Measurement;
7
+ setValue: (v: string) => void;
8
+ }
9
+ export declare function useGenerate({ kind, measurement, setValue }: UseGenerateProps): {
10
+ generate: () => Promise<void>;
11
+ status: GenerateStatus;
12
+ error: string | null;
13
+ };
14
+ export {};
15
+ //# sourceMappingURL=useGenerate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useGenerate.d.ts","sourceRoot":"","sources":["../../../src/components/SeoField/useGenerate.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACzD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAalE,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC;AAE1D,UAAU,gBAAgB;IACxB,IAAI,EAAE,YAAY,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;IACzB,QAAQ,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,gBAAgB;;;;EA8E5E"}