@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.
- package/README.md +12 -1
- package/dist/admin.css +200 -17
- package/dist/components/SeoButton/SeoButtonInner.d.ts.map +1 -1
- package/dist/components/SeoButton/SeoButtonInner.js +46 -16
- package/dist/components/SeoButton/SeoButtonInner.js.map +1 -1
- package/dist/components/SeoDrawer/build-analysis-input.d.ts +2 -2
- package/dist/components/SeoDrawer/build-analysis-input.d.ts.map +1 -1
- package/dist/components/SeoDrawer/build-analysis-input.js +1 -1
- package/dist/components/SeoDrawer/build-analysis-input.js.map +1 -1
- package/dist/components/SeoDrawer/buildInput.d.ts +3 -3
- package/dist/components/SeoDrawer/buildInput.d.ts.map +1 -1
- package/dist/components/SeoDrawer/buildInput.js +3 -2
- package/dist/components/SeoDrawer/buildInput.js.map +1 -1
- package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts +1 -1
- package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.d.ts.map +1 -1
- package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js +10 -3
- package/dist/components/SeoDrawer/components/SerpPreview/highlight-keyphrase.js.map +1 -1
- package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts +2 -1
- package/dist/components/SeoDrawer/components/SerpPreview/index.d.ts.map +1 -1
- package/dist/components/SeoDrawer/components/SerpPreview/index.js +8 -2
- package/dist/components/SeoDrawer/components/SerpPreview/index.js.map +1 -1
- package/dist/components/SeoDrawer/components/SerpPreview/variants.d.ts +6 -6
- package/dist/components/SeoDrawer/components/TabWrapper.d.ts +6 -0
- package/dist/components/SeoDrawer/components/TabWrapper.d.ts.map +1 -0
- package/dist/components/SeoDrawer/components/TabWrapper.js +8 -0
- package/dist/components/SeoDrawer/components/TabWrapper.js.map +1 -0
- package/dist/components/SeoDrawer/index.d.ts +11 -4
- package/dist/components/SeoDrawer/index.d.ts.map +1 -1
- package/dist/components/SeoDrawer/index.js +38 -16
- package/dist/components/SeoDrawer/index.js.map +1 -1
- package/dist/components/SeoDrawer/keyphraseState.d.ts +16 -0
- package/dist/components/SeoDrawer/keyphraseState.d.ts.map +1 -0
- package/dist/components/SeoDrawer/keyphraseState.js +73 -0
- package/dist/components/SeoDrawer/keyphraseState.js.map +1 -0
- package/dist/components/SeoDrawer/keyphraseStorage.d.ts +5 -0
- package/dist/components/SeoDrawer/keyphraseStorage.d.ts.map +1 -0
- package/dist/components/SeoDrawer/keyphraseStorage.js +60 -0
- package/dist/components/SeoDrawer/keyphraseStorage.js.map +1 -0
- package/dist/components/SeoDrawer/tabs/InclusiveTab.d.ts.map +1 -1
- package/dist/components/SeoDrawer/tabs/InclusiveTab.js +3 -2
- package/dist/components/SeoDrawer/tabs/InclusiveTab.js.map +1 -1
- package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts +14 -7
- package/dist/components/SeoDrawer/tabs/KeyphraseTab.d.ts.map +1 -1
- package/dist/components/SeoDrawer/tabs/KeyphraseTab.js +110 -69
- package/dist/components/SeoDrawer/tabs/KeyphraseTab.js.map +1 -1
- package/dist/components/SeoDrawer/tabs/OnPageTab.d.ts.map +1 -1
- package/dist/components/SeoDrawer/tabs/OnPageTab.js +3 -2
- package/dist/components/SeoDrawer/tabs/OnPageTab.js.map +1 -1
- package/dist/components/SeoDrawer/tabs/ReadabilityTab.d.ts.map +1 -1
- package/dist/components/SeoDrawer/tabs/ReadabilityTab.js +3 -2
- package/dist/components/SeoDrawer/tabs/ReadabilityTab.js.map +1 -1
- package/dist/components/SeoDrawer/tabs/SerpTab.d.ts +2 -1
- package/dist/components/SeoDrawer/tabs/SerpTab.d.ts.map +1 -1
- package/dist/components/SeoDrawer/tabs/SerpTab.js +14 -3
- package/dist/components/SeoDrawer/tabs/SerpTab.js.map +1 -1
- package/dist/components/SeoDrawer/tabs/VitalsTab.d.ts.map +1 -1
- package/dist/components/SeoDrawer/tabs/VitalsTab.js +7 -3
- package/dist/components/SeoDrawer/tabs/VitalsTab.js.map +1 -1
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts +21 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.d.ts.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js +38 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.js.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts +25 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.d.ts.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js +127 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.js.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts +12 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.d.ts.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js +54 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.js.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts +7 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.d.ts.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js +93 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/SynonymsField.js.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts +2 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/icons.d.ts.map +1 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/icons.js +7 -0
- package/dist/components/SeoDrawer/tabs/keyphrase/icons.js.map +1 -0
- package/dist/components/SeoDrawer/useAnalysis.d.ts +0 -1
- package/dist/components/SeoDrawer/useAnalysis.d.ts.map +1 -1
- package/dist/components/SeoDrawer/useAnalysis.js +0 -3
- package/dist/components/SeoDrawer/useAnalysis.js.map +1 -1
- package/dist/components/SeoDrawer/useKeyphrases.d.ts +18 -0
- package/dist/components/SeoDrawer/useKeyphrases.d.ts.map +1 -0
- package/dist/components/SeoDrawer/useKeyphrases.js +78 -0
- package/dist/components/SeoDrawer/useKeyphrases.js.map +1 -0
- package/dist/components/SeoDrawer/useLiveDocument.d.ts +4 -3
- package/dist/components/SeoDrawer/useLiveDocument.d.ts.map +1 -1
- package/dist/components/SeoDrawer/useLiveDocument.js +17 -9
- package/dist/components/SeoDrawer/useLiveDocument.js.map +1 -1
- package/dist/components/SeoDrawer/variants.d.ts +1 -1
- package/dist/components/SeoDrawer/variants.d.ts.map +1 -1
- package/dist/components/SeoDrawer/variants.js +2 -1
- package/dist/components/SeoDrawer/variants.js.map +1 -1
- package/dist/constants/index.d.ts +1 -0
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +2 -0
- package/dist/constants/index.js.map +1 -1
- package/dist/engine/buildPaper.d.ts +2 -2
- package/dist/engine/buildPaper.d.ts.map +1 -1
- package/dist/engine/buildPaper.js +4 -2
- package/dist/engine/buildPaper.js.map +1 -1
- package/dist/engine/runAnalysis/index.d.ts.map +1 -1
- package/dist/engine/runAnalysis/index.js +7 -0
- package/dist/engine/runAnalysis/index.js.map +1 -1
- package/dist/engine/runAnalysis/services/derive-related.d.ts +4 -0
- package/dist/engine/runAnalysis/services/derive-related.d.ts.map +1 -0
- package/dist/engine/runAnalysis/services/derive-related.js +16 -0
- package/dist/engine/runAnalysis/services/derive-related.js.map +1 -0
- package/dist/engine/types/analysis.d.ts +11 -0
- package/dist/engine/types/analysis.d.ts.map +1 -1
- package/dist/ui/Button.d.ts +9 -0
- package/dist/ui/Button.d.ts.map +1 -0
- package/dist/ui/Button.js +34 -0
- package/dist/ui/Button.js.map +1 -0
- package/dist/ui/ScoreRing.d.ts +17 -4
- package/dist/ui/ScoreRing.d.ts.map +1 -1
- package/dist/ui/ScoreRing.js +38 -14
- package/dist/ui/ScoreRing.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/SeoDrawer/keyphrasePending.d.ts +0 -2
- package/dist/components/SeoDrawer/keyphrasePending.d.ts.map +0 -1
- package/dist/components/SeoDrawer/keyphrasePending.js +0 -9
- package/dist/components/SeoDrawer/keyphrasePending.js.map +0 -1
|
@@ -5,13 +5,15 @@ import { useState } from "react";
|
|
|
5
5
|
import { SectionCard } from "../../../ui/SectionCard";
|
|
6
6
|
import { SegmentedControl } from "../../../ui/SegmentedControl";
|
|
7
7
|
import { SerpPreview } from "../components/SerpPreview";
|
|
8
|
+
import { TabWrapper } from "../components/TabWrapper";
|
|
8
9
|
function SerpTab({
|
|
9
10
|
data,
|
|
10
11
|
keyphrase,
|
|
12
|
+
synonyms = [],
|
|
11
13
|
faviconUrl
|
|
12
14
|
}) {
|
|
13
15
|
const [mode, setMode] = useState("mobile");
|
|
14
|
-
return /* @__PURE__ */ jsx("section", { className: "flex flex-col gap-[13px]", children: /* @__PURE__ */ jsx(
|
|
16
|
+
return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsx("section", { className: "flex flex-col gap-[13px]", children: /* @__PURE__ */ jsx(
|
|
15
17
|
SectionCard,
|
|
16
18
|
{
|
|
17
19
|
title: "Search result preview",
|
|
@@ -35,9 +37,18 @@ function SerpTab({
|
|
|
35
37
|
label: "Preview device"
|
|
36
38
|
}
|
|
37
39
|
),
|
|
38
|
-
children: /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(
|
|
40
|
+
children: /* @__PURE__ */ jsx("div", { className: "p-4", children: /* @__PURE__ */ jsx(
|
|
41
|
+
SerpPreview,
|
|
42
|
+
{
|
|
43
|
+
data,
|
|
44
|
+
keyphrase,
|
|
45
|
+
synonyms,
|
|
46
|
+
faviconUrl,
|
|
47
|
+
mode
|
|
48
|
+
}
|
|
49
|
+
) })
|
|
39
50
|
}
|
|
40
|
-
) });
|
|
51
|
+
) }) });
|
|
41
52
|
}
|
|
42
53
|
export {
|
|
43
54
|
SerpTab
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { Monitor, Smartphone } from \"lucide-react\";\nimport { useState } from \"react\";\n\nimport type { SerpResult } from \"../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { SegmentedControl } from \"../../../ui/SegmentedControl\";\nimport { SerpPreview } from \"../components/SerpPreview\";\nimport type { SerpMode } from \"../components/SerpPreview\";\n\nexport function SerpTab({\n data,\n keyphrase,\n faviconUrl,\n}: {\n data: SerpResult;\n keyphrase: string;\n faviconUrl: string;\n}) {\n const [mode, setMode] = useState<SerpMode>(\"mobile\");\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/SerpTab.tsx"],"sourcesContent":["\"use client\";\n\nimport { Monitor, Smartphone } from \"lucide-react\";\nimport { useState } from \"react\";\n\nimport type { SerpResult } from \"../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { SegmentedControl } from \"../../../ui/SegmentedControl\";\nimport { SerpPreview } from \"../components/SerpPreview\";\nimport type { SerpMode } from \"../components/SerpPreview\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport function SerpTab({\n data,\n keyphrase,\n synonyms = [],\n faviconUrl,\n}: {\n data: SerpResult;\n keyphrase: string;\n synonyms?: string[];\n faviconUrl: string;\n}) {\n const [mode, setMode] = useState<SerpMode>(\"mobile\");\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <SectionCard\n title=\"Search result preview\"\n widget={\n <SegmentedControl\n options={[\n {\n value: \"mobile\",\n label: \"Mobile\",\n icon: <Smartphone aria-hidden=\"true\" className=\"w-[13px] h-[13px]\" />,\n },\n {\n value: \"desktop\",\n label: \"Desktop\",\n icon: <Monitor aria-hidden=\"true\" className=\"w-[13px] h-[13px]\" />,\n },\n ]}\n value={mode}\n onChange={setMode}\n label=\"Preview device\"\n />\n }\n >\n <div className=\"p-4\">\n <SerpPreview\n data={data}\n keyphrase={keyphrase}\n synonyms={synonyms}\n faviconUrl={faviconUrl}\n mode={mode}\n />\n </div>\n </SectionCard>\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAoCwB;AAlCxB,SAAS,SAAS,kBAAkB;AACpC,SAAS,gBAAgB;AAGzB,SAAS,mBAAmB;AAC5B,SAAS,wBAAwB;AACjC,SAAS,mBAAmB;AAE5B,SAAS,kBAAkB;AAEpB,SAAS,QAAQ;AAAA,EACtB;AAAA,EACA;AAAA,EACA,WAAW,CAAC;AAAA,EACZ;AACF,GAKG;AACD,QAAM,CAAC,MAAM,OAAO,IAAI,SAAmB,QAAQ;AAEnD,SACE,oBAAC,cACC,8BAAC,aAAQ,WAAU,4BACjB;AAAA,IAAC;AAAA;AAAA,MACC,OAAM;AAAA,MACN,QACE;AAAA,QAAC;AAAA;AAAA,UACC,SAAS;AAAA,YACP;AAAA,cACE,OAAO;AAAA,cACP,OAAO;AAAA,cACP,MAAM,oBAAC,cAAW,eAAY,QAAO,WAAU,qBAAoB;AAAA,YACrE;AAAA,YACA;AAAA,cACE,OAAO;AAAA,cACP,OAAO;AAAA,cACP,MAAM,oBAAC,WAAQ,eAAY,QAAO,WAAU,qBAAoB;AAAA,YAClE;AAAA,UACF;AAAA,UACA,OAAO;AAAA,UACP,UAAU;AAAA,UACV,OAAM;AAAA;AAAA,MACR;AAAA,MAGF,8BAAC,SAAI,WAAU,OACb;AAAA,QAAC;AAAA;AAAA,UACC;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA;AAAA,MACF,GACF;AAAA;AAAA,EACF,GACF,GACF;AAEJ;","names":[]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VitalsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;
|
|
1
|
+
{"version":3,"file":"VitalsTab.d.ts","sourceRoot":"","sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gCAAgC,CAAC;AAQnE,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,YAAY,CAAC;IACnB,kBAAkB,EAAE,MAAM,IAAI,CAAC;CAChC;AAED,wBAAgB,SAAS,CAAC,EAAE,IAAI,EAAE,kBAAkB,EAAE,EAAE,cAAc,2CAkErE"}
|
|
@@ -5,10 +5,11 @@ import { KpiCard } from "../../../ui/KpiCard";
|
|
|
5
5
|
import { SectionCard } from "../../../ui/SectionCard";
|
|
6
6
|
import { Pill } from "../../../ui/Pill";
|
|
7
7
|
import { HeadingsSection } from "../components/HeadingsSection";
|
|
8
|
+
import { TabWrapper } from "../components/TabWrapper";
|
|
8
9
|
function VitalsTab({ data, onRequestKeyphrase }) {
|
|
9
10
|
const max = Math.max(1, ...data.prominentWords.map((w) => w.count));
|
|
10
11
|
const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);
|
|
11
|
-
return /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
|
|
12
|
+
return /* @__PURE__ */ jsx(TabWrapper, { children: /* @__PURE__ */ jsxs("section", { className: "flex flex-col gap-[13px]", children: [
|
|
12
13
|
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-3 gap-[9px]", children: [
|
|
13
14
|
/* @__PURE__ */ jsx(KpiCard, { label: "Words", value: data.words.toLocaleString() }),
|
|
14
15
|
/* @__PURE__ */ jsx(KpiCard, { label: "Sentences", value: data.sentences }),
|
|
@@ -39,7 +40,10 @@ function VitalsTab({ data, onRequestKeyphrase }) {
|
|
|
39
40
|
/* @__PURE__ */ jsx("div", { className: "flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden", children: /* @__PURE__ */ jsx(
|
|
40
41
|
"i",
|
|
41
42
|
{
|
|
42
|
-
className: cn(
|
|
43
|
+
className: cn(
|
|
44
|
+
"block h-full",
|
|
45
|
+
w.isKeyphrase ? "bg-neutral-1000" : "bg-neutral-400"
|
|
46
|
+
),
|
|
43
47
|
style: { width: `${w.count / max * 100}%` }
|
|
44
48
|
}
|
|
45
49
|
) }),
|
|
@@ -59,7 +63,7 @@ function VitalsTab({ data, onRequestKeyphrase }) {
|
|
|
59
63
|
children: "Set a focus keyphrase to see which prominent words match it"
|
|
60
64
|
}
|
|
61
65
|
)
|
|
62
|
-
] });
|
|
66
|
+
] }) });
|
|
63
67
|
}
|
|
64
68
|
export {
|
|
65
69
|
VitalsTab
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport type { VitalsResult } from \"../../../engine/types/analysis\";\nimport { cn, ROW_SEPARATOR } from \"../../../utils/style\";\nimport { KpiCard } from \"../../../ui/KpiCard\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { HeadingsSection } from \"../components/HeadingsSection\";\n\nexport interface VitalsTabProps {\n data: VitalsResult;\n onRequestKeyphrase: () => void;\n}\n\nexport function VitalsTab({ data, onRequestKeyphrase }: VitalsTabProps) {\n const max = Math.max(1, ...data.prominentWords.map((w) => w.count));\n const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);\n\n return (\n <section className=\"flex flex-col gap-[13px]\">\n
|
|
1
|
+
{"version":3,"sources":["../../../../src/components/SeoDrawer/tabs/VitalsTab.tsx"],"sourcesContent":["\"use client\";\n\nimport type { VitalsResult } from \"../../../engine/types/analysis\";\nimport { cn, ROW_SEPARATOR } from \"../../../utils/style\";\nimport { KpiCard } from \"../../../ui/KpiCard\";\nimport { SectionCard } from \"../../../ui/SectionCard\";\nimport { Pill } from \"../../../ui/Pill\";\nimport { HeadingsSection } from \"../components/HeadingsSection\";\nimport { TabWrapper } from \"../components/TabWrapper\";\n\nexport interface VitalsTabProps {\n data: VitalsResult;\n onRequestKeyphrase: () => void;\n}\n\nexport function VitalsTab({ data, onRequestKeyphrase }: VitalsTabProps) {\n const max = Math.max(1, ...data.prominentWords.map((w) => w.count));\n const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);\n\n return (\n <TabWrapper>\n <section className=\"flex flex-col gap-[13px]\">\n <div className=\"grid grid-cols-3 gap-[9px]\">\n <KpiCard label=\"Words\" value={data.words.toLocaleString()} />\n <KpiCard label=\"Sentences\" value={data.sentences} />\n <KpiCard label=\"Paragraphs\" value={data.paragraphs} />\n <KpiCard label=\"Images\" value={data.images} />\n <KpiCard label=\"Videos\" value={data.videos} />\n <KpiCard label=\"Reading time\" value={data.readingTimeMinutes} suffix=\"min\" />\n </div>\n\n <HeadingsSection data={data.headings} />\n\n <SectionCard\n title=\"Prominent words\"\n widget={<Pill variant=\"neutral\">{data.prominentWords.length}</Pill>}\n >\n {data.prominentWords.map((w) => (\n <div\n className={cn(\n \"relative flex items-center gap-[12px] px-[15px] py-[9px]\",\n ROW_SEPARATOR\n )}\n key={w.word}\n >\n <div className=\"w-[120px] flex-none text-[12px] font-medium flex items-center gap-[6px]\">\n {w.word}{\" \"}\n {w.isKeyphrase && (\n <span className=\"text-[9px] font-bold uppercase tracking-[0.04em] text-neutral-1000 bg-neutral-150 rounded-[3px] px-[5px] py-[1px]\">\n Key\n </span>\n )}\n </div>\n <div className=\"flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden\">\n <i\n className={cn(\n \"block h-full\",\n w.isKeyphrase ? \"bg-neutral-1000\" : \"bg-neutral-400\"\n )}\n style={{ width: `${(w.count / max) * 100}%` }}\n />\n </div>\n <div className=\"w-[30px] text-right font-mono text-[11px] font-semibold text-neutral-700\">\n {w.count}\n </div>\n </div>\n ))}\n </SectionCard>\n\n {noKeyphraseMatch && (\n <button\n type=\"button\"\n onClick={onRequestKeyphrase}\n className=\"self-start text-[12px] text-neutral-600 underline underline-offset-2 hover:text-neutral-800 cursor-pointer bg-transparent border-0 p-0\"\n >\n Set a focus keyphrase to see which prominent words match it\n </button>\n )}\n </section>\n </TabWrapper>\n );\n}\n"],"mappings":";AAsBQ,SACE,KADF;AAnBR,SAAS,IAAI,qBAAqB;AAClC,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAChC,SAAS,kBAAkB;AAOpB,SAAS,UAAU,EAAE,MAAM,mBAAmB,GAAmB;AACtE,QAAM,MAAM,KAAK,IAAI,GAAG,GAAG,KAAK,eAAe,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AAClE,QAAM,mBAAmB,KAAK,eAAe,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW;AAExE,SACE,oBAAC,cACC,+BAAC,aAAQ,WAAU,4BACjB;AAAA,yBAAC,SAAI,WAAU,8BACb;AAAA,0BAAC,WAAQ,OAAM,SAAQ,OAAO,KAAK,MAAM,eAAe,GAAG;AAAA,MAC3D,oBAAC,WAAQ,OAAM,aAAY,OAAO,KAAK,WAAW;AAAA,MAClD,oBAAC,WAAQ,OAAM,cAAa,OAAO,KAAK,YAAY;AAAA,MACpD,oBAAC,WAAQ,OAAM,UAAS,OAAO,KAAK,QAAQ;AAAA,MAC5C,oBAAC,WAAQ,OAAM,UAAS,OAAO,KAAK,QAAQ;AAAA,MAC5C,oBAAC,WAAQ,OAAM,gBAAe,OAAO,KAAK,oBAAoB,QAAO,OAAM;AAAA,OAC7E;AAAA,IAEA,oBAAC,mBAAgB,MAAM,KAAK,UAAU;AAAA,IAEtC;AAAA,MAAC;AAAA;AAAA,QACC,OAAM;AAAA,QACN,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,eAAe,QAAO;AAAA,QAE3D,eAAK,eAAe,IAAI,CAAC,MACxB;AAAA,UAAC;AAAA;AAAA,YACC,WAAW;AAAA,cACT;AAAA,cACA;AAAA,YACF;AAAA,YAGA;AAAA,mCAAC,SAAI,WAAU,2EACZ;AAAA,kBAAE;AAAA,gBAAM;AAAA,gBACR,EAAE,eACD,oBAAC,UAAK,WAAU,qHAAoH,iBAEpI;AAAA,iBAEJ;AAAA,cACA,oBAAC,SAAI,WAAU,+DACb;AAAA,gBAAC;AAAA;AAAA,kBACC,WAAW;AAAA,oBACT;AAAA,oBACA,EAAE,cAAc,oBAAoB;AAAA,kBACtC;AAAA,kBACA,OAAO,EAAE,OAAO,GAAI,EAAE,QAAQ,MAAO,GAAG,IAAI;AAAA;AAAA,cAC9C,GACF;AAAA,cACA,oBAAC,SAAI,WAAU,4EACZ,YAAE,OACL;AAAA;AAAA;AAAA,UArBK,EAAE;AAAA,QAsBT,CACD;AAAA;AAAA,IACH;AAAA,IAEC,oBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KAEJ,GACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Status } from "../../../../engine/types/analysis";
|
|
2
|
+
import type { KeyphraseEntry } from "../../keyphraseState";
|
|
3
|
+
export type CardState = {
|
|
4
|
+
kind: "score";
|
|
5
|
+
score: number;
|
|
6
|
+
status: Status;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "analyzing";
|
|
9
|
+
} | {
|
|
10
|
+
kind: "idle";
|
|
11
|
+
};
|
|
12
|
+
interface KeyphraseCardProps {
|
|
13
|
+
entry: KeyphraseEntry;
|
|
14
|
+
isFocus: boolean;
|
|
15
|
+
selected: boolean;
|
|
16
|
+
state: CardState;
|
|
17
|
+
onSelect: (id: string) => void;
|
|
18
|
+
}
|
|
19
|
+
export declare function KeyphraseCard({ entry, isFocus, selected, state, onSelect }: KeyphraseCardProps): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export {};
|
|
21
|
+
//# sourceMappingURL=KeyphraseCard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyphraseCard.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAGhE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D,MAAM,MAAM,SAAS,GACjB;IAAE,IAAI,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GAChD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAErB,UAAU,kBAAkB;IAC1B,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,OAAO,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAcD,wBAAgB,aAAa,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CAgC9F"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { ScoreRing } from "../../../../ui/ScoreRing";
|
|
4
|
+
import { cn } from "../../../../utils/style";
|
|
5
|
+
function MiniRing({ state }) {
|
|
6
|
+
if (state.kind === "analyzing") {
|
|
7
|
+
return /* @__PURE__ */ jsx(ScoreRing, { size: "small", status: "loading" });
|
|
8
|
+
}
|
|
9
|
+
if (state.kind === "score") {
|
|
10
|
+
return /* @__PURE__ */ jsx(ScoreRing, { score: state.score, size: "small", status: state.status });
|
|
11
|
+
}
|
|
12
|
+
return /* @__PURE__ */ jsx(ScoreRing, { size: "small", status: "idle" });
|
|
13
|
+
}
|
|
14
|
+
function KeyphraseCard({ entry, isFocus, selected, state, onSelect }) {
|
|
15
|
+
return /* @__PURE__ */ jsxs(
|
|
16
|
+
"button",
|
|
17
|
+
{
|
|
18
|
+
type: "button",
|
|
19
|
+
"aria-pressed": selected,
|
|
20
|
+
onClick: () => onSelect(entry.id),
|
|
21
|
+
className: cn(
|
|
22
|
+
"flex items-center gap-[10px] p-[8px] rounded-rs w-full text-left bg-neutral-0 border",
|
|
23
|
+
selected ? "border-neutral-300 shadow-[0_1px_5px_rgba(30,25,20,0.09)]" : "border-transparent hover:border-neutral-150"
|
|
24
|
+
),
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ jsx(MiniRing, { state }),
|
|
27
|
+
/* @__PURE__ */ jsxs("span", { className: "flex-1 min-w-0 flex flex-col gap-[2px]", children: [
|
|
28
|
+
isFocus && /* @__PURE__ */ jsx("span", { className: "self-start text-[7px] leading-none uppercase bg-neutral-1000 text-neutral-0 px-[6px] py-[2px] rounded-[7px] tracking-[0.06em] font-bold", children: "Focus" }),
|
|
29
|
+
entry.text ? /* @__PURE__ */ jsx("span", { className: "text-[12px] font-semibold text-neutral-800 truncate", children: entry.text }) : /* @__PURE__ */ jsx("span", { className: "text-[12px] font-medium italic text-neutral-300 truncate", children: "New keyphrase" })
|
|
30
|
+
] })
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
export {
|
|
36
|
+
KeyphraseCard
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=KeyphraseCard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseCard.tsx"],"sourcesContent":["\"use client\";\n\nimport type { Status } from \"../../../../engine/types/analysis\";\nimport { ScoreRing } from \"../../../../ui/ScoreRing\";\nimport { cn } from \"../../../../utils/style\";\nimport type { KeyphraseEntry } from \"../../keyphraseState\";\n\nexport type CardState =\n | { kind: \"score\"; score: number; status: Status }\n | { kind: \"analyzing\" }\n | { kind: \"idle\" };\n\ninterface KeyphraseCardProps {\n entry: KeyphraseEntry;\n isFocus: boolean;\n selected: boolean;\n state: CardState;\n onSelect: (id: string) => void;\n}\n\nfunction MiniRing({ state }: { state: CardState }) {\n if (state.kind === \"analyzing\") {\n return <ScoreRing size=\"small\" status=\"loading\" />;\n }\n\n if (state.kind === \"score\") {\n return <ScoreRing score={state.score} size=\"small\" status={state.status} />;\n }\n\n return <ScoreRing size=\"small\" status=\"idle\" />;\n}\n\nexport function KeyphraseCard({ entry, isFocus, selected, state, onSelect }: KeyphraseCardProps) {\n return (\n <button\n type=\"button\"\n aria-pressed={selected}\n onClick={() => onSelect(entry.id)}\n className={cn(\n \"flex items-center gap-[10px] p-[8px] rounded-rs w-full text-left bg-neutral-0 border\",\n selected\n ? \"border-neutral-300 shadow-[0_1px_5px_rgba(30,25,20,0.09)]\"\n : \"border-transparent hover:border-neutral-150\"\n )}\n >\n <MiniRing state={state} />\n\n <span className=\"flex-1 min-w-0 flex flex-col gap-[2px]\">\n {isFocus && (\n <span className=\"self-start text-[7px] leading-none uppercase bg-neutral-1000 text-neutral-0 px-[6px] py-[2px] rounded-[7px] tracking-[0.06em] font-bold\">\n Focus\n </span>\n )}\n\n {entry.text ? (\n <span className=\"text-[12px] font-semibold text-neutral-800 truncate\">{entry.text}</span>\n ) : (\n <span className=\"text-[12px] font-medium italic text-neutral-300 truncate\">\n New keyphrase\n </span>\n )}\n </span>\n </button>\n );\n}\n"],"mappings":";AAsBW,cAyBL,YAzBK;AAnBX,SAAS,iBAAiB;AAC1B,SAAS,UAAU;AAgBnB,SAAS,SAAS,EAAE,MAAM,GAAyB;AACjD,MAAI,MAAM,SAAS,aAAa;AAC9B,WAAO,oBAAC,aAAU,MAAK,SAAQ,QAAO,WAAU;AAAA,EAClD;AAEA,MAAI,MAAM,SAAS,SAAS;AAC1B,WAAO,oBAAC,aAAU,OAAO,MAAM,OAAO,MAAK,SAAQ,QAAQ,MAAM,QAAQ;AAAA,EAC3E;AAEA,SAAO,oBAAC,aAAU,MAAK,SAAQ,QAAO,QAAO;AAC/C;AAEO,SAAS,cAAc,EAAE,OAAO,SAAS,UAAU,OAAO,SAAS,GAAuB;AAC/F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,MAAK;AAAA,MACL,gBAAc;AAAA,MACd,SAAS,MAAM,SAAS,MAAM,EAAE;AAAA,MAChC,WAAW;AAAA,QACT;AAAA,QACA,WACI,8DACA;AAAA,MACN;AAAA,MAEA;AAAA,4BAAC,YAAS,OAAc;AAAA,QAExB,qBAAC,UAAK,WAAU,0CACb;AAAA,qBACC,oBAAC,UAAK,WAAU,2IAA0I,mBAE1J;AAAA,UAGD,MAAM,OACL,oBAAC,UAAK,WAAU,uDAAuD,gBAAM,MAAK,IAElF,oBAAC,UAAK,WAAU,4DAA2D,2BAE3E;AAAA,WAEJ;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CategoryResult } from "../../../../engine/types/analysis";
|
|
2
|
+
import type { KeyphraseEntry } from "../../keyphraseState";
|
|
3
|
+
export type DetailAnalysis = {
|
|
4
|
+
kind: "metrics";
|
|
5
|
+
result: CategoryResult;
|
|
6
|
+
dim: boolean;
|
|
7
|
+
} | {
|
|
8
|
+
kind: "analyzing";
|
|
9
|
+
} | {
|
|
10
|
+
kind: "hint";
|
|
11
|
+
message: string;
|
|
12
|
+
};
|
|
13
|
+
export interface KeyphraseDetailProps {
|
|
14
|
+
entry: KeyphraseEntry;
|
|
15
|
+
isFocus: boolean;
|
|
16
|
+
duplicate: boolean;
|
|
17
|
+
analysis: DetailAnalysis;
|
|
18
|
+
onTextChange: (text: string) => void;
|
|
19
|
+
onAddSynonym: (syn: string) => void;
|
|
20
|
+
onRemoveSynonym: (index: number) => void;
|
|
21
|
+
onSetFocus: () => void;
|
|
22
|
+
onRemove: () => void;
|
|
23
|
+
}
|
|
24
|
+
export declare function KeyphraseDetail({ entry, isFocus, duplicate, analysis, onTextChange, onAddSynonym, onRemoveSynonym, onSetFocus, onRemove, }: KeyphraseDetailProps): import("react/jsx-runtime").JSX.Element;
|
|
25
|
+
//# sourceMappingURL=KeyphraseDetail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyphraseDetail.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mCAAmC,CAAC;AASxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,MAAM,MAAM,cAAc,GACtB;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,MAAM,EAAE,cAAc,CAAC;IAAC,GAAG,EAAE,OAAO,CAAA;CAAE,GACzD;IAAE,IAAI,EAAE,WAAW,CAAA;CAAE,GACrB;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtC,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,cAAc,CAAC;IACtB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,QAAQ,EAAE,cAAc,CAAC;IACzB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,YAAY,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IACpC,eAAe,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzC,UAAU,EAAE,MAAM,IAAI,CAAC;IACvB,QAAQ,EAAE,MAAM,IAAI,CAAC;CACtB;AA0DD,wBAAgB,eAAe,CAAC,EAC9B,KAAK,EACL,OAAO,EACP,SAAS,EACT,QAAQ,EACR,YAAY,EACZ,YAAY,EACZ,eAAe,EACf,UAAU,EACV,QAAQ,GACT,EAAE,oBAAoB,2CAqDtB"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AlertTriangle, Loader2 } from "lucide-react";
|
|
4
|
+
import { useId, useState } from "react";
|
|
5
|
+
import { Button } from "../../../../ui/Button";
|
|
6
|
+
import { CheckRow } from "../../../../ui/CheckRow";
|
|
7
|
+
import { FilterPills } from "../../../../ui/FilterPills";
|
|
8
|
+
import { Pill } from "../../../../ui/Pill";
|
|
9
|
+
import { SectionCard } from "../../../../ui/SectionCard";
|
|
10
|
+
import { TabHeader } from "../../../../ui/TabHeader";
|
|
11
|
+
import { cn } from "../../../../utils/style";
|
|
12
|
+
import { Target, Trash2 } from "./icons";
|
|
13
|
+
import { SynonymsField } from "./SynonymsField";
|
|
14
|
+
function Analysis({ analysis }) {
|
|
15
|
+
const [filter, setFilter] = useState("all");
|
|
16
|
+
if (analysis.kind === "analyzing") {
|
|
17
|
+
return /* @__PURE__ */ jsxs(
|
|
18
|
+
"div",
|
|
19
|
+
{
|
|
20
|
+
"aria-live": "polite",
|
|
21
|
+
className: "flex items-center gap-[9px] text-neutral-500 text-[13px] py-[12px]",
|
|
22
|
+
role: "status",
|
|
23
|
+
children: [
|
|
24
|
+
/* @__PURE__ */ jsx(Loader2, { "aria-hidden": "true", className: "w-[15px] h-[15px] animate-spin" }),
|
|
25
|
+
"Analyzing keyphrase\u2026"
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (analysis.kind === "hint") {
|
|
31
|
+
return /* @__PURE__ */ jsx("div", { className: "text-neutral-500 text-[12px] py-[6px]", children: analysis.message });
|
|
32
|
+
}
|
|
33
|
+
const { result, dim } = analysis;
|
|
34
|
+
const passing = result.checks.filter((check) => check.status === "good").length;
|
|
35
|
+
const visible = result.checks.filter((check) => filter === "all" || check.status === filter);
|
|
36
|
+
return /* @__PURE__ */ jsxs(
|
|
37
|
+
"div",
|
|
38
|
+
{
|
|
39
|
+
className: cn(
|
|
40
|
+
"flex flex-col gap-[13px] transition-opacity",
|
|
41
|
+
dim && "opacity-40 pointer-events-none"
|
|
42
|
+
),
|
|
43
|
+
children: [
|
|
44
|
+
/* @__PURE__ */ jsx(
|
|
45
|
+
TabHeader,
|
|
46
|
+
{
|
|
47
|
+
score: result.ringScore,
|
|
48
|
+
status: result.status,
|
|
49
|
+
subtitle: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
50
|
+
passing,
|
|
51
|
+
" / ",
|
|
52
|
+
result.checks.length,
|
|
53
|
+
" checks passing"
|
|
54
|
+
] }),
|
|
55
|
+
title: "Keyphrase optimization"
|
|
56
|
+
}
|
|
57
|
+
),
|
|
58
|
+
/* @__PURE__ */ jsxs(SectionCard, { title: "Checks", widget: /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: result.checks.length }), children: [
|
|
59
|
+
/* @__PURE__ */ jsx(FilterPills, { checks: result.checks, onChange: setFilter, value: filter }),
|
|
60
|
+
visible.map((check) => /* @__PURE__ */ jsx(CheckRow, { check }, check.id))
|
|
61
|
+
] })
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
function KeyphraseDetail({
|
|
67
|
+
entry,
|
|
68
|
+
isFocus,
|
|
69
|
+
duplicate,
|
|
70
|
+
analysis,
|
|
71
|
+
onTextChange,
|
|
72
|
+
onAddSynonym,
|
|
73
|
+
onRemoveSynonym,
|
|
74
|
+
onSetFocus,
|
|
75
|
+
onRemove
|
|
76
|
+
}) {
|
|
77
|
+
const textInputId = useId();
|
|
78
|
+
const label = isFocus ? "Focus keyphrase" : "Related keyphrase";
|
|
79
|
+
return /* @__PURE__ */ jsxs("section", { className: "flex-1 min-w-0 px-[15px] py-[18px] flex flex-col gap-[13px] overflow-auto", children: [
|
|
80
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-[9px]", children: [
|
|
81
|
+
/* @__PURE__ */ jsx(
|
|
82
|
+
"label",
|
|
83
|
+
{
|
|
84
|
+
className: "block text-[10px] uppercase tracking-[0.05em] text-neutral-500 mb-[6px]",
|
|
85
|
+
htmlFor: textInputId,
|
|
86
|
+
children: label
|
|
87
|
+
}
|
|
88
|
+
),
|
|
89
|
+
/* @__PURE__ */ jsx(
|
|
90
|
+
"input",
|
|
91
|
+
{
|
|
92
|
+
autoComplete: "off",
|
|
93
|
+
className: "w-full border border-neutral-200 rounded-rs px-[12px] py-[10px] text-[13px] text-neutral-800 bg-neutral-0 outline-none focus:border-neutral-800",
|
|
94
|
+
id: textInputId,
|
|
95
|
+
onChange: (e) => onTextChange(e.target.value),
|
|
96
|
+
placeholder: isFocus ? "Type your primary keyphrase" : "Type a related keyphrase",
|
|
97
|
+
type: "text",
|
|
98
|
+
value: entry.text
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
] }),
|
|
102
|
+
duplicate && /* @__PURE__ */ jsxs(
|
|
103
|
+
"div",
|
|
104
|
+
{
|
|
105
|
+
"aria-live": "polite",
|
|
106
|
+
className: "text-[11.5px] text-seo-warn bg-seo-warn-100 rounded-rs px-[10px] py-[8px] flex items-center gap-[7px]",
|
|
107
|
+
role: "status",
|
|
108
|
+
children: [
|
|
109
|
+
/* @__PURE__ */ jsx(AlertTriangle, { "aria-hidden": "true", className: "w-[15px] h-[15px] flex-none" }),
|
|
110
|
+
"\u201C",
|
|
111
|
+
entry.text.trim(),
|
|
112
|
+
"\u201D is already in your keyphrase list."
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
),
|
|
116
|
+
/* @__PURE__ */ jsx("div", { className: "mb-[9px]", children: /* @__PURE__ */ jsx(SynonymsField, { onAdd: onAddSynonym, onRemove: onRemoveSynonym, synonyms: entry.synonyms }) }),
|
|
117
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-[8px] items-center", children: [
|
|
118
|
+
!isFocus && /* @__PURE__ */ jsx(Button, { icon: /* @__PURE__ */ jsx(Target, { "aria-hidden": "true" }), onClick: onSetFocus, variant: "primary", children: "Set as focus" }),
|
|
119
|
+
/* @__PURE__ */ jsx(Button, { icon: /* @__PURE__ */ jsx(Trash2, { "aria-hidden": "true" }), onClick: onRemove, variant: "error", children: "Remove" })
|
|
120
|
+
] }),
|
|
121
|
+
/* @__PURE__ */ jsx(Analysis, { analysis })
|
|
122
|
+
] });
|
|
123
|
+
}
|
|
124
|
+
export {
|
|
125
|
+
KeyphraseDetail
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=KeyphraseDetail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseDetail.tsx"],"sourcesContent":["\"use client\";\n\nimport { AlertTriangle, Loader2 } from \"lucide-react\";\nimport { useId, useState } from \"react\";\nimport type { CategoryResult } from \"../../../../engine/types/analysis\";\nimport { Button } from \"../../../../ui/Button\";\nimport { CheckRow } from \"../../../../ui/CheckRow\";\nimport type { Filter } from \"../../../../ui/FilterPills\";\nimport { FilterPills } from \"../../../../ui/FilterPills\";\nimport { Pill } from \"../../../../ui/Pill\";\nimport { SectionCard } from \"../../../../ui/SectionCard\";\nimport { TabHeader } from \"../../../../ui/TabHeader\";\nimport { cn } from \"../../../../utils/style\";\nimport type { KeyphraseEntry } from \"../../keyphraseState\";\nimport { Target, Trash2 } from \"./icons\";\nimport { SynonymsField } from \"./SynonymsField\";\n\nexport type DetailAnalysis =\n | { kind: \"metrics\"; result: CategoryResult; dim: boolean }\n | { kind: \"analyzing\" }\n | { kind: \"hint\"; message: string };\n\nexport interface KeyphraseDetailProps {\n entry: KeyphraseEntry;\n isFocus: boolean;\n duplicate: boolean;\n analysis: DetailAnalysis;\n onTextChange: (text: string) => void;\n onAddSynonym: (syn: string) => void;\n onRemoveSynonym: (index: number) => void;\n onSetFocus: () => void;\n onRemove: () => void;\n}\n\ninterface AnalysisProps {\n analysis: DetailAnalysis;\n}\n\nfunction Analysis({ analysis }: AnalysisProps) {\n const [filter, setFilter] = useState<Filter>(\"all\");\n\n if (analysis.kind === \"analyzing\") {\n return (\n <div\n aria-live=\"polite\"\n className=\"flex items-center gap-[9px] text-neutral-500 text-[13px] py-[12px]\"\n role=\"status\"\n >\n <Loader2 aria-hidden=\"true\" className=\"w-[15px] h-[15px] animate-spin\" />\n Analyzing keyphrase…\n </div>\n );\n }\n\n if (analysis.kind === \"hint\") {\n return <div className=\"text-neutral-500 text-[12px] py-[6px]\">{analysis.message}</div>;\n }\n\n const { result, dim } = analysis;\n const passing = result.checks.filter((check) => check.status === \"good\").length;\n const visible = result.checks.filter((check) => filter === \"all\" || check.status === filter);\n\n return (\n <div\n className={cn(\n \"flex flex-col gap-[13px] transition-opacity\",\n dim && \"opacity-40 pointer-events-none\"\n )}\n >\n <TabHeader\n score={result.ringScore}\n status={result.status}\n subtitle={\n <>\n {passing} / {result.checks.length} checks passing\n </>\n }\n title=\"Keyphrase optimization\"\n />\n\n <SectionCard title=\"Checks\" widget={<Pill variant=\"neutral\">{result.checks.length}</Pill>}>\n <FilterPills checks={result.checks} onChange={setFilter} value={filter} />\n {visible.map((check) => (\n <CheckRow check={check} key={check.id} />\n ))}\n </SectionCard>\n </div>\n );\n}\n\nexport function KeyphraseDetail({\n entry,\n isFocus,\n duplicate,\n analysis,\n onTextChange,\n onAddSynonym,\n onRemoveSynonym,\n onSetFocus,\n onRemove,\n}: KeyphraseDetailProps) {\n const textInputId = useId();\n const label = isFocus ? \"Focus keyphrase\" : \"Related keyphrase\";\n\n return (\n <section className=\"flex-1 min-w-0 px-[15px] py-[18px] flex flex-col gap-[13px] overflow-auto\">\n <div className=\"mb-[9px]\">\n <label\n className=\"block text-[10px] uppercase tracking-[0.05em] text-neutral-500 mb-[6px]\"\n htmlFor={textInputId}\n >\n {label}\n </label>\n <input\n autoComplete=\"off\"\n className=\"w-full border border-neutral-200 rounded-rs px-[12px] py-[10px] text-[13px] text-neutral-800 bg-neutral-0 outline-none focus:border-neutral-800\"\n id={textInputId}\n onChange={(e) => onTextChange(e.target.value)}\n placeholder={isFocus ? \"Type your primary keyphrase\" : \"Type a related keyphrase\"}\n type=\"text\"\n value={entry.text}\n />\n </div>\n\n {duplicate && (\n <div\n aria-live=\"polite\"\n className=\"text-[11.5px] text-seo-warn bg-seo-warn-100 rounded-rs px-[10px] py-[8px] flex items-center gap-[7px]\"\n role=\"status\"\n >\n <AlertTriangle aria-hidden=\"true\" className=\"w-[15px] h-[15px] flex-none\" />\n “{entry.text.trim()}” is already in your keyphrase list.\n </div>\n )}\n\n <div className=\"mb-[9px]\">\n <SynonymsField onAdd={onAddSynonym} onRemove={onRemoveSynonym} synonyms={entry.synonyms} />\n </div>\n\n <div className=\"flex gap-[8px] items-center\">\n {!isFocus && (\n <Button icon={<Target aria-hidden=\"true\" />} onClick={onSetFocus} variant=\"primary\">\n Set as focus\n </Button>\n )}\n <Button icon={<Trash2 aria-hidden=\"true\" />} onClick={onRemove} variant=\"error\">\n Remove\n </Button>\n </div>\n\n <Analysis analysis={analysis} />\n </section>\n );\n}\n"],"mappings":";AA2CM,SA8BI,UAzBF,KALF;AAzCN,SAAS,eAAe,eAAe;AACvC,SAAS,OAAO,gBAAgB;AAEhC,SAAS,cAAc;AACvB,SAAS,gBAAgB;AAEzB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,mBAAmB;AAC5B,SAAS,iBAAiB;AAC1B,SAAS,UAAU;AAEnB,SAAS,QAAQ,cAAc;AAC/B,SAAS,qBAAqB;AAuB9B,SAAS,SAAS,EAAE,SAAS,GAAkB;AAC7C,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAiB,KAAK;AAElD,MAAI,SAAS,SAAS,aAAa;AACjC,WACE;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAU;AAAA,QACV,MAAK;AAAA,QAEL;AAAA,8BAAC,WAAQ,eAAY,QAAO,WAAU,kCAAiC;AAAA,UAAE;AAAA;AAAA;AAAA,IAE3E;AAAA,EAEJ;AAEA,MAAI,SAAS,SAAS,QAAQ;AAC5B,WAAO,oBAAC,SAAI,WAAU,yCAAyC,mBAAS,SAAQ;AAAA,EAClF;AAEA,QAAM,EAAE,QAAQ,IAAI,IAAI;AACxB,QAAM,UAAU,OAAO,OAAO,OAAO,CAAC,UAAU,MAAM,WAAW,MAAM,EAAE;AACzE,QAAM,UAAU,OAAO,OAAO,OAAO,CAAC,UAAU,WAAW,SAAS,MAAM,WAAW,MAAM;AAE3F,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT;AAAA,QACA,OAAO;AAAA,MACT;AAAA,MAEA;AAAA;AAAA,UAAC;AAAA;AAAA,YACC,OAAO,OAAO;AAAA,YACd,QAAQ,OAAO;AAAA,YACf,UACE,iCACG;AAAA;AAAA,cAAQ;AAAA,cAAI,OAAO,OAAO;AAAA,cAAO;AAAA,eACpC;AAAA,YAEF,OAAM;AAAA;AAAA,QACR;AAAA,QAEA,qBAAC,eAAY,OAAM,UAAS,QAAQ,oBAAC,QAAK,SAAQ,WAAW,iBAAO,OAAO,QAAO,GAChF;AAAA,8BAAC,eAAY,QAAQ,OAAO,QAAQ,UAAU,WAAW,OAAO,QAAQ;AAAA,UACvE,QAAQ,IAAI,CAAC,UACZ,oBAAC,YAAS,SAAmB,MAAM,EAAI,CACxC;AAAA,WACH;AAAA;AAAA;AAAA,EACF;AAEJ;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAyB;AACvB,QAAM,cAAc,MAAM;AAC1B,QAAM,QAAQ,UAAU,oBAAoB;AAE5C,SACE,qBAAC,aAAQ,WAAU,6EACjB;AAAA,yBAAC,SAAI,WAAU,YACb;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,SAAS;AAAA,UAER;AAAA;AAAA,MACH;AAAA,MACA;AAAA,QAAC;AAAA;AAAA,UACC,cAAa;AAAA,UACb,WAAU;AAAA,UACV,IAAI;AAAA,UACJ,UAAU,CAAC,MAAM,aAAa,EAAE,OAAO,KAAK;AAAA,UAC5C,aAAa,UAAU,gCAAgC;AAAA,UACvD,MAAK;AAAA,UACL,OAAO,MAAM;AAAA;AAAA,MACf;AAAA,OACF;AAAA,IAEC,aACC;AAAA,MAAC;AAAA;AAAA,QACC,aAAU;AAAA,QACV,WAAU;AAAA,QACV,MAAK;AAAA,QAEL;AAAA,8BAAC,iBAAc,eAAY,QAAO,WAAU,+BAA8B;AAAA,UAAE;AAAA,UACpE,MAAM,KAAK,KAAK;AAAA,UAAE;AAAA;AAAA;AAAA,IAC5B;AAAA,IAGF,oBAAC,SAAI,WAAU,YACb,8BAAC,iBAAc,OAAO,cAAc,UAAU,iBAAiB,UAAU,MAAM,UAAU,GAC3F;AAAA,IAEA,qBAAC,SAAI,WAAU,+BACZ;AAAA,OAAC,WACA,oBAAC,UAAO,MAAM,oBAAC,UAAO,eAAY,QAAO,GAAI,SAAS,YAAY,SAAQ,WAAU,0BAEpF;AAAA,MAEF,oBAAC,UAAO,MAAM,oBAAC,UAAO,eAAY,QAAO,GAAI,SAAS,UAAU,SAAQ,SAAQ,oBAEhF;AAAA,OACF;AAAA,IAEA,oBAAC,YAAS,UAAoB;AAAA,KAChC;AAEJ;","names":[]}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { KeyphraseEntry } from "../../keyphraseState";
|
|
2
|
+
import type { CardState } from "./KeyphraseCard";
|
|
3
|
+
interface KeyphraseRailProps {
|
|
4
|
+
entries: KeyphraseEntry[];
|
|
5
|
+
selectedId: string | null;
|
|
6
|
+
stateFor: (entry: KeyphraseEntry) => CardState;
|
|
7
|
+
onSelect: (id: string) => void;
|
|
8
|
+
onAdd: () => void;
|
|
9
|
+
}
|
|
10
|
+
export declare function KeyphraseRail({ entries, selectedId, stateFor, onSelect, onAdd, }: KeyphraseRailProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=KeyphraseRail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"KeyphraseRail.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.tsx"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEjD,UAAU,kBAAkB;IAC1B,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,cAAc,KAAK,SAAS,CAAC;IAC/C,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAED,wBAAgB,aAAa,CAAC,EAC5B,OAAO,EACP,UAAU,EACV,QAAQ,EACR,QAAQ,EACR,KAAK,GACN,EAAE,kBAAkB,2CAsCpB"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Plus } from "lucide-react";
|
|
4
|
+
import { MAX_KEYPHRASES } from "../../../../constants";
|
|
5
|
+
import { firstEmptyId } from "../../keyphraseState";
|
|
6
|
+
import { KeyphraseCard } from "./KeyphraseCard";
|
|
7
|
+
function KeyphraseRail({
|
|
8
|
+
entries,
|
|
9
|
+
selectedId,
|
|
10
|
+
stateFor,
|
|
11
|
+
onSelect,
|
|
12
|
+
onAdd
|
|
13
|
+
}) {
|
|
14
|
+
const atMax = entries.length >= MAX_KEYPHRASES;
|
|
15
|
+
const hasEmpty = Boolean(firstEmptyId(entries));
|
|
16
|
+
const addDisabled = atMax || hasEmpty;
|
|
17
|
+
return /* @__PURE__ */ jsx("aside", { className: "w-[264px] flex-none border-r border-neutral-200 bg-neutral-50", children: /* @__PURE__ */ jsxs("div", { className: "sticky top-0 px-[9px] py-[18px] flex flex-col gap-[5px]", children: [
|
|
18
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-[7px] pt-[5px] pb-[3px]", children: [
|
|
19
|
+
/* @__PURE__ */ jsxs("span", { className: "text-[10px] uppercase tracking-[0.05em] text-neutral-500 font-bold", children: [
|
|
20
|
+
"Keyphrases \xB7 ",
|
|
21
|
+
entries.length,
|
|
22
|
+
"/",
|
|
23
|
+
MAX_KEYPHRASES
|
|
24
|
+
] }),
|
|
25
|
+
/* @__PURE__ */ jsx(
|
|
26
|
+
"button",
|
|
27
|
+
{
|
|
28
|
+
type: "button",
|
|
29
|
+
"aria-label": "Add related keyphrase",
|
|
30
|
+
title: atMax ? `Up to ${MAX_KEYPHRASES} keyphrases` : "Add related keyphrase",
|
|
31
|
+
disabled: addDisabled,
|
|
32
|
+
onClick: onAdd,
|
|
33
|
+
className: "w-[26px] h-[26px] rounded-rs border border-neutral-200 grid place-items-center text-neutral-600 bg-neutral-0 hover:border-neutral-800 hover:text-neutral-800 disabled:opacity-35 disabled:cursor-not-allowed [&_svg]:size-[15px]",
|
|
34
|
+
children: /* @__PURE__ */ jsx(Plus, { "aria-hidden": "true" })
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
] }),
|
|
38
|
+
entries.map((entry, i) => /* @__PURE__ */ jsx(
|
|
39
|
+
KeyphraseCard,
|
|
40
|
+
{
|
|
41
|
+
entry,
|
|
42
|
+
isFocus: i === 0,
|
|
43
|
+
selected: entry.id === selectedId,
|
|
44
|
+
state: stateFor(entry),
|
|
45
|
+
onSelect
|
|
46
|
+
},
|
|
47
|
+
entry.id
|
|
48
|
+
))
|
|
49
|
+
] }) });
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
KeyphraseRail
|
|
53
|
+
};
|
|
54
|
+
//# sourceMappingURL=KeyphraseRail.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/KeyphraseRail.tsx"],"sourcesContent":["\"use client\";\n\nimport { Plus } from \"lucide-react\";\nimport { MAX_KEYPHRASES } from \"../../../../constants\";\nimport type { KeyphraseEntry } from \"../../keyphraseState\";\nimport { firstEmptyId } from \"../../keyphraseState\";\nimport { KeyphraseCard } from \"./KeyphraseCard\";\nimport type { CardState } from \"./KeyphraseCard\";\n\ninterface KeyphraseRailProps {\n entries: KeyphraseEntry[];\n selectedId: string | null;\n stateFor: (entry: KeyphraseEntry) => CardState;\n onSelect: (id: string) => void;\n onAdd: () => void;\n}\n\nexport function KeyphraseRail({\n entries,\n selectedId,\n stateFor,\n onSelect,\n onAdd,\n}: KeyphraseRailProps) {\n const atMax = entries.length >= MAX_KEYPHRASES;\n const hasEmpty = Boolean(firstEmptyId(entries));\n const addDisabled = atMax || hasEmpty;\n\n return (\n <aside className=\"w-[264px] flex-none border-r border-neutral-200 bg-neutral-50\">\n <div className=\"sticky top-0 px-[9px] py-[18px] flex flex-col gap-[5px]\">\n <div className=\"flex items-center justify-between px-[7px] pt-[5px] pb-[3px]\">\n <span className=\"text-[10px] uppercase tracking-[0.05em] text-neutral-500 font-bold\">\n Keyphrases · {entries.length}/{MAX_KEYPHRASES}\n </span>\n\n <button\n type=\"button\"\n aria-label=\"Add related keyphrase\"\n title={atMax ? `Up to ${MAX_KEYPHRASES} keyphrases` : \"Add related keyphrase\"}\n disabled={addDisabled}\n onClick={onAdd}\n className=\"w-[26px] h-[26px] rounded-rs border border-neutral-200 grid place-items-center text-neutral-600 bg-neutral-0 hover:border-neutral-800 hover:text-neutral-800 disabled:opacity-35 disabled:cursor-not-allowed [&_svg]:size-[15px]\"\n >\n <Plus aria-hidden=\"true\" />\n </button>\n </div>\n\n {entries.map((entry, i) => (\n <KeyphraseCard\n key={entry.id}\n entry={entry}\n isFocus={i === 0}\n selected={entry.id === selectedId}\n state={stateFor(entry)}\n onSelect={onSelect}\n />\n ))}\n </div>\n </aside>\n );\n}\n"],"mappings":";AAgCU,SAYE,KAZF;AA9BV,SAAS,YAAY;AACrB,SAAS,sBAAsB;AAE/B,SAAS,oBAAoB;AAC7B,SAAS,qBAAqB;AAWvB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAAuB;AACrB,QAAM,QAAQ,QAAQ,UAAU;AAChC,QAAM,WAAW,QAAQ,aAAa,OAAO,CAAC;AAC9C,QAAM,cAAc,SAAS;AAE7B,SACE,oBAAC,WAAM,WAAU,iEACf,+BAAC,SAAI,WAAU,2DACb;AAAA,yBAAC,SAAI,WAAU,gEACb;AAAA,2BAAC,UAAK,WAAU,sEAAqE;AAAA;AAAA,QACrE,QAAQ;AAAA,QAAO;AAAA,QAAE;AAAA,SACjC;AAAA,MAEA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,cAAW;AAAA,UACX,OAAO,QAAQ,SAAS,cAAc,gBAAgB;AAAA,UACtD,UAAU;AAAA,UACV,SAAS;AAAA,UACT,WAAU;AAAA,UAEV,8BAAC,QAAK,eAAY,QAAO;AAAA;AAAA,MAC3B;AAAA,OACF;AAAA,IAEC,QAAQ,IAAI,CAAC,OAAO,MACnB;AAAA,MAAC;AAAA;AAAA,QAEC;AAAA,QACA,SAAS,MAAM;AAAA,QACf,UAAU,MAAM,OAAO;AAAA,QACvB,OAAO,SAAS,KAAK;AAAA,QACrB;AAAA;AAAA,MALK,MAAM;AAAA,IAMb,CACD;AAAA,KACH,GACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface SynonymsFieldProps {
|
|
2
|
+
synonyms: string[];
|
|
3
|
+
onAdd: (syn: string) => void;
|
|
4
|
+
onRemove: (index: number) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare function SynonymsField({ synonyms, onAdd, onRemove }: SynonymsFieldProps): import("react/jsx-runtime").JSX.Element;
|
|
7
|
+
//# sourceMappingURL=SynonymsField.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SynonymsField.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/SynonymsField.tsx"],"names":[],"mappings":"AAMA,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACnC;AAED,wBAAgB,aAAa,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,kBAAkB,2CA8E9E"}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { Plus, X } from "lucide-react";
|
|
4
|
+
import { useState } from "react";
|
|
5
|
+
import { cn } from "../../../../utils/style";
|
|
6
|
+
function SynonymsField({ synonyms, onAdd, onRemove }) {
|
|
7
|
+
const [draft, setDraft] = useState("");
|
|
8
|
+
const [focused, setFocused] = useState(false);
|
|
9
|
+
const canAdd = draft.trim().length > 0;
|
|
10
|
+
const commit = () => {
|
|
11
|
+
if (!canAdd)
|
|
12
|
+
return;
|
|
13
|
+
onAdd(draft.trim());
|
|
14
|
+
setDraft("");
|
|
15
|
+
};
|
|
16
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
17
|
+
/* @__PURE__ */ jsx("span", { className: "block text-[10px] uppercase tracking-[0.05em] text-neutral-500 mb-[6px]", children: "Synonyms" }),
|
|
18
|
+
/* @__PURE__ */ jsxs(
|
|
19
|
+
"div",
|
|
20
|
+
{
|
|
21
|
+
className: cn(
|
|
22
|
+
"border rounded-rs bg-neutral-0 overflow-hidden",
|
|
23
|
+
focused ? "border-neutral-800" : "border-neutral-200"
|
|
24
|
+
),
|
|
25
|
+
children: [
|
|
26
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap gap-[7px] p-[9px] items-center", children: [
|
|
27
|
+
synonyms.map((synonym, index) => /* @__PURE__ */ jsxs(
|
|
28
|
+
"span",
|
|
29
|
+
{
|
|
30
|
+
className: "bg-neutral-1000 text-neutral-0 rounded-[15px] pl-[12px] pr-[6px] py-[4px] text-[11.5px] inline-flex items-center gap-[7px]",
|
|
31
|
+
children: [
|
|
32
|
+
synonym,
|
|
33
|
+
/* @__PURE__ */ jsx(
|
|
34
|
+
"button",
|
|
35
|
+
{
|
|
36
|
+
type: "button",
|
|
37
|
+
"aria-label": `Remove synonym ${synonym} (${index + 1})`,
|
|
38
|
+
className: "grid place-items-center p-0.5 text-[#a9adb4] rounded-full bg-transparent hover:text-neutral-0 hover:bg-neutral-900! [&_svg]:size-[1em] cursor-pointer",
|
|
39
|
+
onClick: () => onRemove(index),
|
|
40
|
+
children: /* @__PURE__ */ jsx(X, { "aria-hidden": "true" })
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
]
|
|
44
|
+
},
|
|
45
|
+
`${synonym}-${index}`
|
|
46
|
+
)),
|
|
47
|
+
/* @__PURE__ */ jsxs(
|
|
48
|
+
"button",
|
|
49
|
+
{
|
|
50
|
+
type: "button",
|
|
51
|
+
disabled: !canAdd,
|
|
52
|
+
onClick: commit,
|
|
53
|
+
className: "inline-flex items-center gap-[5px] rounded-[15px] px-[11px] py-[5px] text-[11.5px] font-semibold bg-neutral-1000 text-neutral-0 disabled:bg-neutral-100 disabled:text-neutral-300 [&_svg]:size-[13px] cursor-pointer",
|
|
54
|
+
children: [
|
|
55
|
+
/* @__PURE__ */ jsx(Plus, { "aria-hidden": "true" }),
|
|
56
|
+
" Add synonym"
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
)
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsx("div", { className: "border-t border-neutral-150" }),
|
|
62
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-[8px] px-[11px] py-[8px]", children: [
|
|
63
|
+
/* @__PURE__ */ jsx(
|
|
64
|
+
"input",
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
value: draft,
|
|
68
|
+
onChange: (e) => setDraft(e.target.value),
|
|
69
|
+
onFocus: () => setFocused(true),
|
|
70
|
+
onBlur: () => setFocused(false),
|
|
71
|
+
onKeyDown: (e) => {
|
|
72
|
+
if (e.key === "Enter") {
|
|
73
|
+
e.preventDefault();
|
|
74
|
+
commit();
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
placeholder: "Add a synonym\u2026",
|
|
78
|
+
"aria-label": "Add a synonym",
|
|
79
|
+
className: "flex-1 border-0 outline-none text-[12.5px] text-neutral-800 bg-transparent"
|
|
80
|
+
}
|
|
81
|
+
),
|
|
82
|
+
/* @__PURE__ */ jsx("span", { className: "text-[9px] text-neutral-500 border border-neutral-200 rounded-[4px] px-[5px] py-[1px] font-semibold", children: "Enter" })
|
|
83
|
+
] })
|
|
84
|
+
]
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
/* @__PURE__ */ jsx("div", { className: "text-[11px] text-neutral-500 mt-[6px] leading-[1.45]", children: "Synonyms count as keyphrase matches in this keyphrase\u2019s checks. One chip per synonym." })
|
|
88
|
+
] });
|
|
89
|
+
}
|
|
90
|
+
export {
|
|
91
|
+
SynonymsField
|
|
92
|
+
};
|
|
93
|
+
//# sourceMappingURL=SynonymsField.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/SynonymsField.tsx"],"sourcesContent":["\"use client\";\n\nimport { Plus, X } from \"lucide-react\";\nimport { useState } from \"react\";\nimport { cn } from \"../../../../utils/style\";\n\nexport interface SynonymsFieldProps {\n synonyms: string[];\n onAdd: (syn: string) => void;\n onRemove: (index: number) => void;\n}\n\nexport function SynonymsField({ synonyms, onAdd, onRemove }: SynonymsFieldProps) {\n const [draft, setDraft] = useState(\"\");\n const [focused, setFocused] = useState(false);\n const canAdd = draft.trim().length > 0;\n\n const commit = () => {\n if (!canAdd) return;\n onAdd(draft.trim());\n setDraft(\"\");\n };\n\n return (\n <div>\n <span className=\"block text-[10px] uppercase tracking-[0.05em] text-neutral-500 mb-[6px]\">\n Synonyms\n </span>\n\n <div\n className={cn(\n \"border rounded-rs bg-neutral-0 overflow-hidden\",\n focused ? \"border-neutral-800\" : \"border-neutral-200\"\n )}\n >\n <div className=\"flex flex-wrap gap-[7px] p-[9px] items-center\">\n {synonyms.map((synonym, index) => (\n <span\n key={`${synonym}-${index}`}\n className=\"bg-neutral-1000 text-neutral-0 rounded-[15px] pl-[12px] pr-[6px] py-[4px] text-[11.5px] inline-flex items-center gap-[7px]\"\n >\n {synonym}\n\n <button\n type=\"button\"\n aria-label={`Remove synonym ${synonym} (${index + 1})`}\n className=\"grid place-items-center p-0.5 text-[#a9adb4] rounded-full bg-transparent hover:text-neutral-0 hover:bg-neutral-900! [&_svg]:size-[1em] cursor-pointer\"\n onClick={() => onRemove(index)}\n >\n <X aria-hidden=\"true\" />\n </button>\n </span>\n ))}\n <button\n type=\"button\"\n disabled={!canAdd}\n onClick={commit}\n className=\"inline-flex items-center gap-[5px] rounded-[15px] px-[11px] py-[5px] text-[11.5px] font-semibold bg-neutral-1000 text-neutral-0 disabled:bg-neutral-100 disabled:text-neutral-300 [&_svg]:size-[13px] cursor-pointer\"\n >\n <Plus aria-hidden=\"true\" /> Add synonym\n </button>\n </div>\n <div className=\"border-t border-neutral-150\" />\n <div className=\"flex items-center gap-[8px] px-[11px] py-[8px]\">\n <input\n type=\"text\"\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n onFocus={() => setFocused(true)}\n onBlur={() => setFocused(false)}\n onKeyDown={(e) => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n commit();\n }\n }}\n placeholder=\"Add a synonym…\"\n aria-label=\"Add a synonym\"\n className=\"flex-1 border-0 outline-none text-[12.5px] text-neutral-800 bg-transparent\"\n />\n <span className=\"text-[9px] text-neutral-500 border border-neutral-200 rounded-[4px] px-[5px] py-[1px] font-semibold\">\n Enter\n </span>\n </div>\n </div>\n <div className=\"text-[11px] text-neutral-500 mt-[6px] leading-[1.45]\">\n Synonyms count as keyphrase matches in this keyphrase’s checks. One chip per synonym.\n </div>\n </div>\n );\n}\n"],"mappings":";AAyBM,cAYM,YAZN;AAvBN,SAAS,MAAM,SAAS;AACxB,SAAS,gBAAgB;AACzB,SAAS,UAAU;AAQZ,SAAS,cAAc,EAAE,UAAU,OAAO,SAAS,GAAuB;AAC/E,QAAM,CAAC,OAAO,QAAQ,IAAI,SAAS,EAAE;AACrC,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,SAAS,MAAM,KAAK,EAAE,SAAS;AAErC,QAAM,SAAS,MAAM;AACnB,QAAI,CAAC;AAAQ;AACb,UAAM,MAAM,KAAK,CAAC;AAClB,aAAS,EAAE;AAAA,EACb;AAEA,SACE,qBAAC,SACC;AAAA,wBAAC,UAAK,WAAU,2EAA0E,sBAE1F;AAAA,IAEA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,UAAU,uBAAuB;AAAA,QACnC;AAAA,QAEA;AAAA,+BAAC,SAAI,WAAU,iDACZ;AAAA,qBAAS,IAAI,CAAC,SAAS,UACtB;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAU;AAAA,gBAET;AAAA;AAAA,kBAED;AAAA,oBAAC;AAAA;AAAA,sBACC,MAAK;AAAA,sBACL,cAAY,kBAAkB,OAAO,KAAK,QAAQ,CAAC;AAAA,sBACnD,WAAU;AAAA,sBACV,SAAS,MAAM,SAAS,KAAK;AAAA,sBAE7B,8BAAC,KAAE,eAAY,QAAO;AAAA;AAAA,kBACxB;AAAA;AAAA;AAAA,cAZK,GAAG,OAAO,IAAI,KAAK;AAAA,YAa1B,CACD;AAAA,YACD;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,UAAU,CAAC;AAAA,gBACX,SAAS;AAAA,gBACT,WAAU;AAAA,gBAEV;AAAA,sCAAC,QAAK,eAAY,QAAO;AAAA,kBAAE;AAAA;AAAA;AAAA,YAC7B;AAAA,aACF;AAAA,UACA,oBAAC,SAAI,WAAU,+BAA8B;AAAA,UAC7C,qBAAC,SAAI,WAAU,kDACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,MAAK;AAAA,gBACL,OAAO;AAAA,gBACP,UAAU,CAAC,MAAM,SAAS,EAAE,OAAO,KAAK;AAAA,gBACxC,SAAS,MAAM,WAAW,IAAI;AAAA,gBAC9B,QAAQ,MAAM,WAAW,KAAK;AAAA,gBAC9B,WAAW,CAAC,MAAM;AAChB,sBAAI,EAAE,QAAQ,SAAS;AACrB,sBAAE,eAAe;AACjB,2BAAO;AAAA,kBACT;AAAA,gBACF;AAAA,gBACA,aAAY;AAAA,gBACZ,cAAW;AAAA,gBACX,WAAU;AAAA;AAAA,YACZ;AAAA,YACA,oBAAC,UAAK,WAAU,uGAAsG,mBAEtH;AAAA,aACF;AAAA;AAAA;AAAA,IACF;AAAA,IACA,oBAAC,SAAI,WAAU,wDAAuD,wGAEtE;AAAA,KACF;AAEJ;","names":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"icons.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/icons.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../src/components/SeoDrawer/tabs/keyphrase/icons.tsx"],"sourcesContent":["\"use client\";\n\nexport { Target, Trash2 } from \"lucide-react\";\n"],"mappings":";AAEA,SAAS,QAAQ,cAAc;","names":[]}
|
|
@@ -10,7 +10,6 @@ export interface UseAnalysisArgs {
|
|
|
10
10
|
export interface UseAnalysisResult {
|
|
11
11
|
result: AnalysisResult | null;
|
|
12
12
|
analyzing: boolean;
|
|
13
|
-
analyzedKeyphrase: string | null;
|
|
14
13
|
analyzeNow: () => void;
|
|
15
14
|
}
|
|
16
15
|
export declare function useAnalysis({ getInput, signature, supportedLocales, enabled, }: UseAnalysisArgs): UseAnalysisResult;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useAnalysis.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/useAnalysis.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,
|
|
1
|
+
{"version":3,"file":"useAnalysis.d.ts","sourceRoot":"","sources":["../../../src/components/SeoDrawer/useAnalysis.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAIjF,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,CAAC,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;IAChE,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,SAAS,EACT,gBAAgB,EAChB,OAAc,GACf,EAAE,eAAe,GAAG,iBAAiB,CAoDrC"}
|