import type { MarkedToken, Token } from 'marked'; import { marked } from 'marked'; import { dedent } from 'ts-dedent'; import type { MarkdownLine, MarkdownWordType } from './types.js'; import type { MermaidConfig } from '../config.type.js'; /** * @param markdown - markdown to process * @returns processed markdown */ function preprocessMarkdown(markdown: string, { markdownAutoWrap }: MermaidConfig): string { //Replace
with \n const withoutBR = markdown.replace(//g, '\n'); // Replace multiple newlines with a single newline const withoutMultipleNewlines = withoutBR.replace(/\n{2,}/g, '\n'); // Remove extra spaces at the beginning of each line const withoutExtraSpaces = dedent(withoutMultipleNewlines); if (markdownAutoWrap === false) { return withoutExtraSpaces.replace(/ /g, ' '); } return withoutExtraSpaces; } /** * @param markdown - markdown to split into lines */ export function markdownToLines(markdown: string, config: MermaidConfig = {}): MarkdownLine[] { const preprocessedMarkdown = preprocessMarkdown(markdown, config); const nodes = marked.lexer(preprocessedMarkdown); const lines: MarkdownLine[] = [[]]; let currentLine = 0; function processNode(node: MarkedToken, parentType: MarkdownWordType = 'normal') { if (node.type === 'text') { const textLines = node.text.split('\n'); textLines.forEach((textLine, index) => { if (index !== 0) { currentLine++; lines.push([]); } textLine.split(' ').forEach((word) => { if (word) { lines[currentLine].push({ content: word, type: parentType }); } }); }); } else if (node.type === 'strong' || node.type === 'em') { node.tokens.forEach((contentNode) => { processNode(contentNode as MarkedToken, node.type); }); } else if (node.type === 'html') { lines[currentLine].push({ content: node.text, type: 'normal' }); } } nodes.forEach((treeNode) => { if (treeNode.type === 'paragraph') { treeNode.tokens?.forEach((contentNode) => { processNode(contentNode as MarkedToken); }); } else if (treeNode.type === 'html') { lines[currentLine].push({ content: treeNode.text, type: 'normal' }); } }); return lines; } export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidConfig = {}) { const nodes = marked.lexer(markdown); function output(node: Token): string { if (node.type === 'text') { if (markdownAutoWrap === false) { return node.text.replace(/\n */g, '
').replace(/ /g, ' '); } return node.text.replace(/\n */g, '
'); } else if (node.type === 'strong') { return `${node.tokens?.map(output).join('')}`; } else if (node.type === 'em') { return `${node.tokens?.map(output).join('')}`; } else if (node.type === 'paragraph') { return `

${node.tokens?.map(output).join('')}

`; } else if (node.type === 'space') { return ''; } else if (node.type === 'html') { return `${node.text}`; } return `Unsupported markdown: ${node.type}`; } return nodes.map(output).join(''); }