@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
@@ -0,0 +1,102 @@
1
+ "use client";
2
+ import { useAllFormFields, useConfig, useDocumentInfo, useLocale } from "@payloadcms/ui";
3
+ import { reduceFieldsToValues } from "payload/shared";
4
+ import { useCallback, useRef, useState } from "react";
5
+ import { getSeoClientConfig } from "../../client-config/registry";
6
+ import { GENERATE_ENDPOINT_PATH } from "../../constants/generation";
7
+ import { createResolveDocs } from "../../content/resolve/resolve-docs";
8
+ import { resolveContentExtractor } from "../../content/registry";
9
+ import {
10
+ compact,
11
+ heading,
12
+ html,
13
+ image,
14
+ link,
15
+ paragraph,
16
+ richText,
17
+ video
18
+ } from "../../content/schema/helpers";
19
+ import { serialize } from "../../content/schema/serialize";
20
+ const helpers = {
21
+ heading,
22
+ paragraph,
23
+ link,
24
+ image,
25
+ video,
26
+ html,
27
+ richText,
28
+ compact
29
+ };
30
+ function useGenerate({ kind, measurement, setValue }) {
31
+ const [formFields] = useAllFormFields();
32
+ const { config } = useConfig();
33
+ const { collectionSlug } = useDocumentInfo();
34
+ const locale = useLocale();
35
+ const [status, setStatus] = useState("idle");
36
+ const [error, setError] = useState(null);
37
+ const ref = useRef({
38
+ formFields,
39
+ collectionSlug,
40
+ locale,
41
+ apiRoute: config.routes.api
42
+ });
43
+ ref.current = {
44
+ formFields,
45
+ collectionSlug,
46
+ locale,
47
+ apiRoute: config.routes.api
48
+ };
49
+ const generate = useCallback(async () => {
50
+ const { formFields: ff, collectionSlug: slug, locale: loc, apiRoute } = ref.current;
51
+ const clientConfig = getSeoClientConfig();
52
+ const extractPath = slug ? clientConfig.extractByCollection[slug] : void 0;
53
+ const extractor = resolveContentExtractor(extractPath);
54
+ if (!extractor) {
55
+ setStatus("error");
56
+ setError("No content extractor is registered for this collection.");
57
+ return;
58
+ }
59
+ setStatus("loading");
60
+ setError(null);
61
+ try {
62
+ const values = reduceFieldsToValues(ff, true);
63
+ const localeCode = loc?.code;
64
+ const ir = await extractor(
65
+ values,
66
+ { locale: localeCode, apiRoute },
67
+ { resolveDocs: createResolveDocs(apiRoute, localeCode), helpers }
68
+ );
69
+ const contentHtml = serialize(ir);
70
+ const res = await fetch(`${apiRoute}${GENERATE_ENDPOINT_PATH}`, {
71
+ method: "POST",
72
+ credentials: "include",
73
+ headers: { "Content-Type": "application/json" },
74
+ body: JSON.stringify({
75
+ kind,
76
+ contentHtml,
77
+ locale: localeCode,
78
+ range: { min: measurement.min, max: measurement.max, unit: measurement.unit }
79
+ })
80
+ });
81
+ if (!res.ok)
82
+ throw new Error(`Request failed (${res.status})`);
83
+ const body = await res.json();
84
+ if (!body.text)
85
+ throw new Error("Empty response");
86
+ setValue(body.text);
87
+ setStatus("idle");
88
+ } catch (err) {
89
+ setStatus("error");
90
+ setError(err.message || "Generation failed");
91
+ }
92
+ }, [kind, measurement.min, measurement.max, measurement.unit, setValue]);
93
+ return {
94
+ generate,
95
+ status,
96
+ error
97
+ };
98
+ }
99
+ export {
100
+ useGenerate
101
+ };
102
+ //# sourceMappingURL=useGenerate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoField/useGenerate.ts"],"sourcesContent":["\"use client\";\n\nimport { useAllFormFields, useConfig, useDocumentInfo, useLocale } from \"@payloadcms/ui\";\nimport { reduceFieldsToValues } from \"payload/shared\";\nimport { useCallback, useRef, useState } from \"react\";\nimport { getSeoClientConfig } from \"../../client-config/registry\";\nimport { GENERATE_ENDPOINT_PATH } from \"../../constants/generation\";\nimport { createResolveDocs } from \"../../content/resolve/resolve-docs\";\nimport { resolveContentExtractor } from \"../../content/registry\";\nimport {\n compact,\n heading,\n html,\n image,\n link,\n paragraph,\n richText,\n video,\n} from \"../../content/schema/helpers\";\nimport { serialize } from \"../../content/schema/serialize\";\nimport type { ContentHelpers } from \"../../types/config\";\nimport type { Measurement } from \"../../measure/measure\";\nimport type { SeoFieldKind } from \"../../server/generate/prompts\";\n\nconst helpers: ContentHelpers = {\n heading,\n paragraph,\n link,\n image,\n video,\n html,\n richText,\n compact,\n};\n\nexport type GenerateStatus = \"idle\" | \"loading\" | \"error\";\n\ninterface UseGenerateProps {\n kind: SeoFieldKind;\n measurement: Measurement;\n setValue: (v: string) => void;\n}\n\nexport function useGenerate({ kind, measurement, setValue }: UseGenerateProps) {\n const [formFields] = useAllFormFields();\n const { config } = useConfig();\n const { collectionSlug } = useDocumentInfo();\n const locale = useLocale();\n\n const [status, setStatus] = useState<GenerateStatus>(\"idle\");\n const [error, setError] = useState<string | null>(null);\n\n const ref = useRef({\n formFields,\n collectionSlug,\n locale,\n apiRoute: config.routes.api,\n });\n ref.current = {\n formFields,\n collectionSlug,\n locale,\n apiRoute: config.routes.api,\n };\n\n const generate = useCallback(async () => {\n const { formFields: ff, collectionSlug: slug, locale: loc, apiRoute } = ref.current;\n\n const clientConfig = getSeoClientConfig();\n const extractPath = slug ? clientConfig.extractByCollection[slug] : undefined;\n\n const extractor = resolveContentExtractor(extractPath);\n if (!extractor) {\n setStatus(\"error\");\n setError(\"No content extractor is registered for this collection.\");\n\n return;\n }\n\n setStatus(\"loading\");\n setError(null);\n\n try {\n const values = reduceFieldsToValues(ff, true) as Record<string, unknown>;\n const localeCode = loc?.code;\n const ir = await extractor(\n values,\n { locale: localeCode, apiRoute },\n { resolveDocs: createResolveDocs(apiRoute, localeCode), helpers }\n );\n const contentHtml = serialize(ir);\n\n const res = await fetch(`${apiRoute}${GENERATE_ENDPOINT_PATH}`, {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({\n kind,\n contentHtml,\n locale: localeCode,\n range: { min: measurement.min, max: measurement.max, unit: measurement.unit },\n }),\n });\n if (!res.ok) throw new Error(`Request failed (${res.status})`);\n\n const body = (await res.json()) as { text?: string };\n if (!body.text) throw new Error(\"Empty response\");\n\n setValue(body.text);\n setStatus(\"idle\");\n } catch (err) {\n setStatus(\"error\");\n setError((err as Error).message || \"Generation failed\");\n }\n }, [kind, measurement.min, measurement.max, measurement.unit, setValue]);\n\n return {\n generate,\n status,\n error,\n };\n}\n"],"mappings":";AAEA,SAAS,kBAAkB,WAAW,iBAAiB,iBAAiB;AACxE,SAAS,4BAA4B;AACrC,SAAS,aAAa,QAAQ,gBAAgB;AAC9C,SAAS,0BAA0B;AACnC,SAAS,8BAA8B;AACvC,SAAS,yBAAyB;AAClC,SAAS,+BAA+B;AACxC;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAK1B,MAAM,UAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAUO,SAAS,YAAY,EAAE,MAAM,aAAa,SAAS,GAAqB;AAC7E,QAAM,CAAC,UAAU,IAAI,iBAAiB;AACtC,QAAM,EAAE,OAAO,IAAI,UAAU;AAC7B,QAAM,EAAE,eAAe,IAAI,gBAAgB;AAC3C,QAAM,SAAS,UAAU;AAEzB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAyB,MAAM;AAC3D,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAwB,IAAI;AAEtD,QAAM,MAAM,OAAO;AAAA,IACjB;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,OAAO,OAAO;AAAA,EAC1B,CAAC;AACD,MAAI,UAAU;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA,UAAU,OAAO,OAAO;AAAA,EAC1B;AAEA,QAAM,WAAW,YAAY,YAAY;AACvC,UAAM,EAAE,YAAY,IAAI,gBAAgB,MAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AAE5E,UAAM,eAAe,mBAAmB;AACxC,UAAM,cAAc,OAAO,aAAa,oBAAoB,IAAI,IAAI;AAEpE,UAAM,YAAY,wBAAwB,WAAW;AACrD,QAAI,CAAC,WAAW;AACd,gBAAU,OAAO;AACjB,eAAS,yDAAyD;AAElE;AAAA,IACF;AAEA,cAAU,SAAS;AACnB,aAAS,IAAI;AAEb,QAAI;AACF,YAAM,SAAS,qBAAqB,IAAI,IAAI;AAC5C,YAAM,aAAa,KAAK;AACxB,YAAM,KAAK,MAAM;AAAA,QACf;AAAA,QACA,EAAE,QAAQ,YAAY,SAAS;AAAA,QAC/B,EAAE,aAAa,kBAAkB,UAAU,UAAU,GAAG,QAAQ;AAAA,MAClE;AACA,YAAM,cAAc,UAAU,EAAE;AAEhC,YAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,GAAG,sBAAsB,IAAI;AAAA,QAC9D,QAAQ;AAAA,QACR,aAAa;AAAA,QACb,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,QAC9C,MAAM,KAAK,UAAU;AAAA,UACnB;AAAA,UACA;AAAA,UACA,QAAQ;AAAA,UACR,OAAO,EAAE,KAAK,YAAY,KAAK,KAAK,YAAY,KAAK,MAAM,YAAY,KAAK;AAAA,QAC9E,CAAC;AAAA,MACH,CAAC;AACD,UAAI,CAAC,IAAI;AAAI,cAAM,IAAI,MAAM,mBAAmB,IAAI,MAAM,GAAG;AAE7D,YAAM,OAAQ,MAAM,IAAI,KAAK;AAC7B,UAAI,CAAC,KAAK;AAAM,cAAM,IAAI,MAAM,gBAAgB;AAEhD,eAAS,KAAK,IAAI;AAClB,gBAAU,MAAM;AAAA,IAClB,SAAS,KAAK;AACZ,gBAAU,OAAO;AACjB,eAAU,IAAc,WAAW,mBAAmB;AAAA,IACxD;AAAA,EACF,GAAG,CAAC,MAAM,YAAY,KAAK,YAAY,KAAK,YAAY,MAAM,QAAQ,CAAC;AAEvE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,13 @@
1
+ export declare const GENERATE_ENDPOINT_PATH = "/seo/generate";
2
+ export declare const DEFAULT_MODEL = "gpt-4o-mini";
3
+ export declare const DEFAULT_MAX_CONTENT_CHARS = 6000;
4
+ export declare const AVG_GLYPH_PX = 8.5;
5
+ export declare const TITLE_RANGE: {
6
+ readonly min: 400;
7
+ readonly max: 600;
8
+ };
9
+ export declare const DESCRIPTION_RANGE: {
10
+ readonly min: 120;
11
+ readonly max: 160;
12
+ };
13
+ //# sourceMappingURL=generation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generation.d.ts","sourceRoot":"","sources":["../../src/constants/generation.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,sBAAsB,kBAAkB,CAAC;AACtD,eAAO,MAAM,aAAa,gBAAgB,CAAC;AAC3C,eAAO,MAAM,yBAAyB,OAAO,CAAC;AAE9C,eAAO,MAAM,YAAY,MAAM,CAAC;AAEhC,eAAO,MAAM,WAAW;;;CAGd,CAAC;AAEX,eAAO,MAAM,iBAAiB;;;CAGpB,CAAC"}
@@ -0,0 +1,21 @@
1
+ const GENERATE_ENDPOINT_PATH = "/seo/generate";
2
+ const DEFAULT_MODEL = "gpt-4o-mini";
3
+ const DEFAULT_MAX_CONTENT_CHARS = 6e3;
4
+ const AVG_GLYPH_PX = 8.5;
5
+ const TITLE_RANGE = {
6
+ min: 400,
7
+ max: 600
8
+ };
9
+ const DESCRIPTION_RANGE = {
10
+ min: 120,
11
+ max: 160
12
+ };
13
+ export {
14
+ AVG_GLYPH_PX,
15
+ DEFAULT_MAX_CONTENT_CHARS,
16
+ DEFAULT_MODEL,
17
+ DESCRIPTION_RANGE,
18
+ GENERATE_ENDPOINT_PATH,
19
+ TITLE_RANGE
20
+ };
21
+ //# sourceMappingURL=generation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/constants/generation.ts"],"sourcesContent":["export const GENERATE_ENDPOINT_PATH = \"/seo/generate\";\nexport const DEFAULT_MODEL = \"gpt-4o-mini\";\nexport const DEFAULT_MAX_CONTENT_CHARS = 6000;\n\nexport const AVG_GLYPH_PX = 8.5;\n\nexport const TITLE_RANGE = {\n min: 400,\n max: 600,\n} as const;\n\nexport const DESCRIPTION_RANGE = {\n min: 120,\n max: 160,\n} as const;\n"],"mappings":"AAAO,MAAM,yBAAyB;AAC/B,MAAM,gBAAgB;AACtB,MAAM,4BAA4B;AAElC,MAAM,eAAe;AAErB,MAAM,cAAc;AAAA,EACzB,KAAK;AAAA,EACL,KAAK;AACP;AAEO,MAAM,oBAAoB;AAAA,EAC/B,KAAK;AAAA,EACL,KAAK;AACP;","names":[]}
@@ -1,5 +1,5 @@
1
- export { heading, paragraph, link, image, video, richText, html } from "./schema/helpers";
1
+ export { heading, paragraph, link, image, video, richText, html, compact } from "./schema/helpers";
2
2
  export type { ContentNode, HeadingLevel } from "./schema/nodes";
3
3
  export { registerContentExtractors, resolveContentExtractor } from "./registry";
4
- export type { ContentExtractor, ContentSelection, ExtractContext } from "../types/config";
4
+ export type { ContentExtractor, ExtractContext, ExtractToolkit, DocQuery, DocStore, ContentHelpers, } from "../types/config";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAC1F,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAChF,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/content/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACnG,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,yBAAyB,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAChF,YAAY,EACV,gBAAgB,EAChB,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,cAAc,GACf,MAAM,iBAAiB,CAAC"}
@@ -1,6 +1,7 @@
1
- import { heading, paragraph, link, image, video, richText, html } from "./schema/helpers";
1
+ import { heading, paragraph, link, image, video, richText, html, compact } from "./schema/helpers";
2
2
  import { registerContentExtractors, resolveContentExtractor } from "./registry";
3
3
  export {
4
+ compact,
4
5
  heading,
5
6
  html,
6
7
  image,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/content/index.ts"],"sourcesContent":["export { heading, paragraph, link, image, video, richText, html } from \"./schema/helpers\";\nexport type { ContentNode, HeadingLevel } from \"./schema/nodes\";\nexport { registerContentExtractors, resolveContentExtractor } from \"./registry\";\nexport type { ContentExtractor, ContentSelection, ExtractContext } from \"../types/config\";\n"],"mappings":"AAAA,SAAS,SAAS,WAAW,MAAM,OAAO,OAAO,UAAU,YAAY;AAEvE,SAAS,2BAA2B,+BAA+B;","names":[]}
1
+ {"version":3,"sources":["../../src/content/index.ts"],"sourcesContent":["export { heading, paragraph, link, image, video, richText, html, compact } from \"./schema/helpers\";\nexport type { ContentNode, HeadingLevel } from \"./schema/nodes\";\nexport { registerContentExtractors, resolveContentExtractor } from \"./registry\";\nexport type {\n ContentExtractor,\n ExtractContext,\n ExtractToolkit,\n DocQuery,\n DocStore,\n ContentHelpers,\n} from \"../types/config\";\n"],"mappings":"AAAA,SAAS,SAAS,WAAW,MAAM,OAAO,OAAO,UAAU,MAAM,eAAe;AAEhF,SAAS,2BAA2B,+BAA+B;","names":[]}
@@ -0,0 +1,3 @@
1
+ import type { DocQuery, DocStore } from "../../types/config";
2
+ export declare function createResolveDocs(apiRoute: string | undefined, locale: string | undefined): (queries: DocQuery[]) => Promise<DocStore>;
3
+ //# sourceMappingURL=resolve-docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-docs.d.ts","sourceRoot":"","sources":["../../../src/content/resolve/resolve-docs.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAkD7D,wBAAgB,iBAAiB,CAC/B,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,GAAG,SAAS,GACzB,CAAC,OAAO,EAAE,QAAQ,EAAE,KAAK,OAAO,CAAC,QAAQ,CAAC,CAc5C"}
@@ -0,0 +1,48 @@
1
+ function key(collection, id) {
2
+ return `${collection}:${id}`;
3
+ }
4
+ async function fetchQuery(apiRoute, query, locale) {
5
+ const ids = [...new Set(query.ids.map(String))];
6
+ if (ids.length === 0)
7
+ return [];
8
+ const params = new URLSearchParams({
9
+ depth: String(query.depth ?? 0),
10
+ limit: String(ids.length)
11
+ });
12
+ if (locale)
13
+ params.set("locale", locale);
14
+ ids.forEach((id, i) => params.set(`where[id][in][${i}]`, id));
15
+ for (const field of query.select ?? [])
16
+ params.set(`select[${field}]`, "true");
17
+ try {
18
+ const res = await fetch(`${apiRoute}/${query.collection}?${params.toString()}`, {
19
+ credentials: "include"
20
+ });
21
+ if (!res.ok)
22
+ return [];
23
+ const body = await res.json();
24
+ return (body.docs ?? []).filter(
25
+ (d) => typeof d.id === "string" || typeof d.id === "number"
26
+ ).map((d) => [key(query.collection, d.id), d]);
27
+ } catch {
28
+ return [];
29
+ }
30
+ }
31
+ function createResolveDocs(apiRoute, locale) {
32
+ return async function resolveDocs(queries) {
33
+ const store = /* @__PURE__ */ new Map();
34
+ if (apiRoute) {
35
+ const results = await Promise.all(queries.map((q) => fetchQuery(apiRoute, q, locale)));
36
+ for (const entries of results)
37
+ for (const [k, doc] of entries)
38
+ store.set(k, doc);
39
+ }
40
+ return {
41
+ get: (collection, id) => store.get(key(collection, id))
42
+ };
43
+ };
44
+ }
45
+ export {
46
+ createResolveDocs
47
+ };
48
+ //# sourceMappingURL=resolve-docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/content/resolve/resolve-docs.ts"],"sourcesContent":["import type { DocQuery, DocStore } from \"../../types/config\";\n\ntype Doc = Record<string, unknown>;\n\ninterface FindResponse {\n docs?: Array<Doc & { id?: unknown }>;\n}\n\nfunction key(collection: string, id: string | number): string {\n return `${collection}:${id}`;\n}\n\nasync function fetchQuery(\n apiRoute: string,\n query: DocQuery,\n locale: string | undefined\n): Promise<Array<[string, Doc]>> {\n const ids = [...new Set(query.ids.map(String))];\n if (ids.length === 0) return [];\n\n const params = new URLSearchParams({\n depth: String(query.depth ?? 0),\n limit: String(ids.length),\n });\n\n if (locale) params.set(\"locale\", locale);\n\n ids.forEach((id, i) => params.set(`where[id][in][${i}]`, id));\n\n for (const field of query.select ?? []) params.set(`select[${field}]`, \"true\");\n\n try {\n const res = await fetch(`${apiRoute}/${query.collection}?${params.toString()}`, {\n credentials: \"include\",\n });\n if (!res.ok) return [];\n\n const body = (await res.json()) as FindResponse;\n\n return (body.docs ?? [])\n .filter(\n (d): d is Doc & { id: string | number } =>\n typeof d.id === \"string\" || typeof d.id === \"number\"\n )\n .map((d) => [key(query.collection, d.id), d] as [string, Doc]);\n } catch {\n return [];\n }\n}\n\nexport function createResolveDocs(\n apiRoute: string | undefined,\n locale: string | undefined\n): (queries: DocQuery[]) => Promise<DocStore> {\n return async function resolveDocs(queries: DocQuery[]): Promise<DocStore> {\n const store = new Map<string, Doc>();\n\n if (apiRoute) {\n const results = await Promise.all(queries.map((q) => fetchQuery(apiRoute, q, locale)));\n\n for (const entries of results) for (const [k, doc] of entries) store.set(k, doc);\n }\n\n return {\n get: (collection, id) => store.get(key(collection, id)),\n };\n };\n}\n"],"mappings":"AAQA,SAAS,IAAI,YAAoB,IAA6B;AAC5D,SAAO,GAAG,UAAU,IAAI,EAAE;AAC5B;AAEA,eAAe,WACb,UACA,OACA,QAC+B;AAC/B,QAAM,MAAM,CAAC,GAAG,IAAI,IAAI,MAAM,IAAI,IAAI,MAAM,CAAC,CAAC;AAC9C,MAAI,IAAI,WAAW;AAAG,WAAO,CAAC;AAE9B,QAAM,SAAS,IAAI,gBAAgB;AAAA,IACjC,OAAO,OAAO,MAAM,SAAS,CAAC;AAAA,IAC9B,OAAO,OAAO,IAAI,MAAM;AAAA,EAC1B,CAAC;AAED,MAAI;AAAQ,WAAO,IAAI,UAAU,MAAM;AAEvC,MAAI,QAAQ,CAAC,IAAI,MAAM,OAAO,IAAI,iBAAiB,CAAC,KAAK,EAAE,CAAC;AAE5D,aAAW,SAAS,MAAM,UAAU,CAAC;AAAG,WAAO,IAAI,UAAU,KAAK,KAAK,MAAM;AAE7E,MAAI;AACF,UAAM,MAAM,MAAM,MAAM,GAAG,QAAQ,IAAI,MAAM,UAAU,IAAI,OAAO,SAAS,CAAC,IAAI;AAAA,MAC9E,aAAa;AAAA,IACf,CAAC;AACD,QAAI,CAAC,IAAI;AAAI,aAAO,CAAC;AAErB,UAAM,OAAQ,MAAM,IAAI,KAAK;AAE7B,YAAQ,KAAK,QAAQ,CAAC,GACnB;AAAA,MACC,CAAC,MACC,OAAO,EAAE,OAAO,YAAY,OAAO,EAAE,OAAO;AAAA,IAChD,EACC,IAAI,CAAC,MAAM,CAAC,IAAI,MAAM,YAAY,EAAE,EAAE,GAAG,CAAC,CAAkB;AAAA,EACjE,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAEO,SAAS,kBACd,UACA,QAC4C;AAC5C,SAAO,eAAe,YAAY,SAAwC;AACxE,UAAM,QAAQ,oBAAI,IAAiB;AAEnC,QAAI,UAAU;AACZ,YAAM,UAAU,MAAM,QAAQ,IAAI,QAAQ,IAAI,CAAC,MAAM,WAAW,UAAU,GAAG,MAAM,CAAC,CAAC;AAErF,iBAAW,WAAW;AAAS,mBAAW,CAAC,GAAG,GAAG,KAAK;AAAS,gBAAM,IAAI,GAAG,GAAG;AAAA,IACjF;AAEA,WAAO;AAAA,MACL,KAAK,CAAC,YAAY,OAAO,MAAM,IAAI,IAAI,YAAY,EAAE,CAAC;AAAA,IACxD;AAAA,EACF;AACF;","names":[]}
@@ -6,4 +6,5 @@ export declare function image(src?: string | null, alt?: string | null): Content
6
6
  export declare function video(src?: string | null, poster?: string | null): ContentNode | null;
7
7
  export declare function richText(value: unknown): ContentNode | null;
8
8
  export declare function html(raw?: string | null): ContentNode | null;
9
+ export declare function compact(nodes: (ContentNode | null | undefined)[]): ContentNode[];
9
10
  //# sourceMappingURL=helpers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/content/schema/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQzD,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAGrF;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAGlE;AAED,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAInF;AAED,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAGlF;AAED,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAKrF;AAUD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAI3D;AAED,wBAAgB,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAG5D"}
1
+ {"version":3,"file":"helpers.d.ts","sourceRoot":"","sources":["../../../src/content/schema/helpers.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAQzD,wBAAgB,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAGrF;AAED,wBAAgB,SAAS,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAGlE;AAED,wBAAgB,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAInF;AAED,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAGlF;AAED,wBAAgB,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAKrF;AAUD,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,WAAW,GAAG,IAAI,CAI3D;AAED,wBAAgB,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,GAAG,IAAI,CAG5D;AAED,wBAAgB,OAAO,CAAC,KAAK,EAAE,CAAC,WAAW,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,WAAW,EAAE,CAEhF"}
@@ -48,7 +48,11 @@ function html(raw) {
48
48
  const r = clean(raw);
49
49
  return r ? { type: "html", html: r } : null;
50
50
  }
51
+ function compact(nodes) {
52
+ return nodes.filter((n) => n != null);
53
+ }
51
54
  export {
55
+ compact,
52
56
  heading,
53
57
  html,
54
58
  image,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/content/schema/helpers.ts"],"sourcesContent":["import { lexicalToHtml } from \"../lexicalToHtml\";\nimport type { ContentNode, HeadingLevel } from \"./nodes\";\n\nfunction clean(v?: string | null): string | undefined {\n if (typeof v !== \"string\") return undefined;\n const t = v.trim();\n return t ? v : undefined;\n}\n\nexport function heading(level: HeadingLevel, text?: string | null): ContentNode | null {\n const t = clean(text);\n return t ? { type: \"heading\", level, text: t } : null;\n}\n\nexport function paragraph(text?: string | null): ContentNode | null {\n const t = clean(text);\n return t ? { type: \"paragraph\", text: t } : null;\n}\n\nexport function link(href?: string | null, text?: string | null): ContentNode | null {\n const h = clean(href);\n const t = clean(text);\n return h && t ? { type: \"link\", href: h, text: t } : null;\n}\n\nexport function image(src?: string | null, alt?: string | null): ContentNode | null {\n const s = clean(src);\n return s ? { type: \"image\", src: s, alt: clean(alt) ?? \"\" } : null;\n}\n\nexport function video(src?: string | null, poster?: string | null): ContentNode | null {\n const s = clean(src);\n if (!s) return null;\n const p = clean(poster);\n return p ? { type: \"video\", src: s, poster: p } : { type: \"video\", src: s };\n}\n\nfunction hasLexicalContent(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const root = (value as Record<string, unknown>).root;\n if (typeof root !== \"object\" || root === null) return false;\n const children = (root as Record<string, unknown>).children;\n return Array.isArray(children) && children.length > 0;\n}\n\nexport function richText(value: unknown): ContentNode | null {\n if (!hasLexicalContent(value)) return null;\n const out = lexicalToHtml(value as never);\n return out ? { type: \"html\", html: out } : null;\n}\n\nexport function html(raw?: string | null): ContentNode | null {\n const r = clean(raw);\n return r ? { type: \"html\", html: r } : null;\n}\n"],"mappings":"AAAA,SAAS,qBAAqB;AAG9B,SAAS,MAAM,GAAuC;AACpD,MAAI,OAAO,MAAM;AAAU,WAAO;AAClC,QAAM,IAAI,EAAE,KAAK;AACjB,SAAO,IAAI,IAAI;AACjB;AAEO,SAAS,QAAQ,OAAqB,MAA0C;AACrF,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,IAAI,EAAE,MAAM,WAAW,OAAO,MAAM,EAAE,IAAI;AACnD;AAEO,SAAS,UAAU,MAA0C;AAClE,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,IAAI,EAAE,MAAM,aAAa,MAAM,EAAE,IAAI;AAC9C;AAEO,SAAS,KAAK,MAAsB,MAA0C;AACnF,QAAM,IAAI,MAAM,IAAI;AACpB,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,KAAK,IAAI,EAAE,MAAM,QAAQ,MAAM,GAAG,MAAM,EAAE,IAAI;AACvD;AAEO,SAAS,MAAM,KAAqB,KAAyC;AAClF,QAAM,IAAI,MAAM,GAAG;AACnB,SAAO,IAAI,EAAE,MAAM,SAAS,KAAK,GAAG,KAAK,MAAM,GAAG,KAAK,GAAG,IAAI;AAChE;AAEO,SAAS,MAAM,KAAqB,QAA4C;AACrF,QAAM,IAAI,MAAM,GAAG;AACnB,MAAI,CAAC;AAAG,WAAO;AACf,QAAM,IAAI,MAAM,MAAM;AACtB,SAAO,IAAI,EAAE,MAAM,SAAS,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,SAAS,KAAK,EAAE;AAC5E;AAEA,SAAS,kBAAkB,OAAyB;AAClD,MAAI,OAAO,UAAU,YAAY,UAAU;AAAM,WAAO;AACxD,QAAM,OAAQ,MAAkC;AAChD,MAAI,OAAO,SAAS,YAAY,SAAS;AAAM,WAAO;AACtD,QAAM,WAAY,KAAiC;AACnD,SAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS;AACtD;AAEO,SAAS,SAAS,OAAoC;AAC3D,MAAI,CAAC,kBAAkB,KAAK;AAAG,WAAO;AACtC,QAAM,MAAM,cAAc,KAAc;AACxC,SAAO,MAAM,EAAE,MAAM,QAAQ,MAAM,IAAI,IAAI;AAC7C;AAEO,SAAS,KAAK,KAAyC;AAC5D,QAAM,IAAI,MAAM,GAAG;AACnB,SAAO,IAAI,EAAE,MAAM,QAAQ,MAAM,EAAE,IAAI;AACzC;","names":[]}
1
+ {"version":3,"sources":["../../../src/content/schema/helpers.ts"],"sourcesContent":["import { lexicalToHtml } from \"../lexicalToHtml\";\nimport type { ContentNode, HeadingLevel } from \"./nodes\";\n\nfunction clean(v?: string | null): string | undefined {\n if (typeof v !== \"string\") return undefined;\n const t = v.trim();\n return t ? v : undefined;\n}\n\nexport function heading(level: HeadingLevel, text?: string | null): ContentNode | null {\n const t = clean(text);\n return t ? { type: \"heading\", level, text: t } : null;\n}\n\nexport function paragraph(text?: string | null): ContentNode | null {\n const t = clean(text);\n return t ? { type: \"paragraph\", text: t } : null;\n}\n\nexport function link(href?: string | null, text?: string | null): ContentNode | null {\n const h = clean(href);\n const t = clean(text);\n return h && t ? { type: \"link\", href: h, text: t } : null;\n}\n\nexport function image(src?: string | null, alt?: string | null): ContentNode | null {\n const s = clean(src);\n return s ? { type: \"image\", src: s, alt: clean(alt) ?? \"\" } : null;\n}\n\nexport function video(src?: string | null, poster?: string | null): ContentNode | null {\n const s = clean(src);\n if (!s) return null;\n const p = clean(poster);\n return p ? { type: \"video\", src: s, poster: p } : { type: \"video\", src: s };\n}\n\nfunction hasLexicalContent(value: unknown): boolean {\n if (typeof value !== \"object\" || value === null) return false;\n const root = (value as Record<string, unknown>).root;\n if (typeof root !== \"object\" || root === null) return false;\n const children = (root as Record<string, unknown>).children;\n return Array.isArray(children) && children.length > 0;\n}\n\nexport function richText(value: unknown): ContentNode | null {\n if (!hasLexicalContent(value)) return null;\n const out = lexicalToHtml(value as never);\n return out ? { type: \"html\", html: out } : null;\n}\n\nexport function html(raw?: string | null): ContentNode | null {\n const r = clean(raw);\n return r ? { type: \"html\", html: r } : null;\n}\n\nexport function compact(nodes: (ContentNode | null | undefined)[]): ContentNode[] {\n return nodes.filter((n): n is ContentNode => n != null);\n}\n"],"mappings":"AAAA,SAAS,qBAAqB;AAG9B,SAAS,MAAM,GAAuC;AACpD,MAAI,OAAO,MAAM;AAAU,WAAO;AAClC,QAAM,IAAI,EAAE,KAAK;AACjB,SAAO,IAAI,IAAI;AACjB;AAEO,SAAS,QAAQ,OAAqB,MAA0C;AACrF,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,IAAI,EAAE,MAAM,WAAW,OAAO,MAAM,EAAE,IAAI;AACnD;AAEO,SAAS,UAAU,MAA0C;AAClE,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,IAAI,EAAE,MAAM,aAAa,MAAM,EAAE,IAAI;AAC9C;AAEO,SAAS,KAAK,MAAsB,MAA0C;AACnF,QAAM,IAAI,MAAM,IAAI;AACpB,QAAM,IAAI,MAAM,IAAI;AACpB,SAAO,KAAK,IAAI,EAAE,MAAM,QAAQ,MAAM,GAAG,MAAM,EAAE,IAAI;AACvD;AAEO,SAAS,MAAM,KAAqB,KAAyC;AAClF,QAAM,IAAI,MAAM,GAAG;AACnB,SAAO,IAAI,EAAE,MAAM,SAAS,KAAK,GAAG,KAAK,MAAM,GAAG,KAAK,GAAG,IAAI;AAChE;AAEO,SAAS,MAAM,KAAqB,QAA4C;AACrF,QAAM,IAAI,MAAM,GAAG;AACnB,MAAI,CAAC;AAAG,WAAO;AACf,QAAM,IAAI,MAAM,MAAM;AACtB,SAAO,IAAI,EAAE,MAAM,SAAS,KAAK,GAAG,QAAQ,EAAE,IAAI,EAAE,MAAM,SAAS,KAAK,EAAE;AAC5E;AAEA,SAAS,kBAAkB,OAAyB;AAClD,MAAI,OAAO,UAAU,YAAY,UAAU;AAAM,WAAO;AACxD,QAAM,OAAQ,MAAkC;AAChD,MAAI,OAAO,SAAS,YAAY,SAAS;AAAM,WAAO;AACtD,QAAM,WAAY,KAAiC;AACnD,SAAO,MAAM,QAAQ,QAAQ,KAAK,SAAS,SAAS;AACtD;AAEO,SAAS,SAAS,OAAoC;AAC3D,MAAI,CAAC,kBAAkB,KAAK;AAAG,WAAO;AACtC,QAAM,MAAM,cAAc,KAAc;AACxC,SAAO,MAAM,EAAE,MAAM,QAAQ,MAAM,IAAI,IAAI;AAC7C;AAEO,SAAS,KAAK,KAAyC;AAC5D,QAAM,IAAI,MAAM,GAAG;AACnB,SAAO,IAAI,EAAE,MAAM,QAAQ,MAAM,EAAE,IAAI;AACzC;AAEO,SAAS,QAAQ,OAA0D;AAChF,SAAO,MAAM,OAAO,CAAC,MAAwB,KAAK,IAAI;AACxD;","names":[]}
@@ -1,5 +1,6 @@
1
1
  import type { LengthProgress } from "@yoast/search-metadata-previews/build/helpers/progress";
2
- export declare const AVG_GLYPH_PX = 8.5;
2
+ import { AVG_GLYPH_PX } from "../../constants/generation";
3
+ export { AVG_GLYPH_PX };
3
4
  export declare const TITLE_FALLBACK_MAX_PX = 600;
4
5
  export declare function getTitleProgressGuarded(title: string): LengthProgress;
5
6
  //# sourceMappingURL=title-progress.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"title-progress.d.ts","sourceRoot":"","sources":["../../../src/engine/helpers/title-progress.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AAE7F,eAAO,MAAM,YAAY,MAAM,CAAC;AAChC,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAarE"}
1
+ {"version":3,"file":"title-progress.d.ts","sourceRoot":"","sources":["../../../src/engine/helpers/title-progress.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,wDAAwD,CAAC;AAC7F,OAAO,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE1D,OAAO,EAAE,YAAY,EAAE,CAAC;AACxB,eAAO,MAAM,qBAAqB,MAAM,CAAC;AAEzC,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,cAAc,CAarE"}
@@ -1,5 +1,5 @@
1
1
  import { getTitleProgress } from "@yoast/search-metadata-previews/build/helpers/progress";
2
- const AVG_GLYPH_PX = 8.5;
2
+ import { AVG_GLYPH_PX } from "../../constants/generation";
3
3
  const TITLE_FALLBACK_MAX_PX = 600;
4
4
  function getTitleProgressGuarded(title) {
5
5
  if (typeof document !== "undefined") {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/engine/helpers/title-progress.ts"],"sourcesContent":["import { getTitleProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\nimport type { LengthProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\n\nexport const AVG_GLYPH_PX = 8.5;\nexport const TITLE_FALLBACK_MAX_PX = 600;\n\nexport function getTitleProgressGuarded(title: string): LengthProgress {\n if (typeof document !== \"undefined\") {\n return getTitleProgress(title);\n }\n\n const actual = Math.round(title.length * AVG_GLYPH_PX);\n const score = actual <= TITLE_FALLBACK_MAX_PX ? 9 : 1;\n\n return {\n actual,\n max: TITLE_FALLBACK_MAX_PX,\n score,\n };\n}\n"],"mappings":"AAAA,SAAS,wBAAwB;AAG1B,MAAM,eAAe;AACrB,MAAM,wBAAwB;AAE9B,SAAS,wBAAwB,OAA+B;AACrE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO,iBAAiB,KAAK;AAAA,EAC/B;AAEA,QAAM,SAAS,KAAK,MAAM,MAAM,SAAS,YAAY;AACrD,QAAM,QAAQ,UAAU,wBAAwB,IAAI;AAEpD,SAAO;AAAA,IACL;AAAA,IACA,KAAK;AAAA,IACL;AAAA,EACF;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../src/engine/helpers/title-progress.ts"],"sourcesContent":["import { getTitleProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\nimport type { LengthProgress } from \"@yoast/search-metadata-previews/build/helpers/progress\";\nimport { AVG_GLYPH_PX } from \"../../constants/generation\";\n\nexport { AVG_GLYPH_PX };\nexport const TITLE_FALLBACK_MAX_PX = 600;\n\nexport function getTitleProgressGuarded(title: string): LengthProgress {\n if (typeof document !== \"undefined\") {\n return getTitleProgress(title);\n }\n\n const actual = Math.round(title.length * AVG_GLYPH_PX);\n const score = actual <= TITLE_FALLBACK_MAX_PX ? 9 : 1;\n\n return {\n actual,\n max: TITLE_FALLBACK_MAX_PX,\n score,\n };\n}\n"],"mappings":"AAAA,SAAS,wBAAwB;AAEjC,SAAS,oBAAoB;AAGtB,MAAM,wBAAwB;AAE9B,SAAS,wBAAwB,OAA+B;AACrE,MAAI,OAAO,aAAa,aAAa;AACnC,WAAO,iBAAiB,KAAK;AAAA,EAC/B;AAEA,QAAM,SAAS,KAAK,MAAM,MAAM,SAAS,YAAY;AACrD,QAAM,QAAQ,UAAU,wBAAwB,IAAI;AAEpD,SAAO;AAAA,IACL;AAAA,IACA,KAAK;AAAA,IACL;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,3 @@
1
+ export { seoTextField } from "./seoTextField";
2
+ export type { SeoTextFieldOptions } from "./seoTextField";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/fields/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,YAAY,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { seoTextField } from "./seoTextField";
2
+ export {
3
+ seoTextField
4
+ };
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/index.ts"],"sourcesContent":["export { seoTextField } from \"./seoTextField\";\nexport type { SeoTextFieldOptions } from \"./seoTextField\";\n"],"mappings":"AAAA,SAAS,oBAAoB;","names":[]}
@@ -0,0 +1,8 @@
1
+ import type { FieldHook } from "payload";
2
+ import type { RangeOverride } from "../measure/measure";
3
+ import type { SeoFieldKind } from "../server/generate/prompts";
4
+ export declare function makeGenerateOnPublishHook(args: {
5
+ kind: SeoFieldKind;
6
+ range: RangeOverride | undefined;
7
+ }): FieldHook;
8
+ //# sourceMappingURL=onPublishHook.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"onPublishHook.d.ts","sourceRoot":"","sources":["../../src/fields/onPublishHook.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAgBzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAIxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AA6B/D,wBAAgB,yBAAyB,CAAC,IAAI,EAAE;IAC9C,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,aAAa,GAAG,SAAS,CAAC;CAClC,GAAG,SAAS,CAqDZ"}
@@ -0,0 +1,95 @@
1
+ import { getPluginConfig } from "../config";
2
+ import { PLUGIN_NAME } from "../constants";
3
+ import { DESCRIPTION_RANGE, TITLE_RANGE } from "../constants/generation";
4
+ import {
5
+ compact,
6
+ heading,
7
+ html,
8
+ image,
9
+ link,
10
+ paragraph,
11
+ richText,
12
+ video
13
+ } from "../content/schema/helpers";
14
+ import { serialize } from "../content/schema/serialize";
15
+ import { resolveApiKey } from "../server/generate/apiKey";
16
+ import { createServerResolveDocs } from "../server/generate/serverResolveDocs";
17
+ import { generateForField } from "../server/generate/generateForField";
18
+ const helpers = {
19
+ heading,
20
+ paragraph,
21
+ link,
22
+ image,
23
+ video,
24
+ html,
25
+ richText,
26
+ compact
27
+ };
28
+ const CONTEXT_KEY = "__fr_seo_content__";
29
+ function isEmpty(v) {
30
+ return typeof v !== "string" || v.trim().length === 0;
31
+ }
32
+ function rangeFor(kind, override) {
33
+ const base = kind === "title" ? TITLE_RANGE : DESCRIPTION_RANGE;
34
+ return {
35
+ min: override?.min ?? base.min,
36
+ max: override?.max ?? base.max,
37
+ unit: kind === "title" ? "px" : "char"
38
+ };
39
+ }
40
+ function makeGenerateOnPublishHook(args) {
41
+ return async ({ value, data, collection, req, operation }) => {
42
+ if (!isEmpty(value))
43
+ return value;
44
+ if (operation !== "create" && operation !== "update")
45
+ return value;
46
+ const status = data?._status;
47
+ if (status && status !== "published")
48
+ return value;
49
+ const config = getPluginConfig();
50
+ const apiKey = resolveApiKey(config.generation);
51
+ if (!apiKey)
52
+ return value;
53
+ const slug = collection?.slug;
54
+ const seoCfg = config.collections.find((c) => c.slug === slug);
55
+ const extractor = seoCfg?.serverExtractContent;
56
+ if (!extractor || !slug)
57
+ return value;
58
+ try {
59
+ const localeCode = typeof req.locale === "string" ? req.locale : void 0;
60
+ const bucket = req.context[CONTEXT_KEY] ??= {};
61
+ const cacheKey = `${slug}:${localeCode ?? ""}`;
62
+ let contentHtml = bucket[cacheKey];
63
+ if (contentHtml === void 0) {
64
+ const toolkit = {
65
+ resolveDocs: createServerResolveDocs(req.payload, localeCode),
66
+ helpers
67
+ };
68
+ const ir = await extractor(
69
+ data,
70
+ { locale: localeCode },
71
+ toolkit
72
+ );
73
+ contentHtml = serialize(ir);
74
+ bucket[cacheKey] = contentHtml;
75
+ }
76
+ return await generateForField({
77
+ kind: args.kind,
78
+ contentHtml,
79
+ range: rangeFor(args.kind, args.range),
80
+ locale: localeCode,
81
+ config: config.generation ?? {},
82
+ apiKey
83
+ });
84
+ } catch (err) {
85
+ req.payload.logger.error(
86
+ `[${PLUGIN_NAME}] on-publish generation failed: ${err.message}`
87
+ );
88
+ return value;
89
+ }
90
+ };
91
+ }
92
+ export {
93
+ makeGenerateOnPublishHook
94
+ };
95
+ //# sourceMappingURL=onPublishHook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/onPublishHook.ts"],"sourcesContent":["import type { FieldHook } from \"payload\";\nimport { getPluginConfig } from \"../config\";\nimport { PLUGIN_NAME } from \"../constants\";\nimport { DESCRIPTION_RANGE, TITLE_RANGE } from \"../constants/generation\";\nimport {\n compact,\n heading,\n html,\n image,\n link,\n paragraph,\n richText,\n video,\n} from \"../content/schema/helpers\";\nimport { serialize } from \"../content/schema/serialize\";\nimport type { ContentHelpers, ExtractToolkit, SeoCollectionConfig } from \"../types/config\";\nimport type { RangeOverride } from \"../measure/measure\";\nimport { resolveApiKey } from \"../server/generate/apiKey\";\nimport { createServerResolveDocs } from \"../server/generate/serverResolveDocs\";\nimport { generateForField } from \"../server/generate/generateForField\";\nimport type { SeoFieldKind } from \"../server/generate/prompts\";\n\nconst helpers: ContentHelpers = {\n heading,\n paragraph,\n link,\n image,\n video,\n html,\n richText,\n compact,\n};\n\nconst CONTEXT_KEY = \"__fr_seo_content__\";\n\nfunction isEmpty(v: unknown): boolean {\n return typeof v !== \"string\" || v.trim().length === 0;\n}\n\nfunction rangeFor(kind: SeoFieldKind, override: RangeOverride | undefined) {\n const base = kind === \"title\" ? TITLE_RANGE : DESCRIPTION_RANGE;\n\n return {\n min: override?.min ?? base.min,\n max: override?.max ?? base.max,\n unit: (kind === \"title\" ? \"px\" : \"char\") as \"px\" | \"char\",\n };\n}\n\nexport function makeGenerateOnPublishHook(args: {\n kind: SeoFieldKind;\n range: RangeOverride | undefined;\n}): FieldHook {\n return async ({ value, data, collection, req, operation }) => {\n if (!isEmpty(value)) return value;\n if (operation !== \"create\" && operation !== \"update\") return value;\n\n const status = (data as { _status?: string } | undefined)?._status;\n if (status && status !== \"published\") return value;\n\n const config = getPluginConfig();\n const apiKey = resolveApiKey(config.generation);\n if (!apiKey) return value;\n\n const slug = collection?.slug;\n const seoCfg = config.collections.find((c: SeoCollectionConfig) => c.slug === slug);\n const extractor = seoCfg?.serverExtractContent;\n if (!extractor || !slug) return value;\n\n try {\n const localeCode = typeof req.locale === \"string\" ? req.locale : undefined;\n\n const bucket = (req.context[CONTEXT_KEY] ??= {}) as Record<string, string>;\n const cacheKey = `${slug}:${localeCode ?? \"\"}`;\n let contentHtml = bucket[cacheKey];\n if (contentHtml === undefined) {\n const toolkit: ExtractToolkit = {\n resolveDocs: createServerResolveDocs(req.payload, localeCode),\n helpers,\n };\n const ir = await extractor(\n data as Record<string, unknown>,\n { locale: localeCode },\n toolkit\n );\n contentHtml = serialize(ir);\n bucket[cacheKey] = contentHtml;\n }\n\n return await generateForField({\n kind: args.kind,\n contentHtml,\n range: rangeFor(args.kind, args.range),\n locale: localeCode,\n config: config.generation ?? {},\n apiKey,\n });\n } catch (err) {\n req.payload.logger.error(\n `[${PLUGIN_NAME}] on-publish generation failed: ${(err as Error).message}`\n );\n\n return value;\n }\n };\n}\n"],"mappings":"AACA,SAAS,uBAAuB;AAChC,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB,mBAAmB;AAC/C;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,iBAAiB;AAG1B,SAAS,qBAAqB;AAC9B,SAAS,+BAA+B;AACxC,SAAS,wBAAwB;AAGjC,MAAM,UAA0B;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,MAAM,cAAc;AAEpB,SAAS,QAAQ,GAAqB;AACpC,SAAO,OAAO,MAAM,YAAY,EAAE,KAAK,EAAE,WAAW;AACtD;AAEA,SAAS,SAAS,MAAoB,UAAqC;AACzE,QAAM,OAAO,SAAS,UAAU,cAAc;AAE9C,SAAO;AAAA,IACL,KAAK,UAAU,OAAO,KAAK;AAAA,IAC3B,KAAK,UAAU,OAAO,KAAK;AAAA,IAC3B,MAAO,SAAS,UAAU,OAAO;AAAA,EACnC;AACF;AAEO,SAAS,0BAA0B,MAG5B;AACZ,SAAO,OAAO,EAAE,OAAO,MAAM,YAAY,KAAK,UAAU,MAAM;AAC5D,QAAI,CAAC,QAAQ,KAAK;AAAG,aAAO;AAC5B,QAAI,cAAc,YAAY,cAAc;AAAU,aAAO;AAE7D,UAAM,SAAU,MAA2C;AAC3D,QAAI,UAAU,WAAW;AAAa,aAAO;AAE7C,UAAM,SAAS,gBAAgB;AAC/B,UAAM,SAAS,cAAc,OAAO,UAAU;AAC9C,QAAI,CAAC;AAAQ,aAAO;AAEpB,UAAM,OAAO,YAAY;AACzB,UAAM,SAAS,OAAO,YAAY,KAAK,CAAC,MAA2B,EAAE,SAAS,IAAI;AAClF,UAAM,YAAY,QAAQ;AAC1B,QAAI,CAAC,aAAa,CAAC;AAAM,aAAO;AAEhC,QAAI;AACF,YAAM,aAAa,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAEjE,YAAM,SAAU,IAAI,QAAQ,WAAW,MAAM,CAAC;AAC9C,YAAM,WAAW,GAAG,IAAI,IAAI,cAAc,EAAE;AAC5C,UAAI,cAAc,OAAO,QAAQ;AACjC,UAAI,gBAAgB,QAAW;AAC7B,cAAM,UAA0B;AAAA,UAC9B,aAAa,wBAAwB,IAAI,SAAS,UAAU;AAAA,UAC5D;AAAA,QACF;AACA,cAAM,KAAK,MAAM;AAAA,UACf;AAAA,UACA,EAAE,QAAQ,WAAW;AAAA,UACrB;AAAA,QACF;AACA,sBAAc,UAAU,EAAE;AAC1B,eAAO,QAAQ,IAAI;AAAA,MACrB;AAEA,aAAO,MAAM,iBAAiB;AAAA,QAC5B,MAAM,KAAK;AAAA,QACX;AAAA,QACA,OAAO,SAAS,KAAK,MAAM,KAAK,KAAK;AAAA,QACrC,QAAQ;AAAA,QACR,QAAQ,OAAO,cAAc,CAAC;AAAA,QAC9B;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,UAAI,QAAQ,OAAO;AAAA,QACjB,IAAI,WAAW,mCAAoC,IAAc,OAAO;AAAA,MAC1E;AAEA,aAAO;AAAA,IACT;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,23 @@
1
+ import type { TextareaField, TextField } from "payload";
2
+ import type { RangeOverride } from "../measure/measure";
3
+ import type { SeoFieldKind } from "../server/generate/prompts";
4
+ export interface SeoTextFieldOptions {
5
+ name: string;
6
+ kind: SeoFieldKind;
7
+ label?: TextField["label"];
8
+ required?: boolean;
9
+ localized?: boolean;
10
+ admin?: TextField["admin"];
11
+ /** Show the manual Generate button.
12
+ * @default false
13
+ */
14
+ showButton?: boolean;
15
+ /** Generate on publish when the field is empty
16
+ * @default false
17
+ */
18
+ generateOnPublish?: boolean;
19
+ /** Length range override in the kind's unit (px for title, chars for description). */
20
+ range?: RangeOverride;
21
+ }
22
+ export declare function seoTextField(options: SeoTextFieldOptions): TextField | TextareaField;
23
+ //# sourceMappingURL=seoTextField.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seoTextField.d.ts","sourceRoot":"","sources":["../../src/fields/seoTextField.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAGxD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,4BAA4B,CAAC;AAE/D,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,KAAK,CAAC,EAAE,SAAS,CAAC,OAAO,CAAC,CAAC;IAC3B;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB;;OAEG;IACH,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,sFAAsF;IACtF,KAAK,CAAC,EAAE,aAAa,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,SAAS,GAAG,aAAa,CA2CpF"}
@@ -0,0 +1,43 @@
1
+ import { getComponentPath } from "../utils/config/getComponentPath";
2
+ import { makeGenerateOnPublishHook } from "./onPublishHook";
3
+ function seoTextField(options) {
4
+ const { name, kind, label, required, localized, admin, range } = options;
5
+ const showButton = options.showButton ?? false;
6
+ const generateOnPublish = options.generateOnPublish ?? false;
7
+ const common = {
8
+ name,
9
+ ...label === void 0 ? {} : { label },
10
+ ...required === void 0 ? {} : { required },
11
+ ...localized === void 0 ? {} : { localized },
12
+ admin: {
13
+ ...admin,
14
+ components: {
15
+ ...admin?.components,
16
+ Field: {
17
+ path: getComponentPath("components/SeoField", "SeoField"),
18
+ clientProps: {
19
+ kind,
20
+ showButton,
21
+ generateOnPublish,
22
+ range
23
+ }
24
+ }
25
+ }
26
+ }
27
+ };
28
+ const field = kind === "description" ? { ...common, type: "textarea" } : { ...common, type: "text" };
29
+ if (generateOnPublish) {
30
+ field.hooks = {
31
+ ...field.hooks,
32
+ beforeChange: [
33
+ ...field.hooks?.beforeChange ?? [],
34
+ makeGenerateOnPublishHook({ kind, range })
35
+ ]
36
+ };
37
+ }
38
+ return field;
39
+ }
40
+ export {
41
+ seoTextField
42
+ };
43
+ //# sourceMappingURL=seoTextField.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/fields/seoTextField.ts"],"sourcesContent":["import type { TextareaField, TextField } from \"payload\";\nimport { getComponentPath } from \"../utils/config/getComponentPath\";\nimport { makeGenerateOnPublishHook } from \"./onPublishHook\";\nimport type { RangeOverride } from \"../measure/measure\";\nimport type { SeoFieldKind } from \"../server/generate/prompts\";\n\nexport interface SeoTextFieldOptions {\n name: string;\n kind: SeoFieldKind;\n label?: TextField[\"label\"];\n required?: boolean;\n localized?: boolean;\n admin?: TextField[\"admin\"];\n /** Show the manual Generate button.\n * @default false\n */\n showButton?: boolean;\n /** Generate on publish when the field is empty\n * @default false\n */\n generateOnPublish?: boolean;\n /** Length range override in the kind's unit (px for title, chars for description). */\n range?: RangeOverride;\n}\n\nexport function seoTextField(options: SeoTextFieldOptions): TextField | TextareaField {\n const { name, kind, label, required, localized, admin, range } = options;\n const showButton = options.showButton ?? false;\n const generateOnPublish = options.generateOnPublish ?? false;\n\n const common = {\n name,\n ...(label === undefined ? {} : { label }),\n ...(required === undefined ? {} : { required }),\n ...(localized === undefined ? {} : { localized }),\n admin: {\n ...admin,\n components: {\n ...admin?.components,\n Field: {\n path: getComponentPath(\"components/SeoField\", \"SeoField\"),\n clientProps: {\n kind,\n showButton,\n generateOnPublish,\n range,\n },\n },\n },\n },\n };\n\n const field: TextField | TextareaField =\n kind === \"description\"\n ? ({ ...common, type: \"textarea\" } as TextareaField)\n : ({ ...common, type: \"text\" } as TextField);\n\n if (generateOnPublish) {\n field.hooks = {\n ...field.hooks,\n beforeChange: [\n ...(field.hooks?.beforeChange ?? []),\n makeGenerateOnPublishHook({ kind, range }),\n ],\n };\n }\n\n return field;\n}\n"],"mappings":"AACA,SAAS,wBAAwB;AACjC,SAAS,iCAAiC;AAuBnC,SAAS,aAAa,SAAyD;AACpF,QAAM,EAAE,MAAM,MAAM,OAAO,UAAU,WAAW,OAAO,MAAM,IAAI;AACjE,QAAM,aAAa,QAAQ,cAAc;AACzC,QAAM,oBAAoB,QAAQ,qBAAqB;AAEvD,QAAM,SAAS;AAAA,IACb;AAAA,IACA,GAAI,UAAU,SAAY,CAAC,IAAI,EAAE,MAAM;AAAA,IACvC,GAAI,aAAa,SAAY,CAAC,IAAI,EAAE,SAAS;AAAA,IAC7C,GAAI,cAAc,SAAY,CAAC,IAAI,EAAE,UAAU;AAAA,IAC/C,OAAO;AAAA,MACL,GAAG;AAAA,MACH,YAAY;AAAA,QACV,GAAG,OAAO;AAAA,QACV,OAAO;AAAA,UACL,MAAM,iBAAiB,uBAAuB,UAAU;AAAA,UACxD,aAAa;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QACJ,SAAS,gBACJ,EAAE,GAAG,QAAQ,MAAM,WAAW,IAC9B,EAAE,GAAG,QAAQ,MAAM,OAAO;AAEjC,MAAI,mBAAmB;AACrB,UAAM,QAAQ;AAAA,MACZ,GAAG,MAAM;AAAA,MACT,cAAc;AAAA,QACZ,GAAI,MAAM,OAAO,gBAAgB,CAAC;AAAA,QAClC,0BAA0B,EAAE,MAAM,MAAM,CAAC;AAAA,MAC3C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;","names":[]}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
1
  export { seoPlugin } from "./plugin";
2
- export type { ContentExtractor, SeoPluginConfig, SeoCollectionConfig, SeoFieldPaths, SeoSiteConfig, ContentSelection } from "./types/config";
2
+ export { seoTextField } from "./fields/seoTextField";
3
+ export type { SeoTextFieldOptions } from "./fields/seoTextField";
4
+ export type { ContentExtractor, SeoPluginConfig, SeoCollectionConfig, SeoFieldPaths, SeoSiteConfig, SeoGenerationConfig, } from "./types/config";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,YAAY,EAAE,gBAAgB,EAAE,eAAe,EAAE,mBAAmB,EAAE,aAAa,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AACrC,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AACrD,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AACjE,YAAY,EACV,gBAAgB,EAChB,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,mBAAmB,GACpB,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { seoPlugin } from "./plugin";
2
+ import { seoTextField } from "./fields/seoTextField";
2
3
  export {
3
- seoPlugin
4
+ seoPlugin,
5
+ seoTextField
4
6
  };
5
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { seoPlugin } from \"./plugin\";\nexport type { ContentExtractor, SeoPluginConfig, SeoCollectionConfig, SeoFieldPaths, SeoSiteConfig, ContentSelection } from \"./types/config\";\n"],"mappings":"AAAA,SAAS,iBAAiB;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { seoPlugin } from \"./plugin\";\nexport { seoTextField } from \"./fields/seoTextField\";\nexport type { SeoTextFieldOptions } from \"./fields/seoTextField\";\nexport type {\n ContentExtractor,\n SeoPluginConfig,\n SeoCollectionConfig,\n SeoFieldPaths,\n SeoSiteConfig,\n SeoGenerationConfig,\n} from \"./types/config\";\n"],"mappings":"AAAA,SAAS,iBAAiB;AAC1B,SAAS,oBAAoB;","names":[]}
@@ -0,0 +1,16 @@
1
+ export type LengthStatus = "good" | "short" | "long";
2
+ export type LengthUnit = "px" | "char";
3
+ export interface Measurement {
4
+ unit: LengthUnit;
5
+ value: number;
6
+ min: number;
7
+ max: number;
8
+ status: LengthStatus;
9
+ }
10
+ export interface RangeOverride {
11
+ min?: number;
12
+ max?: number;
13
+ }
14
+ export declare function measureTitle(text: string, range?: RangeOverride): Measurement;
15
+ export declare function measureDescription(text: string, range?: RangeOverride): Measurement;
16
+ //# sourceMappingURL=measure.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"measure.d.ts","sourceRoot":"","sources":["../../src/measure/measure.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AACrD,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,MAAM,CAAC;AAEvC,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,UAAU,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,YAAY,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAQD,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,WAAW,CAY7E;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,aAAa,GAAG,WAAW,CAYnF"}