import * as commonmark from 'commonmark'; import type { Node as MarkdownNode } from 'commonmark'; import { MarkdownNodeType } from '../common'; import { logActionRequired } from '../utilities'; const reader = new commonmark.Parser(); const writer = new commonmark.HtmlRenderer({ safe: true }); const NODE_TYPE_LIST = Object.values(MarkdownNodeType); export type MarkdownProps = { as?: React.ElementType; config?: { link?: Pick, 'target'>; paragraph?: Pick, 'className'>; }; className?: string; children?: string; } & ( | { allowList?: readonly `${MarkdownNodeType}`[]; blockList?: never; } | { allowList?: never; blockList?: readonly `${MarkdownNodeType}`[]; } ); export default function Markdown({ as: Element = 'div', allowList, blockList, config, className, children, }: MarkdownProps) { if (!children) { return null; } const linkTarget = config?.link?.target ?? '_self'; const paragraphClass = config?.paragraph?.className ?? ''; if (allowList != null && blockList != null) { logActionRequired( 'Markdown supports only one of `allowList` or `blockList` to be used at a time. `blockList` will be ignored.', ); } const parser = (nodes: string) => { const parsed = reader.parse(nodes); const toExclude = allowList != null ? NODE_TYPE_LIST.filter((type) => !allowList.includes(type)) : blockList; return toExclude != null ? stripNodes({ parsed, blockList: toExclude }) : parsed; }; const createMarkup = () => { const parsed = parser(children); return writer .render(parsed) .replace(/`); }; return ; } function stripNodes({ blockList, parsed }: { blockList: readonly string[]; parsed: MarkdownNode }) { if (!parsed) { return parsed; } const walker = parsed.walker(); for (let event = walker.next(); event != null; event = walker.next()) { const { node } = event; if (blockList.includes(node.type) && !event.entering) { while (node.firstChild != null) { node.insertBefore(node.firstChild); } node.unlink(); } } return parsed; }