/
index.tsx
100 lines (94 loc) · 3.58 KB
/
index.tsx
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
100
import React, { useImperativeHandle } from 'react';
import ReactMarkdown, { Options } from 'react-markdown';
import { Element } from 'hast';
import { PluggableList } from 'unified';
import gfm from 'remark-gfm';
import slug from 'rehype-slug';
import headings from 'rehype-autolink-headings';
import rehypeAttrs from 'rehype-attr';
import rehypeIgnore from 'rehype-ignore';
import rehypePrism from 'rehype-prism-plus';
import rehypeRewrite, { getCodeString, RehypeRewriteOptions } from 'rehype-rewrite';
import { octiconLink } from './nodes/octiconLink';
import { copyElement } from './nodes/copy';
import './styles/markdown.less';
import { reservedMeta } from './plugins/reservedMeta';
export interface MarkdownPreviewProps extends Omit<Options, 'children'> {
prefixCls?: string;
className?: string;
source?: string;
disableCopy?: boolean;
style?: React.CSSProperties;
pluginsFilter?: (type: 'rehype' | 'remark', plugin: PluggableList) => PluggableList;
warpperElement?: React.DetailedHTMLProps<React.HTMLAttributes<HTMLDivElement>, HTMLDivElement> & {
'data-color-mode'?: 'light' | 'dark';
};
onScroll?: (e: React.UIEvent<HTMLDivElement>) => void;
onMouseOver?: (e: React.MouseEvent<HTMLDivElement>) => void;
rehypeRewrite?: RehypeRewriteOptions['rewrite'];
}
export interface MarkdownPreviewRef extends MarkdownPreviewProps {
mdp: React.RefObject<HTMLDivElement>;
}
export default React.forwardRef<MarkdownPreviewRef, MarkdownPreviewProps>((props, ref) => {
const {
prefixCls = 'wmde-markdown wmde-markdown-color',
className,
source,
style,
disableCopy = false,
onScroll,
onMouseOver,
pluginsFilter,
rehypeRewrite: rewrite,
warpperElement = {},
...other
} = props;
const mdp = React.createRef<HTMLDivElement>();
useImperativeHandle(ref, () => ({ ...props, mdp }), [mdp, props]);
const cls = `${prefixCls || ''} ${className || ''}`;
const rehypeRewriteHandle: RehypeRewriteOptions['rewrite'] = (node, index, parent) => {
if (node.type === 'element' && parent && parent.type === 'root' && /h(1|2|3|4|5|6)/.test(node.tagName)) {
const child = node.children && (node.children[0] as Element);
if (child && child.properties && child.properties.ariaHidden === 'true') {
child.properties = { class: 'anchor', ...child.properties };
child.children = [octiconLink];
}
}
if (node.type === 'element' && node.tagName === 'pre' && !disableCopy) {
const code = getCodeString(node.children);
node.children.push(copyElement(code));
}
rewrite && rewrite(node, index, parent);
};
const rehypePlugins: PluggableList = [
reservedMeta,
[rehypePrism, { ignoreMissing: true }],
slug,
headings,
rehypeIgnore,
[rehypeRewrite, { rewrite: rehypeRewriteHandle }],
[rehypeAttrs, { properties: 'attr' }],
...(other.rehypePlugins || []),
];
const customProps: MarkdownPreviewProps = {
allowElement: (element, index, parent) => {
if (other.allowElement) {
return other.allowElement(element, index, parent);
}
return /^[A-Za-z0-9]+$/.test(element.tagName);
},
};
const remarkPlugins = [...(other.remarkPlugins || []), gfm];
return (
<div ref={mdp} onScroll={onScroll} onMouseOver={onMouseOver} {...warpperElement} className={cls} style={style}>
<ReactMarkdown
{...other}
{...customProps}
rehypePlugins={pluginsFilter ? pluginsFilter('rehype', rehypePlugins) : rehypePlugins}
remarkPlugins={pluginsFilter ? pluginsFilter('remark', remarkPlugins) : remarkPlugins}
children={source || ''}
/>
</div>
);
});