@focus-reactive/payload-plugin-seo 1.1.0 → 1.2.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 (50) hide show
  1. package/dist/admin.css +148 -1
  2. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingLevelTiles.d.ts +7 -0
  3. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingLevelTiles.d.ts.map +1 -0
  4. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingLevelTiles.js +29 -0
  5. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingLevelTiles.js.map +1 -0
  6. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/Chevron.d.ts +4 -0
  7. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/Chevron.d.ts.map +1 -0
  8. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/Chevron.js +10 -0
  9. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/Chevron.js.map +1 -0
  10. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeGroup.d.ts +10 -0
  11. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeGroup.d.ts.map +1 -0
  12. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeGroup.js +50 -0
  13. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeGroup.js.map +1 -0
  14. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.d.ts +15 -0
  15. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.d.ts.map +1 -0
  16. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.js +38 -0
  17. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.js.map +1 -0
  18. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.d.ts +3 -0
  19. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.d.ts.map +1 -0
  20. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.js +17 -0
  21. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.js.map +1 -0
  22. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/index.d.ts +6 -0
  23. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/index.d.ts.map +1 -0
  24. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/index.js +41 -0
  25. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/index.js.map +1 -0
  26. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/useHeadingRails.d.ts +32 -0
  27. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/useHeadingRails.d.ts.map +1 -0
  28. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/useHeadingRails.js +84 -0
  29. package/dist/components/SeoDrawer/components/HeadingsSection/HeadingTree/useHeadingRails.js.map +1 -0
  30. package/dist/components/SeoDrawer/components/HeadingsSection/index.d.ts +7 -0
  31. package/dist/components/SeoDrawer/components/HeadingsSection/index.d.ts.map +1 -0
  32. package/dist/components/SeoDrawer/components/HeadingsSection/index.js +17 -0
  33. package/dist/components/SeoDrawer/components/HeadingsSection/index.js.map +1 -0
  34. package/dist/components/SeoDrawer/tabs/VitalsTab.d.ts.map +1 -1
  35. package/dist/components/SeoDrawer/tabs/VitalsTab.js +2 -0
  36. package/dist/components/SeoDrawer/tabs/VitalsTab.js.map +1 -1
  37. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.d.ts +7 -0
  38. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.d.ts.map +1 -0
  39. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.js +41 -0
  40. package/dist/engine/runAnalysis/services/derive-vitals/heading-tree.js.map +1 -0
  41. package/dist/engine/runAnalysis/services/derive-vitals/headings.d.ts +8 -0
  42. package/dist/engine/runAnalysis/services/derive-vitals/headings.d.ts.map +1 -0
  43. package/dist/engine/runAnalysis/services/derive-vitals/headings.js +50 -0
  44. package/dist/engine/runAnalysis/services/derive-vitals/headings.js.map +1 -0
  45. package/dist/engine/runAnalysis/services/derive-vitals/index.d.ts.map +1 -1
  46. package/dist/engine/runAnalysis/services/derive-vitals/index.js +4 -1
  47. package/dist/engine/runAnalysis/services/derive-vitals/index.js.map +1 -1
  48. package/dist/engine/types/analysis.d.ts +17 -0
  49. package/dist/engine/types/analysis.d.ts.map +1 -1
  50. package/package.json +1 -1
@@ -0,0 +1,50 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { HeadingTreeRow } from "./HeadingTreeRow";
4
+ import { useHeadingRails } from "./useHeadingRails";
5
+ function HeadingTreeNode({ node, depth, globalFirst, collapsed, onToggle, onBadgeMount }) {
6
+ const hasKids = node.children.length > 0;
7
+ const isOpen = hasKids && !collapsed.has(node.id);
8
+ const { containerRef, setBadgeRef, registerChildBadge, rails } = useHeadingRails({
9
+ node,
10
+ isOpen,
11
+ collapsed,
12
+ onBadgeMount
13
+ });
14
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "relative", children: [
15
+ rails ? /* @__PURE__ */ jsxs(Fragment, { children: [
16
+ /* @__PURE__ */ jsx(
17
+ "span",
18
+ {
19
+ className: "absolute w-[1.1px] bg-neutral-150",
20
+ style: {
21
+ left: rails.vertical.left,
22
+ top: rails.vertical.top,
23
+ height: rails.vertical.height
24
+ }
25
+ }
26
+ ),
27
+ rails.elbows.map((elbow) => /* @__PURE__ */ jsx(
28
+ "span",
29
+ {
30
+ className: "absolute h-[1.1px] bg-neutral-150",
31
+ style: {
32
+ left: elbow.left,
33
+ top: elbow.top,
34
+ width: elbow.width
35
+ }
36
+ },
37
+ elbow.id
38
+ ))
39
+ ] }) : null,
40
+ /* @__PURE__ */ jsx(HeadingTreeRow, { node, depth, hasKids, isOpen, globalFirst, onToggle, badgeRef: setBadgeRef }),
41
+ isOpen ? /* @__PURE__ */ jsx("div", { children: node.children.map((child) => /* @__PURE__ */ jsx(HeadingTreeNode, { node: child, depth: depth + 1, globalFirst: false, collapsed, onToggle, onBadgeMount: registerChildBadge(child.id) }, child.id)) }) : null
42
+ ] });
43
+ }
44
+ function HeadingTreeGroup({ nodes, depth, collapsed, onToggle }) {
45
+ return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node, i) => /* @__PURE__ */ jsx(HeadingTreeNode, { node, depth, globalFirst: depth === 0 && i === 0, collapsed, onToggle }, node.id)) });
46
+ }
47
+ export {
48
+ HeadingTreeGroup
49
+ };
50
+ //# sourceMappingURL=HeadingTreeGroup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeGroup.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HeadingNode } from \"../../../../../engine/types/analysis\";\nimport { HeadingTreeRow } from \"./HeadingTreeRow\";\nimport { useHeadingRails } from \"./useHeadingRails\";\n\ninterface HeadingTreeNodeProps {\n node: HeadingNode;\n depth: number;\n globalFirst: boolean;\n collapsed: ReadonlySet<string>;\n onToggle: (id: string) => void;\n onBadgeMount?: (el: HTMLSpanElement | null) => void;\n}\n\nfunction HeadingTreeNode({ node, depth, globalFirst, collapsed, onToggle, onBadgeMount }: HeadingTreeNodeProps) {\n const hasKids = node.children.length > 0;\n const isOpen = hasKids && !collapsed.has(node.id);\n\n const { containerRef, setBadgeRef, registerChildBadge, rails } = useHeadingRails({\n node,\n isOpen,\n collapsed,\n onBadgeMount,\n });\n\n return (\n <div ref={containerRef} className=\"relative\">\n {rails ? (\n <>\n <span\n className=\"absolute w-[1.1px] bg-neutral-150\"\n style={{\n left: rails.vertical.left,\n top: rails.vertical.top,\n height: rails.vertical.height,\n }}\n />\n\n {rails.elbows.map((elbow) => (\n <span\n key={elbow.id}\n className=\"absolute h-[1.1px] bg-neutral-150\"\n style={{\n left: elbow.left,\n top: elbow.top,\n width: elbow.width,\n }}\n />\n ))}\n </>\n ) : null}\n\n <HeadingTreeRow node={node} depth={depth} hasKids={hasKids} isOpen={isOpen} globalFirst={globalFirst} onToggle={onToggle} badgeRef={setBadgeRef} />\n\n {isOpen ? (\n <div>\n {node.children.map((child) => (\n <HeadingTreeNode key={child.id} node={child} depth={depth + 1} globalFirst={false} collapsed={collapsed} onToggle={onToggle} onBadgeMount={registerChildBadge(child.id)} />\n ))}\n </div>\n ) : null}\n </div>\n );\n}\n\ninterface HeadingTreeGroupProps {\n nodes: HeadingNode[];\n depth: number;\n collapsed: ReadonlySet<string>;\n onToggle: (id: string) => void;\n}\n\nexport function HeadingTreeGroup({ nodes, depth, collapsed, onToggle }: HeadingTreeGroupProps) {\n return (\n <>\n {nodes.map((node, i) => (\n <HeadingTreeNode key={node.id} node={node} depth={depth} globalFirst={depth === 0 && i === 0} collapsed={collapsed} onToggle={onToggle} />\n ))}\n </>\n );\n}\n"],"mappings":";AA6BQ,mBACE,KADF;AA1BR,SAAS,sBAAsB;AAC/B,SAAS,uBAAuB;AAWhC,SAAS,gBAAgB,EAAE,MAAM,OAAO,aAAa,WAAW,UAAU,aAAa,GAAyB;AAC9G,QAAM,UAAU,KAAK,SAAS,SAAS;AACvC,QAAM,SAAS,WAAW,CAAC,UAAU,IAAI,KAAK,EAAE;AAEhD,QAAM,EAAE,cAAc,aAAa,oBAAoB,MAAM,IAAI,gBAAgB;AAAA,IAC/E;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AAED,SACE,qBAAC,SAAI,KAAK,cAAc,WAAU,YAC/B;AAAA,YACC,iCACE;AAAA;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO;AAAA,YACL,MAAM,MAAM,SAAS;AAAA,YACrB,KAAK,MAAM,SAAS;AAAA,YACpB,QAAQ,MAAM,SAAS;AAAA,UACzB;AAAA;AAAA,MACF;AAAA,MAEC,MAAM,OAAO,IAAI,CAAC,UACjB;AAAA,QAAC;AAAA;AAAA,UAEC,WAAU;AAAA,UACV,OAAO;AAAA,YACL,MAAM,MAAM;AAAA,YACZ,KAAK,MAAM;AAAA,YACX,OAAO,MAAM;AAAA,UACf;AAAA;AAAA,QANK,MAAM;AAAA,MAOb,CACD;AAAA,OACH,IACE;AAAA,IAEJ,oBAAC,kBAAe,MAAY,OAAc,SAAkB,QAAgB,aAA0B,UAAoB,UAAU,aAAa;AAAA,IAEhJ,SACC,oBAAC,SACE,eAAK,SAAS,IAAI,CAAC,UAClB,oBAAC,mBAA+B,MAAM,OAAO,OAAO,QAAQ,GAAG,aAAa,OAAO,WAAsB,UAAoB,cAAc,mBAAmB,MAAM,EAAE,KAAhJ,MAAM,EAA6I,CAC1K,GACH,IACE;AAAA,KACN;AAEJ;AASO,SAAS,iBAAiB,EAAE,OAAO,OAAO,WAAW,SAAS,GAA0B;AAC7F,SACE,gCACG,gBAAM,IAAI,CAAC,MAAM,MAChB,oBAAC,mBAA8B,MAAY,OAAc,aAAa,UAAU,KAAK,MAAM,GAAG,WAAsB,YAA9F,KAAK,EAA6G,CACzI,GACH;AAEJ;","names":[]}
@@ -0,0 +1,15 @@
1
+ import type { Ref } from "react";
2
+ import type { HeadingNode } from "../../../../../engine/types/analysis";
3
+ interface HeadingTreeRowProps {
4
+ node: HeadingNode;
5
+ depth: number;
6
+ hasKids: boolean;
7
+ isOpen: boolean;
8
+ globalFirst: boolean;
9
+ onToggle: (id: string) => void;
10
+ rowRef?: Ref<HTMLDivElement>;
11
+ badgeRef?: Ref<HTMLSpanElement>;
12
+ }
13
+ export declare function HeadingTreeRow({ node, depth, hasKids, isOpen, globalFirst, onToggle, rowRef, badgeRef }: HeadingTreeRowProps): import("react/jsx-runtime").JSX.Element;
14
+ export {};
15
+ //# sourceMappingURL=HeadingTreeRow.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HeadingTreeRow.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAIxE,UAAU,mBAAmB;IAC3B,IAAI,EAAE,WAAW,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,EAAE,OAAO,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,QAAQ,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,MAAM,CAAC,EAAE,GAAG,CAAC,cAAc,CAAC,CAAC;IAC7B,QAAQ,CAAC,EAAE,GAAG,CAAC,eAAe,CAAC,CAAC;CACjC;AAED,wBAAgB,cAAc,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,mBAAmB,2CAuC5H"}
@@ -0,0 +1,38 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { cn } from "../../../../../utils/style";
3
+ import { Chevron } from "./Chevron";
4
+ function HeadingTreeRow({ node, depth, hasKids, isOpen, globalFirst, onToggle, rowRef, badgeRef }) {
5
+ return /* @__PURE__ */ jsxs(
6
+ "div",
7
+ {
8
+ ref: rowRef,
9
+ className: cn("relative flex items-center gap-[9px] h-[34px] box-border", hasKids && "cursor-pointer focus-visible:outline-2 focus-visible:outline-neutral-400 focus-visible:-outline-offset-2"),
10
+ style: { paddingLeft: depth * 20 },
11
+ role: hasKids ? "button" : void 0,
12
+ tabIndex: hasKids ? 0 : void 0,
13
+ "aria-expanded": hasKids ? isOpen : void 0,
14
+ onClick: hasKids ? () => onToggle(node.id) : void 0,
15
+ onKeyDown: hasKids ? (e) => {
16
+ if (e.key === "Enter" || e.key === " ") {
17
+ e.preventDefault();
18
+ onToggle(node.id);
19
+ }
20
+ } : void 0,
21
+ title: hasKids ? isOpen ? "Collapse" : "Expand" : void 0,
22
+ children: [
23
+ /* @__PURE__ */ jsxs("span", { ref: badgeRef, className: "flex-none font-mono text-[9px] font-bold leading-[100%] text-neutral-1000 bg-neutral-150 rounded-rs px-[6px] py-[3px] min-w-[22px] text-center", children: [
24
+ "H",
25
+ node.level
26
+ ] }),
27
+ /* @__PURE__ */ jsxs("div", { className: cn("flex-1 min-w-0 flex items-center gap-[9px] h-full", !globalFirst && "border-t border-neutral-150"), children: [
28
+ node.text ? /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate text-[12px] font-medium text-neutral-800", title: node.text, children: node.text }) : /* @__PURE__ */ jsx("span", { className: "flex-1 min-w-0 truncate text-[12px] font-medium italic text-neutral-400", children: "(empty heading)" }),
29
+ hasKids ? /* @__PURE__ */ jsx(Chevron, { open: isOpen }) : null
30
+ ] })
31
+ ]
32
+ }
33
+ );
34
+ }
35
+ export {
36
+ HeadingTreeRow
37
+ };
38
+ //# sourceMappingURL=HeadingTreeRow.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/HeadingTreeRow.tsx"],"sourcesContent":["import type { Ref } from \"react\";\nimport type { HeadingNode } from \"../../../../../engine/types/analysis\";\nimport { cn } from \"../../../../../utils/style\";\nimport { Chevron } from \"./Chevron\";\n\ninterface HeadingTreeRowProps {\n node: HeadingNode;\n depth: number;\n hasKids: boolean;\n isOpen: boolean;\n globalFirst: boolean;\n onToggle: (id: string) => void;\n rowRef?: Ref<HTMLDivElement>;\n badgeRef?: Ref<HTMLSpanElement>;\n}\n\nexport function HeadingTreeRow({ node, depth, hasKids, isOpen, globalFirst, onToggle, rowRef, badgeRef }: HeadingTreeRowProps) {\n return (\n <div\n ref={rowRef}\n className={cn(\"relative flex items-center gap-[9px] h-[34px] box-border\", hasKids && \"cursor-pointer focus-visible:outline-2 focus-visible:outline-neutral-400 focus-visible:-outline-offset-2\")}\n style={{ paddingLeft: depth * 20 }}\n role={hasKids ? \"button\" : undefined}\n tabIndex={hasKids ? 0 : undefined}\n aria-expanded={hasKids ? isOpen : undefined}\n onClick={hasKids ? () => onToggle(node.id) : undefined}\n onKeyDown={\n hasKids\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onToggle(node.id);\n }\n }\n : undefined\n }\n title={hasKids ? (isOpen ? \"Collapse\" : \"Expand\") : undefined}\n >\n <span ref={badgeRef} className=\"flex-none font-mono text-[9px] font-bold leading-[100%] text-neutral-1000 bg-neutral-150 rounded-rs px-[6px] py-[3px] min-w-[22px] text-center\">\n H{node.level}\n </span>\n\n <div className={cn(\"flex-1 min-w-0 flex items-center gap-[9px] h-full\", !globalFirst && \"border-t border-neutral-150\")}>\n {node.text ? (\n <span className=\"flex-1 min-w-0 truncate text-[12px] font-medium text-neutral-800\" title={node.text}>\n {node.text}\n </span>\n ) : (\n <span className=\"flex-1 min-w-0 truncate text-[12px] font-medium italic text-neutral-400\">(empty heading)</span>\n )}\n\n {hasKids ? <Chevron open={isOpen} /> : null}\n </div>\n </div>\n );\n}\n"],"mappings":"AAsCM,SAMI,KANJ;AApCN,SAAS,UAAU;AACnB,SAAS,eAAe;AAajB,SAAS,eAAe,EAAE,MAAM,OAAO,SAAS,QAAQ,aAAa,UAAU,QAAQ,SAAS,GAAwB;AAC7H,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,GAAG,4DAA4D,WAAW,0GAA0G;AAAA,MAC/L,OAAO,EAAE,aAAa,QAAQ,GAAG;AAAA,MACjC,MAAM,UAAU,WAAW;AAAA,MAC3B,UAAU,UAAU,IAAI;AAAA,MACxB,iBAAe,UAAU,SAAS;AAAA,MAClC,SAAS,UAAU,MAAM,SAAS,KAAK,EAAE,IAAI;AAAA,MAC7C,WACE,UACI,CAAC,MAAM;AACL,YAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,KAAK;AACtC,YAAE,eAAe;AACjB,mBAAS,KAAK,EAAE;AAAA,QAClB;AAAA,MACF,IACA;AAAA,MAEN,OAAO,UAAW,SAAS,aAAa,WAAY;AAAA,MAEpD;AAAA,6BAAC,UAAK,KAAK,UAAU,WAAU,kJAAiJ;AAAA;AAAA,UAC5K,KAAK;AAAA,WACT;AAAA,QAEA,qBAAC,SAAI,WAAW,GAAG,qDAAqD,CAAC,eAAe,6BAA6B,GAClH;AAAA,eAAK,OACJ,oBAAC,UAAK,WAAU,oEAAmE,OAAO,KAAK,MAC5F,eAAK,MACR,IAEA,oBAAC,UAAK,WAAU,2EAA0E,6BAAe;AAAA,UAG1G,UAAU,oBAAC,WAAQ,MAAM,QAAQ,IAAK;AAAA,WACzC;AAAA;AAAA;AAAA,EACF;AAEJ;","names":[]}
@@ -0,0 +1,3 @@
1
+ import type { HeadingNode } from "../../../../../engine/types/analysis";
2
+ export declare function collectParentIds(nodes: HeadingNode[]): string[];
3
+ //# sourceMappingURL=headingTreeView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headingTreeView.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,MAAM,EAAE,CAe/D"}
@@ -0,0 +1,17 @@
1
+ function collectParentIds(nodes) {
2
+ const ids = [];
3
+ const walk = (ns) => {
4
+ for (const n of ns) {
5
+ if (n.children.length > 0) {
6
+ ids.push(n.id);
7
+ walk(n.children);
8
+ }
9
+ }
10
+ };
11
+ walk(nodes);
12
+ return ids;
13
+ }
14
+ export {
15
+ collectParentIds
16
+ };
17
+ //# sourceMappingURL=headingTreeView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/headingTreeView.ts"],"sourcesContent":["import type { HeadingNode } from \"../../../../../engine/types/analysis\";\n\nexport function collectParentIds(nodes: HeadingNode[]): string[] {\n const ids: string[] = [];\n\n const walk = (ns: HeadingNode[]) => {\n for (const n of ns) {\n if (n.children.length > 0) {\n ids.push(n.id);\n walk(n.children);\n }\n }\n };\n\n walk(nodes);\n\n return ids;\n}\n"],"mappings":"AAEO,SAAS,iBAAiB,OAAgC;AAC/D,QAAM,MAAgB,CAAC;AAEvB,QAAM,OAAO,CAAC,OAAsB;AAClC,eAAW,KAAK,IAAI;AAClB,UAAI,EAAE,SAAS,SAAS,GAAG;AACzB,YAAI,KAAK,EAAE,EAAE;AACb,aAAK,EAAE,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,OAAK,KAAK;AAEV,SAAO;AACT;","names":[]}
@@ -0,0 +1,6 @@
1
+ import type { HeadingNode } from "../../../../../engine/types/analysis";
2
+ export interface HeadingTreeProps {
3
+ tree: HeadingNode[];
4
+ }
5
+ export declare function HeadingTree({ tree }: HeadingTreeProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/index.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAKxE,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,WAAW,EAAE,CAAC;CACrB;AAID,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAwCrD"}
@@ -0,0 +1,41 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useMemo, useState } from "react";
4
+ import { cn } from "../../../../../utils/style";
5
+ import { collectParentIds } from "./headingTreeView";
6
+ import { HeadingTreeGroup } from "./HeadingTreeGroup";
7
+ const SUB_LABEL = "text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500";
8
+ function HeadingTree({ tree }) {
9
+ const parentIds = useMemo(() => collectParentIds(tree), [tree]);
10
+ const [collapsed, setCollapsed] = useState(() => /* @__PURE__ */ new Set());
11
+ const empty = tree.length === 0;
12
+ const allCollapsed = parentIds.length > 0 && parentIds.every((id) => collapsed.has(id));
13
+ const toggleAll = () => setCollapsed(allCollapsed ? /* @__PURE__ */ new Set() : new Set(parentIds));
14
+ const toggle = (id) => setCollapsed((prev) => {
15
+ const next = new Set(prev);
16
+ if (next.has(id))
17
+ next.delete(id);
18
+ else
19
+ next.add(id);
20
+ return next;
21
+ });
22
+ return /* @__PURE__ */ jsxs("div", { className: "px-[15px] pt-[11px] border-t border-neutral-150", children: [
23
+ /* @__PURE__ */ jsxs("div", { className: cn(SUB_LABEL, "flex items-center justify-between"), children: [
24
+ /* @__PURE__ */ jsx("span", { children: "Structure" }),
25
+ !empty && /* @__PURE__ */ jsx(
26
+ "button",
27
+ {
28
+ type: "button",
29
+ onClick: toggleAll,
30
+ className: "text-[11px] font-medium normal-case tracking-normal text-neutral-600 hover:text-neutral-1000 hover:underline underline-offset-2 bg-transparent border-0 p-0 cursor-pointer",
31
+ children: allCollapsed ? "Expand all" : "Collapse all"
32
+ }
33
+ )
34
+ ] }),
35
+ empty ? /* @__PURE__ */ jsx("div", { className: "px-[15px] pt-[20px] pb-[24px] text-center text-[12px] text-neutral-500", children: "No headings found in this content." }) : /* @__PURE__ */ jsx("div", { className: "relative pt-[5px] pb-[7px]", children: /* @__PURE__ */ jsx(HeadingTreeGroup, { nodes: tree, depth: 0, collapsed, onToggle: toggle }) })
36
+ ] });
37
+ }
38
+ export {
39
+ HeadingTree
40
+ };
41
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/index.tsx"],"sourcesContent":["\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport type { HeadingNode } from \"../../../../../engine/types/analysis\";\nimport { cn } from \"../../../../../utils/style\";\nimport { collectParentIds } from \"./headingTreeView\";\nimport { HeadingTreeGroup } from \"./HeadingTreeGroup\";\n\nexport interface HeadingTreeProps {\n tree: HeadingNode[];\n}\n\nconst SUB_LABEL = \"text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500\";\n\nexport function HeadingTree({ tree }: HeadingTreeProps) {\n const parentIds = useMemo(() => collectParentIds(tree), [tree]);\n const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());\n\n const empty = tree.length === 0;\n const allCollapsed = parentIds.length > 0 && parentIds.every((id) => collapsed.has(id));\n\n const toggleAll = () => setCollapsed(allCollapsed ? new Set() : new Set(parentIds));\n const toggle = (id: string) =>\n setCollapsed((prev) => {\n const next = new Set(prev);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n return next;\n });\n\n return (\n <div className=\"px-[15px] pt-[11px] border-t border-neutral-150\">\n <div className={cn(SUB_LABEL, \"flex items-center justify-between\")}>\n <span>Structure</span>\n\n {!empty && (\n <button\n type=\"button\"\n onClick={toggleAll}\n className=\"text-[11px] font-medium normal-case tracking-normal text-neutral-600 hover:text-neutral-1000 hover:underline underline-offset-2 bg-transparent border-0 p-0 cursor-pointer\"\n >\n {allCollapsed ? \"Expand all\" : \"Collapse all\"}\n </button>\n )}\n </div>\n {empty ? (\n <div className=\"px-[15px] pt-[20px] pb-[24px] text-center text-[12px] text-neutral-500\">No headings found in this content.</div>\n ) : (\n <div className=\"relative pt-[5px] pb-[7px]\">\n <HeadingTreeGroup nodes={tree} depth={0} collapsed={collapsed} onToggle={toggle} />\n </div>\n )}\n </div>\n );\n}\n"],"mappings":";AAgCM,SACE,KADF;AA9BN,SAAS,SAAS,gBAAgB;AAElC,SAAS,UAAU;AACnB,SAAS,wBAAwB;AACjC,SAAS,wBAAwB;AAMjC,MAAM,YAAY;AAEX,SAAS,YAAY,EAAE,KAAK,GAAqB;AACtD,QAAM,YAAY,QAAQ,MAAM,iBAAiB,IAAI,GAAG,CAAC,IAAI,CAAC;AAC9D,QAAM,CAAC,WAAW,YAAY,IAAI,SAAsB,MAAM,oBAAI,IAAI,CAAC;AAEvE,QAAM,QAAQ,KAAK,WAAW;AAC9B,QAAM,eAAe,UAAU,SAAS,KAAK,UAAU,MAAM,CAAC,OAAO,UAAU,IAAI,EAAE,CAAC;AAEtF,QAAM,YAAY,MAAM,aAAa,eAAe,oBAAI,IAAI,IAAI,IAAI,IAAI,SAAS,CAAC;AAClF,QAAM,SAAS,CAAC,OACd,aAAa,CAAC,SAAS;AACrB,UAAM,OAAO,IAAI,IAAI,IAAI;AACzB,QAAI,KAAK,IAAI,EAAE;AAAG,WAAK,OAAO,EAAE;AAAA;AAC3B,WAAK,IAAI,EAAE;AAChB,WAAO;AAAA,EACT,CAAC;AAEH,SACE,qBAAC,SAAI,WAAU,mDACb;AAAA,yBAAC,SAAI,WAAW,GAAG,WAAW,mCAAmC,GAC/D;AAAA,0BAAC,UAAK,uBAAS;AAAA,MAEd,CAAC,SACA;AAAA,QAAC;AAAA;AAAA,UACC,MAAK;AAAA,UACL,SAAS;AAAA,UACT,WAAU;AAAA,UAET,yBAAe,eAAe;AAAA;AAAA,MACjC;AAAA,OAEJ;AAAA,IACC,QACC,oBAAC,SAAI,WAAU,0EAAyE,gDAAkC,IAE1H,oBAAC,SAAI,WAAU,8BACb,8BAAC,oBAAiB,OAAO,MAAM,OAAO,GAAG,WAAsB,UAAU,QAAQ,GACnF;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,32 @@
1
+ import type { RefObject } from "react";
2
+ import type { HeadingNode } from "../../../../../engine/types/analysis";
3
+ interface VerticalRail {
4
+ left: number;
5
+ top: number;
6
+ height: number;
7
+ }
8
+ interface Elbow {
9
+ id: string;
10
+ left: number;
11
+ top: number;
12
+ width: number;
13
+ }
14
+ export interface RailGeometry {
15
+ vertical: VerticalRail;
16
+ elbows: Elbow[];
17
+ }
18
+ interface UseHeadingRailsParams {
19
+ node: HeadingNode;
20
+ isOpen: boolean;
21
+ collapsed: ReadonlySet<string>;
22
+ onBadgeMount?: (el: HTMLSpanElement | null) => void;
23
+ }
24
+ interface UseHeadingRailsResult {
25
+ containerRef: RefObject<HTMLDivElement | null>;
26
+ setBadgeRef: (el: HTMLSpanElement | null) => void;
27
+ registerChildBadge: (childId: string) => (el: HTMLSpanElement | null) => void;
28
+ rails: RailGeometry | null;
29
+ }
30
+ export declare function useHeadingRails({ node, isOpen, collapsed, onBadgeMount }: UseHeadingRailsParams): UseHeadingRailsResult;
31
+ export {};
32
+ //# sourceMappingURL=useHeadingRails.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useHeadingRails.d.ts","sourceRoot":"","sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/useHeadingRails.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sCAAsC,CAAC;AAExE,UAAU,YAAY;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,KAAK;IACb,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,YAAY,CAAC;IACvB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,UAAU,qBAAqB;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC/B,YAAY,CAAC,EAAE,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;CACrD;AAED,UAAU,qBAAqB;IAC7B,YAAY,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC/C,WAAW,EAAE,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IAClD,kBAAkB,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,CAAC,EAAE,EAAE,eAAe,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9E,KAAK,EAAE,YAAY,GAAG,IAAI,CAAC;CAC5B;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,EAAE,qBAAqB,GAAG,qBAAqB,CA6FvH"}
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import { useLayoutEffect, useRef, useState } from "react";
3
+ function useHeadingRails({ node, isOpen, collapsed, onBadgeMount }) {
4
+ const containerRef = useRef(null);
5
+ const badgeRef = useRef(null);
6
+ const childBadges = useRef(/* @__PURE__ */ new Map());
7
+ const childSetters = useRef(/* @__PURE__ */ new Map());
8
+ const [rails, setRails] = useState(null);
9
+ const setBadgeRef = (el) => {
10
+ badgeRef.current = el;
11
+ onBadgeMount?.(el);
12
+ };
13
+ const registerChildBadge = (childId) => {
14
+ let setter = childSetters.current.get(childId);
15
+ if (!setter) {
16
+ setter = (el) => {
17
+ if (el)
18
+ childBadges.current.set(childId, el);
19
+ else
20
+ childBadges.current.delete(childId);
21
+ };
22
+ childSetters.current.set(childId, setter);
23
+ }
24
+ return setter;
25
+ };
26
+ useLayoutEffect(() => {
27
+ if (!isOpen) {
28
+ setRails(null);
29
+ return;
30
+ }
31
+ const container = containerRef.current;
32
+ const parentBadge = badgeRef.current;
33
+ if (!container || !parentBadge)
34
+ return;
35
+ const measure = () => {
36
+ const containerRect = container.getBoundingClientRect();
37
+ const parentBadgeRect = parentBadge.getBoundingClientRect();
38
+ const railCenterX = parentBadgeRect.left + parentBadgeRect.width / 2 - containerRect.left;
39
+ const railStartY = parentBadgeRect.bottom - containerRect.top;
40
+ const elbows = [];
41
+ for (const child of node.children) {
42
+ const childBadge = childBadges.current.get(child.id);
43
+ if (!childBadge)
44
+ continue;
45
+ const childBadgeRect = childBadge.getBoundingClientRect();
46
+ const childCenterX = childBadgeRect.left + childBadgeRect.width / 2 - containerRect.left;
47
+ const childCenterY = childBadgeRect.top + childBadgeRect.height / 2 - containerRect.top;
48
+ elbows.push({
49
+ id: child.id,
50
+ left: railCenterX,
51
+ top: childCenterY,
52
+ width: Math.max(0, childCenterX - railCenterX)
53
+ });
54
+ }
55
+ if (elbows.length === 0) {
56
+ setRails(null);
57
+ return;
58
+ }
59
+ const lastChildCenterY = elbows.at(-1)?.top ?? railStartY;
60
+ setRails({
61
+ vertical: {
62
+ left: railCenterX,
63
+ top: railStartY,
64
+ height: Math.max(0, lastChildCenterY - railStartY)
65
+ },
66
+ elbows
67
+ });
68
+ };
69
+ measure();
70
+ const observer = new ResizeObserver(measure);
71
+ observer.observe(container);
72
+ return () => observer.disconnect();
73
+ }, [isOpen, collapsed, node]);
74
+ return {
75
+ containerRef,
76
+ setBadgeRef,
77
+ registerChildBadge,
78
+ rails
79
+ };
80
+ }
81
+ export {
82
+ useHeadingRails
83
+ };
84
+ //# sourceMappingURL=useHeadingRails.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../src/components/SeoDrawer/components/HeadingsSection/HeadingTree/useHeadingRails.ts"],"sourcesContent":["\"use client\";\n\nimport type { RefObject } from \"react\";\nimport { useLayoutEffect, useRef, useState } from \"react\";\nimport type { HeadingNode } from \"../../../../../engine/types/analysis\";\n\ninterface VerticalRail {\n left: number;\n top: number;\n height: number;\n}\n\ninterface Elbow {\n id: string;\n left: number;\n top: number;\n width: number;\n}\n\nexport interface RailGeometry {\n vertical: VerticalRail;\n elbows: Elbow[];\n}\n\ninterface UseHeadingRailsParams {\n node: HeadingNode;\n isOpen: boolean;\n collapsed: ReadonlySet<string>;\n onBadgeMount?: (el: HTMLSpanElement | null) => void;\n}\n\ninterface UseHeadingRailsResult {\n containerRef: RefObject<HTMLDivElement | null>;\n setBadgeRef: (el: HTMLSpanElement | null) => void;\n registerChildBadge: (childId: string) => (el: HTMLSpanElement | null) => void;\n rails: RailGeometry | null;\n}\n\nexport function useHeadingRails({ node, isOpen, collapsed, onBadgeMount }: UseHeadingRailsParams): UseHeadingRailsResult {\n const containerRef = useRef<HTMLDivElement>(null);\n const badgeRef = useRef<HTMLSpanElement | null>(null);\n const childBadges = useRef(new Map<string, HTMLSpanElement>());\n const childSetters = useRef(new Map<string, (el: HTMLSpanElement | null) => void>());\n const [rails, setRails] = useState<RailGeometry | null>(null);\n\n const setBadgeRef = (el: HTMLSpanElement | null) => {\n badgeRef.current = el;\n onBadgeMount?.(el);\n };\n\n const registerChildBadge = (childId: string) => {\n let setter = childSetters.current.get(childId);\n\n if (!setter) {\n setter = (el: HTMLSpanElement | null) => {\n if (el) childBadges.current.set(childId, el);\n else childBadges.current.delete(childId);\n };\n\n childSetters.current.set(childId, setter);\n }\n\n return setter;\n };\n\n useLayoutEffect(() => {\n if (!isOpen) {\n setRails(null);\n return;\n }\n\n const container = containerRef.current;\n const parentBadge = badgeRef.current;\n if (!container || !parentBadge) return;\n\n const measure = () => {\n const containerRect = container.getBoundingClientRect();\n const parentBadgeRect = parentBadge.getBoundingClientRect();\n\n const railCenterX = parentBadgeRect.left + parentBadgeRect.width / 2 - containerRect.left;\n const railStartY = parentBadgeRect.bottom - containerRect.top;\n\n const elbows: Elbow[] = [];\n for (const child of node.children) {\n const childBadge = childBadges.current.get(child.id);\n if (!childBadge) continue;\n\n const childBadgeRect = childBadge.getBoundingClientRect();\n\n const childCenterX = childBadgeRect.left + childBadgeRect.width / 2 - containerRect.left;\n const childCenterY = childBadgeRect.top + childBadgeRect.height / 2 - containerRect.top;\n\n elbows.push({\n id: child.id,\n left: railCenterX,\n top: childCenterY,\n width: Math.max(0, childCenterX - railCenterX),\n });\n }\n\n if (elbows.length === 0) {\n setRails(null);\n return;\n }\n\n const lastChildCenterY = elbows.at(-1)?.top ?? railStartY;\n setRails({\n vertical: {\n left: railCenterX,\n top: railStartY,\n height: Math.max(0, lastChildCenterY - railStartY),\n },\n elbows,\n });\n };\n\n measure();\n\n const observer = new ResizeObserver(measure);\n\n observer.observe(container);\n\n return () => observer.disconnect();\n }, [isOpen, collapsed, node]);\n\n return {\n containerRef,\n setBadgeRef,\n registerChildBadge,\n rails,\n };\n}\n"],"mappings":";AAGA,SAAS,iBAAiB,QAAQ,gBAAgB;AAmC3C,SAAS,gBAAgB,EAAE,MAAM,QAAQ,WAAW,aAAa,GAAiD;AACvH,QAAM,eAAe,OAAuB,IAAI;AAChD,QAAM,WAAW,OAA+B,IAAI;AACpD,QAAM,cAAc,OAAO,oBAAI,IAA6B,CAAC;AAC7D,QAAM,eAAe,OAAO,oBAAI,IAAkD,CAAC;AACnF,QAAM,CAAC,OAAO,QAAQ,IAAI,SAA8B,IAAI;AAE5D,QAAM,cAAc,CAAC,OAA+B;AAClD,aAAS,UAAU;AACnB,mBAAe,EAAE;AAAA,EACnB;AAEA,QAAM,qBAAqB,CAAC,YAAoB;AAC9C,QAAI,SAAS,aAAa,QAAQ,IAAI,OAAO;AAE7C,QAAI,CAAC,QAAQ;AACX,eAAS,CAAC,OAA+B;AACvC,YAAI;AAAI,sBAAY,QAAQ,IAAI,SAAS,EAAE;AAAA;AACtC,sBAAY,QAAQ,OAAO,OAAO;AAAA,MACzC;AAEA,mBAAa,QAAQ,IAAI,SAAS,MAAM;AAAA,IAC1C;AAEA,WAAO;AAAA,EACT;AAEA,kBAAgB,MAAM;AACpB,QAAI,CAAC,QAAQ;AACX,eAAS,IAAI;AACb;AAAA,IACF;AAEA,UAAM,YAAY,aAAa;AAC/B,UAAM,cAAc,SAAS;AAC7B,QAAI,CAAC,aAAa,CAAC;AAAa;AAEhC,UAAM,UAAU,MAAM;AACpB,YAAM,gBAAgB,UAAU,sBAAsB;AACtD,YAAM,kBAAkB,YAAY,sBAAsB;AAE1D,YAAM,cAAc,gBAAgB,OAAO,gBAAgB,QAAQ,IAAI,cAAc;AACrF,YAAM,aAAa,gBAAgB,SAAS,cAAc;AAE1D,YAAM,SAAkB,CAAC;AACzB,iBAAW,SAAS,KAAK,UAAU;AACjC,cAAM,aAAa,YAAY,QAAQ,IAAI,MAAM,EAAE;AACnD,YAAI,CAAC;AAAY;AAEjB,cAAM,iBAAiB,WAAW,sBAAsB;AAExD,cAAM,eAAe,eAAe,OAAO,eAAe,QAAQ,IAAI,cAAc;AACpF,cAAM,eAAe,eAAe,MAAM,eAAe,SAAS,IAAI,cAAc;AAEpF,eAAO,KAAK;AAAA,UACV,IAAI,MAAM;AAAA,UACV,MAAM;AAAA,UACN,KAAK;AAAA,UACL,OAAO,KAAK,IAAI,GAAG,eAAe,WAAW;AAAA,QAC/C,CAAC;AAAA,MACH;AAEA,UAAI,OAAO,WAAW,GAAG;AACvB,iBAAS,IAAI;AACb;AAAA,MACF;AAEA,YAAM,mBAAmB,OAAO,GAAG,EAAE,GAAG,OAAO;AAC/C,eAAS;AAAA,QACP,UAAU;AAAA,UACR,MAAM;AAAA,UACN,KAAK;AAAA,UACL,QAAQ,KAAK,IAAI,GAAG,mBAAmB,UAAU;AAAA,QACnD;AAAA,QACA;AAAA,MACF,CAAC;AAAA,IACH;AAEA,YAAQ;AAER,UAAM,WAAW,IAAI,eAAe,OAAO;AAE3C,aAAS,QAAQ,SAAS;AAE1B,WAAO,MAAM,SAAS,WAAW;AAAA,EACnC,GAAG,CAAC,QAAQ,WAAW,IAAI,CAAC;AAE5B,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,7 @@
1
+ import type { HeadingStructure } from "../../../../engine/types/analysis";
2
+ interface HeadingsSectionProps {
3
+ data: HeadingStructure;
4
+ }
5
+ export declare function HeadingsSection({ data }: HeadingsSectionProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/index.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAM1E,UAAU,oBAAoB;IAC5B,IAAI,EAAE,gBAAgB,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,oBAAoB,2CAQ7D"}
@@ -0,0 +1,17 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { SectionCard } from "../../../../ui/SectionCard";
4
+ import { Pill } from "../../../../ui/Pill";
5
+ import { HeadingLevelTiles } from "./HeadingLevelTiles";
6
+ import { HeadingTree } from "./HeadingTree";
7
+ function HeadingsSection({ data }) {
8
+ return /* @__PURE__ */ jsxs(SectionCard, { title: "Headings", widget: /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: data.total }), children: [
9
+ /* @__PURE__ */ jsx("div", { className: "text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500 px-[15px] pt-[11px]", children: "Levels" }),
10
+ /* @__PURE__ */ jsx(HeadingLevelTiles, { levels: data.levels }),
11
+ /* @__PURE__ */ jsx(HeadingTree, { tree: data.tree })
12
+ ] });
13
+ }
14
+ export {
15
+ HeadingsSection
16
+ };
17
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/components/SeoDrawer/components/HeadingsSection/index.tsx"],"sourcesContent":["\"use client\";\n\nimport type { HeadingStructure } from \"../../../../engine/types/analysis\";\nimport { SectionCard } from \"../../../../ui/SectionCard\";\nimport { Pill } from \"../../../../ui/Pill\";\nimport { HeadingLevelTiles } from \"./HeadingLevelTiles\";\nimport { HeadingTree } from \"./HeadingTree\";\n\ninterface HeadingsSectionProps {\n data: HeadingStructure;\n}\n\nexport function HeadingsSection({ data }: HeadingsSectionProps) {\n return (\n <SectionCard title=\"Headings\" widget={<Pill variant=\"neutral\">{data.total}</Pill>}>\n <div className=\"text-[9.5px] font-semibold uppercase tracking-[0.05em] text-neutral-500 px-[15px] pt-[11px]\">Levels</div>\n <HeadingLevelTiles levels={data.levels} />\n <HeadingTree tree={data.tree} />\n </SectionCard>\n );\n}\n"],"mappings":";AAcI,SAAsC,KAAtC;AAXJ,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,yBAAyB;AAClC,SAAS,mBAAmB;AAMrB,SAAS,gBAAgB,EAAE,KAAK,GAAyB;AAC9D,SACE,qBAAC,eAAY,OAAM,YAAW,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,OAAM,GACxE;AAAA,wBAAC,SAAI,WAAU,+FAA8F,oBAAM;AAAA,IACnH,oBAAC,qBAAkB,QAAQ,KAAK,QAAQ;AAAA,IACxC,oBAAC,eAAY,MAAM,KAAK,MAAM;AAAA,KAChC;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;AAMnE,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,2CAwCrE"}
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;AAOnE,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,2CA0CrE"}
@@ -4,6 +4,7 @@ import { cn, ROW_SEPARATOR } from "../../../utils/style";
4
4
  import { KpiCard } from "../../../ui/KpiCard";
5
5
  import { SectionCard } from "../../../ui/SectionCard";
6
6
  import { Pill } from "../../../ui/Pill";
7
+ import { HeadingsSection } from "../components/HeadingsSection";
7
8
  function VitalsTab({ data, onRequestKeyphrase }) {
8
9
  const max = Math.max(1, ...data.prominentWords.map((w) => w.count));
9
10
  const noKeyphraseMatch = data.prominentWords.every((w) => !w.isKeyphrase);
@@ -16,6 +17,7 @@ function VitalsTab({ data, onRequestKeyphrase }) {
16
17
  /* @__PURE__ */ jsx(KpiCard, { label: "Videos", value: data.videos }),
17
18
  /* @__PURE__ */ jsx(KpiCard, { label: "Reading time", value: data.readingTimeMinutes, suffix: "min" })
18
19
  ] }),
20
+ /* @__PURE__ */ jsx(HeadingsSection, { data: data.headings }),
19
21
  /* @__PURE__ */ jsx(SectionCard, { title: "Prominent words", widget: /* @__PURE__ */ jsx(Pill, { variant: "neutral", children: data.prominentWords.length }), children: data.prominentWords.map((w) => /* @__PURE__ */ jsxs("div", { className: cn("relative flex items-center gap-[12px] px-[15px] py-[9px]", ROW_SEPARATOR), children: [
20
22
  /* @__PURE__ */ jsxs("div", { className: "w-[120px] flex-none text-[12px] font-medium flex items-center gap-[6px]", children: [
21
23
  w.word,
@@ -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\";\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 <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 <SectionCard title=\"Prominent words\" widget={<Pill variant=\"neutral\">{data.prominentWords.length}</Pill>}>\n {data.prominentWords.map((w) => (\n <div className={cn(\"relative flex items-center gap-[12px] px-[15px] py-[9px]\", ROW_SEPARATOR)} key={w.word}>\n <div className=\"w-[120px] flex-none text-[12px] font-medium flex items-center gap-[6px]\">\n {w.word} {w.isKeyphrase && <span className=\"text-[9px] font-bold uppercase tracking-[0.04em] text-neutral-1000 bg-neutral-150 rounded-[3px] px-[5px] py-[1px]\">Key</span>}\n </div>\n <div className=\"flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden\">\n <i className={cn(\"block h-full\", w.isKeyphrase ? \"bg-neutral-1000\" : \"bg-neutral-400\")} style={{ width: `${(w.count / max) * 100}%` }} />\n </div>\n <div className=\"w-[30px] text-right font-mono text-[11px] font-semibold text-neutral-700\">{w.count}</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 );\n}\n"],"mappings":";AAmBM,SACE,KADF;AAhBN,SAAS,IAAI,qBAAqB;AAClC,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AAOd,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,qBAAC,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,eAAY,OAAM,mBAAkB,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,eAAe,QAAO,GAC9F,eAAK,eAAe,IAAI,CAAC,MACxB,qBAAC,SAAI,WAAW,GAAG,4DAA4D,aAAa,GAC1F;AAAA,2BAAC,SAAI,WAAU,2EACZ;AAAA,UAAE;AAAA,QAAK;AAAA,QAAE,EAAE,eAAe,oBAAC,UAAK,WAAU,qHAAoH,iBAAG;AAAA,SACpK;AAAA,MACA,oBAAC,SAAI,WAAU,+DACb,8BAAC,OAAE,WAAW,GAAG,gBAAgB,EAAE,cAAc,oBAAoB,gBAAgB,GAAG,OAAO,EAAE,OAAO,GAAI,EAAE,QAAQ,MAAO,GAAG,IAAI,GAAG,GACzI;AAAA,MACA,oBAAC,SAAI,WAAU,4EAA4E,YAAE,OAAM;AAAA,SAPD,EAAE,IAQtG,CACD,GACH;AAAA,IAEC,oBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KAEJ;AAEJ;","names":[]}
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 <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 title=\"Prominent words\" widget={<Pill variant=\"neutral\">{data.prominentWords.length}</Pill>}>\n {data.prominentWords.map((w) => (\n <div className={cn(\"relative flex items-center gap-[12px] px-[15px] py-[9px]\", ROW_SEPARATOR)} key={w.word}>\n <div className=\"w-[120px] flex-none text-[12px] font-medium flex items-center gap-[6px]\">\n {w.word} {w.isKeyphrase && <span className=\"text-[9px] font-bold uppercase tracking-[0.04em] text-neutral-1000 bg-neutral-150 rounded-[3px] px-[5px] py-[1px]\">Key</span>}\n </div>\n <div className=\"flex-1 h-[6px] rounded-[3px] bg-neutral-100 overflow-hidden\">\n <i className={cn(\"block h-full\", w.isKeyphrase ? \"bg-neutral-1000\" : \"bg-neutral-400\")} style={{ width: `${(w.count / max) * 100}%` }} />\n </div>\n <div className=\"w-[30px] text-right font-mono text-[11px] font-semibold text-neutral-700\">{w.count}</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 );\n}\n"],"mappings":";AAoBM,SACE,KADF;AAjBN,SAAS,IAAI,qBAAqB;AAClC,SAAS,eAAe;AACxB,SAAS,mBAAmB;AAC5B,SAAS,YAAY;AACrB,SAAS,uBAAuB;AAOzB,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,qBAAC,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,oBAAC,eAAY,OAAM,mBAAkB,QAAQ,oBAAC,QAAK,SAAQ,WAAW,eAAK,eAAe,QAAO,GAC9F,eAAK,eAAe,IAAI,CAAC,MACxB,qBAAC,SAAI,WAAW,GAAG,4DAA4D,aAAa,GAC1F;AAAA,2BAAC,SAAI,WAAU,2EACZ;AAAA,UAAE;AAAA,QAAK;AAAA,QAAE,EAAE,eAAe,oBAAC,UAAK,WAAU,qHAAoH,iBAAG;AAAA,SACpK;AAAA,MACA,oBAAC,SAAI,WAAU,+DACb,8BAAC,OAAE,WAAW,GAAG,gBAAgB,EAAE,cAAc,oBAAoB,gBAAgB,GAAG,OAAO,EAAE,OAAO,GAAI,EAAE,QAAQ,MAAO,GAAG,IAAI,GAAG,GACzI;AAAA,MACA,oBAAC,SAAI,WAAU,4EAA4E,YAAE,OAAM;AAAA,SAPD,EAAE,IAQtG,CACD,GACH;AAAA,IAEC,oBACC;AAAA,MAAC;AAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAS;AAAA,QACT,WAAU;AAAA,QACX;AAAA;AAAA,IAED;AAAA,KAEJ;AAEJ;","names":[]}
@@ -0,0 +1,7 @@
1
+ import type { HeadingLevel, HeadingStructure } from "../../../types/analysis";
2
+ export interface FlatHeading {
3
+ level: HeadingLevel;
4
+ text: string;
5
+ }
6
+ export declare function buildHeadingTree(flat: FlatHeading[]): HeadingStructure;
7
+ //# sourceMappingURL=heading-tree.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"heading-tree.d.ts","sourceRoot":"","sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/heading-tree.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAkC,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE9G,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,YAAY,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;CACd;AAWD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,EAAE,GAAG,gBAAgB,CAkCtE"}
@@ -0,0 +1,41 @@
1
+ const LEVELS = [1, 2, 3, 4, 5, 6];
2
+ function countLevels(flat) {
3
+ return LEVELS.map((level) => ({
4
+ level,
5
+ count: flat.filter((h) => h.level === level).length
6
+ }));
7
+ }
8
+ function buildHeadingTree(flat) {
9
+ const roots = [];
10
+ const stack = [];
11
+ for (const heading of flat) {
12
+ let top = stack.at(-1);
13
+ while (top && top.level >= heading.level) {
14
+ stack.pop();
15
+ top = stack.at(-1);
16
+ }
17
+ const parent = top;
18
+ const id = parent ? `${parent.id}.${parent.children.length}` : `${roots.length}`;
19
+ const node = {
20
+ id,
21
+ level: heading.level,
22
+ text: heading.text,
23
+ children: []
24
+ };
25
+ if (parent) {
26
+ parent.children.push(node);
27
+ } else {
28
+ roots.push(node);
29
+ }
30
+ stack.push(node);
31
+ }
32
+ return {
33
+ total: flat.length,
34
+ levels: countLevels(flat),
35
+ tree: roots
36
+ };
37
+ }
38
+ export {
39
+ buildHeadingTree
40
+ };
41
+ //# sourceMappingURL=heading-tree.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/heading-tree.ts"],"sourcesContent":["import type { HeadingLevel, HeadingLevelCount, HeadingNode, HeadingStructure } from \"../../../types/analysis\";\n\nexport interface FlatHeading {\n level: HeadingLevel;\n text: string;\n}\n\nconst LEVELS: HeadingLevel[] = [1, 2, 3, 4, 5, 6];\n\nfunction countLevels(flat: FlatHeading[]): HeadingLevelCount[] {\n return LEVELS.map((level) => ({\n level,\n count: flat.filter((h) => h.level === level).length,\n }));\n}\n\nexport function buildHeadingTree(flat: FlatHeading[]): HeadingStructure {\n const roots: HeadingNode[] = [];\n const stack: HeadingNode[] = [];\n\n for (const heading of flat) {\n let top = stack.at(-1);\n\n while (top && top.level >= heading.level) {\n stack.pop();\n top = stack.at(-1);\n }\n\n const parent = top;\n const id = parent ? `${parent.id}.${parent.children.length}` : `${roots.length}`;\n\n const node: HeadingNode = {\n id,\n level: heading.level,\n text: heading.text,\n children: [],\n };\n if (parent) {\n parent.children.push(node);\n } else {\n roots.push(node);\n }\n stack.push(node);\n }\n\n return {\n total: flat.length,\n levels: countLevels(flat),\n tree: roots,\n };\n}\n"],"mappings":"AAOA,MAAM,SAAyB,CAAC,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEhD,SAAS,YAAY,MAA0C;AAC7D,SAAO,OAAO,IAAI,CAAC,WAAW;AAAA,IAC5B;AAAA,IACA,OAAO,KAAK,OAAO,CAAC,MAAM,EAAE,UAAU,KAAK,EAAE;AAAA,EAC/C,EAAE;AACJ;AAEO,SAAS,iBAAiB,MAAuC;AACtE,QAAM,QAAuB,CAAC;AAC9B,QAAM,QAAuB,CAAC;AAE9B,aAAW,WAAW,MAAM;AAC1B,QAAI,MAAM,MAAM,GAAG,EAAE;AAErB,WAAO,OAAO,IAAI,SAAS,QAAQ,OAAO;AACxC,YAAM,IAAI;AACV,YAAM,MAAM,GAAG,EAAE;AAAA,IACnB;AAEA,UAAM,SAAS;AACf,UAAM,KAAK,SAAS,GAAG,OAAO,EAAE,IAAI,OAAO,SAAS,MAAM,KAAK,GAAG,MAAM,MAAM;AAE9E,UAAM,OAAoB;AAAA,MACxB;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,MAAM,QAAQ;AAAA,MACd,UAAU,CAAC;AAAA,IACb;AACA,QAAI,QAAQ;AACV,aAAO,SAAS,KAAK,IAAI;AAAA,IAC3B,OAAO;AACL,YAAM,KAAK,IAAI;AAAA,IACjB;AACA,UAAM,KAAK,IAAI;AAAA,EACjB;AAEA,SAAO;AAAA,IACL,OAAO,KAAK;AAAA,IACZ,QAAQ,YAAY,IAAI;AAAA,IACxB,MAAM;AAAA,EACR;AACF;","names":[]}
@@ -0,0 +1,8 @@
1
+ import type { YoastResearcher } from "../../../researcherAdapter";
2
+ import type { FlatHeading } from "./heading-tree";
3
+ interface PaperLike {
4
+ getText?: () => string;
5
+ }
6
+ export declare function extractHeadings(researcher: YoastResearcher, paper: PaperLike): FlatHeading[];
7
+ export {};
8
+ //# sourceMappingURL=headings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.d.ts","sourceRoot":"","sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/headings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAElE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAElD,UAAU,SAAS;IACjB,OAAO,CAAC,EAAE,MAAM,MAAM,CAAC;CACxB;AAmDD,wBAAgB,eAAe,CAAC,UAAU,EAAE,eAAe,EAAE,KAAK,EAAE,SAAS,GAAG,WAAW,EAAE,CAO5F"}
@@ -0,0 +1,50 @@
1
+ import { getResearch } from "../../../researcherAdapter";
2
+ const GLOBAL_HEADING_RE = /<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>/giu;
3
+ const SINGLE_HEADING_RE = /^<h([1-6])\b[^>]*>([\s\S]*?)<\/h\1>$/iu;
4
+ function toText(inner) {
5
+ return inner.replace(/<[^>]*>/gu, "").replace(/\s+/gu, " ").trim();
6
+ }
7
+ function parseEntry(html) {
8
+ const m = SINGLE_HEADING_RE.exec(html.trim());
9
+ if (!m)
10
+ return null;
11
+ return {
12
+ level: Number(m[1] ?? 0),
13
+ text: toText(m[2] ?? "")
14
+ };
15
+ }
16
+ function fromResearch(researcher) {
17
+ const entries = getResearch(researcher, "getSubheadingTextLengths");
18
+ if (!Array.isArray(entries))
19
+ return null;
20
+ const headings = [];
21
+ for (const entry of entries) {
22
+ if (!entry?.subheading)
23
+ continue;
24
+ const parsed = parseEntry(entry.subheading);
25
+ if (parsed)
26
+ headings.push(parsed);
27
+ }
28
+ return headings;
29
+ }
30
+ function fromHtml(html) {
31
+ const headings = [];
32
+ for (const m of html.matchAll(GLOBAL_HEADING_RE)) {
33
+ headings.push({
34
+ level: Number(m[1] ?? 0),
35
+ text: toText(m[2] ?? "")
36
+ });
37
+ }
38
+ return headings;
39
+ }
40
+ function extractHeadings(researcher, paper) {
41
+ const researched = fromResearch(researcher);
42
+ if (researched && researched.length > 0)
43
+ return researched;
44
+ const html = typeof paper.getText === "function" ? paper.getText() : "";
45
+ return fromHtml(html);
46
+ }
47
+ export {
48
+ extractHeadings
49
+ };
50
+ //# sourceMappingURL=headings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/headings.ts"],"sourcesContent":["import { getResearch } from \"../../../researcherAdapter\";\nimport type { YoastResearcher } from \"../../../researcherAdapter\";\nimport type { HeadingLevel } from \"../../../types/analysis\";\nimport type { FlatHeading } from \"./heading-tree\";\n\ninterface PaperLike {\n getText?: () => string;\n}\n\nconst GLOBAL_HEADING_RE = /<h([1-6])\\b[^>]*>([\\s\\S]*?)<\\/h\\1>/giu;\nconst SINGLE_HEADING_RE = /^<h([1-6])\\b[^>]*>([\\s\\S]*?)<\\/h\\1>$/iu;\n\nfunction toText(inner: string): string {\n return inner\n .replace(/<[^>]*>/gu, \"\")\n .replace(/\\s+/gu, \" \")\n .trim();\n}\n\nfunction parseEntry(html: string): FlatHeading | null {\n const m = SINGLE_HEADING_RE.exec(html.trim());\n if (!m) return null;\n\n return {\n level: Number(m[1] ?? 0) as HeadingLevel,\n text: toText(m[2] ?? \"\"),\n };\n}\n\nfunction fromResearch(researcher: YoastResearcher): FlatHeading[] | null {\n const entries = getResearch<{ subheading?: string }[]>(researcher, \"getSubheadingTextLengths\");\n if (!Array.isArray(entries)) return null;\n\n const headings: FlatHeading[] = [];\n\n for (const entry of entries) {\n if (!entry?.subheading) continue;\n\n const parsed = parseEntry(entry.subheading);\n if (parsed) headings.push(parsed);\n }\n\n return headings;\n}\n\nfunction fromHtml(html: string): FlatHeading[] {\n const headings: FlatHeading[] = [];\n\n for (const m of html.matchAll(GLOBAL_HEADING_RE)) {\n headings.push({\n level: Number(m[1] ?? 0) as HeadingLevel,\n text: toText(m[2] ?? \"\"),\n });\n }\n\n return headings;\n}\n\nexport function extractHeadings(researcher: YoastResearcher, paper: PaperLike): FlatHeading[] {\n const researched = fromResearch(researcher);\n if (researched && researched.length > 0) return researched;\n\n const html = typeof paper.getText === \"function\" ? paper.getText() : \"\";\n\n return fromHtml(html);\n}\n"],"mappings":"AAAA,SAAS,mBAAmB;AAS5B,MAAM,oBAAoB;AAC1B,MAAM,oBAAoB;AAE1B,SAAS,OAAO,OAAuB;AACrC,SAAO,MACJ,QAAQ,aAAa,EAAE,EACvB,QAAQ,SAAS,GAAG,EACpB,KAAK;AACV;AAEA,SAAS,WAAW,MAAkC;AACpD,QAAM,IAAI,kBAAkB,KAAK,KAAK,KAAK,CAAC;AAC5C,MAAI,CAAC;AAAG,WAAO;AAEf,SAAO;AAAA,IACL,OAAO,OAAO,EAAE,CAAC,KAAK,CAAC;AAAA,IACvB,MAAM,OAAO,EAAE,CAAC,KAAK,EAAE;AAAA,EACzB;AACF;AAEA,SAAS,aAAa,YAAmD;AACvE,QAAM,UAAU,YAAuC,YAAY,0BAA0B;AAC7F,MAAI,CAAC,MAAM,QAAQ,OAAO;AAAG,WAAO;AAEpC,QAAM,WAA0B,CAAC;AAEjC,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,OAAO;AAAY;AAExB,UAAM,SAAS,WAAW,MAAM,UAAU;AAC1C,QAAI;AAAQ,eAAS,KAAK,MAAM;AAAA,EAClC;AAEA,SAAO;AACT;AAEA,SAAS,SAAS,MAA6B;AAC7C,QAAM,WAA0B,CAAC;AAEjC,aAAW,KAAK,KAAK,SAAS,iBAAiB,GAAG;AAChD,aAAS,KAAK;AAAA,MACZ,OAAO,OAAO,EAAE,CAAC,KAAK,CAAC;AAAA,MACvB,MAAM,OAAO,EAAE,CAAC,KAAK,EAAE;AAAA,IACzB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAEO,SAAS,gBAAgB,YAA6B,OAAiC;AAC5F,QAAM,aAAa,aAAa,UAAU;AAC1C,MAAI,cAAc,WAAW,SAAS;AAAG,WAAO;AAEhD,QAAM,OAAO,OAAO,MAAM,YAAY,aAAa,MAAM,QAAQ,IAAI;AAErE,SAAO,SAAS,IAAI;AACtB;","names":[]}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAG5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAa/F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,UAAU,CAAC;AAEtC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAK5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,CAAC,OAAO,KAAK,CAAC,EAAE,SAAS,EAAE,MAAM,GAAG,YAAY,CAc/F"}
@@ -1,4 +1,6 @@
1
1
  import { makeResearcher } from "../../../researcherAdapter";
2
+ import { buildHeadingTree } from "./heading-tree";
3
+ import { extractHeadings } from "./headings";
2
4
  import { countImages, countParagraphs, countSentences, countVideos, countWords, estimateReadingTime, findProminentWords } from "./researches";
3
5
  function deriveVitals(paper, keyphrase) {
4
6
  const researcher = makeResearcher(paper);
@@ -10,7 +12,8 @@ function deriveVitals(paper, keyphrase) {
10
12
  images: countImages(researcher),
11
13
  videos: countVideos(researcher),
12
14
  readingTimeMinutes: estimateReadingTime({ researcher, words }),
13
- prominentWords: findProminentWords({ researcher, keyphrase })
15
+ prominentWords: findProminentWords({ researcher, keyphrase }),
16
+ headings: buildHeadingTree(extractHeadings(researcher, paper))
14
17
  };
15
18
  }
16
19
  export {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/index.ts"],"sourcesContent":["import type { Paper } from \"yoastseo\";\nimport { makeResearcher } from \"../../../researcherAdapter\";\nimport type { VitalsResult } from \"../../../types/analysis\";\nimport { countImages, countParagraphs, countSentences, countVideos, countWords, estimateReadingTime, findProminentWords } from \"./researches\";\n\nexport function deriveVitals(paper: InstanceType<typeof Paper>, keyphrase: string): VitalsResult {\n const researcher = makeResearcher(paper);\n const words = countWords(researcher);\n\n return {\n words,\n sentences: countSentences(researcher),\n paragraphs: countParagraphs(researcher),\n images: countImages(researcher),\n videos: countVideos(researcher),\n readingTimeMinutes: estimateReadingTime({ researcher, words }),\n prominentWords: findProminentWords({ researcher, keyphrase }),\n };\n}\n"],"mappings":"AACA,SAAS,sBAAsB;AAE/B,SAAS,aAAa,iBAAiB,gBAAgB,aAAa,YAAY,qBAAqB,0BAA0B;AAExH,SAAS,aAAa,OAAmC,WAAiC;AAC/F,QAAM,aAAa,eAAe,KAAK;AACvC,QAAM,QAAQ,WAAW,UAAU;AAEnC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,eAAe,UAAU;AAAA,IACpC,YAAY,gBAAgB,UAAU;AAAA,IACtC,QAAQ,YAAY,UAAU;AAAA,IAC9B,QAAQ,YAAY,UAAU;AAAA,IAC9B,oBAAoB,oBAAoB,EAAE,YAAY,MAAM,CAAC;AAAA,IAC7D,gBAAgB,mBAAmB,EAAE,YAAY,UAAU,CAAC;AAAA,EAC9D;AACF;","names":[]}
1
+ {"version":3,"sources":["../../../../../src/engine/runAnalysis/services/derive-vitals/index.ts"],"sourcesContent":["import type { Paper } from \"yoastseo\";\nimport { makeResearcher } from \"../../../researcherAdapter\";\nimport type { VitalsResult } from \"../../../types/analysis\";\nimport { buildHeadingTree } from \"./heading-tree\";\nimport { extractHeadings } from \"./headings\";\nimport { countImages, countParagraphs, countSentences, countVideos, countWords, estimateReadingTime, findProminentWords } from \"./researches\";\n\nexport function deriveVitals(paper: InstanceType<typeof Paper>, keyphrase: string): VitalsResult {\n const researcher = makeResearcher(paper);\n const words = countWords(researcher);\n\n return {\n words,\n sentences: countSentences(researcher),\n paragraphs: countParagraphs(researcher),\n images: countImages(researcher),\n videos: countVideos(researcher),\n readingTimeMinutes: estimateReadingTime({ researcher, words }),\n prominentWords: findProminentWords({ researcher, keyphrase }),\n headings: buildHeadingTree(extractHeadings(researcher, paper)),\n };\n}\n"],"mappings":"AACA,SAAS,sBAAsB;AAE/B,SAAS,wBAAwB;AACjC,SAAS,uBAAuB;AAChC,SAAS,aAAa,iBAAiB,gBAAgB,aAAa,YAAY,qBAAqB,0BAA0B;AAExH,SAAS,aAAa,OAAmC,WAAiC;AAC/F,QAAM,aAAa,eAAe,KAAK;AACvC,QAAM,QAAQ,WAAW,UAAU;AAEnC,SAAO;AAAA,IACL;AAAA,IACA,WAAW,eAAe,UAAU;AAAA,IACpC,YAAY,gBAAgB,UAAU;AAAA,IACtC,QAAQ,YAAY,UAAU;AAAA,IAC9B,QAAQ,YAAY,UAAU;AAAA,IAC9B,oBAAoB,oBAAoB,EAAE,YAAY,MAAM,CAAC;AAAA,IAC7D,gBAAgB,mBAAmB,EAAE,YAAY,UAAU,CAAC;AAAA,IAC5D,UAAU,iBAAiB,gBAAgB,YAAY,KAAK,CAAC;AAAA,EAC/D;AACF;","names":[]}