Skip to content

Commit

Permalink
Adds Table of Contents support
Browse files Browse the repository at this point in the history
  • Loading branch information
Titou325 committed Sep 9, 2024
1 parent e47c4b6 commit 5e1b56e
Show file tree
Hide file tree
Showing 12 changed files with 231 additions and 6 deletions.
82 changes: 82 additions & 0 deletions docs/components/markdown/markdown.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,85 @@ body {
</CodeBlock>
</CodeBlocks></div>

#### Table of Contents

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.

<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>

<div style={{paddingTop: "1rem", paddingBottom: "1rem"}}><CodeBlocks>
<CodeBlock title="template.tsx">
```jsx
import { Markdown } from "@fileforge/react-print";

<Tailwind
config={{
corePlugins: {
preflight: false,
},
}}
>
<CSS>{`a.-toc-link:after {
content: target-counter(attr(href), page);
float: right;
}`}</CSS>
<Markdown
options={{
overrides: {
PageBreak: {
component: PageBreak, // import { PageBreak } from "@fileforge/react-print";
},
},
}}
tocRenderer={({ level, children, id }) => (
<a
className="block py-2 border-b -toc-link"
style={{
paddingLeft: `${(level - 1) * 1}rem`,
}}
href={`#${id}`}
>
{children}
</a>
)}
>{`# Table of Contents
<Toc />
<PageBreak />
# This is a level 1 header
## This is a level 2 header
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.
## This is another level 2 header
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.
# This is a level 1 header, bis
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>
</Tailwind>;

```
</CodeBlock>
<CodeBlock title="styles.css">
```css
@import url("https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap");

html,
body {
font-size: 28px;
font-family: "Inter", sans-serif;
}

@page {
size: A4;
}

```
</CodeBlock>
</CodeBlocks></div>

Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
Binary file not shown.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file not shown.
2 changes: 1 addition & 1 deletion docs/sortedDocs.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion docs/ui/templates.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ icon: list
<CardGroup>
<Card title="Scientific" href="../../../ui/templates/scientific-report">
<div style={{ marginTop: "1rem", borderRadius: "0.25rem", overflow: "hidden" }}>
<img src="../images/previews/ui-templates-scientific-report-0fc7c85c/document.1.jpg"/>
<img src="../images/previews/ui-templates-scientific-report-31829950/document.1.jpg"/>
</div>
</Card>
<Card title="With charts" href="../../../ui/templates/report-charts">
Expand Down
2 changes: 1 addition & 1 deletion docs/ui/templates/scientific-report.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ icon: flask
category: Reports
---

<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>
<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>

```jsx
import React, { createContext, useEffect, useState } from "react";
Expand Down
149 changes: 146 additions & 3 deletions src/markdown/markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,93 @@
import { DocConfig } from "docgen/types";
import MarkdownJSX from "markdown-to-jsx";
import React from "react";
import { MarkdownToJSX, compiler } from "markdown-to-jsx";
import React, {
Children,
isValidElement,
ReactElement,
ReactNode,
} from "react";
import { CSS, PageBreak, Tailwind } from "..";

export const Markdown = MarkdownJSX;
interface TocRendererProps {
heading: "h1" | "h2" | "h3" | "h4" | "h5" | "h6";
level: number;
children: ReactNode;
id: string;
}

interface MarkdownProps {
children: string;
tocRenderer?: (props: TocRendererProps) => ReactNode;
options?: MarkdownToJSX.Options;
}

export const Markdown = (props: MarkdownProps) => {
const content = compiler(props.children, props.options);

let headers: TocRendererProps[] = [];

const isReactElement = (child: ReactNode): child is ReactElement<any> => {
return typeof child === "object" && child !== null && "type" in child;
};

const detectHeader = (child: ReactNode) => {
if (!child) return;

if (
isReactElement(child) &&
typeof child.type === "string" &&
["h1", "h2", "h3", "h4", "h5", "h6"].includes(child.type)
) {
headers.push({
heading: child.type,
level: parseInt(child.type[1]),
children: child.props.children,
id: child.props.id,
} as TocRendererProps);
}

if (isValidElement(child)) {
if (
typeof child.type === "function" &&
child.type.prototype &&
child.type.prototype.isReactComponent
) {
// @ts-ignore
const instance = new child.type(child.props); // Instantiate the class component
const result = instance.render(); // Call its render method
detectHeader(result);
} else if (typeof child.type === "function") {
// @ts-ignore
const result = child.type(child.props); // call the component
detectHeader(result);
} else if (child.props && child.props.children) {
Children.forEach(child.props.children, detectHeader);
}
}
};

const tocRenderer = props.tocRenderer;

if (tocRenderer) detectHeader(content);

const Toc = !!tocRenderer ? (
<>{headers.map((header) => tocRenderer(header))}</>
) : null;

return compiler(
props.children,
Object.assign({}, props.options, {
overrides: {
Toc: !!tocRenderer
? {
component: () => Toc,
}
: undefined,
...props.options?.overrides,
},
})
);
};

export const __docConfig: DocConfig = {
description: `Render Markdown inside your templates. Provides a simple wrapper around [\`markdown-to-jsx\`](https://github.com/quantizor/markdown-to-jsx).
Expand Down Expand Up @@ -63,6 +148,64 @@ This agreement is signed with <CustomerName />.
<KPI>20/month</KPI>`}</Markdown>
),
},
tableOfContents: {
name: "Table of Contents",
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.
You can also use the \`id\` attribute in your headers to link to them directly.`,
template: (
<Tailwind
config={{
corePlugins: {
preflight: false,
},
}}
>
<CSS>{`a.-toc-link:after {
content: target-counter(attr(href), page);
float: right;
}`}</CSS>
<Markdown
options={{
overrides: {
PageBreak: {
component: PageBreak, // import { PageBreak } from "@fileforge/react-print";
},
},
}}
tocRenderer={({ level, children, id }) => (
<a
className="block py-2 border-b -toc-link"
style={{
paddingLeft: `${(level - 1) * 1}rem`,
}}
href={`#${id}`}
>
{children}
</a>
)}
>{`# Table of Contents
<Toc />
<PageBreak />
# This is a level 1 header
## This is a level 2 header
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.
## This is another level 2 header
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.
# This is a level 1 header, bis
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>
</Tailwind>
),
},
},
},
},
Expand Down

0 comments on commit 5e1b56e

Please sign in to comment.