@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.
Files changed (51) hide show
  1. package/README.md +27 -2
  2. package/dist/components/CopyButton.d.ts +4 -0
  3. package/dist/components/CopyButton.d.ts.map +1 -0
  4. package/dist/components/CopyButton.js +21 -0
  5. package/dist/components/CopyButton.js.map +1 -0
  6. package/dist/components/MarkdownLayout.d.ts +16 -0
  7. package/dist/components/MarkdownLayout.d.ts.map +1 -0
  8. package/dist/components/MarkdownLayout.js +15 -0
  9. package/dist/components/MarkdownLayout.js.map +1 -0
  10. package/dist/components/MarkdownRenderer.d.ts +10 -0
  11. package/dist/components/MarkdownRenderer.d.ts.map +1 -0
  12. package/dist/components/MarkdownRenderer.js +113 -0
  13. package/dist/components/MarkdownRenderer.js.map +1 -0
  14. package/dist/components/MarkdownWithToC.d.ts +12 -0
  15. package/dist/components/MarkdownWithToC.d.ts.map +1 -0
  16. package/dist/components/MarkdownWithToC.js +13 -0
  17. package/dist/components/MarkdownWithToC.js.map +1 -0
  18. package/dist/components/ScrollableMarkdownViewer.d.ts +13 -0
  19. package/dist/components/ScrollableMarkdownViewer.d.ts.map +1 -0
  20. package/dist/components/ScrollableMarkdownViewer.js +13 -0
  21. package/dist/components/ScrollableMarkdownViewer.js.map +1 -0
  22. package/dist/components/TableOfContents.d.ts +10 -0
  23. package/dist/components/TableOfContents.d.ts.map +1 -0
  24. package/dist/components/TableOfContents.js +169 -0
  25. package/dist/components/TableOfContents.js.map +1 -0
  26. package/dist/components/useMarkdownScroller.d.ts +8 -0
  27. package/dist/components/useMarkdownScroller.d.ts.map +1 -0
  28. package/dist/components/useMarkdownScroller.js +32 -0
  29. package/dist/components/useMarkdownScroller.js.map +1 -0
  30. package/dist/data/language-map.json +2766 -0
  31. package/dist/index.d.ts +12 -0
  32. package/dist/index.d.ts.map +1 -0
  33. package/dist/index.js +12 -0
  34. package/dist/index.js.map +1 -0
  35. package/dist/lib/linguist.d.ts +2 -0
  36. package/dist/lib/linguist.d.ts.map +1 -0
  37. package/dist/lib/linguist.js +14 -0
  38. package/dist/lib/linguist.js.map +1 -0
  39. package/dist/lib/markdown-plugins.d.ts +4 -0
  40. package/dist/lib/markdown-plugins.d.ts.map +1 -0
  41. package/dist/lib/markdown-plugins.js +75 -0
  42. package/dist/lib/markdown-plugins.js.map +1 -0
  43. package/dist/lib/markdown-toc.d.ts +20 -0
  44. package/dist/lib/markdown-toc.d.ts.map +1 -0
  45. package/dist/lib/markdown-toc.js +34 -0
  46. package/dist/lib/markdown-toc.js.map +1 -0
  47. package/dist/lib/reading-time.d.ts +9 -0
  48. package/dist/lib/reading-time.d.ts.map +1 -0
  49. package/dist/lib/reading-time.js +14 -0
  50. package/dist/lib/reading-time.js.map +1 -0
  51. package/package.json +58 -3
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # @oud-idk/markdown
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
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,4 @@
1
+ export declare function CopyButton({ code }: {
2
+ code: string;
3
+ }): import("react").JSX.Element;
4
+ //# sourceMappingURL=CopyButton.d.ts.map
@@ -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"}