@focus-reactive/payload-plugin-seo 1.6.1 → 1.7.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 (124) hide show
  1. package/README.md +12 -1
  2. package/dist/admin.css +200 -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/SerpPreview/highlight-keyphrase.d.ts +1 -1
  15. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts.map +1 -1
  16. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js +10 -3
  17. package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js.map +1 -1
  18. package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts +2 -1
  19. package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts.map +1 -1
  20. package/dist/components/SeoDrawer/components/SerpPreview/index.js +8 -2
  21. package/dist/components/SeoDrawer/components/SerpPreview/index.js.map +1 -1
  22. package/dist/components/SeoDrawer/components/SerpPreview/variants.d.ts +6 -6
  23. package/dist/components/SeoDrawer/components/TabWrapper.d.ts +6 -0
  24. package/dist/components/SeoDrawer/components/TabWrapper.d.ts.map +1 -0
  25. package/dist/components/SeoDrawer/components/TabWrapper.js +8 -0
  26. package/dist/components/SeoDrawer/components/TabWrapper.js.map +1 -0
  27. package/dist/components/SeoDrawer/index.d.ts +11 -4
  28. package/dist/components/SeoDrawer/index.d.ts.map +1 -1
  29. package/dist/components/SeoDrawer/index.js +38 -16
  30. package/dist/components/SeoDrawer/index.js.map +1 -1
  31. package/dist/components/SeoDrawer/keyphraseState.d.ts +16 -0
  32. package/dist/components/SeoDrawer/keyphraseState.d.ts.map +1 -0
  33. package/dist/components/SeoDrawer/keyphraseState.js +73 -0
  34. package/dist/components/SeoDrawer/keyphraseState.js.map +1 -0
  35. package/dist/components/SeoDrawer/keyphraseStorage.d.ts +5 -0
  36. package/dist/components/SeoDrawer/keyphraseStorage.d.ts.map +1 -0
  37. package/dist/components/SeoDrawer/keyphraseStorage.js +60 -0
  38. package/dist/components/SeoDrawer/keyphraseStorage.js.map +1 -0
  39. package/dist/components/SeoDrawer/tabs/InclusiveTab.d.ts.map +1 -1
  40. package/dist/components/SeoDrawer/tabs/InclusiveTab.js +3 -2
  41. package/dist/components/SeoDrawer/tabs/InclusiveTab.js.map +1 -1
  42. package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts +14 -7
  43. package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts.map +1 -1
  44. package/dist/components/SeoDrawer/tabs/KeyphraseTab.js +110 -69
  45. package/dist/components/SeoDrawer/tabs/KeyphraseTab.js.map +1 -1
  46. package/dist/components/SeoDrawer/tabs/OnPageTab.d.ts.map +1 -1
  47. package/dist/components/SeoDrawer/tabs/OnPageTab.js +3 -2
  48. package/dist/components/SeoDrawer/tabs/OnPageTab.js.map +1 -1
  49. package/dist/components/SeoDrawer/tabs/ReadabilityTab.d.ts.map +1 -1
  50. package/dist/components/SeoDrawer/tabs/ReadabilityTab.js +3 -2
  51. package/dist/components/SeoDrawer/tabs/ReadabilityTab.js.map +1 -1
  52. package/dist/components/SeoDrawer/tabs/SerpTab.d.ts +2 -1
  53. package/dist/components/SeoDrawer/tabs/SerpTab.d.ts.map +1 -1
  54. package/dist/components/SeoDrawer/tabs/SerpTab.js +14 -3
  55. package/dist/components/SeoDrawer/tabs/SerpTab.js.map +1 -1
  56. package/dist/components/SeoDrawer/tabs/VitalsTab.d.ts.map +1 -1
  57. package/dist/components/SeoDrawer/tabs/VitalsTab.js +7 -3
  58. package/dist/components/SeoDrawer/tabs/VitalsTab.js.map +1 -1
  59. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts +21 -0
  60. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts.map +1 -0
  61. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js +38 -0
  62. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js.map +1 -0
  63. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts +25 -0
  64. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts.map +1 -0
  65. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js +127 -0
  66. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js.map +1 -0
  67. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts +12 -0
  68. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts.map +1 -0
  69. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js +54 -0
  70. package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js.map +1 -0
  71. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts +7 -0
  72. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts.map +1 -0
  73. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js +93 -0
  74. package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js.map +1 -0
  75. package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts +2 -0
  76. package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts.map +1 -0
  77. package/dist/components/SeoDrawer/tabs/keyphrase/icons.js +7 -0
  78. package/dist/components/SeoDrawer/tabs/keyphrase/icons.js.map +1 -0
  79. package/dist/components/SeoDrawer/useAnalysis.d.ts +0 -1
  80. package/dist/components/SeoDrawer/useAnalysis.d.ts.map +1 -1
  81. package/dist/components/SeoDrawer/useAnalysis.js +0 -3
  82. package/dist/components/SeoDrawer/useAnalysis.js.map +1 -1
  83. package/dist/components/SeoDrawer/useKeyphrases.d.ts +18 -0
  84. package/dist/components/SeoDrawer/useKeyphrases.d.ts.map +1 -0
  85. package/dist/components/SeoDrawer/useKeyphrases.js +78 -0
  86. package/dist/components/SeoDrawer/useKeyphrases.js.map +1 -0
  87. package/dist/components/SeoDrawer/useLiveDocument.d.ts +4 -3
  88. package/dist/components/SeoDrawer/useLiveDocument.d.ts.map +1 -1
  89. package/dist/components/SeoDrawer/useLiveDocument.js +17 -9
  90. package/dist/components/SeoDrawer/useLiveDocument.js.map +1 -1
  91. package/dist/components/SeoDrawer/variants.d.ts +1 -1
  92. package/dist/components/SeoDrawer/variants.d.ts.map +1 -1
  93. package/dist/components/SeoDrawer/variants.js +2 -1
  94. package/dist/components/SeoDrawer/variants.js.map +1 -1
  95. package/dist/constants/index.d.ts +1 -0
  96. package/dist/constants/index.d.ts.map +1 -1
  97. package/dist/constants/index.js +2 -0
  98. package/dist/constants/index.js.map +1 -1
  99. package/dist/engine/buildPaper.d.ts +2 -2
  100. package/dist/engine/buildPaper.d.ts.map +1 -1
  101. package/dist/engine/buildPaper.js +4 -2
  102. package/dist/engine/buildPaper.js.map +1 -1
  103. package/dist/engine/runAnalysis/index.d.ts.map +1 -1
  104. package/dist/engine/runAnalysis/index.js +7 -0
  105. package/dist/engine/runAnalysis/index.js.map +1 -1
  106. package/dist/engine/runAnalysis/services/derive-related.d.ts +4 -0
  107. package/dist/engine/runAnalysis/services/derive-related.d.ts.map +1 -0
  108. package/dist/engine/runAnalysis/services/derive-related.js +16 -0
  109. package/dist/engine/runAnalysis/services/derive-related.js.map +1 -0
  110. package/dist/engine/types/analysis.d.ts +11 -0
  111. package/dist/engine/types/analysis.d.ts.map +1 -1
  112. package/dist/ui/Button.d.ts +9 -0
  113. package/dist/ui/Button.d.ts.map +1 -0
  114. package/dist/ui/Button.js +34 -0
  115. package/dist/ui/Button.js.map +1 -0
  116. package/dist/ui/ScoreRing.d.ts +17 -4
  117. package/dist/ui/ScoreRing.d.ts.map +1 -1
  118. package/dist/ui/ScoreRing.js +38 -14
  119. package/dist/ui/ScoreRing.js.map +1 -1
  120. package/package.json +1 -1
  121. package/dist/components/SeoDrawer/keyphrasePending.d.ts +0 -2
  122. package/dist/components/SeoDrawer/keyphrasePending.d.ts.map +0 -1
  123. package/dist/components/SeoDrawer/keyphrasePending.js +0 -9
  124. package/dist/components/SeoDrawer/keyphrasePending.js.map +0 -1
@@ -0,0 +1,73 @@
1
+ import { MAX_KEYPHRASES } from "../../constants";
2
+ function makeId() {
3
+ const c = globalThis.crypto;
4
+ return c?.randomUUID ? c.randomUUID() : `kp_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;
5
+ }
6
+ function createEntry(text = "", synonyms = []) {
7
+ return {
8
+ id: makeId(),
9
+ text,
10
+ synonyms: [...synonyms]
11
+ };
12
+ }
13
+ function firstEmptyId(list) {
14
+ return list.find((k) => k.text.trim() === "")?.id;
15
+ }
16
+ function addRelated(list) {
17
+ if (list.length >= MAX_KEYPHRASES)
18
+ return list;
19
+ if (firstEmptyId(list))
20
+ return list;
21
+ return [...list, createEntry()];
22
+ }
23
+ function updateText(list, id, text) {
24
+ return list.map((k) => k.id === id ? { ...k, text } : k);
25
+ }
26
+ function addSynonym(list, id, syn) {
27
+ const value = syn.trim();
28
+ if (!value)
29
+ return list;
30
+ return list.map((k) => {
31
+ if (k.id !== id)
32
+ return k;
33
+ if (k.synonyms.some((s) => s.toLowerCase() === value.toLowerCase()))
34
+ return k;
35
+ return { ...k, synonyms: [...k.synonyms, value] };
36
+ });
37
+ }
38
+ function removeSynonym(list, id, index) {
39
+ return list.map(
40
+ (k) => k.id === id ? { ...k, synonyms: k.synonyms.filter((_, i) => i !== index) } : k
41
+ );
42
+ }
43
+ function remove(list, id) {
44
+ return list.filter((k) => k.id !== id);
45
+ }
46
+ function setFocus(list, id) {
47
+ const target = list.find((k) => k.id === id);
48
+ if (!target)
49
+ return list;
50
+ return [target, ...list.filter((k) => k.id !== id)];
51
+ }
52
+ function isDuplicate(list, id, text) {
53
+ const value = text.trim().toLowerCase();
54
+ if (!value)
55
+ return false;
56
+ return list.some((k) => k.id !== id && k.text.trim().toLowerCase() === value);
57
+ }
58
+ function pruneEmpties(list) {
59
+ return list.filter((k) => k.text.trim() !== "");
60
+ }
61
+ export {
62
+ addRelated,
63
+ addSynonym,
64
+ createEntry,
65
+ firstEmptyId,
66
+ isDuplicate,
67
+ pruneEmpties,
68
+ remove,
69
+ removeSynonym,
70
+ setFocus,
71
+ updateText
72
+ };
73
+ //# sourceMappingURL=keyphraseState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoDrawer/keyphraseState.ts"],"sourcesContent":["import { MAX_KEYPHRASES } from \"../../constants\";\n\nexport interface KeyphraseEntry {\n id: string;\n text: string;\n synonyms: string[];\n}\n\nfunction makeId(): string {\n const c = (globalThis as { crypto?: { randomUUID?: () => string } }).crypto;\n\n return c?.randomUUID\n ? c.randomUUID()\n : `kp_${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`;\n}\n\nexport function createEntry(text = \"\", synonyms: string[] = []): KeyphraseEntry {\n return {\n id: makeId(),\n text,\n synonyms: [...synonyms],\n };\n}\n\nexport function firstEmptyId(list: KeyphraseEntry[]): string | undefined {\n return list.find((k) => k.text.trim() === \"\")?.id;\n}\n\nexport function addRelated(list: KeyphraseEntry[]): KeyphraseEntry[] {\n if (list.length >= MAX_KEYPHRASES) return list;\n if (firstEmptyId(list)) return list;\n\n return [...list, createEntry()];\n}\n\nexport function updateText(list: KeyphraseEntry[], id: string, text: string): KeyphraseEntry[] {\n return list.map((k) => (k.id === id ? { ...k, text } : k));\n}\n\nexport function addSynonym(list: KeyphraseEntry[], id: string, syn: string): KeyphraseEntry[] {\n const value = syn.trim();\n if (!value) return list;\n\n return list.map((k) => {\n if (k.id !== id) return k;\n if (k.synonyms.some((s) => s.toLowerCase() === value.toLowerCase())) return k;\n return { ...k, synonyms: [...k.synonyms, value] };\n });\n}\n\nexport function removeSynonym(list: KeyphraseEntry[], id: string, index: number): KeyphraseEntry[] {\n return list.map((k) =>\n k.id === id ? { ...k, synonyms: k.synonyms.filter((_, i) => i !== index) } : k\n );\n}\n\nexport function remove(list: KeyphraseEntry[], id: string): KeyphraseEntry[] {\n return list.filter((k) => k.id !== id);\n}\n\nexport function setFocus(list: KeyphraseEntry[], id: string): KeyphraseEntry[] {\n const target = list.find((k) => k.id === id);\n if (!target) return list;\n\n return [target, ...list.filter((k) => k.id !== id)];\n}\n\nexport function isDuplicate(list: KeyphraseEntry[], id: string, text: string): boolean {\n const value = text.trim().toLowerCase();\n if (!value) return false;\n\n return list.some((k) => k.id !== id && k.text.trim().toLowerCase() === value);\n}\n\nexport function pruneEmpties(list: KeyphraseEntry[]): KeyphraseEntry[] {\n return list.filter((k) => k.text.trim() !== \"\");\n}\n"],"mappings":"AAAA,SAAS,sBAAsB;AAQ/B,SAAS,SAAiB;AACxB,QAAM,IAAK,WAA0D;AAErE,SAAO,GAAG,aACN,EAAE,WAAW,IACb,MAAM,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC,GAAG,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC;AACzE;AAEO,SAAS,YAAY,OAAO,IAAI,WAAqB,CAAC,GAAmB;AAC9E,SAAO;AAAA,IACL,IAAI,OAAO;AAAA,IACX;AAAA,IACA,UAAU,CAAC,GAAG,QAAQ;AAAA,EACxB;AACF;AAEO,SAAS,aAAa,MAA4C;AACvE,SAAO,KAAK,KAAK,CAAC,MAAM,EAAE,KAAK,KAAK,MAAM,EAAE,GAAG;AACjD;AAEO,SAAS,WAAW,MAA0C;AACnE,MAAI,KAAK,UAAU;AAAgB,WAAO;AAC1C,MAAI,aAAa,IAAI;AAAG,WAAO;AAE/B,SAAO,CAAC,GAAG,MAAM,YAAY,CAAC;AAChC;AAEO,SAAS,WAAW,MAAwB,IAAY,MAAgC;AAC7F,SAAO,KAAK,IAAI,CAAC,MAAO,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,KAAK,IAAI,CAAE;AAC3D;AAEO,SAAS,WAAW,MAAwB,IAAY,KAA+B;AAC5F,QAAM,QAAQ,IAAI,KAAK;AACvB,MAAI,CAAC;AAAO,WAAO;AAEnB,SAAO,KAAK,IAAI,CAAC,MAAM;AACrB,QAAI,EAAE,OAAO;AAAI,aAAO;AACxB,QAAI,EAAE,SAAS,KAAK,CAAC,MAAM,EAAE,YAAY,MAAM,MAAM,YAAY,CAAC;AAAG,aAAO;AAC5E,WAAO,EAAE,GAAG,GAAG,UAAU,CAAC,GAAG,EAAE,UAAU,KAAK,EAAE;AAAA,EAClD,CAAC;AACH;AAEO,SAAS,cAAc,MAAwB,IAAY,OAAiC;AACjG,SAAO,KAAK;AAAA,IAAI,CAAC,MACf,EAAE,OAAO,KAAK,EAAE,GAAG,GAAG,UAAU,EAAE,SAAS,OAAO,CAAC,GAAG,MAAM,MAAM,KAAK,EAAE,IAAI;AAAA,EAC/E;AACF;AAEO,SAAS,OAAO,MAAwB,IAA8B;AAC3E,SAAO,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AACvC;AAEO,SAAS,SAAS,MAAwB,IAA8B;AAC7E,QAAM,SAAS,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE;AAC3C,MAAI,CAAC;AAAQ,WAAO;AAEpB,SAAO,CAAC,QAAQ,GAAG,KAAK,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;AACpD;AAEO,SAAS,YAAY,MAAwB,IAAY,MAAuB;AACrF,QAAM,QAAQ,KAAK,KAAK,EAAE,YAAY;AACtC,MAAI,CAAC;AAAO,WAAO;AAEnB,SAAO,KAAK,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,EAAE,KAAK,KAAK,EAAE,YAAY,MAAM,KAAK;AAC9E;AAEO,SAAS,aAAa,MAA0C;AACrE,SAAO,KAAK,OAAO,CAAC,MAAM,EAAE,KAAK,KAAK,MAAM,EAAE;AAChD;","names":[]}
@@ -0,0 +1,5 @@
1
+ import type { KeyphraseEntry } from './keyphraseState';
2
+ export declare function storageKey(collectionSlug: string, docId: string, locale: string): string;
3
+ export declare function loadKeyphrases(key: string): KeyphraseEntry[];
4
+ export declare function saveKeyphrases(key: string, list: KeyphraseEntry[]): void;
5
+ //# sourceMappingURL=keyphraseStorage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keyphraseStorage.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/keyphraseStorage.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AASvD,wBAAgB,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExF;AAWD,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,EAAE,CAyB5D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc,EAAE,GAAG,IAAI,CAmBxE"}
@@ -0,0 +1,60 @@
1
+ import { createEntry, pruneEmpties } from "./keyphraseState";
2
+ const PREFIX = "seo-kw";
3
+ function storageKey(collectionSlug, docId, locale) {
4
+ return `${PREFIX}:${collectionSlug}:${docId}:${locale}`;
5
+ }
6
+ function store() {
7
+ try {
8
+ if (typeof window === "undefined" || !window.localStorage)
9
+ return null;
10
+ return window.localStorage;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function loadKeyphrases(key) {
16
+ const fallback = () => [createEntry()];
17
+ const s = store();
18
+ if (!s)
19
+ return fallback();
20
+ try {
21
+ const raw = s.getItem(key);
22
+ if (!raw)
23
+ return fallback();
24
+ const parsed = JSON.parse(raw);
25
+ if (!Array.isArray(parsed) || parsed.length === 0)
26
+ return fallback();
27
+ const list = parsed.filter((e) => e && typeof e.text === "string").map(
28
+ (e) => createEntry(
29
+ e.text,
30
+ Array.isArray(e.synonyms) ? e.synonyms.filter((x) => typeof x === "string") : []
31
+ )
32
+ );
33
+ return list.length ? list : fallback();
34
+ } catch {
35
+ return fallback();
36
+ }
37
+ }
38
+ function saveKeyphrases(key, list) {
39
+ const s = store();
40
+ if (!s)
41
+ return;
42
+ try {
43
+ const persistable = pruneEmpties(list).map((k) => ({
44
+ text: k.text,
45
+ synonyms: k.synonyms
46
+ }));
47
+ if (persistable.length === 0) {
48
+ s.removeItem(key);
49
+ return;
50
+ }
51
+ s.setItem(key, JSON.stringify(persistable));
52
+ } catch {
53
+ }
54
+ }
55
+ export {
56
+ loadKeyphrases,
57
+ saveKeyphrases,
58
+ storageKey
59
+ };
60
+ //# sourceMappingURL=keyphraseStorage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/components/SeoDrawer/keyphraseStorage.ts"],"sourcesContent":["import { createEntry, pruneEmpties } from './keyphraseState';\nimport type { KeyphraseEntry } from './keyphraseState';\n\nconst PREFIX = \"seo-kw\";\n\ninterface StoredEntry {\n text: string;\n synonyms: string[];\n}\n\nexport function storageKey(collectionSlug: string, docId: string, locale: string): string {\n return `${PREFIX}:${collectionSlug}:${docId}:${locale}`;\n}\n\nfunction store(): Storage | null {\n try {\n if (typeof window === \"undefined\" || !window.localStorage) return null;\n return window.localStorage;\n } catch {\n return null;\n }\n}\n\nexport function loadKeyphrases(key: string): KeyphraseEntry[] {\n const fallback = () => [createEntry()];\n const s = store();\n if (!s) return fallback();\n\n try {\n const raw = s.getItem(key);\n if (!raw) return fallback();\n\n const parsed = JSON.parse(raw) as StoredEntry[];\n if (!Array.isArray(parsed) || parsed.length === 0) return fallback();\n\n const list = parsed\n .filter((e) => e && typeof e.text === \"string\")\n .map((e) =>\n createEntry(\n e.text,\n Array.isArray(e.synonyms) ? e.synonyms.filter((x) => typeof x === \"string\") : []\n )\n );\n\n return list.length ? list : fallback();\n } catch {\n return fallback();\n }\n}\n\nexport function saveKeyphrases(key: string, list: KeyphraseEntry[]): void {\n const s = store();\n if (!s) return;\n\n try {\n const persistable = pruneEmpties(list).map<StoredEntry>((k) => ({\n text: k.text,\n synonyms: k.synonyms,\n }));\n\n if (persistable.length === 0) {\n s.removeItem(key);\n return;\n }\n\n s.setItem(key, JSON.stringify(persistable));\n } catch {\n // quota / serialization — fail soft\n }\n}\n"],"mappings":"AAAA,SAAS,aAAa,oBAAoB;AAG1C,MAAM,SAAS;AAOR,SAAS,WAAW,gBAAwB,OAAe,QAAwB;AACxF,SAAO,GAAG,MAAM,IAAI,cAAc,IAAI,KAAK,IAAI,MAAM;AACvD;AAEA,SAAS,QAAwB;AAC/B,MAAI;AACF,QAAI,OAAO,WAAW,eAAe,CAAC,OAAO;AAAc,aAAO;AAClE,WAAO,OAAO;AAAA,EAChB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEO,SAAS,eAAe,KAA+B;AAC5D,QAAM,WAAW,MAAM,CAAC,YAAY,CAAC;AACrC,QAAM,IAAI,MAAM;AAChB,MAAI,CAAC;AAAG,WAAO,SAAS;AAExB,MAAI;AACF,UAAM,MAAM,EAAE,QAAQ,GAAG;AACzB,QAAI,CAAC;AAAK,aAAO,SAAS;AAE1B,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,OAAO,WAAW;AAAG,aAAO,SAAS;AAEnE,UAAM,OAAO,OACV,OAAO,CAAC,MAAM,KAAK,OAAO,EAAE,SAAS,QAAQ,EAC7C;AAAA,MAAI,CAAC,MACJ;AAAA,QACE,EAAE;AAAA,QACF,MAAM,QAAQ,EAAE,QAAQ,IAAI,EAAE,SAAS,OAAO,CAAC,MAAM,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,MACjF;AAAA,IACF;AAEF,WAAO,KAAK,SAAS,OAAO,SAAS;AAAA,EACvC,QAAQ;AACN,WAAO,SAAS;AAAA,EAClB;AACF;AAEO,SAAS,eAAe,KAAa,MAA8B;AACxE,QAAM,IAAI,MAAM;AAChB,MAAI,CAAC;AAAG;AAER,MAAI;AACF,UAAM,cAAc,aAAa,IAAI,EAAE,IAAiB,CAAC,OAAO;AAAA,MAC9D,MAAM,EAAE;AAAA,MACR,UAAU,EAAE;AAAA,IACd,EAAE;AAEF,QAAI,YAAY,WAAW,GAAG;AAC5B,QAAE,WAAW,GAAG;AAChB;AAAA,IACF;AAEA,MAAE,QAAQ,KAAK,KAAK,UAAU,WAAW,CAAC;AAAA,EAC5C,QAAQ;AAAA,EAER;AACF;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"InclusiveTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/InclusiveTab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAMrE,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC,CAAA;CAAE,2CA8D3E"}
1
+ {"version":3,"file":"InclusiveTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/InclusiveTab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAOrE,wBAAgB,YAAY,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAC,WAAW,CAAC,CAAA;CAAE,2CAgE3E"}
@@ -4,9 +4,10 @@ import { cn, ROW_SEPARATOR } from "../../../utils/style";
4
4
  import { TabHeader } from "../../../ui/TabHeader";
5
5
  import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
+ import { TabWrapper } from "../components/TabWrapper";
7
8
  function InclusiveTab({ data }) {
8
9
  const flagged = data.categories.reduce((n, c) => n + c.flags.length, 0);
9
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
10
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
10
11
  /* @__PURE__ */ jsx(
11
12
  TabHeader,
12
13
  {
@@ -59,7 +60,7 @@ function InclusiveTab({ data }) {
59
60
  )) })
60
61
  }
61
62
  )
62
- ] });
63
+ ] }) });
63
64
  }
64
65
  export {
65
66
  InclusiveTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/InclusiveTab.tsx"],"sourcesContent":["\"use client\";\n\nimport type { AnalysisResult } from \"../../../engine/types/analysis\";\nimport { cn, ROW_SEPARATOR } from \"../../../utils/style\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\n\nexport function InclusiveTab({ data }: { data: AnalysisResult[\"inclusive\"] }) {\n const flagged = data.categories.reduce((n, c) => n + c.flags.length, 0);\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"Inclusive language\"\n score={data.ringScore}\n status={data.status}\n statusLabel={data.status === \"good\" ? \"Good\" : \"Needs work\"}\n subtitle={\n <>\n {flagged} phrases flagged across {data.categories.length} categories\n </>\n }\n />\n\n {data.categories.length > 0 && (\n <SectionCard title=\"Marked by category\" widget={<Pill variant=\"neutral\">{flagged}</Pill>}>\n {data.categories.map((cat) => (\n <div className={cn(\"relative px-[15px] py-[12px]\", ROW_SEPARATOR)} key={cat.name}>\n <div className=\"flex items-center gap-[8px] mb-[9px]\">\n <span className=\"font-bold text-[12.5px]\">{cat.name}</span>\n <Pill variant=\"bad\">{cat.flags.length}</Pill>\n </div>\n {cat.flags.map((f, i) => (\n <div\n className=\"flex items-center gap-[9px] py-[6px] text-[12px]\"\n key={`${f.term}-${i}`}\n >\n <span className=\"text-seo-bad font-medium whitespace-nowrap\">{f.term}</span>\n <span className=\"text-neutral-300\">›</span>\n <span className=\"text-seo-good font-medium flex-1\">{f.suggestion}</span>\n <span className=\"text-neutral-500 font-mono text-[10.5px] whitespace-nowrap\">\n {f.location}\n </span>\n </div>\n ))}\n </div>\n ))}\n </SectionCard>\n )}\n\n {data.cleanCategories.length > 0 && (\n <SectionCard\n title=\"No issues found\"\n widget={<Pill variant=\"neutral\">{data.cleanCategories.length}</Pill>}\n >\n <div className=\"flex gap-[8px] flex-wrap px-[15px] py-[13px]\">\n {data.cleanCategories.map((n) => (\n <span\n className=\"inline-flex items-center gap-[6px] text-[11.5px] text-seo-good bg-seo-good-100 border border-seo-good-200 rounded-[20px] px-[11px] py-[4px] font-medium\"\n key={n}\n >\n ✓ {n}\n </span>\n ))}\n </div>\n </SectionCard>\n )}\n </section>\n );\n}\n"],"mappings":";AAaM,SAMI,UANJ,KAMI,YANJ;AAVN,SAAS,IAAI,qBAAqB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AAEd,SAAS,aAAa,EAAE,KAAK,GAA0C;AAC5E,QAAM,UAAU,KAAK,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEtE,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,aAAa,KAAK,WAAW,SAAS,SAAS;AAAA,QAC/C,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAyB,KAAK,WAAW;AAAA,UAAO;AAAA,WAC3D;AAAA;AAAA,IAEJ;AAAA,IAEC,KAAK,WAAW,SAAS,KACxB,oBAAC,eAAY,OAAM,sBAAqB,QAAQ,oBAAC,QAAK,SAAQ,WAAW,mBAAQ,GAC9E,eAAK,WAAW,IAAI,CAAC,QACpB,qBAAC,SAAI,WAAW,GAAG,gCAAgC,aAAa,GAC9D;AAAA,2BAAC,SAAI,WAAU,wCACb;AAAA,4BAAC,UAAK,WAAU,2BAA2B,cAAI,MAAK;AAAA,QACpD,oBAAC,QAAK,SAAQ,OAAO,cAAI,MAAM,QAAO;AAAA,SACxC;AAAA,MACC,IAAI,MAAM,IAAI,CAAC,GAAG,MACjB;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UAGV;AAAA,gCAAC,UAAK,WAAU,8CAA8C,YAAE,MAAK;AAAA,YACrE,oBAAC,UAAK,WAAU,oBAAmB,oBAAC;AAAA,YACpC,oBAAC,UAAK,WAAU,oCAAoC,YAAE,YAAW;AAAA,YACjE,oBAAC,UAAK,WAAU,8DACb,YAAE,UACL;AAAA;AAAA;AAAA,QAPK,GAAG,EAAE,IAAI,IAAI,CAAC;AAAA,MAQrB,CACD;AAAA,SAjBqE,IAAI,IAkB5E,CACD,GACH;AAAA,IAGD,KAAK,gBAAgB,SAAS,KAC7B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,gBAAgB,QAAO;AAAA,QAE7D,8BAAC,SAAI,WAAU,gDACZ,eAAK,gBAAgB,IAAI,CAAC,MACzB;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YAEX;AAAA;AAAA,cACI;AAAA;AAAA;AAAA,UAFE;AAAA,QAGP,CACD,GACH;AAAA;AAAA,IACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/InclusiveTab.tsx"],"sourcesContent":["\"use client\";\n\nimport type { AnalysisResult } from \"../../../engine/types/analysis\";\nimport { cn, ROW_SEPARATOR } from \"../../../utils/style\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function InclusiveTab({ data }: { data: AnalysisResult[\"inclusive\"] }) {\n const flagged = data.categories.reduce((n, c) => n + c.flags.length, 0);\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"Inclusive language\"\n score={data.ringScore}\n status={data.status}\n statusLabel={data.status === \"good\" ? \"Good\" : \"Needs work\"}\n subtitle={\n <>\n {flagged} phrases flagged across {data.categories.length} categories\n </>\n }\n />\n\n {data.categories.length > 0 && (\n <SectionCard title=\"Marked by category\" widget={<Pill variant=\"neutral\">{flagged}</Pill>}>\n {data.categories.map((cat) => (\n <div className={cn(\"relative px-[15px] py-[12px]\", ROW_SEPARATOR)} key={cat.name}>\n <div className=\"flex items-center gap-[8px] mb-[9px]\">\n <span className=\"font-bold text-[12.5px]\">{cat.name}</span>\n <Pill variant=\"bad\">{cat.flags.length}</Pill>\n </div>\n {cat.flags.map((f, i) => (\n <div\n className=\"flex items-center gap-[9px] py-[6px] text-[12px]\"\n key={`${f.term}-${i}`}\n >\n <span className=\"text-seo-bad font-medium whitespace-nowrap\">{f.term}</span>\n <span className=\"text-neutral-300\">›</span>\n <span className=\"text-seo-good font-medium flex-1\">{f.suggestion}</span>\n <span className=\"text-neutral-500 font-mono text-[10.5px] whitespace-nowrap\">\n {f.location}\n </span>\n </div>\n ))}\n </div>\n ))}\n </SectionCard>\n )}\n\n {data.cleanCategories.length > 0 && (\n <SectionCard\n title=\"No issues found\"\n widget={<Pill variant=\"neutral\">{data.cleanCategories.length}</Pill>}\n >\n <div className=\"flex gap-[8px] flex-wrap px-[15px] py-[13px]\">\n {data.cleanCategories.map((n) => (\n <span\n className=\"inline-flex items-center gap-[6px] text-[11.5px] text-seo-good bg-seo-good-100 border border-seo-good-200 rounded-[20px] px-[11px] py-[4px] font-medium\"\n key={n}\n >\n ✓ {n}\n </span>\n ))}\n </div>\n </SectionCard>\n )}\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAeQ,SAMI,UANJ,KAMI,YANJ;AAZR,SAAS,IAAI,qBAAqB;AAClC,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAEpB,SAAS,aAAa,EAAE,KAAK,GAA0C;AAC5E,QAAM,UAAU,KAAK,WAAW,OAAO,CAAC,GAAG,MAAM,IAAI,EAAE,MAAM,QAAQ,CAAC;AAEtE,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,aAAa,KAAK,WAAW,SAAS,SAAS;AAAA,QAC/C,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAyB,KAAK,WAAW;AAAA,UAAO;AAAA,WAC3D;AAAA;AAAA,IAEJ;AAAA,IAEC,KAAK,WAAW,SAAS,KACxB,oBAAC,eAAY,OAAM,sBAAqB,QAAQ,oBAAC,QAAK,SAAQ,WAAW,mBAAQ,GAC9E,eAAK,WAAW,IAAI,CAAC,QACpB,qBAAC,SAAI,WAAW,GAAG,gCAAgC,aAAa,GAC9D;AAAA,2BAAC,SAAI,WAAU,wCACb;AAAA,4BAAC,UAAK,WAAU,2BAA2B,cAAI,MAAK;AAAA,QACpD,oBAAC,QAAK,SAAQ,OAAO,cAAI,MAAM,QAAO;AAAA,SACxC;AAAA,MACC,IAAI,MAAM,IAAI,CAAC,GAAG,MACjB;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UAGV;AAAA,gCAAC,UAAK,WAAU,8CAA8C,YAAE,MAAK;AAAA,YACrE,oBAAC,UAAK,WAAU,oBAAmB,oBAAC;AAAA,YACpC,oBAAC,UAAK,WAAU,oCAAoC,YAAE,YAAW;AAAA,YACjE,oBAAC,UAAK,WAAU,8DACb,YAAE,UACL;AAAA;AAAA;AAAA,QAPK,GAAG,EAAE,IAAI,IAAI,CAAC;AAAA,MAQrB,CACD;AAAA,SAjBqE,IAAI,IAkB5E,CACD,GACH;AAAA,IAGD,KAAK,gBAAgB,SAAS,KAC7B;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,gBAAgB,QAAO;AAAA,QAE7D,8BAAC,SAAI,WAAU,gDACZ,eAAK,gBAAgB,IAAI,CAAC,MACzB;AAAA,UAAC;AAAA;AAAA,YACC,WAAU;AAAA,YAEX;AAAA;AAAA,cACI;AAAA;AAAA;AAAA,UAFE;AAAA,QAGP,CACD,GACH;AAAA;AAAA,IACF;AAAA,KAEJ,GACF;AAEJ;","names":[]}
@@ -1,11 +1,18 @@
1
- import type { CategoryResult } from "../../../engine/types/analysis";
1
+ import type { AnalysisResult } from "../../../engine/types/analysis";
2
+ import type { KeyphraseEntry } from "../keyphraseState";
2
3
  export interface KeyphraseTabProps {
3
- data: CategoryResult;
4
- keyphrase: string;
5
- setKeyphrase: (keyphrase: string) => void;
4
+ keyphrases: KeyphraseEntry[];
5
+ selectedId: string | null;
6
+ onSelect: (id: string) => void;
7
+ result: AnalysisResult | null;
6
8
  analyzing: boolean;
7
- keyphrasePending: boolean;
8
- analyzeNow: () => void;
9
+ onAddRelated: () => void;
10
+ onTextChange: (id: string, text: string) => void;
11
+ onAddSynonym: (id: string, syn: string) => void;
12
+ onRemoveSynonym: (id: string, index: number) => void;
13
+ onRemove: (id: string) => void;
14
+ onSetFocus: (id: string) => void;
15
+ isDuplicate: (id: string, text: string) => boolean;
9
16
  }
10
- export declare function KeyphraseTab({ data, keyphrase, setKeyphrase, analyzing, keyphrasePending, analyzeNow, }: KeyphraseTabProps): import("react/jsx-runtime").JSX.Element;
17
+ export declare function KeyphraseTab({ keyphrases, selectedId: selectedIdProp, onSelect, result, analyzing, onAddRelated, onTextChange, onAddSynonym, onRemoveSynonym, onRemove, onSetFocus, isDuplicate, }: KeyphraseTabProps): import("react/jsx-runtime").JSX.Element;
11
18
  //# sourceMappingURL=KeyphraseTab.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"KeyphraseTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/KeyphraseTab.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAQrE,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,SAAS,EAAE,OAAO,CAAC;IACnB,gBAAgB,EAAE,OAAO,CAAC;IAC1B,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,YAAY,CAAC,EAC3B,IAAI,EACJ,SAAS,EACT,YAAY,EACZ,SAAS,EACT,gBAAgB,EAChB,UAAU,GACX,EAAE,iBAAiB,2CAqEnB"}
1
+ {"version":3,"file":"KeyphraseTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/KeyphraseTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAkB,MAAM,gCAAgC,CAAC;AACrF,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAMxD,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,cAAc,EAAE,CAAC;IAC7B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,YAAY,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,eAAe,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACrD,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,UAAU,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACpD;AAYD,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,UAAU,EAAE,cAAc,EAC1B,QAAQ,EACR,MAAM,EACN,SAAS,EACT,YAAY,EACZ,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,UAAU,EACV,WAAW,GACZ,EAAE,iBAAiB,2CAkGnB"}
@@ -1,76 +1,117 @@
1
1
  "use client";
2
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
- import { Loader2 } from "lucide-react";
4
- import { useState } from "react";
5
- import { hasKeyphrase } from "../../../engine/helpers/has-keyphrase";
6
- import { TabHeader } from "../../../ui/TabHeader";
7
- import { SectionCard } from "../../../ui/SectionCard";
8
- import { Pill } from "../../../ui/Pill";
9
- import { FilterPills } from "../../../ui/FilterPills";
10
- import { CheckRow } from "../../../ui/CheckRow";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useRef } from "react";
4
+ import { KeyphraseDetail } from "./keyphrase/KeyphraseDetail";
5
+ import { KeyphraseRail } from "./keyphrase/KeyphraseRail";
6
+ function resultForEntry(entry, isFocus, result) {
7
+ if (!result)
8
+ return void 0;
9
+ if (isFocus)
10
+ return result.keyphraseText === entry.text ? result.keyphrase : void 0;
11
+ return result.relatedKeyphrases.find((r) => r.text === entry.text)?.result;
12
+ }
11
13
  function KeyphraseTab({
12
- data,
13
- keyphrase,
14
- setKeyphrase,
14
+ keyphrases,
15
+ selectedId: selectedIdProp,
16
+ onSelect,
17
+ result,
15
18
  analyzing,
16
- keyphrasePending,
17
- analyzeNow
19
+ onAddRelated,
20
+ onTextChange,
21
+ onAddSynonym,
22
+ onRemoveSynonym,
23
+ onRemove,
24
+ onSetFocus,
25
+ isDuplicate
18
26
  }) {
19
- const [filter, setFilter] = useState("all");
20
- const visible = data.checks.filter((c) => filter === "all" || c.status === filter);
21
- const passing = data.checks.filter((c) => c.status === "good").length;
22
- const keyphrasePresent = hasKeyphrase(keyphrase);
23
- const checksReady = keyphrasePresent && !keyphrasePending && !analyzing && data.checks.length > 0;
24
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
25
- /* @__PURE__ */ jsxs("div", { className: "flex gap-[8px]", children: [
26
- /* @__PURE__ */ jsx("label", { className: "flex-1 flex items-center gap-[8px] px-[12px] py-[9px] border border-neutral-200 rounded-rs bg-neutral-0", children: /* @__PURE__ */ jsx(
27
- "input",
28
- {
29
- type: "text",
30
- className: "border-0 outline-none flex-1 text-[13px] text-neutral-800 bg-transparent",
31
- value: keyphrase,
32
- onChange: (e) => setKeyphrase(e.target.value),
33
- placeholder: "Focus keyphrase",
34
- "aria-label": "Focus keyphrase"
35
- }
36
- ) }),
37
- /* @__PURE__ */ jsx("span", { "aria-live": "polite", className: "sr-only", role: "status", children: keyphrasePending ? "Analyzing keyphrase\u2026" : "" }),
38
- /* @__PURE__ */ jsx(
39
- "button",
40
- {
41
- type: "button",
42
- className: "px-[18px] py-[9px] bg-neutral-1000 text-neutral-0 border-0 rounded-rs font-medium text-[13px] cursor-pointer disabled:opacity-50",
43
- disabled: !keyphrasePresent,
44
- onClick: () => analyzeNow(),
45
- children: analyzing ? "Analyzing\u2026" : "Analyze"
46
- }
47
- )
48
- ] }),
49
- !keyphrasePresent && /* @__PURE__ */ jsx("p", { className: "text-neutral-500 text-[13px]", children: "Enter a focus keyphrase to analyze how well your content targets it." }),
50
- keyphrasePresent && !checksReady && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[8px] text-neutral-500 text-[13px]", children: [
51
- /* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "w-[14px] h-[14px] animate-spin" }),
52
- "Analyzing keyphrase\u2026"
53
- ] }),
54
- checksReady && /* @__PURE__ */ jsxs(Fragment, { children: [
55
- /* @__PURE__ */ jsx(
56
- TabHeader,
57
- {
58
- title: "Keyphrase optimization",
59
- score: data.ringScore,
60
- status: data.status,
61
- subtitle: /* @__PURE__ */ jsxs(Fragment, { children: [
62
- passing,
63
- " / ",
64
- data.checks.length,
65
- " checks passing"
66
- ] })
67
- }
68
- ),
69
- /* @__PURE__ */ jsxs(SectionCard, { title: "Checks", widget: /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: data.checks.length }), children: [
70
- /* @__PURE__ */ jsx(FilterPills, { checks: data.checks, value: filter, onChange: setFilter }),
71
- visible.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))
72
- ] })
73
- ] })
27
+ const selectedId = selectedIdProp ?? keyphrases[0]?.id ?? null;
28
+ const selected = keyphrases.find((k) => k.id === selectedId) ?? keyphrases[0] ?? null;
29
+ const selectedIsFocus = selected ? keyphrases.indexOf(selected) === 0 : true;
30
+ const lastMetrics = useRef(/* @__PURE__ */ new Map());
31
+ const liveIds = new Set(keyphrases.map((k) => k.id));
32
+ for (const id of [...lastMetrics.current.keys()]) {
33
+ if (!liveIds.has(id))
34
+ lastMetrics.current.delete(id);
35
+ }
36
+ keyphrases.forEach((entry, index) => {
37
+ if (entry.text.trim() === "") {
38
+ lastMetrics.current.delete(entry.id);
39
+ return;
40
+ }
41
+ const fresh = resultForEntry(entry, index === 0, result);
42
+ if (fresh)
43
+ lastMetrics.current.set(entry.id, fresh);
44
+ });
45
+ const stateFor = (entry) => {
46
+ if (!entry.text.trim())
47
+ return { kind: "idle" };
48
+ const isFocus = keyphrases.indexOf(entry) === 0;
49
+ if (!isFocus && isDuplicate(entry.id, entry.text))
50
+ return { kind: "idle" };
51
+ const category = resultForEntry(entry, isFocus, result);
52
+ if (category)
53
+ return { kind: "score", score: category.ringScore, status: category.status };
54
+ return { kind: "analyzing" };
55
+ };
56
+ const detailAnalysis = () => {
57
+ if (!selected)
58
+ return { kind: "hint", message: "" };
59
+ if (isDuplicate(selected.id, selected.text)) {
60
+ return {
61
+ kind: "hint",
62
+ message: "Resolve the duplicate to analyse this keyphrase."
63
+ };
64
+ }
65
+ if (!selected.text.trim()) {
66
+ return {
67
+ kind: "hint",
68
+ message: "Start typing a keyphrase to see its analysis."
69
+ };
70
+ }
71
+ const category = resultForEntry(selected, selectedIsFocus, result);
72
+ if (category) {
73
+ return {
74
+ kind: "metrics",
75
+ result: category,
76
+ dim: analyzing
77
+ };
78
+ }
79
+ const previous = lastMetrics.current.get(selected.id);
80
+ if (previous) {
81
+ return {
82
+ kind: "metrics",
83
+ result: previous,
84
+ dim: true
85
+ };
86
+ }
87
+ return { kind: "analyzing" };
88
+ };
89
+ const analysis = detailAnalysis();
90
+ return /* @__PURE__ */ jsxs("div", { className: "flex min-h-[420px]", children: [
91
+ /* @__PURE__ */ jsx(
92
+ KeyphraseRail,
93
+ {
94
+ entries: keyphrases,
95
+ onAdd: onAddRelated,
96
+ onSelect,
97
+ selectedId,
98
+ stateFor
99
+ }
100
+ ),
101
+ selected && /* @__PURE__ */ jsx(
102
+ KeyphraseDetail,
103
+ {
104
+ analysis,
105
+ duplicate: isDuplicate(selected.id, selected.text),
106
+ entry: selected,
107
+ isFocus: selectedIsFocus,
108
+ onAddSynonym: (syn) => onAddSynonym(selected.id, syn),
109
+ onRemove: () => onRemove(selected.id),
110
+ onRemoveSynonym: (index) => onRemoveSynonym(selected.id, index),
111
+ onSetFocus: () => onSetFocus(selected.id),
112
+ onTextChange: (text) => onTextChange(selected.id, text)
113
+ }
114
+ )
74
115
  ] });
75
116
  }
76
117
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/KeyphraseTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { Loader2 } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { hasKeyphrase } from \"../../../engine/helpers/has-keyphrase\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\n\nexport interface KeyphraseTabProps {\n data: CategoryResult;\n keyphrase: string;\n setKeyphrase: (keyphrase: string) => void;\n analyzing: boolean;\n keyphrasePending: boolean;\n analyzeNow: () => void;\n}\n\nexport function KeyphraseTab({\n data,\n keyphrase,\n setKeyphrase,\n analyzing,\n keyphrasePending,\n analyzeNow,\n}: KeyphraseTabProps) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n const keyphrasePresent = hasKeyphrase(keyphrase);\n const checksReady = keyphrasePresent && !keyphrasePending && !analyzing && data.checks.length > 0;\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <div className=\"flex gap-[8px]\">\n <label className=\"flex-1 flex items-center gap-[8px] px-[12px] py-[9px] border border-neutral-200 rounded-rs bg-neutral-0\">\n <input\n type=\"text\"\n className=\"border-0 outline-none flex-1 text-[13px] text-neutral-800 bg-transparent\"\n value={keyphrase}\n onChange={(e) => setKeyphrase(e.target.value)}\n placeholder=\"Focus keyphrase\"\n aria-label=\"Focus keyphrase\"\n />\n </label>\n <span aria-live=\"polite\" className=\"sr-only\" role=\"status\">\n {keyphrasePending ? \"Analyzing keyphrase…\" : \"\"}\n </span>\n <button\n type=\"button\"\n className=\"px-[18px] py-[9px] bg-neutral-1000 text-neutral-0 border-0 rounded-rs font-medium text-[13px] cursor-pointer disabled:opacity-50\"\n disabled={!keyphrasePresent}\n onClick={() => analyzeNow()}\n >\n {analyzing ? \"Analyzing…\" : \"Analyze\"}\n </button>\n </div>\n\n {!keyphrasePresent && (\n <p className=\"text-neutral-500 text-[13px]\">\n Enter a focus keyphrase to analyze how well your content targets it.\n </p>\n )}\n\n {keyphrasePresent && !checksReady && (\n <div className=\"flex items-center gap-[8px] text-neutral-500 text-[13px]\">\n <Loader2 aria-hidden=\"true\" className=\"w-[14px] h-[14px] animate-spin\" />\n Analyzing keyphrase…\n </div>\n )}\n\n {checksReady && (\n <>\n <TabHeader\n title=\"Keyphrase optimization\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </>\n )}\n </section>\n );\n}\n"],"mappings":";AAsCM,SA4CQ,UA1CJ,KAFJ;AApCN,SAAS,eAAe;AACxB,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;AAE7B,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAWlB,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAC/D,QAAM,mBAAmB,aAAa,SAAS;AAC/C,QAAM,cAAc,oBAAoB,CAAC,oBAAoB,CAAC,aAAa,KAAK,OAAO,SAAS;AAEhG,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA,yBAAC,SAAI,WAAU,kBACb;AAAA,0BAAC,WAAM,WAAU,2GACf;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,OAAO;AAAA,UACP,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,UAC5C,aAAY;AAAA,UACZ,cAAW;AAAA;AAAA,MACb,GACF;AAAA,MACA,oBAAC,UAAK,aAAU,UAAS,WAAU,WAAU,MAAK,UAC/C,6BAAmB,8BAAyB,IAC/C;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,WAAU;AAAA,UACV,UAAU,CAAC;AAAA,UACX,SAAS,MAAM,WAAW;AAAA,UAEzB,sBAAY,oBAAe;AAAA;AAAA,MAC9B;AAAA,OACF;AAAA,IAEC,CAAC,oBACA,oBAAC,OAAE,WAAU,gCAA+B,kFAE5C;AAAA,IAGD,oBAAoB,CAAC,eACpB,qBAAC,SAAI,WAAU,4DACb;AAAA,0BAAC,WAAQ,eAAY,QAAO,WAAU,kCAAiC;AAAA,MAAE;AAAA,OAE3E;AAAA,IAGD,eACC,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,OAAM;AAAA,UACN,OAAO,KAAK;AAAA,UACZ,QAAQ,KAAK;AAAA,UACb,UACE,iCACG;AAAA;AAAA,YAAQ;AAAA,YAAI,KAAK,OAAO;AAAA,YAAO;AAAA,aAClC;AAAA;AAAA,MAEJ;AAAA,MAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,4BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,QACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,SACH;AAAA,OACF;AAAA,KAEJ;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/KeyphraseTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useRef } from \"react\";\nimport type { AnalysisResult, CategoryResult } from \"../../../engine/types/analysis\";\nimport type { KeyphraseEntry } from \"../keyphraseState\";\nimport type { CardState } from \"./keyphrase/KeyphraseCard\";\nimport { KeyphraseDetail } from \"./keyphrase/KeyphraseDetail\";\nimport type { DetailAnalysis } from \"./keyphrase/KeyphraseDetail\";\nimport { KeyphraseRail } from \"./keyphrase/KeyphraseRail\";\n\nexport interface KeyphraseTabProps {\n keyphrases: KeyphraseEntry[];\n selectedId: string | null;\n onSelect: (id: string) => void;\n result: AnalysisResult | null;\n analyzing: boolean;\n onAddRelated: () => void;\n onTextChange: (id: string, text: string) => void;\n onAddSynonym: (id: string, syn: string) => void;\n onRemoveSynonym: (id: string, index: number) => void;\n onRemove: (id: string) => void;\n onSetFocus: (id: string) => void;\n isDuplicate: (id: string, text: string) => boolean;\n}\n\nfunction resultForEntry(\n entry: KeyphraseEntry,\n isFocus: boolean,\n result: AnalysisResult | null\n): CategoryResult | undefined {\n if (!result) return undefined;\n if (isFocus) return result.keyphraseText === entry.text ? result.keyphrase : undefined;\n return result.relatedKeyphrases.find((r) => r.text === entry.text)?.result;\n}\n\nexport function KeyphraseTab({\n keyphrases,\n selectedId: selectedIdProp,\n onSelect,\n result,\n analyzing,\n onAddRelated,\n onTextChange,\n onAddSynonym,\n onRemoveSynonym,\n onRemove,\n onSetFocus,\n isDuplicate,\n}: KeyphraseTabProps) {\n const selectedId = selectedIdProp ?? keyphrases[0]?.id ?? null;\n const selected = keyphrases.find((k) => k.id === selectedId) ?? keyphrases[0] ?? null;\n const selectedIsFocus = selected ? keyphrases.indexOf(selected) === 0 : true;\n\n const lastMetrics = useRef<Map<string, CategoryResult>>(new Map());\n const liveIds = new Set(keyphrases.map((k) => k.id));\n\n for (const id of [...lastMetrics.current.keys()]) {\n if (!liveIds.has(id)) lastMetrics.current.delete(id);\n }\n\n keyphrases.forEach((entry, index) => {\n if (entry.text.trim() === \"\") {\n lastMetrics.current.delete(entry.id);\n return;\n }\n const fresh = resultForEntry(entry, index === 0, result);\n if (fresh) lastMetrics.current.set(entry.id, fresh);\n });\n\n const stateFor = (entry: KeyphraseEntry): CardState => {\n if (!entry.text.trim()) return { kind: \"idle\" };\n\n const isFocus = keyphrases.indexOf(entry) === 0;\n if (!isFocus && isDuplicate(entry.id, entry.text)) return { kind: \"idle\" };\n\n const category = resultForEntry(entry, isFocus, result);\n if (category) return { kind: \"score\", score: category.ringScore, status: category.status };\n\n return { kind: \"analyzing\" };\n };\n\n const detailAnalysis = (): DetailAnalysis => {\n if (!selected) return { kind: \"hint\", message: \"\" };\n\n if (isDuplicate(selected.id, selected.text)) {\n return {\n kind: \"hint\",\n message: \"Resolve the duplicate to analyse this keyphrase.\",\n };\n }\n\n if (!selected.text.trim()) {\n return {\n kind: \"hint\",\n message: \"Start typing a keyphrase to see its analysis.\",\n };\n }\n\n const category = resultForEntry(selected, selectedIsFocus, result);\n if (category) {\n return {\n kind: \"metrics\",\n result: category,\n dim: analyzing,\n };\n }\n\n const previous = lastMetrics.current.get(selected.id);\n if (previous) {\n return {\n kind: \"metrics\",\n result: previous,\n dim: true,\n };\n }\n\n return { kind: \"analyzing\" };\n };\n\n const analysis = detailAnalysis();\n\n return (\n <div className=\"flex min-h-[420px]\">\n <KeyphraseRail\n entries={keyphrases}\n onAdd={onAddRelated}\n onSelect={onSelect}\n selectedId={selectedId}\n stateFor={stateFor}\n />\n\n {selected && (\n <KeyphraseDetail\n analysis={analysis}\n duplicate={isDuplicate(selected.id, selected.text)}\n entry={selected}\n isFocus={selectedIsFocus}\n onAddSynonym={(syn) => onAddSynonym(selected.id, syn)}\n onRemove={() => onRemove(selected.id)}\n onRemoveSynonym={(index) => onRemoveSynonym(selected.id, index)}\n onSetFocus={() => onSetFocus(selected.id)}\n onTextChange={(text) => onTextChange(selected.id, text)}\n />\n )}\n </div>\n );\n}\n"],"mappings":";AA0HI,SACE,KADF;AAxHJ,SAAS,cAAc;AAIvB,SAAS,uBAAuB;AAEhC,SAAS,qBAAqB;AAiB9B,SAAS,eACP,OACA,SACA,QAC4B;AAC5B,MAAI,CAAC;AAAQ,WAAO;AACpB,MAAI;AAAS,WAAO,OAAO,kBAAkB,MAAM,OAAO,OAAO,YAAY;AAC7E,SAAO,OAAO,kBAAkB,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,IAAI,GAAG;AACtE;AAEO,SAAS,aAAa;AAAA,EAC3B;AAAA,EACA,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAsB;AACpB,QAAM,aAAa,kBAAkB,WAAW,CAAC,GAAG,MAAM;AAC1D,QAAM,WAAW,WAAW,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK,WAAW,CAAC,KAAK;AACjF,QAAM,kBAAkB,WAAW,WAAW,QAAQ,QAAQ,MAAM,IAAI;AAExE,QAAM,cAAc,OAAoC,oBAAI,IAAI,CAAC;AACjE,QAAM,UAAU,IAAI,IAAI,WAAW,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAEnD,aAAW,MAAM,CAAC,GAAG,YAAY,QAAQ,KAAK,CAAC,GAAG;AAChD,QAAI,CAAC,QAAQ,IAAI,EAAE;AAAG,kBAAY,QAAQ,OAAO,EAAE;AAAA,EACrD;AAEA,aAAW,QAAQ,CAAC,OAAO,UAAU;AACnC,QAAI,MAAM,KAAK,KAAK,MAAM,IAAI;AAC5B,kBAAY,QAAQ,OAAO,MAAM,EAAE;AACnC;AAAA,IACF;AACA,UAAM,QAAQ,eAAe,OAAO,UAAU,GAAG,MAAM;AACvD,QAAI;AAAO,kBAAY,QAAQ,IAAI,MAAM,IAAI,KAAK;AAAA,EACpD,CAAC;AAED,QAAM,WAAW,CAAC,UAAqC;AACrD,QAAI,CAAC,MAAM,KAAK,KAAK;AAAG,aAAO,EAAE,MAAM,OAAO;AAE9C,UAAM,UAAU,WAAW,QAAQ,KAAK,MAAM;AAC9C,QAAI,CAAC,WAAW,YAAY,MAAM,IAAI,MAAM,IAAI;AAAG,aAAO,EAAE,MAAM,OAAO;AAEzE,UAAM,WAAW,eAAe,OAAO,SAAS,MAAM;AACtD,QAAI;AAAU,aAAO,EAAE,MAAM,SAAS,OAAO,SAAS,WAAW,QAAQ,SAAS,OAAO;AAEzF,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B;AAEA,QAAM,iBAAiB,MAAsB;AAC3C,QAAI,CAAC;AAAU,aAAO,EAAE,MAAM,QAAQ,SAAS,GAAG;AAElD,QAAI,YAAY,SAAS,IAAI,SAAS,IAAI,GAAG;AAC3C,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAEA,QAAI,CAAC,SAAS,KAAK,KAAK,GAAG;AACzB,aAAO;AAAA,QACL,MAAM;AAAA,QACN,SAAS;AAAA,MACX;AAAA,IACF;AAEA,UAAM,WAAW,eAAe,UAAU,iBAAiB,MAAM;AACjE,QAAI,UAAU;AACZ,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,KAAK;AAAA,MACP;AAAA,IACF;AAEA,UAAM,WAAW,YAAY,QAAQ,IAAI,SAAS,EAAE;AACpD,QAAI,UAAU;AACZ,aAAO;AAAA,QACL,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,KAAK;AAAA,MACP;AAAA,IACF;AAEA,WAAO,EAAE,MAAM,YAAY;AAAA,EAC7B;AAEA,QAAM,WAAW,eAAe;AAEhC,SACE,qBAAC,SAAI,WAAU,sBACb;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,SAAS;AAAA,QACT,OAAO;AAAA,QACP;AAAA,QACA;AAAA,QACA;AAAA;AAAA,IACF;AAAA,IAEC,YACC;AAAA,MAAC;AAAA;AAAA,QACC;AAAA,QACA,WAAW,YAAY,SAAS,IAAI,SAAS,IAAI;AAAA,QACjD,OAAO;AAAA,QACP,SAAS;AAAA,QACT,cAAc,CAAC,QAAQ,aAAa,SAAS,IAAI,GAAG;AAAA,QACpD,UAAU,MAAM,SAAS,SAAS,EAAE;AAAA,QACpC,iBAAiB,CAAC,UAAU,gBAAgB,SAAS,IAAI,KAAK;AAAA,QAC9D,YAAY,MAAM,WAAW,SAAS,EAAE;AAAA,QACxC,cAAc,CAAC,SAAS,aAAa,SAAS,IAAI,IAAI;AAAA;AAAA,IACxD;AAAA,KAEJ;AAEJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"OnPageTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAQrE,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA0B3D"}
1
+ {"version":3,"file":"OnPageTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AASrE,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA4B3D"}
@@ -6,11 +6,12 @@ import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
7
  import { FilterPills } from "../../../ui/FilterPills";
8
8
  import { CheckRow } from "../../../ui/CheckRow";
9
+ import { TabWrapper } from "../components/TabWrapper";
9
10
  function OnPageTab({ data }) {
10
11
  const [filter, setFilter] = useState("all");
11
12
  const visible = data.checks.filter((c) => filter === "all" || c.status === filter);
12
13
  const passing = data.checks.filter((c) => c.status === "good").length;
13
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
15
  /* @__PURE__ */ jsx(
15
16
  TabHeader,
16
17
  {
@@ -29,7 +30,7 @@ function OnPageTab({ data }) {
29
30
  /* @__PURE__ */ jsx(FilterPills, { checks: data.checks, value: filter, onChange: setFilter }),
30
31
  visible.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))
31
32
  ] })
32
- ] });
33
+ ] }) });
33
34
  }
34
35
  export {
35
36
  OnPageTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\n\nexport function OnPageTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"On-page structure\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n );\n}\n"],"mappings":";AAkBM,SAKI,UALJ,KAKI,YALJ;AAhBN,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAElB,SAAS,UAAU,EAAE,KAAK,GAA6B;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/OnPageTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function OnPageTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"On-page structure\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAoBQ,SAKI,UALJ,KAKI,YALJ;AAlBR,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAEpB,SAAS,UAAU,EAAE,KAAK,GAA6B;AAC5D,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF,GACF;AAEJ;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"ReadabilityTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAQrE,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA0BhE"}
1
+ {"version":3,"file":"ReadabilityTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AASrE,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,cAAc,CAAA;CAAE,2CA4BhE"}
@@ -6,11 +6,12 @@ import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
7
  import { FilterPills } from "../../../ui/FilterPills";
8
8
  import { CheckRow } from "../../../ui/CheckRow";
9
+ import { TabWrapper } from "../components/TabWrapper";
9
10
  function ReadabilityTab({ data }) {
10
11
  const [filter, setFilter] = useState("all");
11
12
  const visible = data.checks.filter((c) => filter === "all" || c.status === filter);
12
13
  const passing = data.checks.filter((c) => c.status === "good").length;
13
- return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
+ return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
14
15
  /* @__PURE__ */ jsx(
15
16
  TabHeader,
16
17
  {
@@ -29,7 +30,7 @@ function ReadabilityTab({ data }) {
29
30
  /* @__PURE__ */ jsx(FilterPills, { checks: data.checks, value: filter, onChange: setFilter }),
30
31
  visible.map((c) => /* @__PURE__ */ jsx(CheckRow, { check: c }, c.id))
31
32
  ] })
32
- ] });
33
+ ] }) });
33
34
  }
34
35
  export {
35
36
  ReadabilityTab
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\n\nexport function ReadabilityTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"Readability\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n );\n}\n"],"mappings":";AAkBM,SAKI,UALJ,KAKI,YALJ;AAhBN,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AAElB,SAAS,eAAe,EAAE,KAAK,GAA6B;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,qBAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/ReadabilityTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { useState } from \"react\";\nimport type { CategoryResult } from \"../../../engine/types/analysis\";\nimport { TabHeader } from \"../../../ui/TabHeader\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { FilterPills } from \"../../../ui/FilterPills\";\nimport type { Filter } from \"../../../ui/FilterPills\";\nimport { CheckRow } from \"../../../ui/CheckRow\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function ReadabilityTab({ data }: { data: CategoryResult }) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n const visible = data.checks.filter((c) => filter === \"all\" || c.status === filter);\n const passing = data.checks.filter((c) => c.status === \"good\").length;\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <TabHeader\n title=\"Readability\"\n score={data.ringScore}\n status={data.status}\n subtitle={\n <>\n {passing} / {data.checks.length} checks passing\n </>\n }\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{data.checks.length}</Pill>}>\n <FilterPills checks={data.checks} value={filter} onChange={setFilter} />\n {visible.map((c) => (\n <CheckRow key={c.id} check={c} />\n ))}\n </SectionCard>\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAoBQ,SAKI,UALJ,KAKI,YALJ;AAlBR,SAAS,gBAAgB;AAEzB,SAAS,iBAAiB;AAC1B,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAE5B,SAAS,gBAAgB;AACzB,SAAS,kBAAkB;AAEpB,SAAS,eAAe,EAAE,KAAK,GAA6B;AACjE,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAClD,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,WAAW,SAAS,EAAE,WAAW,MAAM;AACjF,QAAM,UAAU,KAAK,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAE/D,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,OAAO,KAAK;AAAA,QACZ,QAAQ,KAAK;AAAA,QACb,UACE,iCACG;AAAA;AAAA,UAAQ;AAAA,UAAI,KAAK,OAAO;AAAA,UAAO;AAAA,WAClC;AAAA;AAAA,IAEJ;AAAA,IAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAO,QAAO,GAC9E;AAAA,0BAAC,eAAY,QAAQ,KAAK,QAAQ,OAAO,QAAQ,UAAU,WAAW;AAAA,MACrE,QAAQ,IAAI,CAAC,MACZ,oBAAC,YAAoB,OAAO,KAAb,EAAE,EAAc,CAChC;AAAA,OACH;AAAA,KACF,GACF;AAEJ;","names":[]}
@@ -1,7 +1,8 @@
1
1
  import type { SerpResult } from "../../../engine/types/analysis";
2
- export declare function SerpTab({ data, keyphrase, faviconUrl, }: {
2
+ export declare function SerpTab({ data, keyphrase, synonyms, faviconUrl, }: {
3
3
  data: SerpResult;
4
4
  keyphrase: string;
5
+ synonyms?: string[];
5
6
  faviconUrl: string;
6
7
  }): import("react/jsx-runtime").JSX.Element;
7
8
  //# sourceMappingURL=SerpTab.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SerpTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAMjE,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,SAAS,EACT,UAAU,GACX,EAAE;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB,2CAiCA"}
1
+ {"version":3,"file":"SerpTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,gCAAgC,CAAC;AAOjE,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,SAAS,EACT,QAAa,EACb,UAAU,GACX,EAAE;IACD,IAAI,EAAE,UAAU,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;CACpB,2CAyCA"}