/* eslint-disable react/no-array-index-key */ import { Fragment, useState } from 'react'; import { marked, type Tokens } from 'marked'; import { markedSmartypants } from 'marked-smartypants'; import { useDocsData , useDocsVersion } from '@docusaurus/plugin-content-docs/client'; import CodeBlock from '@theme/CodeBlock'; import MDX from '@theme/MDXComponents'; import { useReflectionMap } from '../hooks/useReflectionMap'; import { replaceLinkTokens } from '../utils/markdown'; interface Admonition { type: 'admonition'; raw: string; title?: string; keyword?: string; text: string; tokens: Token[]; } type Token = | Admonition | Tokens.Blockquote | Tokens.Br | Tokens.Code | Tokens.Codespan | Tokens.Def | Tokens.Del | Tokens.Em | Tokens.Escape | Tokens.Heading | Tokens.Hr | Tokens.HTML | Tokens.Image | Tokens.Link | Tokens.List | Tokens.ListItem | Tokens.Paragraph | Tokens.Space | Tokens.Strong | Tokens.Table | Tokens.Tag | Tokens.Text; type TokensList = Token[]; const ADMONITION_START = /^:{3}([a-z]+)? *(.*)\n/; const ADMONITION_END = '\n:::'; marked.setOptions({ gfm: true, }); marked.use(markedSmartypants()); marked.use({ extensions: [ { name: 'admonition', level: 'block', start(src) { return src.match(ADMONITION_START)?.index; }, tokenizer(src, tokens) { const match = ADMONITION_START.exec(src); if (match) { const keyword = match[1] ?? 'info'; const title = match[2] ? match[2].trim() : undefined; const startIndex = match.index; const startEndIndex = startIndex + match[0].length; const endIndex = src.indexOf(ADMONITION_END, startEndIndex); const endEndIndex = endIndex + 4; const token: Admonition = { type: 'admonition', raw: src.slice(startIndex, endEndIndex), text: src.slice(startEndIndex, endIndex), title, keyword, tokens: [], }; this.lexer.blockTokens(token.text, token.tokens); return token; } return undefined; }, }, ], }); const TOKEN_TO_TAG: Record = { blockquote: 'blockquote', br: 'br', code: 'pre', codespan: 'code', def: 'span', // ??? del: 'del', em: 'em', escape: 'span', // ??? hr: 'hr', html: 'div', paragraph: 'p', strong: 'strong', table: 'table', tablecell: 'td', tablerow: 'tr', }; function convertAstToElements(ast: TokensList): React.ReactNode[] | undefined { const elements: React.ReactNode[] = []; let counter = 0; // eslint-disable-next-line complexity ast.forEach((token) => { // Nested tokens aren't typed for some reason... const children = (token as unknown as { tokens: TokensList }).tokens ?? []; switch (token.type) { case 'code': elements.push( /* {token.text} , */ {token.text} , ); break; case 'codespan': // Non-raw is escaped and doesn't work with JSX, so use the raw value // but remove the wrapping backticks! elements.push({token.raw.slice(1, -1)}); break; case 'heading': { const Comp = MDX[`h${token.depth}` as 'h1']; elements.push({convertAstToElements(children) ?? token.text}); break; } case 'image': elements.push(); break; case 'link': elements.push( {convertAstToElements(children) ?? token.text} , ); break; case 'list': { const Comp = token.ordered ? 'ol' : 'ul'; elements.push( {convertAstToElements((token.items as TokensList) ?? children)} , ); break; } case 'list_item': elements.push(
  • {token.task && ( <> {' '} )} {convertAstToElements(children) ?? token.text}
  • , ); break; case 'space': elements.push(token.raw || ' '); break; case 'table': elements.push( {token.header.map((h, i) => ( ))} {token.rows.map((cells, i) => ( {cells.map((c, i2) => ( ))} ))}
    {convertAstToElements(h.tokens as TokensList)}
    {convertAstToElements(c.tokens as TokensList)}
    , ); break; case 'text': elements.push( children.length === 0 ? ( token.text ) : ( {convertAstToElements(children)} ), ); break; case 'admonition': elements.push( {convertAstToElements(children) ?? token.text} , ); break; default: { const Comp = (TOKEN_TO_TAG[token.type] || token.type) as unknown as React.ComponentType<{ children: React.ReactNode; }>; const innerText = 'text' in token ? token.text : ''; elements.push({convertAstToElements(children) ?? innerText}); break; } } counter += 1; }); if (elements.length === 0) { return undefined; } return elements; } export interface MarkdownProps { content: string; } // Too bad we cant use `@mdx-js` here... export function Markdown({ content }: MarkdownProps) { const reflections = useReflectionMap(); const version = useDocsVersion(); const docsData = useDocsData(version.pluginId); const [ast] = useState( () => marked.lexer( replaceLinkTokens(content, reflections, version, docsData.path), ) as unknown as TokensList, ); if (!content) { return null; } return
    {convertAstToElements(ast)}
    ; }