.pragma library // This exists only because I haven't been able to get linkColor to work with MarkdownText // May not be necessary if that's possible tbh. function markdownToHtml(text) { if (!text) return ""; const codeBlocks = []; const inlineCode = []; let blockIndex = 0; let inlineIndex = 0; let html = text.replace(/```([\s\S]*?)```/g, (match, code) => { const trimmedCode = code.replace(/^\n+|\n+$/g, ''); const escapedCode = trimmedCode.replace(/&/g, '&') .replace(//g, '>'); codeBlocks.push(`
${escapedCode}
`); return `\x00CODEBLOCK${blockIndex++}\x00`; }); html = html.replace(/`([^`]+)`/g, (match, code) => { const escapedCode = code.replace(/&/g, '&') .replace(//g, '>'); inlineCode.push(`${escapedCode}`); return `\x00INLINECODE${inlineIndex++}\x00`; }); // Extract plain URLs before escaping so & in query strings is preserved const urls = []; let urlIndex = 0; html = html.replace(/(^|[\s])((?:https?|file):\/\/[^\s]+)/gm, (match, prefix, url) => { urls.push(url); return prefix + `\x00URL${urlIndex++}\x00`; }); html = html.replace(/&/g, '&') .replace(//g, '>'); html = html.replace(/^### (.*?)$/gm, '

$1

'); html = html.replace(/^## (.*?)$/gm, '

$1

'); html = html.replace(/^# (.*?)$/gm, '

$1

'); // Bold and italic (order matters!) html = html.replace(/\*\*\*(.*?)\*\*\*/g, '$1'); html = html.replace(/\*\*(.*?)\*\*/g, '$1'); html = html.replace(/\*(.*?)\*/g, '$1'); html = html.replace(/___(.*?)___/g, '$1'); html = html.replace(/__(.*?)__/g, '$1'); html = html.replace(/_(.*?)_/g, '$1'); html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '$1'); html = html.replace(/^\* (.*?)$/gm, '
  • $1
  • '); html = html.replace(/^- (.*?)$/gm, '
  • $1
  • '); html = html.replace(/^\d+\. (.*?)$/gm, '
  • $1
  • '); html = html.replace(/(
  • [\s\S]*?<\/li>\s*)+/g, function(match) { return ''; }); html = html.replace(/\x00URL(\d+)\x00/g, (_, index) => { const url = urls[parseInt(index)]; const display = url.replace(/&/g, '&').replace(//g, '>'); return `${display}`; }); // Restore code blocks and inline code BEFORE line break processing html = html.replace(/\x00CODEBLOCK(\d+)\x00/g, (match, index) => { return codeBlocks[parseInt(index)]; }); html = html.replace(/\x00INLINECODE(\d+)\x00/g, (match, index) => { return inlineCode[parseInt(index)]; }); html = html.replace(/\n\n/g, '

    '); html = html.replace(/\n/g, '
    '); if (!html.startsWith('<')) { html = '

    ' + html + '

    '; } html = html.replace(/\s*
    /g, '
    ');
        html = html.replace(/\s*
      /g, '
        '); html = html.replace(/\s*/g, ''); html = html.replace(/

        \s*<\/p>/g, ''); html = html.replace(/

        \s*\s*<\/p>/g, ''); html = html.replace(/(){3,}/g, '

        '); html = html.replace(/(<\/p>)\s*(

        )/g, '$1$2'); html = html.trim(); return html; }