forked from keystonejs/keystone
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMarkdoc.tsx
More file actions
99 lines (85 loc) · 3.07 KB
/
Copy pathMarkdoc.tsx
File metadata and controls
99 lines (85 loc) · 3.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import React, { ElementType, ReactNode } from 'react';
import { RenderableTreeNodes, Scalar, RenderableTreeNode, Tag } from '@markdoc/markdoc';
import { Heading as HeadingType } from '../lib/getHeadings';
import { Code, InlineCode } from './primitives/Code';
import { Heading } from './docs/Heading';
import { RelatedContent } from './RelatedContent';
import { Well } from './primitives/Well';
import { Emoji } from './primitives/Emoji';
import { ComingSoon } from './docs/ComingSoon';
const renderers: Record<string, ElementType> = {
code: InlineCode,
CodeBlock(props: { children: string; language: string }) {
return (
<pre>
<Code className={`language-${props.language}`}>{props.children}</Code>
</pre>
);
},
ComingSoon,
Emoji,
Well,
RelatedContent,
Heading(props: { children: ReactNode; level: 1 | 2 | 3 | 4 | 5 | 6; id: string }) {
return <Heading {...props} />;
},
};
// inlined from markdoc because
// - it's so trivial to write
// - the markdoc implementation was doing weird things with the components(you couldn't override built in tags)
// - to avoid bundling all of markdoc on the front-end
export function Markdoc(props: { content: RenderableTreeNodes }) {
function deepRender(value: any): any {
if (value == null || typeof value !== 'object') return value;
if (Array.isArray(value)) return value.map(item => deepRender(item));
if (value.$$mdtype === 'Tag') return render(value);
if (typeof value !== 'object') return value;
const output: Record<string, Scalar> = {};
for (const [k, v] of Object.entries(value)) output[k] = deepRender(v);
return output;
}
function render(node: RenderableTreeNodes): ReactNode {
if (Array.isArray(node)) {
return React.createElement(React.Fragment, null, ...node.map(render));
}
if (node === null || typeof node !== 'object') return node;
const { name, attributes: { class: className, ...attrs } = {}, children = [] } = node;
if (className) attrs.className = className;
let elementType = renderers[name];
if (elementType === undefined) {
if (name[0].toLowerCase() === name[0]) {
elementType = name as ElementType;
} else {
throw new Error(`No renderer provided for element type: ${name}`);
}
}
return React.createElement(
elementType,
Object.keys(attrs).length == 0 ? null : deepRender(attrs),
...children.map(render)
);
}
return render(props.content) as JSX.Element;
}
export function extractHeadings(content: Tag): HeadingType[] {
const headings: HeadingType[] = [];
for (const child of content.children) {
if (typeof child !== 'string' && child !== null && child.name === 'Heading') {
headings.push({
id: child.attributes.id,
depth: child.attributes.level,
label: stringifyDocContent(child),
});
}
}
return headings;
}
function stringifyDocContent(node: RenderableTreeNode): string {
if (typeof node === 'string') {
return node;
}
if (node === null) {
return '';
}
return node.children.map(stringifyDocContent).join('');
}