@oud-idk/markdown 0.0.0-stage → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +27 -2
- package/dist/components/CopyButton.d.ts +4 -0
- package/dist/components/CopyButton.d.ts.map +1 -0
- package/dist/components/CopyButton.js +21 -0
- package/dist/components/CopyButton.js.map +1 -0
- package/dist/components/MarkdownLayout.d.ts +16 -0
- package/dist/components/MarkdownLayout.d.ts.map +1 -0
- package/dist/components/MarkdownLayout.js +15 -0
- package/dist/components/MarkdownLayout.js.map +1 -0
- package/dist/components/MarkdownRenderer.d.ts +10 -0
- package/dist/components/MarkdownRenderer.d.ts.map +1 -0
- package/dist/components/MarkdownRenderer.js +113 -0
- package/dist/components/MarkdownRenderer.js.map +1 -0
- package/dist/components/MarkdownWithToC.d.ts +12 -0
- package/dist/components/MarkdownWithToC.d.ts.map +1 -0
- package/dist/components/MarkdownWithToC.js +13 -0
- package/dist/components/MarkdownWithToC.js.map +1 -0
- package/dist/components/ScrollableMarkdownViewer.d.ts +13 -0
- package/dist/components/ScrollableMarkdownViewer.d.ts.map +1 -0
- package/dist/components/ScrollableMarkdownViewer.js +13 -0
- package/dist/components/ScrollableMarkdownViewer.js.map +1 -0
- package/dist/components/TableOfContents.d.ts +10 -0
- package/dist/components/TableOfContents.d.ts.map +1 -0
- package/dist/components/TableOfContents.js +169 -0
- package/dist/components/TableOfContents.js.map +1 -0
- package/dist/components/useMarkdownScroller.d.ts +8 -0
- package/dist/components/useMarkdownScroller.d.ts.map +1 -0
- package/dist/components/useMarkdownScroller.js +32 -0
- package/dist/components/useMarkdownScroller.js.map +1 -0
- package/dist/data/language-map.json +2766 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +12 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/linguist.d.ts +2 -0
- package/dist/lib/linguist.d.ts.map +1 -0
- package/dist/lib/linguist.js +14 -0
- package/dist/lib/linguist.js.map +1 -0
- package/dist/lib/markdown-plugins.d.ts +4 -0
- package/dist/lib/markdown-plugins.d.ts.map +1 -0
- package/dist/lib/markdown-plugins.js +75 -0
- package/dist/lib/markdown-plugins.js.map +1 -0
- package/dist/lib/markdown-toc.d.ts +20 -0
- package/dist/lib/markdown-toc.d.ts.map +1 -0
- package/dist/lib/markdown-toc.js +34 -0
- package/dist/lib/markdown-toc.js.map +1 -0
- package/dist/lib/reading-time.d.ts +9 -0
- package/dist/lib/reading-time.d.ts.map +1 -0
- package/dist/lib/reading-time.js +14 -0
- package/dist/lib/reading-time.js.map +1 -0
- package/package.json +58 -3
package/README.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @oud-idk/markdown
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Private markdown rendering system for React/Next.js projects.
|
|
4
|
+
|
|
5
|
+
Includes `MarkdownRenderer`, `MarkdownWithToc`, table of contents, scroller, copy button, remark/rehype plugin lists, reading time, and language detection helpers.
|
|
6
|
+
|
|
7
|
+
## Development
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
pnpm install
|
|
11
|
+
pnpm --filter @oud-idk/markdown build
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Consuming in this workspace
|
|
15
|
+
|
|
16
|
+
The portfolio depends on this package via `workspace:*`.
|
|
17
|
+
|
|
18
|
+
## Publish
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
pnpm --filter @oud-idk/markdown publish
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`prepublishOnly` rebuilds the package first.
|
|
25
|
+
|
|
26
|
+
This package ships compiled output from `dist/` and expects the consumer to
|
|
27
|
+
provide the Tailwind design tokens/classes it uses (e.g. `bg-surface`,
|
|
28
|
+
`text-muted-foreground`, `border-border`, `text-brand`).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyButton.d.ts","sourceRoot":"","sources":["../../src/components/CopyButton.tsx"],"names":[],"mappings":"AAKA,wBAAgB,UAAU,CAAC,EAAE,IAAI,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,+BAkCpD"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState } from "react";
|
|
4
|
+
import { ClipboardCopyIcon, CheckIcon } from "lucide-react";
|
|
5
|
+
export function CopyButton({ code }) {
|
|
6
|
+
const [isCopied, setIsCopied] = useState(false);
|
|
7
|
+
const handleCopy = async () => {
|
|
8
|
+
if (!code)
|
|
9
|
+
return;
|
|
10
|
+
try {
|
|
11
|
+
await navigator.clipboard.writeText(code);
|
|
12
|
+
setIsCopied(true);
|
|
13
|
+
setTimeout(() => setIsCopied(false), 2000);
|
|
14
|
+
}
|
|
15
|
+
catch (err) {
|
|
16
|
+
console.error("Failed to copy code: ", err);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
return (_jsx("button", { onClick: () => void handleCopy(), "aria-label": "Copy code", type: "button", className: "inline-flex items-center gap-1.5 px-2 py-1 bg-surface rounded-md text-xs font-sans text-muted-foreground hover:text-foreground hover:bg-surface-active border border-border-subtle transition-all focus-ring", children: isCopied ? (_jsxs(_Fragment, { children: [_jsx(CheckIcon, { className: "h-3.5 w-3.5 text-success" }), _jsx("span", { className: "text-success font-medium", children: "Copied!" })] })) : (_jsxs(_Fragment, { children: [_jsx(ClipboardCopyIcon, { className: "h-3.5 w-3.5" }), _jsx("span", { children: "Copy" })] })) }));
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=CopyButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"CopyButton.js","sourceRoot":"","sources":["../../src/components/CopyButton.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACjC,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE5D,MAAM,UAAU,UAAU,CAAC,EAAE,IAAI,EAAoB;IACjD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAEhD,MAAM,UAAU,GAAG,KAAK,IAAmB,EAAE;QACzC,IAAI,CAAC,IAAI;YAAE,OAAO;QAClB,IAAI,CAAC;YACD,MAAM,SAAS,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAC1C,WAAW,CAAC,IAAI,CAAC,CAAC;YAClB,UAAU,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,KAAK,CAAC,uBAAuB,EAAE,GAAG,CAAC,CAAC;QAChD,CAAC;IACL,CAAC,CAAC;IAEF,OAAO,CACH,iBACI,OAAO,EAAE,GAAG,EAAE,CAAC,KAAK,UAAU,EAAE,gBACrB,WAAW,EACtB,IAAI,EAAC,QAAQ,EACb,SAAS,EAAC,8MAA8M,YAEvN,QAAQ,CAAC,CAAC,CAAC,CACR,8BACI,KAAC,SAAS,IAAC,SAAS,EAAC,0BAA0B,GAAG,EAClD,eAAM,SAAS,EAAC,0BAA0B,wBAAe,IAC1D,CACN,CAAC,CAAC,CAAC,CACA,8BACI,KAAC,iBAAiB,IAAC,SAAS,EAAC,aAAa,GAAG,EAC7C,kCAAiB,IAClB,CACN,GACI,CACZ,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import type { TocItem } from "../lib/markdown-toc";
|
|
3
|
+
interface MarkdownLayoutProps {
|
|
4
|
+
/** Server-rendered document. Kept opaque here — only the ref matters to the ToC. */
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** Heading outline, extracted from the markdown while it was rendered. */
|
|
7
|
+
headings: TocItem[];
|
|
8
|
+
showToc?: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Client half of the markdown view: layout, the scroll container the ToC
|
|
12
|
+
* measures, and the ToC's own scroll-spy. Everything else arrives as children.
|
|
13
|
+
*/
|
|
14
|
+
export declare const MarkdownLayout: React.FC<MarkdownLayoutProps>;
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=MarkdownLayout.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownLayout.d.ts","sourceRoot":"","sources":["../../src/components/MarkdownLayout.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAU,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGtD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAGnD,UAAU,mBAAmB;IACzB,oFAAoF;IACpF,QAAQ,EAAE,SAAS,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,eAAO,MAAM,cAAc,EAAE,KAAK,CAAC,EAAE,CAAC,mBAAmB,CAyBxD,CAAC"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useRef } from "react";
|
|
4
|
+
import { TableOfContents } from "./TableOfContents";
|
|
5
|
+
import { cn } from "cn";
|
|
6
|
+
/**
|
|
7
|
+
* Client half of the markdown view: layout, the scroll container the ToC
|
|
8
|
+
* measures, and the ToC's own scroll-spy. Everything else arrives as children.
|
|
9
|
+
*/
|
|
10
|
+
export const MarkdownLayout = ({ children, headings, showToc = true }) => {
|
|
11
|
+
const contentRef = useRef(null);
|
|
12
|
+
const hasToc = showToc && headings.length > 0;
|
|
13
|
+
return (_jsxs("div", { className: "relative flex w-full justify-center gap-8", children: [_jsx("div", { ref: contentRef, className: cn("min-w-0 flex-1 max-w-7xl"), children: children }), hasToc && (_jsx("aside", { className: "hidden xl:block max-w-80 shrink-0", children: _jsx("div", { className: "sticky top-20 max-h-[calc(100vh-30rem)] overflow-y-auto pr-2 scrollbar-thin", children: _jsx(TableOfContents, { containerRef: contentRef, headings: headings }) }) }))] }));
|
|
14
|
+
};
|
|
15
|
+
//# sourceMappingURL=MarkdownLayout.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownLayout.js","sourceRoot":"","sources":["../../src/components/MarkdownLayout.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAc,EAAE,MAAM,EAAkB,MAAM,OAAO,CAAC;AAEtD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,OAAO,EAAE,EAAE,EAAE,MAAM,IAAI,CAAC;AAUxB;;;GAGG;AACH,MAAM,CAAC,MAAM,cAAc,GAAkC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,GAAG,IAAI,EAAE,EAAE,EAAE;IACpG,MAAM,UAAU,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAEhD,MAAM,MAAM,GAAG,OAAO,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC;IAE9C,OAAO,CACH,eAAK,SAAS,EAAC,2CAA2C,aACtD,cACI,GAAG,EAAE,UAAU,EACf,SAAS,EAAE,EAAE,CACT,0BAA0B,CAC7B,YAEA,QAAQ,GACP,EAEL,MAAM,IAAI,CACP,gBAAO,SAAS,EAAC,mCAAmC,YAChD,cAAK,SAAS,EAAC,6EAA6E,YACxF,KAAC,eAAe,IAAC,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,QAAQ,GAAI,GAC/D,GACF,CACX,IACC,CACT,CAAC;AACN,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import "katex/dist/katex.min.css";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { type TocItem } from "../lib/markdown-toc";
|
|
4
|
+
export interface MarkdownRendererProps {
|
|
5
|
+
content?: string;
|
|
6
|
+
className?: string;
|
|
7
|
+
headings?: TocItem[];
|
|
8
|
+
}
|
|
9
|
+
export declare function MarkdownRenderer({ content, className, headings }: MarkdownRendererProps): Promise<React.JSX.Element>;
|
|
10
|
+
//# sourceMappingURL=MarkdownRenderer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer.d.ts","sourceRoot":"","sources":["../../src/components/MarkdownRenderer.tsx"],"names":[],"mappings":"AAAA,OAAO,0BAA0B,CAAC;AAElC,OAAO,KAAyB,MAAM,OAAO,CAAC;AAM9C,OAAO,EAAyB,KAAK,OAAO,EAAE,MAAM,qBAAqB,CAAC;AA6G1E,MAAM,WAAW,qBAAqB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;CACxB;AAyCD,wBAAsB,gBAAgB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,qBAAqB,8BAoB7F"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import "katex/dist/katex.min.css";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import { MarkdownAsync } from "react-markdown";
|
|
5
|
+
import { getLinguist } from "../lib/linguist";
|
|
6
|
+
import { rehypePlugins, remarkPlugins } from "../lib/markdown-plugins";
|
|
7
|
+
import { rehypeCollectHeadings } from "../lib/markdown-toc";
|
|
8
|
+
import { CopyButton } from "./CopyButton";
|
|
9
|
+
function extractText(node) {
|
|
10
|
+
if (typeof node === "string")
|
|
11
|
+
return node;
|
|
12
|
+
if (typeof node === "number")
|
|
13
|
+
return String(node);
|
|
14
|
+
if (Array.isArray(node))
|
|
15
|
+
return node.map(extractText).join("");
|
|
16
|
+
if (React.isValidElement(node)) {
|
|
17
|
+
return extractText(node.props.children);
|
|
18
|
+
}
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* react-markdown passes every component override the hast `node` alongside the
|
|
23
|
+
* real DOM props. Spreading it through emits `node="[object Object]"` into the
|
|
24
|
+
* markup, so drop it once, here, instead of at each call site.
|
|
25
|
+
*/
|
|
26
|
+
function withoutNode(props) {
|
|
27
|
+
const { node, ...rest } = props;
|
|
28
|
+
void node;
|
|
29
|
+
return rest;
|
|
30
|
+
}
|
|
31
|
+
const CodeBlock = (raw) => {
|
|
32
|
+
const { children, className, "data-language": language, ...props } = withoutNode(raw);
|
|
33
|
+
if (!language && !className?.includes("shiki")) {
|
|
34
|
+
return (_jsx("pre", { className: `${className ?? ""} border border-border rounded-xl`, ...props, children: children }));
|
|
35
|
+
}
|
|
36
|
+
const code = extractText(children).replace(/\n$/, "");
|
|
37
|
+
const languageName = language ? getLinguist(language) : "Plaintext";
|
|
38
|
+
return (_jsxs("div", { className: "relative group bg-surface my-4 rounded-xl border border-border overflow-hidden shadow-xs transition-all", children: [_jsxs("div", { className: "flex items-center justify-between px-4 py-2 border-b border-border-subtle bg-surface/50 text-xs font-mono text-muted-foreground", children: [_jsx("span", { className: "font-medium tracking-wide uppercase", children: languageName ?? language }), _jsx(CopyButton, { code: code })] }), _jsx("div", { className: "p-3 overflow-x-auto text-sm min-h-10", children: _jsx("pre", { className: `${className ?? ""} bg-transparent! p-0! m-0!`, ...props, children: children }) })] }));
|
|
39
|
+
};
|
|
40
|
+
const markdownComponents = {
|
|
41
|
+
hr() {
|
|
42
|
+
return _jsx("hr", { className: "my-8 border-border" });
|
|
43
|
+
},
|
|
44
|
+
p({ children }) {
|
|
45
|
+
return _jsx("p", { className: "mb-4 leading-relaxed text-foreground", children: children });
|
|
46
|
+
},
|
|
47
|
+
code(raw) {
|
|
48
|
+
const { className, children, ...props } = withoutNode(raw);
|
|
49
|
+
if ("data-block" in props) {
|
|
50
|
+
return _jsx("code", { className: className, ...props, children: children });
|
|
51
|
+
}
|
|
52
|
+
return (_jsx("code", { className: `${className ?? ""} bg-surface text-foreground border border-border px-1.5 py-0.5 rounded-md text-xs font-mono font-normal inline-block`, style: { fontFamily: "var(--font-geist-mono)" }, ...props, children: children }));
|
|
53
|
+
},
|
|
54
|
+
pre: CodeBlock,
|
|
55
|
+
input({ type, checked }) {
|
|
56
|
+
return (_jsx("input", { type: type, checked: checked, readOnly: true, className: "mr-2 rounded border-border text-brand accent-brand align-middle focus-ring" }));
|
|
57
|
+
},
|
|
58
|
+
a(raw) {
|
|
59
|
+
const { className, ...props } = withoutNode(raw);
|
|
60
|
+
return (_jsx("a", { className: `${className ?? ""} text-link hover:text-link-hover underline underline-offset-4 decoration-link/40 hover:decoration-link-hover font-medium transition-colors wrap-break-word break-all focus-ring rounded-xs`, ...props }));
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
/**
|
|
64
|
+
* Server component: parses and renders markdown here, so the browser receives
|
|
65
|
+
* finished markup instead of the source plus a parser.
|
|
66
|
+
*/
|
|
67
|
+
/**
|
|
68
|
+
* Cross-request cache of rendered markdown. The Shiki highlighter and KaTeX
|
|
69
|
+
* are expensive (~1s+ cold each), and the body copy rarely changes, so we
|
|
70
|
+
* render once per unique (content, headings) key and reuse the elements.
|
|
71
|
+
*/
|
|
72
|
+
const markdownCache = new Map();
|
|
73
|
+
function getMarkdownBody(content, headings) {
|
|
74
|
+
return getMarkdown(content, headings !== undefined).then((r) => {
|
|
75
|
+
if (headings !== undefined)
|
|
76
|
+
headings.push(...r.headings);
|
|
77
|
+
return r.body;
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
function getMarkdown(content, collectToc) {
|
|
81
|
+
const key = `${collectToc ? "toc" : "plain"}:${content}`;
|
|
82
|
+
let hit = markdownCache.get(key);
|
|
83
|
+
if (!hit) {
|
|
84
|
+
const collected = [];
|
|
85
|
+
const rehype = collectToc
|
|
86
|
+
? [...rehypePlugins, [rehypeCollectHeadings, { into: collected }]]
|
|
87
|
+
: rehypePlugins;
|
|
88
|
+
hit = MarkdownAsync({
|
|
89
|
+
remarkPlugins,
|
|
90
|
+
rehypePlugins: rehype,
|
|
91
|
+
components: markdownComponents,
|
|
92
|
+
children: content,
|
|
93
|
+
}).then((body) => ({ body, headings: collected }));
|
|
94
|
+
hit.catch(() => markdownCache.delete(key));
|
|
95
|
+
markdownCache.set(key, hit);
|
|
96
|
+
}
|
|
97
|
+
return hit;
|
|
98
|
+
}
|
|
99
|
+
export async function MarkdownRenderer({ content, className, headings }) {
|
|
100
|
+
// Must await to render elements for ToC
|
|
101
|
+
const body = await getMarkdownBody(content ?? "", headings);
|
|
102
|
+
return (_jsx("div", { className: `
|
|
103
|
+
prose dark:prose-invert max-w-none w-full wrap-break-word
|
|
104
|
+
prose-headings:scroll-mt-24
|
|
105
|
+
prose-headings:text-foreground prose-headings:font-semibold prose-headings:tracking-tight prose-h1:mt-8
|
|
106
|
+
prose-h2:text-xl prose-h2:mb-3 prose-li:my-0
|
|
107
|
+
prose-blockquote:border-l-brand prose-blockquote:py-0.5 prose-blockquote:px-4 prose-blockquote:rounded-r-xl prose-blockquote:not-italic
|
|
108
|
+
prose-pre:bg-surface
|
|
109
|
+
prose-img:rounded-xl prose-img:border prose-img:border-border
|
|
110
|
+
${className ?? ''}
|
|
111
|
+
`, children: body }));
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=MarkdownRenderer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownRenderer.js","sourceRoot":"","sources":["../../src/components/MarkdownRenderer.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
interface MarkdownWithTocProps {
|
|
2
|
+
content?: string;
|
|
3
|
+
className?: string;
|
|
4
|
+
showToc?: boolean;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Server component: renders the document and derives its ToC from the same
|
|
8
|
+
* parse, then hands both to the client shell that owns the scroll-spy.
|
|
9
|
+
*/
|
|
10
|
+
export declare function MarkdownWithToc({ content, className, showToc }: MarkdownWithTocProps): Promise<import("react").JSX.Element>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=MarkdownWithToC.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownWithToC.d.ts","sourceRoot":"","sources":["../../src/components/MarkdownWithToC.tsx"],"names":[],"mappings":"AAIA,UAAU,oBAAoB;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACrB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CAAC,EAAE,OAAO,EAAE,SAAc,EAAE,OAAc,EAAE,EAAE,oBAAoB,wCAUtG"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { MarkdownLayout } from "./MarkdownLayout";
|
|
3
|
+
import { MarkdownRenderer } from "./MarkdownRenderer";
|
|
4
|
+
/**
|
|
5
|
+
* Server component: renders the document and derives its ToC from the same
|
|
6
|
+
* parse, then hands both to the client shell that owns the scroll-spy.
|
|
7
|
+
*/
|
|
8
|
+
export async function MarkdownWithToc({ content, className = "", showToc = true }) {
|
|
9
|
+
const headings = [];
|
|
10
|
+
const document = await MarkdownRenderer({ content, className, headings });
|
|
11
|
+
return (_jsx(MarkdownLayout, { headings: headings, showToc: showToc, children: document }));
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=MarkdownWithToC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MarkdownWithToC.js","sourceRoot":"","sources":["../../src/components/MarkdownWithToC.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAStD;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,EAAE,OAAO,EAAE,SAAS,GAAG,EAAE,EAAE,OAAO,GAAG,IAAI,EAAwB;IACnG,MAAM,QAAQ,GAAc,EAAE,CAAC;IAE/B,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC;IAE1E,OAAO,CACH,KAAC,cAAc,IAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,YAC/C,QAAQ,GACI,CACpB,CAAC;AACN,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
interface ScrollableMarkdownViewerProps {
|
|
3
|
+
/** Server-rendered markdown, e.g. `<MarkdownRenderer content={...} />`. */
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
className?: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Scroll container with in-container anchor scrolling. Takes children rather
|
|
9
|
+
* than a source string because the document itself is rendered on the server.
|
|
10
|
+
*/
|
|
11
|
+
export declare const ScrollableMarkdownViewer: React.FC<ScrollableMarkdownViewerProps>;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=ScrollableMarkdownViewer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollableMarkdownViewer.d.ts","sourceRoot":"","sources":["../../src/components/ScrollableMarkdownViewer.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAE,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAK9C,UAAU,6BAA6B;IACnC,2EAA2E;IAC3E,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;GAGG;AACH,eAAO,MAAM,wBAAwB,EAAE,KAAK,CAAC,EAAE,CAAC,6BAA6B,CAe5E,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { useMarkdownScroller } from "./useMarkdownScroller";
|
|
4
|
+
import { cn } from "cn";
|
|
5
|
+
/**
|
|
6
|
+
* Scroll container with in-container anchor scrolling. Takes children rather
|
|
7
|
+
* than a source string because the document itself is rendered on the server.
|
|
8
|
+
*/
|
|
9
|
+
export const ScrollableMarkdownViewer = ({ children, className, }) => {
|
|
10
|
+
const { containerRef, handleLinkClick } = useMarkdownScroller();
|
|
11
|
+
return (_jsx("div", { ref: containerRef, onClick: handleLinkClick, className: cn("overflow-auto", className), children: children }));
|
|
12
|
+
};
|
|
13
|
+
//# sourceMappingURL=ScrollableMarkdownViewer.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ScrollableMarkdownViewer.js","sourceRoot":"","sources":["../../src/components/ScrollableMarkdownViewer.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAIb,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,EAAE,EAAE,MAAM,IAAI,CAAC;AAQxB;;;GAGG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA4C,CAAC,EAC9E,QAAQ,EACR,SAAS,GACZ,EAAE,EAAE;IACD,MAAM,EAAE,YAAY,EAAE,eAAe,EAAE,GAAG,mBAAmB,EAAE,CAAC;IAEhE,OAAO,CACH,cACI,GAAG,EAAE,YAAY,EACjB,OAAO,EAAE,eAAe,EACxB,SAAS,EAAE,EAAE,CAAC,eAAe,EAAE,SAAS,CAAC,YAExC,QAAQ,GACP,CACT,CAAC;AACN,CAAC,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import type { TocItem } from "../lib/markdown-toc";
|
|
3
|
+
export type { TocItem };
|
|
4
|
+
interface TableOfContentsProps {
|
|
5
|
+
containerRef: React.RefObject<HTMLElement | null>;
|
|
6
|
+
/** Heading outline, extracted from the markdown so it renders server-side. */
|
|
7
|
+
headings: TocItem[];
|
|
8
|
+
}
|
|
9
|
+
export declare const TableOfContents: React.FC<TableOfContentsProps>;
|
|
10
|
+
//# sourceMappingURL=TableOfContents.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableOfContents.d.ts","sourceRoot":"","sources":["../../src/components/TableOfContents.tsx"],"names":[],"mappings":"AAEA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAEjF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAEnD,YAAY,EAAE,OAAO,EAAE,CAAC;AAExB,UAAU,oBAAoB;IAC1B,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,CAAC;IAClD,8EAA8E;IAC9E,QAAQ,EAAE,OAAO,EAAE,CAAC;CACvB;AAeD,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,EAAE,CAAC,oBAAoB,CAiP1D,CAAC"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useEffect, useRef, useState, useCallback, useMemo } from "react";
|
|
4
|
+
function getScrollContainer(element) {
|
|
5
|
+
if (typeof window === "undefined" || !element)
|
|
6
|
+
return window;
|
|
7
|
+
let parent = element.parentElement;
|
|
8
|
+
while (parent && parent !== document.body && parent !== document.documentElement) {
|
|
9
|
+
const { overflowY } = window.getComputedStyle(parent);
|
|
10
|
+
if (overflowY === "auto" || overflowY === "scroll") {
|
|
11
|
+
return parent;
|
|
12
|
+
}
|
|
13
|
+
parent = parent.parentElement;
|
|
14
|
+
}
|
|
15
|
+
return window;
|
|
16
|
+
}
|
|
17
|
+
export const TableOfContents = ({ containerRef, headings }) => {
|
|
18
|
+
const [activeId, setActiveId] = useState(headings[0]?.id ?? "");
|
|
19
|
+
const itemRefs = useRef(new Map());
|
|
20
|
+
const navRef = useRef(null);
|
|
21
|
+
const [indicator, setIndicator] = useState({ top: 0, height: 0, visible: false });
|
|
22
|
+
const isClickScrollingRef = useRef(false);
|
|
23
|
+
const scrollTimeoutRef = useRef(null);
|
|
24
|
+
// Calculate minimum level so indentation starts from 0 even if page starts at H2
|
|
25
|
+
const minLevel = useMemo(() => {
|
|
26
|
+
if (headings.length === 0)
|
|
27
|
+
return 1;
|
|
28
|
+
return Math.min(...headings.map((h) => h.level));
|
|
29
|
+
}, [headings]);
|
|
30
|
+
// Re-runs (and re-syncs `activeId`) whenever the outline changes, which is
|
|
31
|
+
// what resets the active entry when navigating to another document.
|
|
32
|
+
const updateActiveHeading = useCallback(() => {
|
|
33
|
+
if (headings.length === 0 || isClickScrollingRef.current)
|
|
34
|
+
return;
|
|
35
|
+
const activationOffset = Math.max(120, window.innerHeight * 0.35);
|
|
36
|
+
let currentActive = headings[0].id;
|
|
37
|
+
for (const heading of headings) {
|
|
38
|
+
const escapedId = CSS.escape(heading.id);
|
|
39
|
+
const el = containerRef.current?.querySelector(`#${escapedId}`) ??
|
|
40
|
+
document.getElementById(heading.id);
|
|
41
|
+
if (!el)
|
|
42
|
+
continue;
|
|
43
|
+
const rect = el.getBoundingClientRect();
|
|
44
|
+
if (rect.top <= activationOffset) {
|
|
45
|
+
currentActive = heading.id;
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
setActiveId(currentActive);
|
|
52
|
+
}, [headings, containerRef]);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
if (headings.length === 0) {
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
const scrollContainer = getScrollContainer(containerRef.current);
|
|
58
|
+
const target = scrollContainer === window ? window : scrollContainer;
|
|
59
|
+
let ticking = false;
|
|
60
|
+
const handleScroll = () => {
|
|
61
|
+
if (isClickScrollingRef.current)
|
|
62
|
+
return;
|
|
63
|
+
if (!ticking) {
|
|
64
|
+
window.requestAnimationFrame(() => {
|
|
65
|
+
if (!isClickScrollingRef.current) {
|
|
66
|
+
updateActiveHeading();
|
|
67
|
+
}
|
|
68
|
+
ticking = false;
|
|
69
|
+
});
|
|
70
|
+
ticking = true;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
target.addEventListener("scroll", handleScroll, { passive: true });
|
|
74
|
+
updateActiveHeading();
|
|
75
|
+
return () => {
|
|
76
|
+
target.removeEventListener("scroll", handleScroll);
|
|
77
|
+
if (scrollTimeoutRef.current) {
|
|
78
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}, [headings, containerRef, updateActiveHeading]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
const el = activeId !== "" ? (itemRefs.current.get(activeId) ?? null) : null;
|
|
84
|
+
if (!el) {
|
|
85
|
+
setIndicator((prev) => ({ ...prev, visible: false }));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
setIndicator({ top: el.offsetTop, height: el.offsetHeight, visible: true });
|
|
89
|
+
const sidebarContainer = navRef.current?.parentElement;
|
|
90
|
+
if (sidebarContainer && sidebarContainer.scrollHeight > sidebarContainer.clientHeight) {
|
|
91
|
+
const elRect = el.getBoundingClientRect();
|
|
92
|
+
const containerRect = sidebarContainer.getBoundingClientRect();
|
|
93
|
+
const diffTop = elRect.top - (containerRect.top + 16);
|
|
94
|
+
const diffBottom = elRect.bottom - (containerRect.bottom - 16);
|
|
95
|
+
if (diffTop < 0) {
|
|
96
|
+
sidebarContainer.scrollBy({ top: diffTop, behavior: "smooth" });
|
|
97
|
+
}
|
|
98
|
+
else if (diffBottom > 0) {
|
|
99
|
+
sidebarContainer.scrollBy({ top: diffBottom, behavior: "smooth" });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}, [activeId, headings]);
|
|
103
|
+
// 4. Click handler with smooth scroll lock
|
|
104
|
+
const scrollToHeading = (id) => {
|
|
105
|
+
const escapedId = CSS.escape(id);
|
|
106
|
+
const el = containerRef.current?.querySelector(`#${escapedId}`) ??
|
|
107
|
+
document.getElementById(id);
|
|
108
|
+
if (!el)
|
|
109
|
+
return;
|
|
110
|
+
isClickScrollingRef.current = true;
|
|
111
|
+
setActiveId(id);
|
|
112
|
+
if (scrollTimeoutRef.current) {
|
|
113
|
+
clearTimeout(scrollTimeoutRef.current);
|
|
114
|
+
}
|
|
115
|
+
scrollTimeoutRef.current = setTimeout(() => {
|
|
116
|
+
isClickScrollingRef.current = false;
|
|
117
|
+
}, 800);
|
|
118
|
+
const scrollContainer = getScrollContainer(containerRef.current);
|
|
119
|
+
const headerOffset = 80;
|
|
120
|
+
if (scrollContainer === window) {
|
|
121
|
+
const elementPosition = el.getBoundingClientRect().top + window.scrollY;
|
|
122
|
+
window.scrollTo({
|
|
123
|
+
top: elementPosition - headerOffset,
|
|
124
|
+
behavior: "smooth",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
else if (scrollContainer instanceof HTMLElement) {
|
|
128
|
+
const containerRect = scrollContainer.getBoundingClientRect();
|
|
129
|
+
const elementRect = el.getBoundingClientRect();
|
|
130
|
+
const offsetTop = elementRect.top - containerRect.top + scrollContainer.scrollTop - headerOffset;
|
|
131
|
+
scrollContainer.scrollTo({
|
|
132
|
+
top: offsetTop,
|
|
133
|
+
behavior: "smooth",
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
if (headings.length === 0)
|
|
138
|
+
return null;
|
|
139
|
+
const INDENT_WIDTH_REM = 1.0;
|
|
140
|
+
return (_jsxs("nav", { ref: navRef, "aria-label": "Table of contents", className: "w-full text-sm select-none", children: [_jsx("h4", { className: "font-semibold text-muted-foreground mb-3 text-xs tracking-wider uppercase", children: "On This Page" }), _jsxs("div", { className: "relative border-l border-border/60 pl-2", children: [_jsx("div", { className: "absolute -left-px w-0.5 bg-brand-active rounded-full transition-all duration-300 ease-out pointer-events-none", style: {
|
|
141
|
+
top: indicator.top,
|
|
142
|
+
height: indicator.height,
|
|
143
|
+
opacity: indicator.visible ? 1 : 0,
|
|
144
|
+
} }), _jsx("ul", { className: "relative space-y-0.5", children: headings.map(({ id, text, level }) => {
|
|
145
|
+
const isActive = activeId === id;
|
|
146
|
+
const depth = Math.max(0, level - minLevel);
|
|
147
|
+
return (_jsxs("li", { ref: (node) => {
|
|
148
|
+
if (node)
|
|
149
|
+
itemRefs.current.set(id, node);
|
|
150
|
+
else
|
|
151
|
+
itemRefs.current.delete(id);
|
|
152
|
+
}, className: "relative flex items-center", style: {
|
|
153
|
+
paddingLeft: depth > 0 ? `${(depth * INDENT_WIDTH_REM).toString()}rem` : undefined,
|
|
154
|
+
}, children: [depth > 0 && (_jsx("div", { className: "absolute top-0 bottom-0 left-0 flex pointer-events-none", style: { width: `${(depth * INDENT_WIDTH_REM).toString()}rem` }, "aria-hidden": "true", children: Array.from({ length: depth }).map((_, i) => {
|
|
155
|
+
const isLastLevel = i === depth - 1;
|
|
156
|
+
return (_jsxs("div", { className: "relative h-full", style: { width: `${INDENT_WIDTH_REM.toString()}rem` }, children: [_jsx("span", { className: `absolute left-2 top-0 bottom-0 w-px transition-colors duration-200 ${isActive
|
|
157
|
+
? "bg-brand-active/40"
|
|
158
|
+
: "bg-border/60"}` }), isLastLevel && (_jsx("span", { className: `absolute left-2 top-1/2 -translate-y-1/2 w-2 h-px transition-colors duration-200 ${isActive
|
|
159
|
+
? "bg-brand-active/50"
|
|
160
|
+
: "bg-border/60"}` }))] }, i));
|
|
161
|
+
}) })), _jsx("button", { type: "button", title: text, "aria-current": isActive ? "location" : undefined, onClick: (e) => {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
scrollToHeading(id);
|
|
164
|
+
}, className: `w-full rounded-md px-2 py-1.5 text-left leading-snug transition-colors duration-200 cursor-pointer hover:bg-muted/80 hover:text-foreground ${isActive
|
|
165
|
+
? "text-brand-active bg-muted/40"
|
|
166
|
+
: "text-muted-foreground"}`, children: text })] }, id));
|
|
167
|
+
}) })] })] }));
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=TableOfContents.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TableOfContents.js","sourceRoot":"","sources":["../../src/components/TableOfContents.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React, { RefObject } from 'react';
|
|
2
|
+
interface UseMarkdownScrollerReturn {
|
|
3
|
+
containerRef: RefObject<HTMLDivElement | null>;
|
|
4
|
+
handleLinkClick: (e: React.MouseEvent<HTMLDivElement>) => void;
|
|
5
|
+
}
|
|
6
|
+
export declare const useMarkdownScroller: () => UseMarkdownScrollerReturn;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=useMarkdownScroller.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMarkdownScroller.d.ts","sourceRoot":"","sources":["../../src/components/useMarkdownScroller.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,EAAuB,SAAS,EAAE,MAAM,OAAO,CAAC;AAE9D,UAAU,yBAAyB;IAC/B,YAAY,EAAE,SAAS,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAC/C,eAAe,EAAE,CAAC,CAAC,EAAE,KAAK,CAAC,UAAU,CAAC,cAAc,CAAC,KAAK,IAAI,CAAC;CAClE;AAGD,eAAO,MAAM,mBAAmB,QAAO,yBAsCtC,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useRef, useCallback } from 'react';
|
|
3
|
+
export const useMarkdownScroller = () => {
|
|
4
|
+
const containerRef = useRef(null);
|
|
5
|
+
const handleLinkClick = useCallback((event) => {
|
|
6
|
+
if (!(event.target instanceof Element)) {
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
const link = event.target.closest("a");
|
|
10
|
+
if (link !== null &&
|
|
11
|
+
link.hash.length > 0 &&
|
|
12
|
+
link.pathname === window.location.pathname) {
|
|
13
|
+
event.preventDefault();
|
|
14
|
+
event.stopPropagation();
|
|
15
|
+
const id = decodeURIComponent(link.hash.substring(1));
|
|
16
|
+
const container = containerRef.current;
|
|
17
|
+
// Using CSS.escape prevents errors if your heading ID starts with a number or special char
|
|
18
|
+
const element = container?.querySelector(`#${CSS.escape(id)}`);
|
|
19
|
+
if (element !== null && element !== undefined && container !== null) {
|
|
20
|
+
const targetTop = element.getBoundingClientRect().top;
|
|
21
|
+
const containerTop = container.getBoundingClientRect().top;
|
|
22
|
+
const scrollPosition = targetTop - containerTop + container.scrollTop;
|
|
23
|
+
container.scrollTo({
|
|
24
|
+
top: scrollPosition,
|
|
25
|
+
behavior: "smooth",
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}, []);
|
|
30
|
+
return { containerRef, handleLinkClick };
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=useMarkdownScroller.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMarkdownScroller.js","sourceRoot":"","sources":["../../src/components/useMarkdownScroller.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAc,EAAE,MAAM,EAAE,WAAW,EAAa,MAAM,OAAO,CAAC;AAQ9D,MAAM,CAAC,MAAM,mBAAmB,GAAG,GAA8B,EAAE;IAC/D,MAAM,YAAY,GAAG,MAAM,CAAiB,IAAI,CAAC,CAAC;IAElD,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,KAAuC,EAAQ,EAAE;QAClF,IAAI,CAAC,CAAC,KAAK,CAAC,MAAM,YAAY,OAAO,CAAC,EAAE,CAAC;YACrC,OAAO;QACX,CAAC;QAED,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAEvC,IACI,IAAI,KAAK,IAAI;YACb,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;YACpB,IAAI,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CAAC,QAAQ,EAC5C,CAAC;YACC,KAAK,CAAC,cAAc,EAAE,CAAC;YACvB,KAAK,CAAC,eAAe,EAAE,CAAC;YAExB,MAAM,EAAE,GAAG,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;YACtD,MAAM,SAAS,GAAG,YAAY,CAAC,OAAO,CAAC;YAEvC,2FAA2F;YAC3F,MAAM,OAAO,GAAG,SAAS,EAAE,aAAa,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;YAE/D,IAAI,OAAO,KAAK,IAAI,IAAI,OAAO,KAAK,SAAS,IAAI,SAAS,KAAK,IAAI,EAAE,CAAC;gBAClE,MAAM,SAAS,GAAG,OAAO,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAC;gBACtD,MAAM,YAAY,GAAG,SAAS,CAAC,qBAAqB,EAAE,CAAC,GAAG,CAAC;gBAC3D,MAAM,cAAc,GAAG,SAAS,GAAG,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC;gBAEtE,SAAS,CAAC,QAAQ,CAAC;oBACf,GAAG,EAAE,cAAc;oBACnB,QAAQ,EAAE,QAAQ;iBACrB,CAAC,CAAC;YACP,CAAC;QACL,CAAC;IACL,CAAC,EAAE,EAAE,CAAC,CAAC;IAEP,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,CAAC;AAC7C,CAAC,CAAC"}
|