Skip to content

Commit 5e1b56e

Browse files
committed
Adds Table of Contents support
1 parent e47c4b6 commit 5e1b56e

File tree

12 files changed

+231
-6
lines changed

12 files changed

+231
-6
lines changed

docs/components/markdown/markdown.mdx

+82
Original file line numberDiff line numberDiff line change
@@ -106,3 +106,85 @@ body {
106106
</CodeBlock>
107107
</CodeBlocks></div>
108108

109+
#### Table of Contents
110+
111+
You can use the `tocRenderer` prop to render a table of contents from your Markdown content. The headers will be automatically detected and rendered in the order they appear. You need to place the `<Toc />` component in your Markdown content to render the table of contents.
112+
113+
<Frame background="subtle"><img src="../../images/previews/markdown-344947c9/document.1.jpg" style={{ width: '100%', height: 'auto', maxHeight: '500px', borderRadius: "0.25rem", overflow: "hidden", border: '1px solid #E5E4E2' }} /></Frame>
114+
115+
<div style={{paddingTop: "1rem", paddingBottom: "1rem"}}><CodeBlocks>
116+
<CodeBlock title="template.tsx">
117+
```jsx
118+
import { Markdown } from "@fileforge/react-print";
119+
120+
<Tailwind
121+
config={{
122+
corePlugins: {
123+
preflight: false,
124+
},
125+
}}
126+
>
127+
<CSS>{`a.-toc-link:after {
128+
content: target-counter(attr(href), page);
129+
float: right;
130+
}`}</CSS>
131+
<Markdown
132+
options={{
133+
overrides: {
134+
PageBreak: {
135+
component: PageBreak, // import { PageBreak } from "@fileforge/react-print";
136+
},
137+
},
138+
}}
139+
tocRenderer={({ level, children, id }) => (
140+
<a
141+
className="block py-2 border-b -toc-link"
142+
style={{
143+
paddingLeft: `${(level - 1) * 1}rem`,
144+
}}
145+
href={`#${id}`}
146+
>
147+
{children}
148+
</a>
149+
)}
150+
>{`# Table of Contents
151+
152+
<Toc />
153+
154+
<PageBreak />
155+
156+
# This is a level 1 header
157+
158+
## This is a level 2 header
159+
160+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.
161+
162+
## This is another level 2 header
163+
164+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.
165+
166+
# This is a level 1 header, bis
167+
168+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.`}</Markdown>
169+
</Tailwind>;
170+
171+
```
172+
</CodeBlock>
173+
<CodeBlock title="styles.css">
174+
```css
175+
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");
176+
177+
html,
178+
body {
179+
font-size: 28px;
180+
font-family: "Inter", sans-serif;
181+
}
182+
183+
@page {
184+
size: A4;
185+
}
186+
187+
```
188+
</CodeBlock>
189+
</CodeBlocks></div>
190+
Loading
Loading
Binary file not shown.
Binary file not shown.
Loading

docs/sortedDocs.json

+1-1
Large diffs are not rendered by default.

docs/ui/templates.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ icon: list
88
<CardGroup>
99
<Card title="Scientific" href="../../../ui/templates/scientific-report">
1010
<div style={{ marginTop: "1rem", borderRadius: "0.25rem", overflow: "hidden" }}>
11-
<img src="../images/previews/ui-templates-scientific-report-0fc7c85c/document.1.jpg"/>
11+
<img src="../images/previews/ui-templates-scientific-report-31829950/document.1.jpg"/>
1212
</div>
1313
</Card>
1414
<Card title="With charts" href="../../../ui/templates/report-charts">

docs/ui/templates/scientific-report.mdx

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ icon: flask
44
category: Reports
55
---
66

7-
<Frame background="subtle"><img src="../../images/previews/ui-templates-scientific-report-0fc7c85c/document.1.jpg" style={{ width: '100%', height: 'auto', maxHeight: '500px', borderRadius: "0.25rem", overflow: "hidden", border: '1px solid #E5E4E2' }} /></Frame>
7+
<Frame background="subtle"><img src="../../images/previews/ui-templates-scientific-report-31829950/document.1.jpg" style={{ width: '100%', height: 'auto', maxHeight: '500px', borderRadius: "0.25rem", overflow: "hidden", border: '1px solid #E5E4E2' }} /></Frame>
88

99
```jsx
1010
import React, { createContext, useEffect, useState } from "react";

src/markdown/markdown.tsx

+146-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,93 @@
11
import { DocConfig } from "docgen/types";
2-
import MarkdownJSX from "markdown-to-jsx";
3-
import React from "react";
2+
import { MarkdownToJSX, compiler } from "markdown-to-jsx";
3+
import React, {
4+
Children,
5+
isValidElement,
6+
ReactElement,
7+
ReactNode,
8+
} from "react";
9+
import { CSS, PageBreak, Tailwind } from "..";
410

5-
export const Markdown = MarkdownJSX;
11+
interface TocRendererProps {
12+
heading: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
13+
level: number;
14+
children: ReactNode;
15+
id: string;
16+
}
17+
18+
interface MarkdownProps {
19+
children: string;
20+
tocRenderer?: (props: TocRendererProps) => ReactNode;
21+
options?: MarkdownToJSX.Options;
22+
}
23+
24+
export const Markdown = (props: MarkdownProps) => {
25+
const content = compiler(props.children, props.options);
26+
27+
let headers: TocRendererProps[] = [];
28+
29+
const isReactElement = (child: ReactNode): child is ReactElement<any> => {
30+
return typeof child === "object" && child !== null && "type" in child;
31+
};
32+
33+
const detectHeader = (child: ReactNode) => {
34+
if (!child) return;
35+
36+
if (
37+
isReactElement(child) &&
38+
typeof child.type === "string" &&
39+
["h1", "h2", "h3", "h4", "h5", "h6"].includes(child.type)
40+
) {
41+
headers.push({
42+
heading: child.type,
43+
level: parseInt(child.type[1]),
44+
children: child.props.children,
45+
id: child.props.id,
46+
} as TocRendererProps);
47+
}
48+
49+
if (isValidElement(child)) {
50+
if (
51+
typeof child.type === "function" &&
52+
child.type.prototype &&
53+
child.type.prototype.isReactComponent
54+
) {
55+
// @ts-ignore
56+
const instance = new child.type(child.props); // Instantiate the class component
57+
const result = instance.render(); // Call its render method
58+
detectHeader(result);
59+
} else if (typeof child.type === "function") {
60+
// @ts-ignore
61+
const result = child.type(child.props); // call the component
62+
detectHeader(result);
63+
} else if (child.props && child.props.children) {
64+
Children.forEach(child.props.children, detectHeader);
65+
}
66+
}
67+
};
68+
69+
const tocRenderer = props.tocRenderer;
70+
71+
if (tocRenderer) detectHeader(content);
72+
73+
const Toc = !!tocRenderer ? (
74+
<>{headers.map((header) => tocRenderer(header))}</>
75+
) : null;
76+
77+
return compiler(
78+
props.children,
79+
Object.assign({}, props.options, {
80+
overrides: {
81+
Toc: !!tocRenderer
82+
? {
83+
component: () => Toc,
84+
}
85+
: undefined,
86+
...props.options?.overrides,
87+
},
88+
})
89+
);
90+
};
691

792
export const __docConfig: DocConfig = {
893
description: `Render Markdown inside your templates. Provides a simple wrapper around [\`markdown-to-jsx\`](https://github.com/quantizor/markdown-to-jsx).
@@ -63,6 +148,64 @@ This agreement is signed with <CustomerName />.
63148
<KPI>20/month</KPI>`}</Markdown>
64149
),
65150
},
151+
tableOfContents: {
152+
name: "Table of Contents",
153+
description: `You can use the \`tocRenderer\` prop to render a table of contents from your Markdown content. The headers will be automatically detected and rendered in the order they appear. You need to place the \`<Toc />\` component in your Markdown content to render the table of contents.
154+
155+
You can also use the \`id\` attribute in your headers to link to them directly.`,
156+
template: (
157+
<Tailwind
158+
config={{
159+
corePlugins: {
160+
preflight: false,
161+
},
162+
}}
163+
>
164+
<CSS>{`a.-toc-link:after {
165+
content: target-counter(attr(href), page);
166+
float: right;
167+
}`}</CSS>
168+
<Markdown
169+
options={{
170+
overrides: {
171+
PageBreak: {
172+
component: PageBreak, // import { PageBreak } from "@fileforge/react-print";
173+
},
174+
},
175+
}}
176+
tocRenderer={({ level, children, id }) => (
177+
<a
178+
className="block py-2 border-b -toc-link"
179+
style={{
180+
paddingLeft: `${(level - 1) * 1}rem`,
181+
}}
182+
href={`#${id}`}
183+
>
184+
{children}
185+
</a>
186+
)}
187+
>{`# Table of Contents
188+
189+
<Toc />
190+
191+
<PageBreak />
192+
193+
# This is a level 1 header
194+
195+
## This is a level 2 header
196+
197+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.
198+
199+
## This is another level 2 header
200+
201+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.
202+
203+
# This is a level 1 header, bis
204+
205+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi.`}</Markdown>
206+
</Tailwind>
207+
),
208+
},
66209
},
67210
},
68211
},

0 commit comments

Comments
 (0)