@focus-reactive/payload-plugin-seo 1.6.1 → 1.8.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 (153) hide show
  1. package/README.md +12 -1
  2. package/dist/admin.css +232 -17
  3. package/dist/components/SeoButton/SeoButtonInner.d.ts.map +1 -1
  4. package/dist/components/SeoButton/SeoButtonInner.js +46 -16
  5. package/dist/components/SeoButton/SeoButtonInner.js.map +1 -1
  6. package/dist/components/SeoDrawer/build-analysis-input.d.ts +2 -2
  7. package/dist/components/SeoDrawer/build-analysis-input.d.ts.map +1 -1
  8. package/dist/components/SeoDrawer/build-analysis-input.js +1 -1
  9. package/dist/components/SeoDrawer/build-analysis-input.js.map +1 -1
  10. package/dist/components/SeoDrawer/buildInput.d.ts +3 -3
  11. package/dist/components/SeoDrawer/buildInput.d.ts.map +1 -1
  12. package/dist/components/SeoDrawer/buildInput.js +3 -2
  13. package/dist/components/SeoDrawer/buildInput.js.map +1 -1
  14. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.d.ts +7 -0
  15. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.d.ts.map +1 -0
  16. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.js +26 -0
  17. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.js.map +1 -0
  18. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.d.ts +8 -0
  19. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.d.ts.map +1 -0
  20. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.js +17 -0
  21. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.js.map +1 -0
  22. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.d.ts.map +1 -1
  23. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.js +2 -0
  24. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.js.map +1 -1
  25. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.d.ts +1 -0
  26. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.d.ts.map +1 -1
  27. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.js +14 -1
  28. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.js.map +1 -1
  29. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.d.ts +7 -0
  30. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.d.ts.map +1 -0
  31. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.js +26 -0
  32. package/dist/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.js.map +1 -0
  33. package/dist/components/SeoDrawer/components/HeadingsSection/index.d.ts.map +1 -1
  34. package/dist/components/SeoDrawer/components/HeadingsSection/index.js +17 -6
  35. package/dist/components/SeoDrawer/components/HeadingsSection/index.js.map +1 -1
  36. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts +1 -1
  37. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts.map +1 -1
  38. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js +10 -3
  39. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js.map +1 -1
  40. package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts +2 -1
  41. package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts.map +1 -1
  42. package/dist/components/SeoDrawer/components/SerpPreview/index.js +8 -2
  43. package/dist/components/SeoDrawer/components/SerpPreview/index.js.map +1 -1
  44. package/dist/components/SeoDrawer/components/SerpPreview/variants.d.ts +6 -6
  45. package/dist/components/SeoDrawer/components/TabWrapper.d.ts +6 -0
  46. package/dist/components/SeoDrawer/components/TabWrapper.d.ts.map +1 -0
  47. package/dist/components/SeoDrawer/components/TabWrapper.js +8 -0
  48. package/dist/components/SeoDrawer/components/TabWrapper.js.map +1 -0
  49. package/dist/components/SeoDrawer/index.d.ts +11 -4
  50. package/dist/components/SeoDrawer/index.d.ts.map +1 -1
  51. package/dist/components/SeoDrawer/index.js +38 -16
  52. package/dist/components/SeoDrawer/index.js.map +1 -1
  53. package/dist/components/SeoDrawer/keyphraseState.d.ts +16 -0
  54. package/dist/components/SeoDrawer/keyphraseState.d.ts.map +1 -0
  55. package/dist/components/SeoDrawer/keyphraseState.js +73 -0
  56. package/dist/components/SeoDrawer/keyphraseState.js.map +1 -0
  57. package/dist/components/SeoDrawer/keyphraseStorage.d.ts +5 -0
  58. package/dist/components/SeoDrawer/keyphraseStorage.d.ts.map +1 -0
  59. package/dist/components/SeoDrawer/keyphraseStorage.js +60 -0
  60. package/dist/components/SeoDrawer/keyphraseStorage.js.map +1 -0
  61. package/dist/components/SeoDrawer/tabs/InclusiveTab.d.ts.map +1 -1
  62. package/dist/components/SeoDrawer/tabs/InclusiveTab.js +3 -2
  63. package/dist/components/SeoDrawer/tabs/InclusiveTab.js.map +1 -1
  64. package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts +14 -7
  65. package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts.map +1 -1
  66. package/dist/components/SeoDrawer/tabs/KeyphraseTab.js +110 -69
  67. package/dist/components/SeoDrawer/tabs/KeyphraseTab.js.map +1 -1
  68. package/dist/components/SeoDrawer/tabs/OnPageTab.d.ts.map +1 -1
  69. package/dist/components/SeoDrawer/tabs/OnPageTab.js +3 -2
  70. package/dist/components/SeoDrawer/tabs/OnPageTab.js.map +1 -1
  71. package/dist/components/SeoDrawer/tabs/ReadabilityTab.d.ts.map +1 -1
  72. package/dist/components/SeoDrawer/tabs/ReadabilityTab.js +3 -2
  73. package/dist/components/SeoDrawer/tabs/ReadabilityTab.js.map +1 -1
  74. package/dist/components/SeoDrawer/tabs/SerpTab.d.ts +2 -1
  75. package/dist/components/SeoDrawer/tabs/SerpTab.d.ts.map +1 -1
  76. package/dist/components/SeoDrawer/tabs/SerpTab.js +14 -3
  77. package/dist/components/SeoDrawer/tabs/SerpTab.js.map +1 -1
  78. package/dist/components/SeoDrawer/tabs/VitalsTab.d.ts.map +1 -1
  79. package/dist/components/SeoDrawer/tabs/VitalsTab.js +7 -3
  80. package/dist/components/SeoDrawer/tabs/VitalsTab.js.map +1 -1
  81. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts +21 -0
  82. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts.map +1 -0
  83. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js +38 -0
  84. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js.map +1 -0
  85. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts +25 -0
  86. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts.map +1 -0
  87. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js +127 -0
  88. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js.map +1 -0
  89. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts +12 -0
  90. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts.map +1 -0
  91. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js +54 -0
  92. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js.map +1 -0
  93. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts +7 -0
  94. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts.map +1 -0
  95. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js +93 -0
  96. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js.map +1 -0
  97. package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts +2 -0
  98. package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts.map +1 -0
  99. package/dist/components/SeoDrawer/tabs/keyphrase/icons.js +7 -0
  100. package/dist/components/SeoDrawer/tabs/keyphrase/icons.js.map +1 -0
  101. package/dist/components/SeoDrawer/useAnalysis.d.ts +0 -1
  102. package/dist/components/SeoDrawer/useAnalysis.d.ts.map +1 -1
  103. package/dist/components/SeoDrawer/useAnalysis.js +0 -3
  104. package/dist/components/SeoDrawer/useAnalysis.js.map +1 -1
  105. package/dist/components/SeoDrawer/useKeyphrases.d.ts +18 -0
  106. package/dist/components/SeoDrawer/useKeyphrases.d.ts.map +1 -0
  107. package/dist/components/SeoDrawer/useKeyphrases.js +78 -0
  108. package/dist/components/SeoDrawer/useKeyphrases.js.map +1 -0
  109. package/dist/components/SeoDrawer/useLiveDocument.d.ts +4 -3
  110. package/dist/components/SeoDrawer/useLiveDocument.d.ts.map +1 -1
  111. package/dist/components/SeoDrawer/useLiveDocument.js +17 -9
  112. package/dist/components/SeoDrawer/useLiveDocument.js.map +1 -1
  113. package/dist/components/SeoDrawer/variants.d.ts +1 -1
  114. package/dist/components/SeoDrawer/variants.d.ts.map +1 -1
  115. package/dist/components/SeoDrawer/variants.js +2 -1
  116. package/dist/components/SeoDrawer/variants.js.map +1 -1
  117. package/dist/constants/index.d.ts +1 -0
  118. package/dist/constants/index.d.ts.map +1 -1
  119. package/dist/constants/index.js +2 -0
  120. package/dist/constants/index.js.map +1 -1
  121. package/dist/engine/buildPaper.d.ts +2 -2
  122. package/dist/engine/buildPaper.d.ts.map +1 -1
  123. package/dist/engine/buildPaper.js +4 -2
  124. package/dist/engine/buildPaper.js.map +1 -1
  125. package/dist/engine/runAnalysis/index.d.ts.map +1 -1
  126. package/dist/engine/runAnalysis/index.js +7 -0
  127. package/dist/engine/runAnalysis/index.js.map +1 -1
  128. package/dist/engine/runAnalysis/services/derive-related.d.ts +4 -0
  129. package/dist/engine/runAnalysis/services/derive-related.d.ts.map +1 -0
  130. package/dist/engine/runAnalysis/services/derive-related.js +16 -0
  131. package/dist/engine/runAnalysis/services/derive-related.js.map +1 -0
  132. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.d.ts +8 -0
  133. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.d.ts.map +1 -0
  134. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.js +37 -0
  135. package/dist/engine/runAnalysis/services/derive-vitals/heading-issues.js.map +1 -0
  136. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.d.ts.map +1 -1
  137. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.js +7 -3
  138. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.js.map +1 -1
  139. package/dist/engine/types/analysis.d.ts +23 -0
  140. package/dist/engine/types/analysis.d.ts.map +1 -1
  141. package/dist/ui/Button.d.ts +9 -0
  142. package/dist/ui/Button.d.ts.map +1 -0
  143. package/dist/ui/Button.js +34 -0
  144. package/dist/ui/Button.js.map +1 -0
  145. package/dist/ui/ScoreRing.d.ts +17 -4
  146. package/dist/ui/ScoreRing.d.ts.map +1 -1
  147. package/dist/ui/ScoreRing.js +38 -14
  148. package/dist/ui/ScoreRing.js.map +1 -1
  149. package/package.json +1 -1
  150. package/dist/components/SeoDrawer/keyphrasePending.d.ts +0 -2
  151. package/dist/components/SeoDrawer/keyphrasePending.d.ts.map +0 -1
  152. package/dist/components/SeoDrawer/keyphrasePending.js +0 -9
  153. package/dist/components/SeoDrawer/keyphrasePending.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"SeoButtonInner.d.ts","sourceRoot":"","sources":["../../../src/components/SeoButton/SeoButtonInner.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAID,wBAAgB,cAAc,CAAC,EAC7B,cAAc,EACd,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,kBAAkB,GACnB,EAAE,cAAc,2CAqDhB"}
1
+ {"version":3,"file":"SeoButtonInner.d.ts","sourceRoot":"","sources":["../../../src/components/SeoButton/SeoButtonInner.tsx"],"names":[],"mappings":"AAWA,MAAM,WAAW,cAAc;IAC7B,cAAc,EAAE,MAAM,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAA;KAAE,CAAC;IAC5D,gBAAgB,EAAE,MAAM,EAAE,CAAC;CAC5B;AAID,wBAAgB,cAAc,CAAC,EAC7B,cAAc,EACd,MAAM,EACN,IAAI,EACJ,gBAAgB,EAChB,kBAAkB,GACnB,EAAE,cAAc,2CAqFhB"}
@@ -1,12 +1,12 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
- import { Button, useModal } from "@payloadcms/ui";
3
+ import { Button, useDocumentInfo, useLocale, useModal } from "@payloadcms/ui";
4
4
  import { Gauge } from "lucide-react";
5
5
  import { useCallback, useState } from "react";
6
6
  import { ScoreBadge } from "./ScoreBadge";
7
7
  import { SeoDrawer } from "../SeoDrawer";
8
- import { isKeyphrasePending } from "../SeoDrawer/keyphrasePending";
9
8
  import { useAnalysis } from "../SeoDrawer/useAnalysis";
9
+ import { useKeyphrases } from "../SeoDrawer/useKeyphrases";
10
10
  import { useLiveDocument } from "../SeoDrawer/useLiveDocument";
11
11
  const DRAWER_SLUG = "seo-analytics-drawer";
12
12
  function SeoButtonInner({
@@ -17,25 +17,49 @@ function SeoButtonInner({
17
17
  extractContentPath
18
18
  }) {
19
19
  const { openModal } = useModal();
20
- const [keyphrase, setKeyphrase] = useState("");
20
+ const { id } = useDocumentInfo();
21
+ const locale = useLocale();
22
+ const docId = id == null ? "" : String(id);
23
+ const localeCode = locale?.code ?? "en";
24
+ const {
25
+ addRelated,
26
+ addSynonym,
27
+ isDuplicate,
28
+ keyphrases,
29
+ remove: removeKeyphrase,
30
+ removeSynonym,
31
+ setFocus,
32
+ updateText
33
+ } = useKeyphrases({ collectionSlug, docId, localeCode });
34
+ const [selectedId, setSelectedId] = useState(null);
21
35
  const { signature, getInput } = useLiveDocument({
22
36
  collectionSlug,
23
37
  fields,
24
- site: { name: site.name, baseUrl: site.baseUrl },
25
- keyphrase,
38
+ site: {
39
+ name: site.name,
40
+ baseUrl: site.baseUrl
41
+ },
42
+ keyphrases,
26
43
  extractContentPath
27
44
  });
28
- const { result, analyzing, analyzedKeyphrase, analyzeNow } = useAnalysis({
29
- getInput,
30
- signature,
31
- supportedLocales
32
- });
33
- const keyphrasePending = isKeyphrasePending(keyphrase, analyzedKeyphrase);
45
+ const { result, analyzing, analyzeNow } = useAnalysis({ getInput, signature, supportedLocales });
34
46
  const overall = result?.overall ?? null;
35
47
  const open = useCallback(() => {
36
48
  analyzeNow();
37
49
  openModal(DRAWER_SLUG);
38
50
  }, [analyzeNow, openModal]);
51
+ const handleAddRelated = useCallback(() => {
52
+ const newId = addRelated();
53
+ if (newId)
54
+ setSelectedId(newId);
55
+ }, [addRelated]);
56
+ const handleRemove = useCallback(
57
+ (id2) => {
58
+ removeKeyphrase(id2);
59
+ setSelectedId((current) => current === id2 ? null : current);
60
+ },
61
+ [removeKeyphrase]
62
+ );
39
63
  return /* @__PURE__ */ jsxs("span", { className: "relative inline-flex", children: [
40
64
  /* @__PURE__ */ jsx(
41
65
  Button,
@@ -56,13 +80,19 @@ function SeoButtonInner({
56
80
  /* @__PURE__ */ jsx(
57
81
  SeoDrawer,
58
82
  {
59
- analyzeNow,
60
- analyzing,
61
83
  drawerSlug: DRAWER_SLUG,
62
- keyphrase,
63
- keyphrasePending,
84
+ keyphrases,
85
+ selectedId,
86
+ onSelect: setSelectedId,
64
87
  result,
65
- setKeyphrase,
88
+ analyzing,
89
+ onAddRelated: handleAddRelated,
90
+ onTextChange: updateText,
91
+ onAddSynonym: addSynonym,
92
+ onRemoveSynonym: removeSynonym,
93
+ onRemove: handleRemove,
94
+ onSetFocus: setFocus,
95
+ isDuplicate,
66
96
  site
67
97
  }
68
98
  )
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/components/SeoButton/SeoButtonInner.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, useModal } from \"@payloadcms/ui\";\nimport { Gauge } from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { ScoreBadge } from \"./ScoreBadge\";\nimport { SeoDrawer } from \"../SeoDrawer\";\nimport { isKeyphrasePending } from \"../SeoDrawer/keyphrasePending\";\nimport { useAnalysis } from \"../SeoDrawer/useAnalysis\";\nimport { useLiveDocument } from \"../SeoDrawer/useLiveDocument\";\n\nexport interface SeoButtonProps {\n collectionSlug: string;\n fields: Record<string, string>;\n extractContentPath: string;\n site: { name: string; baseUrl: string; faviconUrl: string };\n supportedLocales: string[];\n}\n\nconst DRAWER_SLUG = \"seo-analytics-drawer\";\n\nexport function SeoButtonInner({\n collectionSlug,\n fields,\n site,\n supportedLocales,\n extractContentPath,\n}: SeoButtonProps) {\n const { openModal } = useModal();\n const [keyphrase, setKeyphrase] = useState(\"\");\n\n const { signature, getInput } = useLiveDocument({\n collectionSlug,\n fields,\n site: { name: site.name, baseUrl: site.baseUrl },\n keyphrase,\n extractContentPath,\n });\n const { result, analyzing, analyzedKeyphrase, analyzeNow } = useAnalysis({\n getInput,\n signature,\n supportedLocales,\n });\n\n const keyphrasePending = isKeyphrasePending(keyphrase, analyzedKeyphrase);\n const overall = result?.overall ?? null;\n\n const open = useCallback(() => {\n analyzeNow();\n openModal(DRAWER_SLUG);\n }, [analyzeNow, openModal]);\n\n return (\n <span className=\"relative inline-flex\">\n <Button\n aria-label=\"SEO Analytics\"\n buttonStyle=\"none\"\n className=\"seo-doc-btn m-0 w-[calc(var(--base)*1.6)] h-[calc(var(--base)*1.6)] inline-flex items-center justify-center border border-[var(--theme-elevation-100)] rounded-rs bg-transparent text-neutral-800 transition-[border-color,background-color] duration-100 hover:border-neutral-300 hover:bg-neutral-100\"\n extraButtonProps={{ title: undefined }}\n icon={<Gauge />}\n iconStyle=\"without-border\"\n margin={false}\n onClick={open}\n size=\"small\"\n tooltip=\"SEO Analytics\"\n />\n {overall && <ScoreBadge score={overall.seoScore} status={overall.status} />}\n\n <SeoDrawer\n analyzeNow={analyzeNow}\n analyzing={analyzing}\n drawerSlug={DRAWER_SLUG}\n keyphrase={keyphrase}\n keyphrasePending={keyphrasePending}\n result={result}\n setKeyphrase={setKeyphrase}\n site={site}\n />\n </span>\n );\n}\n"],"mappings":";AAqDI,SAMU,KANV;AAnDJ,SAAS,QAAQ,gBAAgB;AACjC,SAAS,aAAa;AACtB,SAAS,aAAa,gBAAgB;AACtC,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,0BAA0B;AACnC,SAAS,mBAAmB;AAC5B,SAAS,uBAAuB;AAUhC,MAAM,cAAc;AAEb,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,EAAE,UAAU,IAAI,SAAS;AAC/B,QAAM,CAAC,WAAW,YAAY,IAAI,SAAS,EAAE;AAE7C,QAAM,EAAE,WAAW,SAAS,IAAI,gBAAgB;AAAA,IAC9C;AAAA,IACA;AAAA,IACA,MAAM,EAAE,MAAM,KAAK,MAAM,SAAS,KAAK,QAAQ;AAAA,IAC/C;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,EAAE,QAAQ,WAAW,mBAAmB,WAAW,IAAI,YAAY;AAAA,IACvE;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,QAAM,mBAAmB,mBAAmB,WAAW,iBAAiB;AACxE,QAAM,UAAU,QAAQ,WAAW;AAEnC,QAAM,OAAO,YAAY,MAAM;AAC7B,eAAW;AACX,cAAU,WAAW;AAAA,EACvB,GAAG,CAAC,YAAY,SAAS,CAAC;AAE1B,SACE,qBAAC,UAAK,WAAU,wBACd;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,cAAW;AAAA,QACX,aAAY;AAAA,QACZ,WAAU;AAAA,QACV,kBAAkB,EAAE,OAAO,OAAU;AAAA,QACrC,MAAM,oBAAC,SAAM;AAAA,QACb,WAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAK;AAAA,QACL,SAAQ;AAAA;AAAA,IACV;AAAA,IACC,WAAW,oBAAC,cAAW,OAAO,QAAQ,UAAU,QAAQ,QAAQ,QAAQ;AAAA,IAEzE;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA;AAAA,QACA,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../src/components/SeoButton/SeoButtonInner.tsx"],"sourcesContent":["\"use client\";\n\nimport { Button, useDocumentInfo, useLocale, useModal } from \"@payloadcms/ui\";\nimport { Gauge } from \"lucide-react\";\nimport { useCallback, useState } from \"react\";\nimport { ScoreBadge } from \"./ScoreBadge\";\nimport { SeoDrawer } from \"../SeoDrawer\";\nimport { useAnalysis } from \"../SeoDrawer/useAnalysis\";\nimport { useKeyphrases } from \"../SeoDrawer/useKeyphrases\";\nimport { useLiveDocument } from \"../SeoDrawer/useLiveDocument\";\n\nexport interface SeoButtonProps {\n collectionSlug: string;\n fields: Record<string, string>;\n extractContentPath: string;\n site: { name: string; baseUrl: string; faviconUrl: string };\n supportedLocales: string[];\n}\n\nconst DRAWER_SLUG = \"seo-analytics-drawer\";\n\nexport function SeoButtonInner({\n collectionSlug,\n fields,\n site,\n supportedLocales,\n extractContentPath,\n}: SeoButtonProps) {\n const { openModal } = useModal();\n const { id } = useDocumentInfo();\n const locale = useLocale();\n\n const docId = id == null ? \"\" : String(id);\n const localeCode = locale?.code ?? \"en\";\n\n const {\n addRelated,\n addSynonym,\n isDuplicate,\n keyphrases,\n remove: removeKeyphrase,\n removeSynonym,\n setFocus,\n updateText,\n } = useKeyphrases({ collectionSlug, docId, localeCode });\n const [selectedId, setSelectedId] = useState<string | null>(null);\n const { signature, getInput } = useLiveDocument({\n collectionSlug,\n fields,\n site: {\n name: site.name,\n baseUrl: site.baseUrl,\n },\n keyphrases,\n extractContentPath,\n });\n const { result, analyzing, analyzeNow } = useAnalysis({ getInput, signature, supportedLocales });\n\n const overall = result?.overall ?? null;\n\n const open = useCallback(() => {\n analyzeNow();\n openModal(DRAWER_SLUG);\n }, [analyzeNow, openModal]);\n\n const handleAddRelated = useCallback(() => {\n const newId = addRelated();\n if (newId) setSelectedId(newId);\n }, [addRelated]);\n\n const handleRemove = useCallback(\n (id: string) => {\n removeKeyphrase(id);\n setSelectedId((current) => (current === id ? null : current));\n },\n [removeKeyphrase]\n );\n\n return (\n <span className=\"relative inline-flex\">\n <Button\n aria-label=\"SEO Analytics\"\n buttonStyle=\"none\"\n className=\"seo-doc-btn m-0 w-[calc(var(--base)*1.6)] h-[calc(var(--base)*1.6)] inline-flex items-center justify-center border border-[var(--theme-elevation-100)] rounded-rs bg-transparent text-neutral-800 transition-[border-color,background-color] duration-100 hover:border-neutral-300 hover:bg-neutral-100\"\n extraButtonProps={{ title: undefined }}\n icon={<Gauge />}\n iconStyle=\"without-border\"\n margin={false}\n onClick={open}\n size=\"small\"\n tooltip=\"SEO Analytics\"\n />\n {overall && <ScoreBadge score={overall.seoScore} status={overall.status} />}\n\n <SeoDrawer\n drawerSlug={DRAWER_SLUG}\n keyphrases={keyphrases}\n selectedId={selectedId}\n onSelect={setSelectedId}\n result={result}\n analyzing={analyzing}\n onAddRelated={handleAddRelated}\n onTextChange={updateText}\n onAddSynonym={addSynonym}\n onRemoveSynonym={removeSynonym}\n onRemove={handleRemove}\n onSetFocus={setFocus}\n isDuplicate={isDuplicate}\n site={site}\n />\n </span>\n );\n}\n"],"mappings":";AA+EI,SAMU,KANV;AA7EJ,SAAS,QAAQ,iBAAiB,WAAW,gBAAgB;AAC7D,SAAS,aAAa;AACtB,SAAS,aAAa,gBAAgB;AACtC,SAAS,kBAAkB;AAC3B,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,qBAAqB;AAC9B,SAAS,uBAAuB;AAUhC,MAAM,cAAc;AAEb,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAmB;AACjB,QAAM,EAAE,UAAU,IAAI,SAAS;AAC/B,QAAM,EAAE,GAAG,IAAI,gBAAgB;AAC/B,QAAM,SAAS,UAAU;AAEzB,QAAM,QAAQ,MAAM,OAAO,KAAK,OAAO,EAAE;AACzC,QAAM,aAAa,QAAQ,QAAQ;AAEnC,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR;AAAA,IACA;AAAA,IACA;AAAA,EACF,IAAI,cAAc,EAAE,gBAAgB,OAAO,WAAW,CAAC;AACvD,QAAM,CAAC,YAAY,aAAa,IAAI,SAAwB,IAAI;AAChE,QAAM,EAAE,WAAW,SAAS,IAAI,gBAAgB;AAAA,IAC9C;AAAA,IACA;AAAA,IACA,MAAM;AAAA,MACJ,MAAM,KAAK;AAAA,MACX,SAAS,KAAK;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,EAAE,QAAQ,WAAW,WAAW,IAAI,YAAY,EAAE,UAAU,WAAW,iBAAiB,CAAC;AAE/F,QAAM,UAAU,QAAQ,WAAW;AAEnC,QAAM,OAAO,YAAY,MAAM;AAC7B,eAAW;AACX,cAAU,WAAW;AAAA,EACvB,GAAG,CAAC,YAAY,SAAS,CAAC;AAE1B,QAAM,mBAAmB,YAAY,MAAM;AACzC,UAAM,QAAQ,WAAW;AACzB,QAAI;AAAO,oBAAc,KAAK;AAAA,EAChC,GAAG,CAAC,UAAU,CAAC;AAEf,QAAM,eAAe;AAAA,IACnB,CAACA,QAAe;AACd,sBAAgBA,GAAE;AAClB,oBAAc,CAAC,YAAa,YAAYA,MAAK,OAAO,OAAQ;AAAA,IAC9D;AAAA,IACA,CAAC,eAAe;AAAA,EAClB;AAEA,SACE,qBAAC,UAAK,WAAU,wBACd;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,cAAW;AAAA,QACX,aAAY;AAAA,QACZ,WAAU;AAAA,QACV,kBAAkB,EAAE,OAAO,OAAU;AAAA,QACrC,MAAM,oBAAC,SAAM;AAAA,QACb,WAAU;AAAA,QACV,QAAQ;AAAA,QACR,SAAS;AAAA,QACT,MAAK;AAAA,QACL,SAAQ;AAAA;AAAA,IACV;AAAA,IACC,WAAW,oBAAC,cAAW,OAAO,QAAQ,UAAU,QAAQ,QAAQ,QAAQ;AAAA,IAEzE;AAAA,MAAC;AAAA;AAAA,QACC,YAAY;AAAA,QACZ;AAAA,QACA;AAAA,QACA,UAAU;AAAA,QACV;AAAA,QACA;AAAA,QACA,cAAc;AAAA,QACd,cAAc;AAAA,QACd,cAAc;AAAA,QACd,iBAAiB;AAAA,QACjB,UAAU;AAAA,QACV,YAAY;AAAA,QACZ;AAAA,QACA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;","names":["id"]}
@@ -1,4 +1,4 @@
1
- import type { AnalysisInput } from "../../engine/types/analysis";
1
+ import type { AnalysisInput, KeyphraseInput } from "../../engine/types/analysis";
2
2
  import type { ContentExtractor, SeoFieldPaths } from "../../types/config";
3
3
  export interface BuildAnalysisInputArgs {
4
4
  values: Record<string, unknown>;
@@ -7,7 +7,7 @@ export interface BuildAnalysisInputArgs {
7
7
  } | null | undefined;
8
8
  payloadLocale: string | undefined;
9
9
  apiRoute?: string;
10
- keyphrase: string;
10
+ keyphrases: KeyphraseInput[];
11
11
  fields: SeoFieldPaths;
12
12
  site: {
13
13
  name: string;
@@ -1 +1 @@
1
- {"version":3,"file":"build-analysis-input.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/build-analysis-input.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EACV,gBAAgB,EAIhB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAY7F"}
1
+ {"version":3,"file":"build-analysis-input.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/build-analysis-input.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,KAAK,EACV,gBAAgB,EAIhB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAK5B,MAAM,WAAW,sBAAsB;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,aAAa,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC,SAAS,CAAC,EAAE,gBAAgB,CAAC;CAC9B;AAED,wBAAsB,kBAAkB,CAAC,IAAI,EAAE,sBAAsB,GAAG,OAAO,CAAC,aAAa,CAAC,CAY7F"}
@@ -19,7 +19,7 @@ async function buildAnalysisInput(args) {
19
19
  values: args.values,
20
20
  contentHtml,
21
21
  locale: args.locale,
22
- keyphrase: args.keyphrase,
22
+ keyphrases: args.keyphrases,
23
23
  fields: args.fields,
24
24
  site: args.site
25
25
  });
@@ -1 +1 @@
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":[]}
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, KeyphraseInput } 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 keyphrases: KeyphraseInput[];\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 keyphrases: args.keyphrases,\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,YAAY,KAAK;AAAA,IACjB,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":[]}
@@ -1,4 +1,4 @@
1
- import type { AnalysisInput } from "../../engine/types/analysis";
1
+ import type { AnalysisInput, KeyphraseInput } from "../../engine/types/analysis";
2
2
  import type { SeoFieldPaths } from "../../types/config";
3
3
  export interface BuildInputArgs {
4
4
  values: Record<string, unknown>;
@@ -6,12 +6,12 @@ export interface BuildInputArgs {
6
6
  locale: string | {
7
7
  code?: string;
8
8
  } | null | undefined;
9
- keyphrase: string;
9
+ keyphrases: KeyphraseInput[];
10
10
  fields: SeoFieldPaths;
11
11
  site: {
12
12
  name: string;
13
13
  baseUrl: string;
14
14
  };
15
15
  }
16
- export declare function buildInput({ values, contentHtml, locale, keyphrase, fields, site, }: BuildInputArgs): AnalysisInput;
16
+ export declare function buildInput({ values, contentHtml, locale, keyphrases, fields, site, }: BuildInputArgs): AnalysisInput;
17
17
  //# sourceMappingURL=buildInput.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"buildInput.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/buildInput.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA2BxD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,WAAW,EACX,MAAM,EACN,SAAS,EACT,MAAM,EACN,IAAI,GACL,EAAE,cAAc,GAAG,aAAa,CAkBhC"}
1
+ {"version":3,"file":"buildInput.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/buildInput.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AACjF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AA2BxD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,GAAG,SAAS,CAAC;IACtD,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,MAAM,EAAE,aAAa,CAAC;IACtB,IAAI,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;CACzC;AAED,wBAAgB,UAAU,CAAC,EACzB,MAAM,EACN,WAAW,EACX,MAAM,EACN,UAAU,EACV,MAAM,EACN,IAAI,GACL,EAAE,cAAc,GAAG,aAAa,CAmBhC"}
@@ -15,7 +15,7 @@ function buildInput({
15
15
  values,
16
16
  contentHtml,
17
17
  locale,
18
- keyphrase,
18
+ keyphrases,
19
19
  fields,
20
20
  site
21
21
  }) {
@@ -25,7 +25,8 @@ function buildInput({
25
25
  slug: valueAt(values, fields.slug ?? "slug"),
26
26
  description: valueAt(values, fields.metaDescription),
27
27
  contentHtml,
28
- keyphrase,
28
+ keyphrase: keyphrases[0]?.text ?? "",
29
+ keyphrases,
29
30
  locale: normalizeLocale(locale),
30
31
  site,
31
32
  has: {
@@ -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: 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":[]}
1
+ {"version":3,"sources":["../../../src/components/SeoDrawer/buildInput.ts"],"sourcesContent":["import type { AnalysisInput, KeyphraseInput } 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 keyphrases: KeyphraseInput[];\n fields: SeoFieldPaths;\n site: { name: string; baseUrl: string };\n}\n\nexport function buildInput({\n values,\n contentHtml,\n locale,\n keyphrases,\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: keyphrases[0]?.text ?? \"\",\n keyphrases,\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,WAAW,WAAW,CAAC,GAAG,QAAQ;AAAA,IAClC;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":[]}
@@ -0,0 +1,7 @@
1
+ import type { HeadingDocIssue } from "../../../../engine/types/analysis";
2
+ interface HeadingIssueBannerProps {
3
+ issue: HeadingDocIssue;
4
+ }
5
+ export declare function HeadingIssueBanner({ issue }: HeadingIssueBannerProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=HeadingIssueBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeadingIssueBanner.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mCAAmC,CAAC;AAGzE,UAAU,uBAAuB;IAC/B,KAAK,EAAE,eAAe,CAAC;CACxB;AAED,wBAAgB,kBAAkB,CAAC,EAAE,KAAK,EAAE,EAAE,uBAAuB,2CAiBpE"}
@@ -0,0 +1,26 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { TriangleAlert } from "lucide-react";
4
+ import { describeDocIssue } from "./headingIssueCopy";
5
+ function HeadingIssueBanner({ issue }) {
6
+ const { title, body } = describeDocIssue(issue);
7
+ return /* @__PURE__ */ jsxs("div", { className: "mx-[15px] mt-[12px] flex items-start gap-[9px] rounded-rs border border-seo-bad/25 bg-seo-bad-100 px-[11px] py-[9px]", children: [
8
+ /* @__PURE__ */ jsx(
9
+ TriangleAlert,
10
+ {
11
+ size: 15,
12
+ strokeWidth: 2.2,
13
+ "aria-hidden": "true",
14
+ className: "mt-[1px] flex-none text-seo-bad"
15
+ }
16
+ ),
17
+ /* @__PURE__ */ jsxs("div", { children: [
18
+ /* @__PURE__ */ jsx("div", { className: "text-[12px] font-bold leading-[1.35] text-seo-bad", children: title }),
19
+ /* @__PURE__ */ jsx("div", { className: "mt-[2px] text-[11.5px] leading-[1.4] text-neutral-700", children: body })
20
+ ] })
21
+ ] });
22
+ }
23
+ export {
24
+ HeadingIssueBanner
25
+ };
26
+ //# sourceMappingURL=HeadingIssueBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingIssueBanner.tsx"],"sourcesContent":["\"use client\";\n\nimport { TriangleAlert } from \"lucide-react\";\nimport type { HeadingDocIssue } from \"../../../../engine/types/analysis\";\nimport { describeDocIssue } from \"./headingIssueCopy\";\n\ninterface HeadingIssueBannerProps {\n issue: HeadingDocIssue;\n}\n\nexport function HeadingIssueBanner({ issue }: HeadingIssueBannerProps) {\n const { title, body } = describeDocIssue(issue);\n\n return (\n <div className=\"mx-[15px] mt-[12px] flex items-start gap-[9px] rounded-rs border border-seo-bad/25 bg-seo-bad-100 px-[11px] py-[9px]\">\n <TriangleAlert\n size={15}\n strokeWidth={2.2}\n aria-hidden=\"true\"\n className=\"mt-[1px] flex-none text-seo-bad\"\n />\n <div>\n <div className=\"text-[12px] font-bold leading-[1.35] text-seo-bad\">{title}</div>\n <div className=\"mt-[2px] text-[11.5px] leading-[1.4] text-neutral-700\">{body}</div>\n </div>\n </div>\n );\n}\n"],"mappings":";AAeM,cAMA,YANA;AAbN,SAAS,qBAAqB;AAE9B,SAAS,wBAAwB;AAM1B,SAAS,mBAAmB,EAAE,MAAM,GAA4B;AACrE,QAAM,EAAE,OAAO,KAAK,IAAI,iBAAiB,KAAK;AAE9C,SACE,qBAAC,SAAI,WAAU,wHACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM;AAAA,QACN,aAAa;AAAA,QACb,eAAY;AAAA,QACZ,WAAU;AAAA;AAAA,IACZ;AAAA,IACA,qBAAC,SACC;AAAA,0BAAC,SAAI,WAAU,qDAAqD,iBAAM;AAAA,MAC1E,oBAAC,SAAI,WAAU,yDAAyD,gBAAK;AAAA,OAC/E;AAAA,KACF;AAEJ;","names":[]}
@@ -0,0 +1,8 @@
1
+ interface HeadingIssuePillsProps {
2
+ errors: number;
3
+ warnings: number;
4
+ total: number;
5
+ }
6
+ export declare function HeadingIssuePills({ errors, warnings, total }: HeadingIssuePillsProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=HeadingIssuePills.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeadingIssuePills.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.tsx"],"names":[],"mappings":"AAIA,UAAU,sBAAsB;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,wBAAgB,iBAAiB,CAAC,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,EAAE,sBAAsB,2CAQpF"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { Pill } from "../../../../ui/Pill";
4
+ function countLabel(count, noun) {
5
+ return count === 1 ? `1 ${noun}` : `${count} ${noun}s`;
6
+ }
7
+ function HeadingIssuePills({ errors, warnings, total }) {
8
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[6px]", children: [
9
+ errors > 0 ? /* @__PURE__ */ jsx(Pill, { variant: "bad", children: countLabel(errors, "error") }) : null,
10
+ warnings > 0 ? /* @__PURE__ */ jsx(Pill, { variant: "warn", children: countLabel(warnings, "warning") }) : null,
11
+ /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: total })
12
+ ] });
13
+ }
14
+ export {
15
+ HeadingIssuePills
16
+ };
17
+ //# sourceMappingURL=HeadingIssuePills.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingIssuePills.tsx"],"sourcesContent":["\"use client\";\n\nimport { Pill } from \"../../../../ui/Pill\";\n\ninterface HeadingIssuePillsProps {\n errors: number;\n warnings: number;\n total: number;\n}\n\nfunction countLabel(count: number, noun: string): string {\n return count === 1 ? `1 ${noun}` : `${count} ${noun}s`;\n}\n\nexport function HeadingIssuePills({ errors, warnings, total }: HeadingIssuePillsProps) {\n return (\n <div className=\"flex items-center gap-[6px]\">\n {errors > 0 ? <Pill variant=\"bad\">{countLabel(errors, \"error\")}</Pill> : null}\n {warnings > 0 ? <Pill variant=\"warn\">{countLabel(warnings, \"warning\")}</Pill> : null}\n <Pill variant=\"neutral\">{total}</Pill>\n </div>\n );\n}\n"],"mappings":";AAgBI,SACgB,KADhB;AAdJ,SAAS,YAAY;AAQrB,SAAS,WAAW,OAAe,MAAsB;AACvD,SAAO,UAAU,IAAI,KAAK,IAAI,KAAK,GAAG,KAAK,IAAI,IAAI;AACrD;AAEO,SAAS,kBAAkB,EAAE,QAAQ,UAAU,MAAM,GAA2B;AACrF,SACE,qBAAC,SAAI,WAAU,+BACZ;AAAA,aAAS,IAAI,oBAAC,QAAK,SAAQ,OAAO,qBAAW,QAAQ,OAAO,GAAE,IAAU;AAAA,IACxE,WAAW,IAAI,oBAAC,QAAK,SAAQ,QAAQ,qBAAW,UAAU,SAAS,GAAE,IAAU;AAAA,IAChF,oBAAC,QAAK,SAAQ,WAAW,iBAAM;AAAA,KACjC;AAEJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"HeadingTreeRow.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAIxE,UAAU,mBAAmB;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;CACjC;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,MAAM,EACN,WAAW,EACX,QAAQ,EACR,MAAM,EACN,QAAQ,GACT,EAAE,mBAAmB,2CAwDrB"}
1
+ {"version":3,"file":"HeadingTreeRow.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAKxE,UAAU,mBAAmB;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;CACjC;AAED,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,KAAK,EACL,OAAO,EACP,MAAM,EACN,WAAW,EACX,QAAQ,EACR,MAAM,EACN,QAAQ,GACT,EAAE,mBAAmB,2CA8DrB"}
@@ -1,5 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { cn } from "../../../../../utils/style";
3
+ import { formatSkippedLevels } from "../headingIssueCopy";
3
4
  import { Chevron } from "./Chevron";
4
5
  function HeadingTreeRow({
5
6
  node,
@@ -59,6 +60,7 @@ function HeadingTreeRow({
59
60
  children: node.text
60
61
  }
61
62
  ) : /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate text-[12px] font-medium italic text-neutral-400", children: "(empty heading)" }),
63
+ node.issue ? /* @__PURE__ */ jsx("span", { className: "flex-none inline-flex items-center rounded-[20px] bg-seo-warn-100 px-[8px] py-[2px] text-[9.5px] font-bold tracking-[0.02em] text-seo-warn", children: formatSkippedLevels(node.issue.skipped) }) : null,
62
64
  hasKids ? /* @__PURE__ */ jsx(Chevron, { open: isOpen }) : null
63
65
  ]
64
66
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.tsx"],"sourcesContent":["import type { Ref } from \"react\";\nimport type { HeadingNode } from \"../../../../../engine/types/analysis\";\nimport { cn } from \"../../../../../utils/style\";\nimport { Chevron } from \"./Chevron\";\n\ninterface HeadingTreeRowProps {\n node: HeadingNode;\n depth: number;\n hasKids: boolean;\n isOpen: boolean;\n globalFirst: boolean;\n onToggle: (id: string) => void;\n rowRef?: Ref<HTMLDivElement>;\n badgeRef?: Ref<HTMLSpanElement>;\n}\n\nexport function HeadingTreeRow({\n node,\n depth,\n hasKids,\n isOpen,\n globalFirst,\n onToggle,\n rowRef,\n badgeRef,\n}: HeadingTreeRowProps) {\n return (\n <div\n ref={rowRef}\n className={cn(\n \"relative flex items-center gap-[9px] h-[34px] box-border\",\n hasKids &&\n \"cursor-pointer focus-visible:outline-2 focus-visible:outline-neutral-400 focus-visible:-outline-offset-2\"\n )}\n style={{ paddingLeft: depth * 20 }}\n role={hasKids ? \"button\" : undefined}\n tabIndex={hasKids ? 0 : undefined}\n aria-expanded={hasKids ? isOpen : undefined}\n onClick={hasKids ? () => onToggle(node.id) : undefined}\n onKeyDown={\n hasKids\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onToggle(node.id);\n }\n }\n : undefined\n }\n title={hasKids ? (isOpen ? \"Collapse\" : \"Expand\") : undefined}\n >\n <span\n ref={badgeRef}\n className=\"flex-none font-mono text-[9px] font-bold leading-[100%] text-neutral-1000 bg-neutral-150 rounded-rs px-[6px] py-[3px] min-w-[22px] text-center\"\n >\n H{node.level}\n </span>\n\n <div\n className={cn(\n \"flex-1 min-w-0 flex items-center gap-[9px] h-full\",\n !globalFirst && \"border-t border-neutral-150\"\n )}\n >\n {node.text ? (\n <span\n className=\"flex-1 min-w-0 truncate text-[12px] font-medium text-neutral-800\"\n title={node.text}\n >\n {node.text}\n </span>\n ) : (\n <span className=\"flex-1 min-w-0 truncate text-[12px] font-medium italic text-neutral-400\">\n (empty heading)\n </span>\n )}\n\n {hasKids ? <Chevron open={isOpen} /> : null}\n </div>\n </div>\n );\n}\n"],"mappings":"AAmDM,SAcI,KAdJ;AAjDN,SAAS,UAAU;AACnB,SAAS,eAAe;AAajB,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW;AAAA,QACT;AAAA,QACA,WACE;AAAA,MACJ;AAAA,MACA,OAAO,EAAE,aAAa,QAAQ,GAAG;AAAA,MACjC,MAAM,UAAU,WAAW;AAAA,MAC3B,UAAU,UAAU,IAAI;AAAA,MACxB,iBAAe,UAAU,SAAS;AAAA,MAClC,SAAS,UAAU,MAAM,SAAS,KAAK,EAAE,IAAI;AAAA,MAC7C,WACE,UACI,CAAC,MAAM;AACL,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAe;AACjB,mBAAS,KAAK,EAAE;AAAA,QAClB;AAAA,MACF,IACA;AAAA,MAEN,OAAO,UAAW,SAAS,aAAa,WAAY;AAAA,MAEpD;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAU;AAAA,YACX;AAAA;AAAA,cACG,KAAK;AAAA;AAAA;AAAA,QACT;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,CAAC,eAAe;AAAA,YAClB;AAAA,YAEC;AAAA,mBAAK,OACJ;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,KAAK;AAAA,kBAEX,eAAK;AAAA;AAAA,cACR,IAEA,oBAAC,UAAK,WAAU,2EAA0E,6BAE1F;AAAA,cAGD,UAAU,oBAAC,WAAQ,MAAM,QAAQ,IAAK;AAAA;AAAA;AAAA,QACzC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.tsx"],"sourcesContent":["import type { Ref } from \"react\";\nimport type { HeadingNode } from \"../../../../../engine/types/analysis\";\nimport { cn } from \"../../../../../utils/style\";\nimport { formatSkippedLevels } from \"../headingIssueCopy\";\nimport { Chevron } from \"./Chevron\";\n\ninterface HeadingTreeRowProps {\n node: HeadingNode;\n depth: number;\n hasKids: boolean;\n isOpen: boolean;\n globalFirst: boolean;\n onToggle: (id: string) => void;\n rowRef?: Ref<HTMLDivElement>;\n badgeRef?: Ref<HTMLSpanElement>;\n}\n\nexport function HeadingTreeRow({\n node,\n depth,\n hasKids,\n isOpen,\n globalFirst,\n onToggle,\n rowRef,\n badgeRef,\n}: HeadingTreeRowProps) {\n return (\n <div\n ref={rowRef}\n className={cn(\n \"relative flex items-center gap-[9px] h-[34px] box-border\",\n hasKids &&\n \"cursor-pointer focus-visible:outline-2 focus-visible:outline-neutral-400 focus-visible:-outline-offset-2\"\n )}\n style={{ paddingLeft: depth * 20 }}\n role={hasKids ? \"button\" : undefined}\n tabIndex={hasKids ? 0 : undefined}\n aria-expanded={hasKids ? isOpen : undefined}\n onClick={hasKids ? () => onToggle(node.id) : undefined}\n onKeyDown={\n hasKids\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onToggle(node.id);\n }\n }\n : undefined\n }\n title={hasKids ? (isOpen ? \"Collapse\" : \"Expand\") : undefined}\n >\n <span\n ref={badgeRef}\n className=\"flex-none font-mono text-[9px] font-bold leading-[100%] text-neutral-1000 bg-neutral-150 rounded-rs px-[6px] py-[3px] min-w-[22px] text-center\"\n >\n H{node.level}\n </span>\n\n <div\n className={cn(\n \"flex-1 min-w-0 flex items-center gap-[9px] h-full\",\n !globalFirst && \"border-t border-neutral-150\"\n )}\n >\n {node.text ? (\n <span\n className=\"flex-1 min-w-0 truncate text-[12px] font-medium text-neutral-800\"\n title={node.text}\n >\n {node.text}\n </span>\n ) : (\n <span className=\"flex-1 min-w-0 truncate text-[12px] font-medium italic text-neutral-400\">\n (empty heading)\n </span>\n )}\n\n {node.issue ? (\n <span className=\"flex-none inline-flex items-center rounded-[20px] bg-seo-warn-100 px-[8px] py-[2px] text-[9.5px] font-bold tracking-[0.02em] text-seo-warn\">\n {formatSkippedLevels(node.issue.skipped)}\n </span>\n ) : null}\n\n {hasKids ? <Chevron open={isOpen} /> : null}\n </div>\n </div>\n );\n}\n"],"mappings":"AAoDM,SAcI,KAdJ;AAlDN,SAAS,UAAU;AACnB,SAAS,2BAA2B;AACpC,SAAS,eAAe;AAajB,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAwB;AACtB,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW;AAAA,QACT;AAAA,QACA,WACE;AAAA,MACJ;AAAA,MACA,OAAO,EAAE,aAAa,QAAQ,GAAG;AAAA,MACjC,MAAM,UAAU,WAAW;AAAA,MAC3B,UAAU,UAAU,IAAI;AAAA,MACxB,iBAAe,UAAU,SAAS;AAAA,MAClC,SAAS,UAAU,MAAM,SAAS,KAAK,EAAE,IAAI;AAAA,MAC7C,WACE,UACI,CAAC,MAAM;AACL,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAe;AACjB,mBAAS,KAAK,EAAE;AAAA,QAClB;AAAA,MACF,IACA;AAAA,MAEN,OAAO,UAAW,SAAS,aAAa,WAAY;AAAA,MAEpD;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,KAAK;AAAA,YACL,WAAU;AAAA,YACX;AAAA;AAAA,cACG,KAAK;AAAA;AAAA;AAAA,QACT;AAAA,QAEA;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA,CAAC,eAAe;AAAA,YAClB;AAAA,YAEC;AAAA,mBAAK,OACJ;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAU;AAAA,kBACV,OAAO,KAAK;AAAA,kBAEX,eAAK;AAAA;AAAA,cACR,IAEA,oBAAC,UAAK,WAAU,2EAA0E,6BAE1F;AAAA,cAGD,KAAK,QACJ,oBAAC,UAAK,WAAU,8IACb,8BAAoB,KAAK,MAAM,OAAO,GACzC,IACE;AAAA,cAEH,UAAU,oBAAC,WAAQ,MAAM,QAAQ,IAAK;AAAA;AAAA;AAAA,QACzC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -1,3 +1,4 @@
1
1
  import type { HeadingNode } from "../../../../../engine/types/analysis";
2
2
  export declare function collectParentIds(nodes: HeadingNode[]): string[];
3
+ export declare function countHeadingWarnings(nodes: HeadingNode[]): number;
3
4
  //# sourceMappingURL=headingTreeView.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"headingTreeView.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAe/D"}
1
+ {"version":3,"file":"headingTreeView.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAe/D;AAED,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,CAajE"}
@@ -11,7 +11,20 @@ function collectParentIds(nodes) {
11
11
  walk(nodes);
12
12
  return ids;
13
13
  }
14
+ function countHeadingWarnings(nodes) {
15
+ let count = 0;
16
+ const walk = (ns) => {
17
+ for (const n of ns) {
18
+ if (n.issue)
19
+ count++;
20
+ walk(n.children);
21
+ }
22
+ };
23
+ walk(nodes);
24
+ return count;
25
+ }
14
26
  export {
15
- collectParentIds
27
+ collectParentIds,
28
+ countHeadingWarnings
16
29
  };
17
30
  //# sourceMappingURL=headingTreeView.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.ts"],"sourcesContent":["import type { HeadingNode } from \"../../../../../engine/types/analysis\";\n\nexport function collectParentIds(nodes: HeadingNode[]): string[] {\n const ids: string[] = [];\n\n const walk = (ns: HeadingNode[]) => {\n for (const n of ns) {\n if (n.children.length > 0) {\n ids.push(n.id);\n walk(n.children);\n }\n }\n };\n\n walk(nodes);\n\n return ids;\n}\n"],"mappings":"AAEO,SAAS,iBAAiB,OAAgC;AAC/D,QAAM,MAAgB,CAAC;AAEvB,QAAM,OAAO,CAAC,OAAsB;AAClC,eAAW,KAAK,IAAI;AAClB,UAAI,EAAE,SAAS,SAAS,GAAG;AACzB,YAAI,KAAK,EAAE,EAAE;AACb,aAAK,EAAE,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,OAAK,KAAK;AAEV,SAAO;AACT;","names":[]}
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.ts"],"sourcesContent":["import type { HeadingNode } from \"../../../../../engine/types/analysis\";\n\nexport function collectParentIds(nodes: HeadingNode[]): string[] {\n const ids: string[] = [];\n\n const walk = (ns: HeadingNode[]) => {\n for (const n of ns) {\n if (n.children.length > 0) {\n ids.push(n.id);\n walk(n.children);\n }\n }\n };\n\n walk(nodes);\n\n return ids;\n}\n\nexport function countHeadingWarnings(nodes: HeadingNode[]): number {\n let count = 0;\n\n const walk = (ns: HeadingNode[]) => {\n for (const n of ns) {\n if (n.issue) count++;\n walk(n.children);\n }\n };\n\n walk(nodes);\n\n return count;\n}\n"],"mappings":"AAEO,SAAS,iBAAiB,OAAgC;AAC/D,QAAM,MAAgB,CAAC;AAEvB,QAAM,OAAO,CAAC,OAAsB;AAClC,eAAW,KAAK,IAAI;AAClB,UAAI,EAAE,SAAS,SAAS,GAAG;AACzB,YAAI,KAAK,EAAE,EAAE;AACb,aAAK,EAAE,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,OAAK,KAAK;AAEV,SAAO;AACT;AAEO,SAAS,qBAAqB,OAA8B;AACjE,MAAI,QAAQ;AAEZ,QAAM,OAAO,CAAC,OAAsB;AAClC,eAAW,KAAK,IAAI;AAClB,UAAI,EAAE;AAAO;AACb,WAAK,EAAE,QAAQ;AAAA,IACjB;AAAA,EACF;AAEA,OAAK,KAAK;AAEV,SAAO;AACT;","names":[]}
@@ -0,0 +1,7 @@
1
+ import type { HeadingDocIssue, HeadingLevel } from "../../../../engine/types/analysis";
2
+ export declare function describeDocIssue(issue: HeadingDocIssue): {
3
+ title: string;
4
+ body: string;
5
+ };
6
+ export declare function formatSkippedLevels(skipped: HeadingLevel[]): string;
7
+ //# sourceMappingURL=headingIssueCopy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headingIssueCopy.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,mCAAmC,CAAC;AAEvF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,eAAe,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAiBxF;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,YAAY,EAAE,GAAG,MAAM,CAEnE"}
@@ -0,0 +1,26 @@
1
+ function describeDocIssue(issue) {
2
+ switch (issue.type) {
3
+ case "missing-h1":
4
+ return {
5
+ title: "No H1 heading found",
6
+ body: "Add a single top-level heading so the page has a clear title anchor."
7
+ };
8
+ case "multiple-h1":
9
+ return {
10
+ title: `Multiple H1 headings (${issue.count})`,
11
+ body: "Use exactly one H1 so search engines and screen readers get a single page title."
12
+ };
13
+ default: {
14
+ const _exhaustive = issue;
15
+ throw new Error(`Unhandled heading doc issue: ${JSON.stringify(_exhaustive)}`);
16
+ }
17
+ }
18
+ }
19
+ function formatSkippedLevels(skipped) {
20
+ return `Skipped ${skipped.map((level) => `H${level}`).join("\xB7")}`;
21
+ }
22
+ export {
23
+ describeDocIssue,
24
+ formatSkippedLevels
25
+ };
26
+ //# sourceMappingURL=headingIssueCopy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/headingIssueCopy.ts"],"sourcesContent":["import type { HeadingDocIssue, HeadingLevel } from \"../../../../engine/types/analysis\";\n\nexport function describeDocIssue(issue: HeadingDocIssue): { title: string; body: string } {\n switch (issue.type) {\n case \"missing-h1\":\n return {\n title: \"No H1 heading found\",\n body: \"Add a single top-level heading so the page has a clear title anchor.\",\n };\n case \"multiple-h1\":\n return {\n title: `Multiple H1 headings (${issue.count})`,\n body: \"Use exactly one H1 so search engines and screen readers get a single page title.\",\n };\n default: {\n const _exhaustive: never = issue;\n throw new Error(`Unhandled heading doc issue: ${JSON.stringify(_exhaustive)}`);\n }\n }\n}\n\nexport function formatSkippedLevels(skipped: HeadingLevel[]): string {\n return `Skipped ${skipped.map((level) => `H${level}`).join(\"·\")}`;\n}\n"],"mappings":"AAEO,SAAS,iBAAiB,OAAyD;AACxF,UAAQ,MAAM,MAAM;AAAA,IAClB,KAAK;AACH,aAAO;AAAA,QACL,OAAO;AAAA,QACP,MAAM;AAAA,MACR;AAAA,IACF,KAAK;AACH,aAAO;AAAA,QACL,OAAO,yBAAyB,MAAM,KAAK;AAAA,QAC3C,MAAM;AAAA,MACR;AAAA,IACF,SAAS;AACP,YAAM,cAAqB;AAC3B,YAAM,IAAI,MAAM,gCAAgC,KAAK,UAAU,WAAW,CAAC,EAAE;AAAA,IAC/E;AAAA,EACF;AACF;AAEO,SAAS,oBAAoB,SAAiC;AACnE,SAAO,WAAW,QAAQ,IAAI,CAAC,UAAU,IAAI,KAAK,EAAE,EAAE,KAAK,MAAG,CAAC;AACjE;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAM1E,UAAU,oBAAoB;IAC5B,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,oBAAoB,2CAU7D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAQ1E,UAAU,oBAAoB;IAC5B,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,oBAAoB,2CAqB7D"}
@@ -1,15 +1,26 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
3
  import { SectionCard } from "../../../../ui/SectionCard";
4
- import { Pill } from "../../../../ui/Pill";
4
+ import { HeadingIssueBanner } from "./HeadingIssueBanner";
5
+ import { HeadingIssuePills } from "./HeadingIssuePills";
5
6
  import { HeadingLevelTiles } from "./HeadingLevelTiles";
6
7
  import { HeadingTree } from "./HeadingTree";
8
+ import { countHeadingWarnings } from "./HeadingTree/headingTreeView";
7
9
  function HeadingsSection({ data }) {
8
- return /* @__PURE__ */ jsxs(SectionCard, { title: "Headings", widget: /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: data.total }), children: [
9
- /* @__PURE__ */ jsx("div", { className: "text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500 px-[15px] pt-[11px]", children: "Levels" }),
10
- /* @__PURE__ */ jsx(HeadingLevelTiles, { levels: data.levels }),
11
- /* @__PURE__ */ jsx(HeadingTree, { tree: data.tree })
12
- ] });
10
+ const warnings = countHeadingWarnings(data.tree);
11
+ return /* @__PURE__ */ jsxs(
12
+ SectionCard,
13
+ {
14
+ title: "Headings",
15
+ widget: /* @__PURE__ */ jsx(HeadingIssuePills, { errors: data.issues.length, warnings, total: data.total }),
16
+ children: [
17
+ data.issues.map((issue) => /* @__PURE__ */ jsx(HeadingIssueBanner, { issue }, issue.type)),
18
+ /* @__PURE__ */ jsx("div", { className: "text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500 px-[15px] pt-[11px]", children: "Levels" }),
19
+ /* @__PURE__ */ jsx(HeadingLevelTiles, { levels: data.levels }),
20
+ /* @__PURE__ */ jsx(HeadingTree, { tree: data.tree })
21
+ ]
22
+ }
23
+ );
13
24
  }
14
25
  export {
15
26
  HeadingsSection
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HeadingStructure } from \"../../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../../ui/SectionCard\";\nimport { Pill } from \"../../../../ui/Pill\";\nimport { HeadingLevelTiles } from \"./HeadingLevelTiles\";\nimport { HeadingTree } from \"./HeadingTree\";\n\ninterface HeadingsSectionProps {\n data: HeadingStructure;\n}\n\nexport function HeadingsSection({ data }: HeadingsSectionProps) {\n return (\n <SectionCard title=\"Headings\" widget={<Pill variant=\"neutral\">{data.total}</Pill>}>\n <div className=\"text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500 px-[15px] pt-[11px]\">\n Levels\n </div>\n <HeadingLevelTiles levels={data.levels} />\n <HeadingTree tree={data.tree} />\n </SectionCard>\n );\n}\n"],"mappings":";AAcI,SAAsC,KAAtC;AAXJ,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAMrB,SAAS,gBAAgB,EAAE,KAAK,GAAyB;AAC9D,SACE,qBAAC,eAAY,OAAM,YAAW,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAM,GACxE;AAAA,wBAAC,SAAI,WAAU,+FAA8F,oBAE7G;AAAA,IACA,oBAAC,qBAAkB,QAAQ,KAAK,QAAQ;AAAA,IACxC,oBAAC,eAAY,MAAM,KAAK,MAAM;AAAA,KAChC;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HeadingStructure } from \"../../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../../ui/SectionCard\";\nimport { HeadingIssueBanner } from \"./HeadingIssueBanner\";\nimport { HeadingIssuePills } from \"./HeadingIssuePills\";\nimport { HeadingLevelTiles } from \"./HeadingLevelTiles\";\nimport { HeadingTree } from \"./HeadingTree\";\nimport { countHeadingWarnings } from \"./HeadingTree/headingTreeView\";\n\ninterface HeadingsSectionProps {\n data: HeadingStructure;\n}\n\nexport function HeadingsSection({ data }: HeadingsSectionProps) {\n const warnings = countHeadingWarnings(data.tree);\n\n return (\n <SectionCard\n title=\"Headings\"\n widget={\n <HeadingIssuePills errors={data.issues.length} warnings={warnings} total={data.total} />\n }\n >\n {data.issues.map((issue) => (\n <HeadingIssueBanner key={issue.type} issue={issue} />\n ))}\n\n <div className=\"text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500 px-[15px] pt-[11px]\">\n Levels\n </div>\n <HeadingLevelTiles levels={data.levels} />\n <HeadingTree tree={data.tree} />\n </SectionCard>\n );\n}\n"],"mappings":";AAkBI,SAGI,KAHJ;AAfJ,SAAS,mBAAmB;AAC5B,SAAS,0BAA0B;AACnC,SAAS,yBAAyB;AAClC,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAC5B,SAAS,4BAA4B;AAM9B,SAAS,gBAAgB,EAAE,KAAK,GAAyB;AAC9D,QAAM,WAAW,qBAAqB,KAAK,IAAI;AAE/C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE,oBAAC,qBAAkB,QAAQ,KAAK,OAAO,QAAQ,UAAoB,OAAO,KAAK,OAAO;AAAA,MAGvF;AAAA,aAAK,OAAO,IAAI,CAAC,UAChB,oBAAC,sBAAoC,SAAZ,MAAM,IAAoB,CACpD;AAAA,QAED,oBAAC,SAAI,WAAU,+FAA8F,oBAE7G;AAAA,QACA,oBAAC,qBAAkB,QAAQ,KAAK,QAAQ;AAAA,QACxC,oBAAC,eAAY,MAAM,KAAK,MAAM;AAAA;AAAA;AAAA,EAChC;AAEJ;","names":[]}
@@ -1,3 +1,3 @@
1
1
  import type { ReactNode } from "react";
2
- export declare function highlightKeyphrase(text: string, keyphrase: string): ReactNode[];
2
+ export declare function highlightKeyphrase(text: string, keyphrase: string, synonyms?: string[]): ReactNode[];
3
3
  //# sourceMappingURL=highlight-keyphrase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"highlight-keyphrase.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,SAAS,EAAE,CAQ/E"}
1
+ {"version":3,"file":"highlight-keyphrase.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAkBvC,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,QAAQ,GAAE,MAAM,EAAO,GACtB,SAAS,EAAE,CASb"}
@@ -2,10 +2,17 @@ import { jsx } from "react/jsx-runtime";
2
2
  function escapeRegex(value) {
3
3
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4
4
  }
5
- function highlightKeyphrase(text, keyphrase) {
6
- if (!keyphrase.trim())
5
+ function buildHighlightPattern(keyphrase, synonyms) {
6
+ const terms = [keyphrase, ...synonyms].map((term) => term.trim()).filter((term) => term.length > 0).sort((a, b) => b.length - a.length).map(escapeRegex);
7
+ if (terms.length === 0)
8
+ return null;
9
+ return terms.join("|");
10
+ }
11
+ function highlightKeyphrase(text, keyphrase, synonyms = []) {
12
+ const pattern = buildHighlightPattern(keyphrase, synonyms);
13
+ if (!pattern)
7
14
  return [text];
8
- const segments = text.split(new RegExp(`(${escapeRegex(keyphrase)})`, "gi"));
15
+ const segments = text.split(new RegExp(`(${pattern})`, "gi"));
9
16
  return segments.map(
10
17
  (segment, index) => index % 2 === 1 ? /* @__PURE__ */ jsx("strong", { children: segment }, index) : segment
11
18
  );
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nexport function highlightKeyphrase(text: string, keyphrase: string): ReactNode[] {\n if (!keyphrase.trim()) return [text];\n\n const segments = text.split(new RegExp(`(${escapeRegex(keyphrase)})`, \"gi\"));\n\n return segments.map((segment, index) =>\n index % 2 === 1 ? <strong key={index}>{segment}</strong> : segment\n );\n}\n"],"mappings":"AAYsB;AAVtB,SAAS,YAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEO,SAAS,mBAAmB,MAAc,WAAgC;AAC/E,MAAI,CAAC,UAAU,KAAK;AAAG,WAAO,CAAC,IAAI;AAEnC,QAAM,WAAW,KAAK,MAAM,IAAI,OAAO,IAAI,YAAY,SAAS,CAAC,KAAK,IAAI,CAAC;AAE3E,SAAO,SAAS;AAAA,IAAI,CAAC,SAAS,UAC5B,QAAQ,MAAM,IAAI,oBAAC,YAAoB,qBAAR,KAAgB,IAAY;AAAA,EAC7D;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.tsx"],"sourcesContent":["import type { ReactNode } from \"react\";\n\nfunction escapeRegex(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\nfunction buildHighlightPattern(keyphrase: string, synonyms: string[]): string | null {\n const terms = [keyphrase, ...synonyms]\n .map((term) => term.trim())\n .filter((term) => term.length > 0)\n .sort((a, b) => b.length - a.length)\n .map(escapeRegex);\n\n if (terms.length === 0) return null;\n\n return terms.join(\"|\");\n}\n\nexport function highlightKeyphrase(\n text: string,\n keyphrase: string,\n synonyms: string[] = []\n): ReactNode[] {\n const pattern = buildHighlightPattern(keyphrase, synonyms);\n if (!pattern) return [text];\n\n const segments = text.split(new RegExp(`(${pattern})`, \"gi\"));\n\n return segments.map((segment, index) =>\n index % 2 === 1 ? <strong key={index}>{segment}</strong> : segment\n );\n}\n"],"mappings":"AA6BsB;AA3BtB,SAAS,YAAY,OAAuB;AAC1C,SAAO,MAAM,QAAQ,uBAAuB,MAAM;AACpD;AAEA,SAAS,sBAAsB,WAAmB,UAAmC;AACnF,QAAM,QAAQ,CAAC,WAAW,GAAG,QAAQ,EAClC,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,CAAC,SAAS,KAAK,SAAS,CAAC,EAChC,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,EAClC,IAAI,WAAW;AAElB,MAAI,MAAM,WAAW;AAAG,WAAO;AAE/B,SAAO,MAAM,KAAK,GAAG;AACvB;AAEO,SAAS,mBACd,MACA,WACA,WAAqB,CAAC,GACT;AACb,QAAM,UAAU,sBAAsB,WAAW,QAAQ;AACzD,MAAI,CAAC;AAAS,WAAO,CAAC,IAAI;AAE1B,QAAM,WAAW,KAAK,MAAM,IAAI,OAAO,IAAI,OAAO,KAAK,IAAI,CAAC;AAE5D,SAAO,SAAS;AAAA,IAAI,CAAC,SAAS,UAC5B,QAAQ,MAAM,IAAI,oBAAC,YAAoB,qBAAR,KAAgB,IAAY;AAAA,EAC7D;AACF;","names":[]}
@@ -4,9 +4,10 @@ export type SerpMode = "mobile" | "desktop";
4
4
  interface SerpPreviewProps {
5
5
  data: SerpResult;
6
6
  keyphrase: string;
7
+ synonyms?: string[];
7
8
  faviconUrl: string;
8
9
  mode: SerpMode;
9
10
  }
10
- export declare function SerpPreview({ data, keyphrase, faviconUrl, mode }: SerpPreviewProps): ReactElement;
11
+ export declare function SerpPreview({ data, keyphrase, synonyms, faviconUrl, mode, }: SerpPreviewProps): ReactElement;
11
12
  export {};
12
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/SerpPreview/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAapE,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5C,UAAU,gBAAgB;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,UAAU,EAAE,IAAI,EAAE,EAAE,gBAAgB,GAAG,YAAY,CAqBjG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/SerpPreview/index.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,OAAO,CAAC;AAC1C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAapE,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5C,UAAU,gBAAgB;IACxB,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,QAAQ,CAAC;CAChB;AAED,wBAAgB,WAAW,CAAC,EAC1B,IAAI,EACJ,SAAS,EACT,QAAa,EACb,UAAU,EACV,IAAI,GACL,EAAE,gBAAgB,GAAG,YAAY,CAqBjC"}