@flamingo-stack/openframe-frontend-core 0.0.285 → 0.0.286
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/dist/chunk-3MCHAFHB.js +89 -0
- package/dist/chunk-3MCHAFHB.js.map +1 -0
- package/dist/{chunk-IMNGGLKL.js → chunk-3ZXUQQL4.js} +2 -2
- package/dist/{chunk-44ZFLMYC.cjs → chunk-4WACBTZU.cjs} +30 -30
- package/dist/{chunk-44ZFLMYC.cjs.map → chunk-4WACBTZU.cjs.map} +1 -1
- package/dist/{chunk-23UOJUDF.js → chunk-5BTZOVDQ.js} +4 -4
- package/dist/chunk-5KD3S25X.cjs +312 -0
- package/dist/chunk-5KD3S25X.cjs.map +1 -0
- package/dist/chunk-6N26CURS.cjs +2417 -0
- package/dist/chunk-6N26CURS.cjs.map +1 -0
- package/dist/{chunk-XX7P4IL2.js → chunk-7NM7DEUK.js} +4 -143
- package/dist/chunk-7NM7DEUK.js.map +1 -0
- package/dist/chunk-BBZ7AX5H.cjs +290 -0
- package/dist/chunk-BBZ7AX5H.cjs.map +1 -0
- package/dist/chunk-C6SCWXDP.js +290 -0
- package/dist/chunk-C6SCWXDP.js.map +1 -0
- package/dist/{chunk-GVSTRN36.cjs → chunk-D3LEFMOA.cjs} +3 -3
- package/dist/{chunk-GVSTRN36.cjs.map → chunk-D3LEFMOA.cjs.map} +1 -1
- package/dist/{chunk-P45Q4QCU.cjs → chunk-D5YY5U6J.cjs} +19 -15
- package/dist/chunk-D5YY5U6J.cjs.map +1 -0
- package/dist/chunk-DDAT4RKX.js +2417 -0
- package/dist/chunk-DDAT4RKX.js.map +1 -0
- package/dist/{chunk-PVXCVPJD.cjs → chunk-EI4WALN2.cjs} +67 -1626
- package/dist/chunk-EI4WALN2.cjs.map +1 -0
- package/dist/{chunk-ZIODAAFY.cjs → chunk-EL5GCMPU.cjs} +20 -159
- package/dist/chunk-EL5GCMPU.cjs.map +1 -0
- package/dist/{chunk-VQW7CEGS.js → chunk-FSBDVT6R.js} +1831 -3672
- package/dist/chunk-FSBDVT6R.js.map +1 -0
- package/dist/{chunk-DPKUSBQH.cjs → chunk-GDF2R2ER.cjs} +1073 -2914
- package/dist/chunk-GDF2R2ER.cjs.map +1 -0
- package/dist/chunk-HLQW7MWJ.cjs +1594 -0
- package/dist/chunk-HLQW7MWJ.cjs.map +1 -0
- package/dist/chunk-I6ZPGKZ2.cjs +151 -0
- package/dist/chunk-I6ZPGKZ2.cjs.map +1 -0
- package/dist/{chunk-J66U3LPZ.cjs → chunk-KBKZYJRI.cjs} +33 -332
- package/dist/chunk-KBKZYJRI.cjs.map +1 -0
- package/dist/{chunk-USPATRSC.js → chunk-N6ZM5PYZ.js} +45 -344
- package/dist/chunk-N6ZM5PYZ.js.map +1 -0
- package/dist/{chunk-RGALV4UX.js → chunk-POKKCWKF.js} +3 -82
- package/dist/chunk-POKKCWKF.js.map +1 -0
- package/dist/{chunk-XDI5JSWB.cjs → chunk-QCKN37OP.cjs} +45 -41
- package/dist/chunk-QCKN37OP.cjs.map +1 -0
- package/dist/{chunk-KNCDUWY2.js → chunk-QF2X6PTD.js} +10 -6
- package/dist/{chunk-KNCDUWY2.js.map → chunk-QF2X6PTD.js.map} +1 -1
- package/dist/{chunk-Z3V2SGQZ.js → chunk-R2KT5GDD.js} +324 -1883
- package/dist/chunk-R2KT5GDD.js.map +1 -0
- package/dist/{chunk-RJUGNL2A.cjs → chunk-RQ6RTBKF.cjs} +16 -12
- package/dist/chunk-RQ6RTBKF.cjs.map +1 -0
- package/dist/chunk-TFSYSWPS.cjs +89 -0
- package/dist/chunk-TFSYSWPS.cjs.map +1 -0
- package/dist/chunk-TYZEMPPH.js +312 -0
- package/dist/chunk-TYZEMPPH.js.map +1 -0
- package/dist/{chunk-O5N57CRV.js → chunk-VCQ3CTYK.js} +12 -8
- package/dist/{chunk-O5N57CRV.js.map → chunk-VCQ3CTYK.js.map} +1 -1
- package/dist/chunk-VRSXJ5QJ.js +1599 -0
- package/dist/chunk-VRSXJ5QJ.js.map +1 -0
- package/dist/{chunk-53CFL7PH.js → chunk-XQL4WDML.js} +11 -7
- package/dist/{chunk-53CFL7PH.js.map → chunk-XQL4WDML.js.map} +1 -1
- package/dist/chunk-Y4JNA4W6.cjs +1599 -0
- package/dist/chunk-Y4JNA4W6.cjs.map +1 -0
- package/dist/{chunk-ZSUNQJRO.cjs → chunk-YETA25JW.cjs} +2 -81
- package/dist/chunk-YETA25JW.cjs.map +1 -0
- package/dist/chunk-YVB3VDIQ.js +1594 -0
- package/dist/chunk-YVB3VDIQ.js.map +1 -0
- package/dist/chunk-ZLN6SM2U.js +151 -0
- package/dist/chunk-ZLN6SM2U.js.map +1 -0
- package/dist/components/chat/chat-attachment-bar.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +12 -6
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +45 -39
- package/dist/components/contact/index.cjs +11 -7
- package/dist/components/contact/index.cjs.map +1 -1
- package/dist/components/contact/index.js +10 -6
- package/dist/components/docs/doc-navigation-context.d.ts +39 -0
- package/dist/components/docs/doc-navigation-context.d.ts.map +1 -0
- package/dist/components/docs/doc-viewer.d.ts +66 -0
- package/dist/components/docs/doc-viewer.d.ts.map +1 -0
- package/dist/components/docs/index.cjs +34 -0
- package/dist/components/docs/index.cjs.map +1 -0
- package/dist/components/docs/index.d.ts +8 -0
- package/dist/components/docs/index.d.ts.map +1 -0
- package/dist/components/docs/index.js +34 -0
- package/dist/components/docs/index.js.map +1 -0
- package/dist/components/docs/use-document-tree.d.ts +41 -0
- package/dist/components/docs/use-document-tree.d.ts.map +1 -0
- package/dist/components/docs/use-scroll-spy.d.ts +16 -0
- package/dist/components/docs/use-scroll-spy.d.ts.map +1 -0
- package/dist/components/embeds/embed-iframe.d.ts +29 -0
- package/dist/components/embeds/embed-iframe.d.ts.map +1 -0
- package/dist/components/embeds/figma-embed.d.ts +27 -0
- package/dist/components/embeds/figma-embed.d.ts.map +1 -0
- package/dist/components/embeds/google-sheets-viewer.d.ts +8 -0
- package/dist/components/embeds/google-sheets-viewer.d.ts.map +1 -0
- package/dist/components/embeds/index.cjs +37 -0
- package/dist/components/embeds/index.cjs.map +1 -0
- package/dist/components/embeds/index.d.ts +9 -0
- package/dist/components/embeds/index.d.ts.map +1 -0
- package/dist/components/embeds/index.js +37 -0
- package/dist/components/embeds/index.js.map +1 -0
- package/dist/components/embeds/pdf-viewer.d.ts +10 -0
- package/dist/components/embeds/pdf-viewer.d.ts.map +1 -0
- package/dist/components/faq/index.cjs +12 -8
- package/dist/components/faq/index.cjs.map +1 -1
- package/dist/components/faq/index.js +11 -7
- package/dist/components/features/index.cjs +10 -6
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +9 -5
- package/dist/components/index.cjs +247 -1120
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +645 -1518
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/index.cjs +15 -6
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.d.ts +2 -0
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +15 -6
- package/dist/components/navigation/multi-level-navigation.d.ts +28 -0
- package/dist/components/navigation/multi-level-navigation.d.ts.map +1 -0
- package/dist/components/onboarding-guides/index.cjs +34 -29
- package/dist/components/onboarding-guides/index.cjs.map +1 -1
- package/dist/components/onboarding-guides/index.js +13 -8
- package/dist/components/onboarding-guides/index.js.map +1 -1
- package/dist/components/related-content/index.cjs +12 -8
- package/dist/components/related-content/index.cjs.map +1 -1
- package/dist/components/related-content/index.js +11 -7
- package/dist/components/tickets/index.cjs +75 -70
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +16 -11
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/index.cjs +12 -6
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +65 -59
- package/dist/embed-shims/index.cjs +3 -3
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +8 -8
- package/dist/hooks/index.cjs +6 -3
- package/dist/hooks/index.cjs.map +1 -1
- package/dist/hooks/index.js +7 -4
- package/dist/index.cjs +62 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +257 -201
- package/dist/types/doc-source.cjs +4 -0
- package/dist/types/doc-source.cjs.map +1 -0
- package/dist/types/doc-source.d.ts +86 -0
- package/dist/types/doc-source.d.ts.map +1 -0
- package/dist/types/doc-source.js +3 -0
- package/dist/types/doc-source.js.map +1 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/doc-path-utils.d.ts +24 -0
- package/dist/utils/doc-path-utils.d.ts.map +1 -0
- package/dist/utils/doc-tree-nav.d.ts +35 -0
- package/dist/utils/doc-tree-nav.d.ts.map +1 -0
- package/dist/utils/embed-url-converters.d.ts +16 -0
- package/dist/utils/embed-url-converters.d.ts.map +1 -0
- package/dist/utils/format.d.ts +9 -0
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/index.cjs +297 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +7 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +276 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/markdown-section-extractor.d.ts +23 -0
- package/dist/utils/markdown-section-extractor.d.ts.map +1 -0
- package/dist/utils/markdown-to-plain.d.ts +32 -0
- package/dist/utils/markdown-to-plain.d.ts.map +1 -0
- package/dist/utils/tree-builder.d.ts +30 -0
- package/dist/utils/tree-builder.d.ts.map +1 -0
- package/package.json +19 -1
- package/src/components/chat/chat-attachment-bar.tsx +6 -11
- package/src/components/docs/doc-navigation-context.tsx +123 -0
- package/src/components/docs/doc-viewer.tsx +345 -0
- package/src/components/docs/index.ts +12 -0
- package/src/components/docs/use-document-tree.ts +427 -0
- package/src/components/docs/use-scroll-spy.ts +93 -0
- package/src/components/embeds/embed-iframe.tsx +103 -0
- package/src/components/embeds/figma-embed.tsx +147 -0
- package/src/components/embeds/google-sheets-viewer.tsx +54 -0
- package/src/components/embeds/index.ts +11 -0
- package/src/components/embeds/pdf-viewer.tsx +64 -0
- package/src/components/index.ts +2 -0
- package/src/components/navigation/index.ts +5 -0
- package/src/components/navigation/multi-level-navigation.tsx +358 -0
- package/src/types/doc-source.ts +88 -0
- package/src/types/index.ts +4 -0
- package/src/utils/doc-path-utils.ts +47 -0
- package/src/utils/doc-tree-nav.ts +111 -0
- package/src/utils/embed-url-converters.ts +77 -0
- package/src/utils/format.ts +16 -0
- package/src/utils/index.ts +11 -0
- package/src/utils/markdown-section-extractor.ts +106 -0
- package/src/utils/markdown-to-plain.ts +69 -0
- package/src/utils/tree-builder.ts +114 -0
- package/dist/chunk-DPKUSBQH.cjs.map +0 -1
- package/dist/chunk-J66U3LPZ.cjs.map +0 -1
- package/dist/chunk-P45Q4QCU.cjs.map +0 -1
- package/dist/chunk-PVXCVPJD.cjs.map +0 -1
- package/dist/chunk-RGALV4UX.js.map +0 -1
- package/dist/chunk-RJUGNL2A.cjs.map +0 -1
- package/dist/chunk-USPATRSC.js.map +0 -1
- package/dist/chunk-VQW7CEGS.js.map +0 -1
- package/dist/chunk-XDI5JSWB.cjs.map +0 -1
- package/dist/chunk-XX7P4IL2.js.map +0 -1
- package/dist/chunk-Z3V2SGQZ.js.map +0 -1
- package/dist/chunk-ZIODAAFY.cjs.map +0 -1
- package/dist/chunk-ZSUNQJRO.cjs.map +0 -1
- /package/dist/{chunk-IMNGGLKL.js.map → chunk-3ZXUQQL4.js.map} +0 -0
- /package/dist/{chunk-23UOJUDF.js.map → chunk-5BTZOVDQ.js.map} +0 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared markdown section extractor
|
|
3
|
+
* Extracts heading sections from markdown content for navigation.
|
|
4
|
+
*/
|
|
5
|
+
export interface MarkdownSection {
|
|
6
|
+
id: string;
|
|
7
|
+
title: string;
|
|
8
|
+
level: number;
|
|
9
|
+
}
|
|
10
|
+
export interface ExtractSectionsOptions {
|
|
11
|
+
/** Maximum heading level to include (default: 2 = H1-H2) */
|
|
12
|
+
maxLevel?: number;
|
|
13
|
+
/** Remove emoji characters from IDs (default: true) */
|
|
14
|
+
removeEmojis?: boolean;
|
|
15
|
+
/** Handle duplicate IDs by appending a counter suffix (default: true) */
|
|
16
|
+
handleDuplicateIds?: boolean;
|
|
17
|
+
/** Strip bold, italic, and inline code markers from titles (default: true) */
|
|
18
|
+
stripFormattingMarkers?: boolean;
|
|
19
|
+
/** Skip code blocks and YAML frontmatter (default: true) */
|
|
20
|
+
skipCodeAndYamlBlocks?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export declare function extractSections(markdown: string, options?: ExtractSectionsOptions): MarkdownSection[];
|
|
23
|
+
//# sourceMappingURL=markdown-section-extractor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-section-extractor.d.ts","sourceRoot":"","sources":["../../src/utils/markdown-section-extractor.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,MAAM,CAAA;CACd;AAED,MAAM,WAAW,sBAAsB;IACrC,4DAA4D;IAC5D,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,uDAAuD;IACvD,YAAY,CAAC,EAAE,OAAO,CAAA;IACtB,yEAAyE;IACzE,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B,8EAA8E;IAC9E,sBAAsB,CAAC,EAAE,OAAO,CAAA;IAChC,4DAA4D;IAC5D,qBAAqB,CAAC,EAAE,OAAO,CAAA;CAChC;AAUD,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,eAAe,EAAE,CAoEnB"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert markdown source to plain text. Used by both the SEO server component
|
|
3
|
+
* (article-body echo in JSON-LD) and chat entity-card preview (clamped previews
|
|
4
|
+
* inside chat cards).
|
|
5
|
+
*
|
|
6
|
+
* The two consumers differ only in pre/post processing — `mode: 'article'`
|
|
7
|
+
* strips code blocks + images + headings + list bullets and preserves paragraph
|
|
8
|
+
* breaks; `mode: 'preview'` collapses everything to a single line. Both share
|
|
9
|
+
* the same inline-marker strip (link / bold / italic / inline-code).
|
|
10
|
+
*
|
|
11
|
+
* All regex patterns are linear (negated character classes, non-greedy with
|
|
12
|
+
* bounded targets) — no nested quantifiers, no catastrophic backtracking on
|
|
13
|
+
* pathological input.
|
|
14
|
+
*/
|
|
15
|
+
export interface MarkdownToPlainOptions {
|
|
16
|
+
/**
|
|
17
|
+
* - `'article'` (default): preserve paragraph structure (`\n\n` as separator),
|
|
18
|
+
* strip code blocks + images + headings + list bullets. Used for the
|
|
19
|
+
* article-body echo on SEO surfaces.
|
|
20
|
+
* - `'preview'`: collapse all whitespace to single spaces; result is a single
|
|
21
|
+
* line. Used for chat-card previews / OG-card snippets where line breaks
|
|
22
|
+
* would wrap awkwardly.
|
|
23
|
+
*/
|
|
24
|
+
mode?: 'article' | 'preview';
|
|
25
|
+
/** Optional max-character cap. Truncates with no ellipsis (caller-decided). */
|
|
26
|
+
maxChars?: number;
|
|
27
|
+
}
|
|
28
|
+
/** Strip inline markdown markers (bold, italic, inline-code, links). Shared
|
|
29
|
+
* by `extractSections`'s `stripFormattingMarkers` and `markdownToPlainText`. */
|
|
30
|
+
export declare function stripInlineMarkdown(s: string): string;
|
|
31
|
+
export declare function markdownToPlainText(markdown: string, options?: MarkdownToPlainOptions): string;
|
|
32
|
+
//# sourceMappingURL=markdown-to-plain.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"markdown-to-plain.d.ts","sourceRoot":"","sources":["../../src/utils/markdown-to-plain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,sBAAsB;IACrC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAA;IAC5B,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED;iFACiF;AACjF,wBAAgB,mBAAmB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAMrD;AAED,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,sBAA2B,GACnC,MAAM,CA0BR"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base interface for tree nodes. Consumers extend this with additional fields.
|
|
3
|
+
*/
|
|
4
|
+
export interface TreeNodeBase {
|
|
5
|
+
id: string;
|
|
6
|
+
name: string;
|
|
7
|
+
path: string;
|
|
8
|
+
type: 'file' | 'folder';
|
|
9
|
+
sortOrder?: number;
|
|
10
|
+
children?: TreeNodeBase[];
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Format a document name for display: remove .md extension and capitalize.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatDocName(name: string): string;
|
|
16
|
+
/**
|
|
17
|
+
* Sort children: README first, then folders, then files — each group by sort_order then alphabetical.
|
|
18
|
+
* Recurses into folder children to ensure consistent ordering at all levels.
|
|
19
|
+
*/
|
|
20
|
+
export declare function sortTreeChildren<TNode extends TreeNodeBase>(nodes: TNode[]): TNode[];
|
|
21
|
+
/**
|
|
22
|
+
* Build a hierarchical document tree from a flat list of records.
|
|
23
|
+
*
|
|
24
|
+
* 3-pass algorithm:
|
|
25
|
+
* 1. Create node map from flat records using the provided `mapFn`
|
|
26
|
+
* 2. Build parent-child hierarchy using `getParentPath`
|
|
27
|
+
* 3. Sort children (folders first, README, then others)
|
|
28
|
+
*/
|
|
29
|
+
export declare function buildDocumentTree<TNode extends TreeNodeBase, TDoc>(docs: TDoc[], mapFn: (doc: TDoc) => TNode, getParentPath: (doc: TDoc) => string | null, getPath: (doc: TDoc) => string): TNode[];
|
|
30
|
+
//# sourceMappingURL=tree-builder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree-builder.d.ts","sourceRoot":"","sources":["../../src/utils/tree-builder.ts"],"names":[],"mappings":"AAMA;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,QAAQ,CAAA;IACvB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;CAC1B;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CASlD;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,SAAS,YAAY,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,KAAK,EAAE,CAwBpF;AAED;;;;;;;GAOG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,SAAS,YAAY,EAAE,IAAI,EAChE,IAAI,EAAE,IAAI,EAAE,EACZ,KAAK,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,KAAK,EAC3B,aAAa,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,MAAM,GAAG,IAAI,EAC3C,OAAO,EAAE,CAAC,GAAG,EAAE,IAAI,KAAK,MAAM,GAC7B,KAAK,EAAE,CAsCT"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flamingo-stack/openframe-frontend-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.286",
|
|
4
4
|
"description": "Shared design system and components for all Flamingo platforms",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -126,6 +126,24 @@
|
|
|
126
126
|
"require": "./dist/components/navigation/index.cjs",
|
|
127
127
|
"default": "./dist/components/navigation/index.js"
|
|
128
128
|
},
|
|
129
|
+
"./components/docs": {
|
|
130
|
+
"types": "./dist/components/docs/index.d.ts",
|
|
131
|
+
"import": "./dist/components/docs/index.js",
|
|
132
|
+
"require": "./dist/components/docs/index.cjs",
|
|
133
|
+
"default": "./dist/components/docs/index.js"
|
|
134
|
+
},
|
|
135
|
+
"./components/embeds": {
|
|
136
|
+
"types": "./dist/components/embeds/index.d.ts",
|
|
137
|
+
"import": "./dist/components/embeds/index.js",
|
|
138
|
+
"require": "./dist/components/embeds/index.cjs",
|
|
139
|
+
"default": "./dist/components/embeds/index.js"
|
|
140
|
+
},
|
|
141
|
+
"./types/doc-source": {
|
|
142
|
+
"types": "./dist/types/doc-source.d.ts",
|
|
143
|
+
"import": "./dist/types/doc-source.js",
|
|
144
|
+
"require": "./dist/types/doc-source.cjs",
|
|
145
|
+
"default": "./dist/types/doc-source.js"
|
|
146
|
+
},
|
|
129
147
|
"./hooks": {
|
|
130
148
|
"types": "./dist/hooks/index.d.ts",
|
|
131
149
|
"import": "./dist/hooks/index.js",
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
'use client'
|
|
2
2
|
// compact-size support added for the ticket-drawer reply composer.
|
|
3
3
|
|
|
4
|
+
import { formatFileSize } from '../../utils/format'
|
|
5
|
+
|
|
4
6
|
/**
|
|
5
7
|
* Chat-attachment UI primitives.
|
|
6
8
|
*
|
|
@@ -321,19 +323,12 @@ function AttachmentChip({ attachment, onRemove, disabled, size = 'default' }: At
|
|
|
321
323
|
}
|
|
322
324
|
|
|
323
325
|
// ---------------------------------------------------------------------------
|
|
324
|
-
// Helpers
|
|
326
|
+
// Helpers
|
|
325
327
|
// ---------------------------------------------------------------------------
|
|
326
328
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
function formatFileSize(bytes: number): string {
|
|
331
|
-
if (bytes === 0) return '0 B'
|
|
332
|
-
const k = 1024
|
|
333
|
-
const sizes = ['B', 'KB', 'MB', 'GB']
|
|
334
|
-
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
|
335
|
-
return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`
|
|
336
|
-
}
|
|
329
|
+
// formatFileSize was previously inlined here as a copy of the hub's helper. It
|
|
330
|
+
// now lives in `../../utils/format.ts` (lifted during the doc-viewer unification).
|
|
331
|
+
// Imported at the top of this file.
|
|
337
332
|
|
|
338
333
|
function extLabel(fileName: string): string {
|
|
339
334
|
const dot = fileName.lastIndexOf('.')
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { createContext, useCallback, useContext, useRef, type ReactNode } from 'react'
|
|
4
|
+
import type { DocNode } from '../../types/doc-source'
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Types
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
export interface DocNavigator {
|
|
11
|
+
/** Base route this navigator owns (e.g., '/knowledge-base', '/data-room'). */
|
|
12
|
+
baseRoute: string
|
|
13
|
+
/** Look up a node in the tree by its storage path (e.g., 'openframe-cli/README.md'). */
|
|
14
|
+
findNodeByPath: (path: string) => DocNode | null
|
|
15
|
+
/** Navigate to a node using the same flow as a sidebar menu click. */
|
|
16
|
+
selectNode: (node: DocNode) => void
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface DocNavigationContextValue {
|
|
20
|
+
/**
|
|
21
|
+
* Register a navigator for a `baseRoute`. Returns a cleanup function that
|
|
22
|
+
* removes the navigator from the map IF the slot still owns it (StrictMode-safe).
|
|
23
|
+
* Duplicate registration for the same baseRoute logs a console.warn and replaces.
|
|
24
|
+
*/
|
|
25
|
+
register: (nav: DocNavigator) => () => void
|
|
26
|
+
/**
|
|
27
|
+
* Navigate to a path inside whichever registered navigator owns its baseRoute prefix.
|
|
28
|
+
* Returns true if handled, false otherwise (caller falls back to opening in a new tab).
|
|
29
|
+
*/
|
|
30
|
+
navigate: (path: string) => boolean
|
|
31
|
+
/** Whether ANY navigator is currently mounted. */
|
|
32
|
+
isAvailable: () => boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// =============================================================================
|
|
36
|
+
// Context
|
|
37
|
+
// =============================================================================
|
|
38
|
+
|
|
39
|
+
const DocNavigationContext = createContext<DocNavigationContextValue | null>(null)
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Bridges `useDocumentTree` instances (deep in the page tree) to `GlobalAskAI`
|
|
43
|
+
* (high up near the root) without global events or URL parsing.
|
|
44
|
+
*
|
|
45
|
+
* Multi-navigator design: a `Map<baseRoute, DocNavigator>` lets multiple
|
|
46
|
+
* viewers coexist (nested viewers, dual-pane scenarios). Today only one
|
|
47
|
+
* viewer mounts at a time, so behavior is unchanged.
|
|
48
|
+
*/
|
|
49
|
+
export function DocNavigationProvider({ children }: { children: ReactNode }) {
|
|
50
|
+
const navMap = useRef<Map<string, DocNavigator>>(new Map())
|
|
51
|
+
|
|
52
|
+
const register = useCallback((nav: DocNavigator) => {
|
|
53
|
+
const existing = navMap.current.get(nav.baseRoute)
|
|
54
|
+
if (existing && existing !== nav) {
|
|
55
|
+
// eslint-disable-next-line no-console
|
|
56
|
+
console.warn(
|
|
57
|
+
`[DocNavigationContext] duplicate registration for ${nav.baseRoute}; replacing`,
|
|
58
|
+
)
|
|
59
|
+
}
|
|
60
|
+
navMap.current.set(nav.baseRoute, nav)
|
|
61
|
+
return () => {
|
|
62
|
+
// StrictMode-safe identity check: only delete if THIS nav still owns the slot.
|
|
63
|
+
if (navMap.current.get(nav.baseRoute) === nav) {
|
|
64
|
+
navMap.current.delete(nav.baseRoute)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}, [])
|
|
68
|
+
|
|
69
|
+
const findOwningNavigator = useCallback((path: string): DocNavigator | null => {
|
|
70
|
+
// First try: a navigator whose baseRoute is a prefix of `path`. Walks the
|
|
71
|
+
// longest match first so nested routes win over a parent.
|
|
72
|
+
const baseRoutes = Array.from(navMap.current.keys()).sort((a, b) => b.length - a.length)
|
|
73
|
+
for (const baseRoute of baseRoutes) {
|
|
74
|
+
if (path === baseRoute || path.startsWith(`${baseRoute}/`)) {
|
|
75
|
+
return navMap.current.get(baseRoute) ?? null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// Fallback: a single navigator handles any path (existing behavior — chat
|
|
79
|
+
// chips emit storage paths, not absolute URLs, so prefix matching misses).
|
|
80
|
+
if (navMap.current.size === 1) {
|
|
81
|
+
return navMap.current.values().next().value ?? null
|
|
82
|
+
}
|
|
83
|
+
return null
|
|
84
|
+
}, [])
|
|
85
|
+
|
|
86
|
+
const navigate = useCallback(
|
|
87
|
+
(path: string): boolean => {
|
|
88
|
+
const nav = findOwningNavigator(path)
|
|
89
|
+
if (!nav) return false
|
|
90
|
+
const node = nav.findNodeByPath(path)
|
|
91
|
+
if (!node) return false
|
|
92
|
+
nav.selectNode(node)
|
|
93
|
+
return true
|
|
94
|
+
},
|
|
95
|
+
[findOwningNavigator],
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
const isAvailable = useCallback(() => navMap.current.size > 0, [])
|
|
99
|
+
|
|
100
|
+
const value = React.useMemo(
|
|
101
|
+
() => ({ register, navigate, isAvailable }),
|
|
102
|
+
[register, navigate, isAvailable],
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
return <DocNavigationContext.Provider value={value}>{children}</DocNavigationContext.Provider>
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// =============================================================================
|
|
109
|
+
// Hooks
|
|
110
|
+
// =============================================================================
|
|
111
|
+
|
|
112
|
+
export function useDocNavigation(): DocNavigationContextValue {
|
|
113
|
+
const ctx = useContext(DocNavigationContext)
|
|
114
|
+
if (!ctx) {
|
|
115
|
+
// Safe no-op fallback when used outside the provider.
|
|
116
|
+
return {
|
|
117
|
+
register: () => () => {},
|
|
118
|
+
navigate: () => false,
|
|
119
|
+
isAvailable: () => false,
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return ctx
|
|
123
|
+
}
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import React, { useMemo } from "react"
|
|
4
|
+
import { MultiLevelNavigation, MobileNavigationDropdown } from "../navigation/multi-level-navigation"
|
|
5
|
+
import { PageHeading } from "../layout/page-heading"
|
|
6
|
+
import { PersistentSidebar, PersistentMobileDropdown } from "../persistent-filter-controls"
|
|
7
|
+
import { CategorySidebarSkeleton } from "../loading/page-layout-skeleton"
|
|
8
|
+
import { DocSearchBar, useDocSearch } from "../shared/doc-search"
|
|
9
|
+
import { StickySectionNav } from "../navigation/sticky-section-nav"
|
|
10
|
+
import { useDocumentTree } from "./use-document-tree"
|
|
11
|
+
import { useScrollSpy } from "./use-scroll-spy"
|
|
12
|
+
import { useDocNavigation } from "./doc-navigation-context"
|
|
13
|
+
import { findDocNodeByPath } from "../../utils/doc-tree-nav"
|
|
14
|
+
import type { DocContent, DocNode, DocRenderHandlers, DocSourceId } from "../../types/doc-source"
|
|
15
|
+
|
|
16
|
+
/** Color tokens for the doc-viewer chrome. Hub-side `DocViewer` callers share
|
|
17
|
+
* this constant; no need to override per source — the palette is intentionally
|
|
18
|
+
* uniform across knowledge-base + data-room (both use ODS dark tokens). */
|
|
19
|
+
export const DEFAULT_DOC_VIEWER_PALETTE = {
|
|
20
|
+
background: "bg-ods-bg",
|
|
21
|
+
containerBackground: "transparent",
|
|
22
|
+
headerText: "text-ods-text-primary",
|
|
23
|
+
primaryText: "text-ods-text-primary",
|
|
24
|
+
secondaryText: "text-ods-text-secondary",
|
|
25
|
+
accent: "var(--ods-accent)",
|
|
26
|
+
border: "border-ods-border",
|
|
27
|
+
cardBackground: "bg-ods-card",
|
|
28
|
+
} as const
|
|
29
|
+
|
|
30
|
+
export interface DocViewerProps {
|
|
31
|
+
/**
|
|
32
|
+
* Registry source id (`'openframe-docs'`, `'data-room-docs'`, …). Flowed through
|
|
33
|
+
* `renderContent`'s handlers for `/api/resolve-link` POSTs.
|
|
34
|
+
*/
|
|
35
|
+
sourceId: DocSourceId
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Render the content body. The page shell owns this — it picks the markdown
|
|
39
|
+
* renderer, dispatches PDF/Figma/Sheets/file branches, etc. No renderer
|
|
40
|
+
* interface in between.
|
|
41
|
+
*/
|
|
42
|
+
renderContent: (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Render the loading skeleton. Receives the selected node's `documentType`
|
|
46
|
+
* (undefined while structure is still loading) so the caller can return a
|
|
47
|
+
* markdown-shaped skeleton vs an embed-shaped skeleton.
|
|
48
|
+
*/
|
|
49
|
+
renderSkeleton: (documentType: DocNode['documentType']) => React.ReactNode
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Chat-source identifier — passed in by the page shell from server-side
|
|
53
|
+
* `currentPlatform()`. Lib has no platform context; the page shell is the
|
|
54
|
+
* trusted boundary that wires this. NEVER pass user input here.
|
|
55
|
+
*/
|
|
56
|
+
chatSource: string
|
|
57
|
+
|
|
58
|
+
title: string | React.ReactNode
|
|
59
|
+
/** Override the default ODS palette. Optional — most callers should omit. */
|
|
60
|
+
colorPalette?: typeof DEFAULT_DOC_VIEWER_PALETTE
|
|
61
|
+
className?: string
|
|
62
|
+
|
|
63
|
+
/** Initial doc path (URL `[...path]`). */
|
|
64
|
+
docPath?: string
|
|
65
|
+
|
|
66
|
+
/** Sidebar header copy (`'DOCUMENTATION'`, `'DATA ROOM'`). */
|
|
67
|
+
sidebarLabel?: string
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* API endpoint for fetching the document tree structure. Defaults to the
|
|
71
|
+
* dispatcher path `/api/docs/sources/${sourceId}/structure`. Override only
|
|
72
|
+
* if hosting the viewer behind a different route.
|
|
73
|
+
*/
|
|
74
|
+
structureEndpoint?: string
|
|
75
|
+
/** Same shape as `structureEndpoint`. Defaults to `/api/docs/sources/${sourceId}/content`. */
|
|
76
|
+
contentEndpoint?: string
|
|
77
|
+
/** Base route path for URL navigation. */
|
|
78
|
+
baseRoute: string
|
|
79
|
+
|
|
80
|
+
/** Empty state copy when no doc is selected. */
|
|
81
|
+
emptyStateText?: string
|
|
82
|
+
|
|
83
|
+
/** Whether to render the doc-search bar (bound to chat). */
|
|
84
|
+
showAIChat?: boolean
|
|
85
|
+
|
|
86
|
+
/** Folder-index filename (default `'README.md'`). */
|
|
87
|
+
folderIndexFile?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function DocViewer(props: DocViewerProps) {
|
|
91
|
+
return <DocViewerContent {...props} />
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function DocViewerContent({
|
|
95
|
+
sourceId,
|
|
96
|
+
renderContent,
|
|
97
|
+
renderSkeleton,
|
|
98
|
+
chatSource,
|
|
99
|
+
title,
|
|
100
|
+
colorPalette = DEFAULT_DOC_VIEWER_PALETTE,
|
|
101
|
+
className = "",
|
|
102
|
+
docPath,
|
|
103
|
+
sidebarLabel = "DOCUMENTATION",
|
|
104
|
+
structureEndpoint,
|
|
105
|
+
contentEndpoint,
|
|
106
|
+
baseRoute,
|
|
107
|
+
emptyStateText,
|
|
108
|
+
showAIChat = false,
|
|
109
|
+
folderIndexFile,
|
|
110
|
+
}: DocViewerProps) {
|
|
111
|
+
// Default endpoints derived from sourceId. Hub callers omit the props in 99%
|
|
112
|
+
// of cases; the override is for embed contexts where the doc-viewer sits
|
|
113
|
+
// behind a non-standard route.
|
|
114
|
+
const resolvedStructureEndpoint =
|
|
115
|
+
structureEndpoint ?? `/api/docs/sources/${sourceId}/structure`
|
|
116
|
+
const resolvedContentEndpoint =
|
|
117
|
+
contentEndpoint ?? `/api/docs/sources/${sourceId}/content`
|
|
118
|
+
const {
|
|
119
|
+
structure,
|
|
120
|
+
selectedPath,
|
|
121
|
+
content,
|
|
122
|
+
isLoadingStructure,
|
|
123
|
+
isLoadingContent,
|
|
124
|
+
error,
|
|
125
|
+
expandedNodes,
|
|
126
|
+
selectNode,
|
|
127
|
+
toggleNode,
|
|
128
|
+
navigateToDoc,
|
|
129
|
+
} = useDocumentTree(
|
|
130
|
+
{
|
|
131
|
+
structureEndpoint: resolvedStructureEndpoint,
|
|
132
|
+
contentEndpoint: resolvedContentEndpoint,
|
|
133
|
+
baseRoute,
|
|
134
|
+
folderIndexFile,
|
|
135
|
+
},
|
|
136
|
+
docPath,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
const { activeSection, handleSectionClick } = useScrollSpy(content?.sections)
|
|
140
|
+
|
|
141
|
+
const docNav = useDocNavigation()
|
|
142
|
+
const docSearch = useDocSearch({
|
|
143
|
+
source: chatSource,
|
|
144
|
+
baseRoute,
|
|
145
|
+
onNavigate: (path) => navigateToDoc(path, { fromInternalLink: true }),
|
|
146
|
+
onInPageSwap: (path) => docNav.navigate(path),
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
const renderedContent = useMemo(() => {
|
|
150
|
+
if (!content) return null
|
|
151
|
+
return renderContent(content, {
|
|
152
|
+
onInternalLinkClick: navigateToDoc,
|
|
153
|
+
currentPath: selectedPath,
|
|
154
|
+
sourceId,
|
|
155
|
+
})
|
|
156
|
+
}, [content, selectedPath, renderContent, navigateToDoc, sourceId])
|
|
157
|
+
|
|
158
|
+
// Selected node's documentType drives:
|
|
159
|
+
// - which skeleton the caller renders during fetch (markdown vs embed)
|
|
160
|
+
// - the article max-width + sticky-nav visibility (markdown only)
|
|
161
|
+
// `undefined` documentType is treated as `'markdown'` (per the DocNode
|
|
162
|
+
// discriminator's documented default).
|
|
163
|
+
const selectedNodeDocType =
|
|
164
|
+
selectedPath && structure.length > 0
|
|
165
|
+
? findDocNodeByPath(selectedPath, structure)?.documentType
|
|
166
|
+
: undefined
|
|
167
|
+
// During loading, the in-flight content's type isn't known yet — fall back
|
|
168
|
+
// to the selected node's type (or markdown if neither is set).
|
|
169
|
+
const activeDocType = content?.documentType ?? selectedNodeDocType
|
|
170
|
+
const isMarkdownContent = !activeDocType || activeDocType === 'markdown'
|
|
171
|
+
const showStickyNav = isMarkdownContent
|
|
172
|
+
|
|
173
|
+
const stickyNavSections =
|
|
174
|
+
content?.sections?.map((s) => ({ id: s.id, label: s.title })) ?? []
|
|
175
|
+
|
|
176
|
+
const isColorValue =
|
|
177
|
+
colorPalette.background.startsWith('#') ||
|
|
178
|
+
colorPalette.background.startsWith('rgb') ||
|
|
179
|
+
colorPalette.background.startsWith('var(')
|
|
180
|
+
|
|
181
|
+
const bgStyle = isColorValue ? { backgroundColor: colorPalette.background } : {}
|
|
182
|
+
const bgClass = !isColorValue ? colorPalette.background : ''
|
|
183
|
+
const containerBgStyle =
|
|
184
|
+
colorPalette.containerBackground !== 'transparent'
|
|
185
|
+
? { backgroundColor: colorPalette.containerBackground }
|
|
186
|
+
: {}
|
|
187
|
+
|
|
188
|
+
const defaultEmptyText =
|
|
189
|
+
structure.length > 0
|
|
190
|
+
? 'Select a document from the sidebar to view'
|
|
191
|
+
: 'No documents yet. Add content from the admin panel.'
|
|
192
|
+
const resolvedEmptyText = emptyStateText || defaultEmptyText
|
|
193
|
+
|
|
194
|
+
return (
|
|
195
|
+
<section className={`${bgClass} ${className}`} style={bgStyle}>
|
|
196
|
+
<div
|
|
197
|
+
className="max-w-[1920px] px-6 md:px-20 py-6 md:py-10 mx-auto"
|
|
198
|
+
style={containerBgStyle}
|
|
199
|
+
>
|
|
200
|
+
<div className="flex flex-col gap-6">
|
|
201
|
+
<div className="flex flex-col gap-4">
|
|
202
|
+
{typeof title === 'string' ? <PageHeading>{title}</PageHeading> : title}
|
|
203
|
+
</div>
|
|
204
|
+
|
|
205
|
+
{showAIChat && (
|
|
206
|
+
<DocSearchBar
|
|
207
|
+
placeholder={`Search ${sidebarLabel?.toLowerCase() || 'documents'}...`}
|
|
208
|
+
query={docSearch.query}
|
|
209
|
+
onQueryChange={docSearch.setQuery}
|
|
210
|
+
results={docSearch.results}
|
|
211
|
+
isLoading={docSearch.isLoading}
|
|
212
|
+
onResultSelect={docSearch.handleResultSelect}
|
|
213
|
+
showDropdown={docSearch.keepDropdownOpen}
|
|
214
|
+
/>
|
|
215
|
+
)}
|
|
216
|
+
|
|
217
|
+
{error && (
|
|
218
|
+
<div className="flex justify-center">
|
|
219
|
+
<div className="rounded-lg border bg-ods-card p-8 text-center max-w-md border-ods-border">
|
|
220
|
+
<h2 className="text-xl font-semibold text-ods-text-primary">
|
|
221
|
+
Error Loading Documents
|
|
222
|
+
</h2>
|
|
223
|
+
<p className="mt-2 text-ods-text-secondary">{error}. Please try again later.</p>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
|
|
228
|
+
{!error && (
|
|
229
|
+
<div className="flex flex-col lg:flex-row gap-6 lg:gap-10 items-start flex-1">
|
|
230
|
+
<div className="w-full lg:w-[320px] lg:shrink-0">
|
|
231
|
+
<div className="lg:sticky lg:top-20">
|
|
232
|
+
{isLoadingStructure ? (
|
|
233
|
+
<CategorySidebarSkeleton />
|
|
234
|
+
) : (
|
|
235
|
+
<>
|
|
236
|
+
<PersistentMobileDropdown isLoading={false}>
|
|
237
|
+
<MobileNavigationDropdown
|
|
238
|
+
nodes={structure}
|
|
239
|
+
selectedPath={selectedPath}
|
|
240
|
+
expandedNodes={expandedNodes}
|
|
241
|
+
onNodeClick={selectNode}
|
|
242
|
+
onToggleExpand={toggleNode}
|
|
243
|
+
isLoading={false}
|
|
244
|
+
folderIndexFile={folderIndexFile}
|
|
245
|
+
/>
|
|
246
|
+
</PersistentMobileDropdown>
|
|
247
|
+
|
|
248
|
+
<PersistentSidebar isLoading={false}>
|
|
249
|
+
<div className="hidden lg:block">
|
|
250
|
+
<div className="space-y-4">
|
|
251
|
+
<h3 className="text-[14px] font-['Azeret_Mono'] font-semibold uppercase text-ods-text-secondary tracking-[-0.02em] leading-[1.43em]">
|
|
252
|
+
{sidebarLabel}
|
|
253
|
+
</h3>
|
|
254
|
+
<MultiLevelNavigation
|
|
255
|
+
nodes={structure}
|
|
256
|
+
selectedPath={selectedPath}
|
|
257
|
+
expandedNodes={expandedNodes}
|
|
258
|
+
onNodeClick={selectNode}
|
|
259
|
+
onToggleExpand={toggleNode}
|
|
260
|
+
isLoading={false}
|
|
261
|
+
folderIndexFile={folderIndexFile}
|
|
262
|
+
/>
|
|
263
|
+
</div>
|
|
264
|
+
</div>
|
|
265
|
+
</PersistentSidebar>
|
|
266
|
+
</>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
</div>
|
|
270
|
+
|
|
271
|
+
<div className="flex-1 min-w-0 w-full">
|
|
272
|
+
<div
|
|
273
|
+
className={`grid grid-cols-1 ${
|
|
274
|
+
(showStickyNav && stickyNavSections.length > 0) ||
|
|
275
|
+
isLoadingContent ||
|
|
276
|
+
isLoadingStructure
|
|
277
|
+
? 'lg:grid-cols-[1fr_280px]'
|
|
278
|
+
: ''
|
|
279
|
+
} gap-8`}
|
|
280
|
+
>
|
|
281
|
+
<div className={`w-full ${isMarkdownContent ? 'max-w-4xl mx-auto' : ''}`}>
|
|
282
|
+
<article className="space-y-2">
|
|
283
|
+
{(isLoadingContent || isLoadingStructure) ? (
|
|
284
|
+
renderSkeleton(selectedNodeDocType)
|
|
285
|
+
) : !content ? (
|
|
286
|
+
<div className="text-center py-16">
|
|
287
|
+
<p className="text-xl text-ods-text-secondary">{resolvedEmptyText}</p>
|
|
288
|
+
</div>
|
|
289
|
+
) : (
|
|
290
|
+
renderedContent
|
|
291
|
+
)}
|
|
292
|
+
</article>
|
|
293
|
+
</div>
|
|
294
|
+
|
|
295
|
+
{(isLoadingContent || isLoadingStructure) && (
|
|
296
|
+
<div className="hidden lg:block">
|
|
297
|
+
<div className="sticky top-24">
|
|
298
|
+
<div className="h-[14px] w-28 bg-ods-border rounded animate-pulse mb-5" />
|
|
299
|
+
<div className="space-y-0">
|
|
300
|
+
{[130, 170, 190, 220, 110, 200, 80, 100, 120, 140, 90].map((w, i) => (
|
|
301
|
+
<div
|
|
302
|
+
key={i}
|
|
303
|
+
className={`py-[13px] pl-3 border-l-2 ${
|
|
304
|
+
i === 0 ? 'border-ods-accent' : 'border-transparent'
|
|
305
|
+
}`}
|
|
306
|
+
>
|
|
307
|
+
<div
|
|
308
|
+
className="h-[13px] bg-ods-border rounded animate-pulse"
|
|
309
|
+
style={{ width: w }}
|
|
310
|
+
/>
|
|
311
|
+
</div>
|
|
312
|
+
))}
|
|
313
|
+
</div>
|
|
314
|
+
</div>
|
|
315
|
+
</div>
|
|
316
|
+
)}
|
|
317
|
+
|
|
318
|
+
{showStickyNav &&
|
|
319
|
+
content &&
|
|
320
|
+
stickyNavSections.length > 0 &&
|
|
321
|
+
!isLoadingContent && (
|
|
322
|
+
<div className="hidden lg:block">
|
|
323
|
+
<div className="sticky top-24">
|
|
324
|
+
<h3 className="text-[14px] font-['Azeret_Mono'] font-semibold uppercase text-ods-text-secondary tracking-[-0.02em] leading-[1.43em] mb-4">
|
|
325
|
+
ON THIS PAGE
|
|
326
|
+
</h3>
|
|
327
|
+
<StickySectionNav
|
|
328
|
+
sections={stickyNavSections}
|
|
329
|
+
activeSection={activeSection}
|
|
330
|
+
onSectionClick={handleSectionClick}
|
|
331
|
+
ribbonPosition="left"
|
|
332
|
+
ribbonColor="var(--ods-accent)"
|
|
333
|
+
/>
|
|
334
|
+
</div>
|
|
335
|
+
</div>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
</div>
|
|
340
|
+
)}
|
|
341
|
+
</div>
|
|
342
|
+
</div>
|
|
343
|
+
</section>
|
|
344
|
+
)
|
|
345
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
export { DocViewer } from './doc-viewer'
|
|
4
|
+
export type { DocViewerProps } from './doc-viewer'
|
|
5
|
+
|
|
6
|
+
export { useDocumentTree } from './use-document-tree'
|
|
7
|
+
export type { UseDocumentTreeConfig } from './use-document-tree'
|
|
8
|
+
|
|
9
|
+
export { useScrollSpy } from './use-scroll-spy'
|
|
10
|
+
|
|
11
|
+
export { DocNavigationProvider, useDocNavigation } from './doc-navigation-context'
|
|
12
|
+
export type { DocNavigator } from './doc-navigation-context'
|