// Requires importing: // // // // // // // // // // // // // // // // // // // // // // // When served with lollms, just use // Don't forget to get the css too // Make sure there is a global variable called mr that instanciate MarkdownRenderer // mr = new MarkdownRenderer() class MarkdownRenderer { async renderMermaidDiagrams(text) { const mermaidCodeRegex = /```mermaid\n([\s\S]*?)```/g; const matches = text.match(mermaidCodeRegex); if (!matches) return text; for (const match of matches) { const mermaidCode = match.replace(/```mermaid\n/, '').replace(/```$/, ''); const uniqueId = 'mermaid-' + Math.random().toString(36).substr(2, 9); try { const result = await mermaid.render(uniqueId, mermaidCode); const htmlCode = `
${result.svg}
`; text = text.replace(match, htmlCode); } catch (error) { console.error('Mermaid rendering failed:', error); text = text.replace(match, ``); } } return text; } async renderCodeBlocks(text) { if (typeof Prism === 'undefined') { throw new Error('Prism is not loaded. Please include Prism.js in your project.'); } const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g; const renderedText = await text.replace(codeBlockRegex, (match, language, code) => { language = language || 'plaintext'; if (!Prism.languages[language]) { console.warn(`Language '${language}' is not supported by Prism. Falling back to plaintext.`); language = 'plaintext'; } const highlightedCode = Prism.highlight(code.trim(), Prism.languages[language], language); const lines = highlightedCode.split(/\r?\n/); const numberedLines = lines.map((line, index) => `${index + 1}${line}` ).join('\n'); return `
${language}
${numberedLines}
`; }); return renderedText; } handleInlineCode(text) { return text.replace(/`([^`]+)`/g, function(match, code) { return `${code}`; }); } handleMathEquations(text) { return text.replace(/\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$|\$([^\n]+?)\$/g, function(match, p1, p2, p3) { const equation = p1 || p2 || p3; return '' + equation + ''; }); } async handleTables(text) { let alignments = []; let tableRows = []; let isInTable = false; let hasHeader = false; // Process the text line by line text = text.split('\n').map(line => { // Check if the line is a table row if (line.trim().startsWith('|') && line.trim().endsWith('|')) { isInTable = true; const tableRow = line.trim().slice(1, -1); // Remove leading and trailing | const cells = tableRow.split('|').map(cell => cell.trim()); if (cells.every(cell => cell.match(/^:?-+:?$/))) { // This is the header separator row alignments = cells.map(cell => { if (cell.startsWith(':') && cell.endsWith(':')) return 'center'; if (cell.endsWith(':')) return 'right'; return 'left'; }); hasHeader = true; return ''; // Remove separator row } const cellType = !hasHeader ? 'th' : 'td'; const renderedCells = cells.map((cell, cellIndex) => `<${cellType} class="border px-4 py-2" style="text-align: ${alignments[cellIndex] || 'left'};">${cell}` ).join(''); tableRows.push(`${renderedCells}`); return ''; // Remove the original Markdown line } else if (isInTable) { // We've reached the end of the table isInTable = false; hasHeader = false; const tableContent = tableRows.join(''); tableRows = []; // Reset for next table return `${tableContent}
`; } return line; // Return non-table lines unchanged }).join('\n'); // Handle case where table is at the end of the text if (isInTable) { const tableContent = tableRows.join(''); text += `${tableContent}
`; } return text } handleHeaders(text) { return text.replace(/^(#{1,6})\s+(.*?)$/gm, function(match, hashes, content) { const level = hashes.length; const fontSize = 2.5 - (level * 0.3); // Decreasing font size for each level return `${content}`; }); } handleBoldText(text) { return text.replace(/\*\*(.*?)\*\*/g, '$1'); } handleItalicText(text) { return text.replace(/\*(.*?)\*/g, '$1'); } handleLinks(text) { return text.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, '$1'); } handleUnorderedLists(text) { return text.replace(/^\s*[-*+]\s+(.*?)$/gm, '
  • $1
  • ') .replace(/(
  • .*<\/li>)/s, ''); } handleOrderedLists(text) { return text.replace(/^\s*(\d+)\.\s+(.*?)$/gm, '
  • $2
  • ') .replace(/(
  • .*<\/li>)/s, '
      $1
    '); } handleBlockquotes(text) { return text.replace(/^>\s+(.*?)$/gm, '
    $1
    '); } handleHorizontalRules(text) { return text.replace(/^(-{3,}|_{3,}|\*{3,})$/gm, '
    '); } handleParagraphs(text) { //return text.replace(/^(?!<[uo]l|$1

    '); // No need to handle paragraphs separately, they will be handled as the remaining content return text; } async renderMarkdown(text) { // Handle Mermaid graphs first text = await this.renderMermaidDiagrams(text); // Handle code blocks with syntax highlighting and copy button text = await this.renderCodeBlocks(text); // Handle inline code text = this.handleInlineCode(text); // Handle LaTeX-style math equations text = this.handleMathEquations(text); // Handle tables text = await this.handleTables(text); // Handle headers text = this.handleHeaders(text); // Handle bold text text = this.handleBoldText(text); // Handle italic text text = this.handleItalicText(text); // Handle links text = this.handleLinks(text); // Handle unordered lists text = this.handleUnorderedLists(text); // Handle ordered lists text = this.handleOrderedLists(text); // Handle blockquotes text = this.handleBlockquotes(text); // Handle horizontal rules text = this.handleHorizontalRules(text); // Handle paragraphs text = this.handleParagraphs(text); return text; } initMermaid() { if (typeof mermaid !== 'undefined') { mermaid.initialize({ startOnLoad: false }); } else { console.error('Mermaid library is not loaded'); } } initPrism() { if (typeof Prism !== 'undefined') { // No further initialization needed } else { console.error('Prism library is not loaded'); } } // Helper functions for Mermaid and code block handling saveMermaidAsPNG(id) { const svg = document.querySelector(`#${id} svg`); const svgData = new XMLSerializer().serializeToString(svg); // Create a canvas with a higher resolution const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // Set a scale factor for higher resolution (e.g., 2 for double resolution) const scaleFactor = 2; const img = new Image(); img.onload = function() { canvas.width = img.width * scaleFactor; canvas.height = img.height * scaleFactor; // Scale the context to draw the image at a higher resolution ctx.scale(scaleFactor, scaleFactor); ctx.drawImage(img, 0, 0); const pngFile = canvas.toDataURL("image/png"); const downloadLink = document.createElement("a"); downloadLink.download = "mermaid_diagram.png"; downloadLink.href = pngFile; downloadLink.click(); }; img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData))); } saveMermaidAsSVG(id) { const svg = document.querySelector(`#${id} svg`); const svgData = new XMLSerializer().serializeToString(svg); const svgBlob = new Blob([svgData], {type: "image/svg+xml;charset=utf-8"}); const svgUrl = URL.createObjectURL(svgBlob); const downloadLink = document.createElement("a"); downloadLink.href = svgUrl; downloadLink.download = "mermaid_diagram.svg"; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); } zoomMermaid(id, factor) { const svg = document.getElementById(id).firstElementChild; const currentScale = svg.style.transform ? parseFloat(svg.style.transform.replace('scale(', '').replace(')', '')) : 1; const newScale = currentScale * factor; svg.style.transform = `scale(${newScale})`; } copyCode(button) { const codeBlock = button.closest('.code-block-wrapper').querySelector('code'); const codeLines = codeBlock.querySelectorAll('.code-line'); let codeText = ''; codeLines.forEach((line) => { const lineNumber = line.querySelector('.line-number').textContent; const lineContent = line.querySelector('.line-content').textContent; codeText += `${lineNumber} ${lineContent}\n`; }); navigator.clipboard.writeText(codeText.trim()).then(() => { button.classList.add('copied'); button.querySelector('svg').style.display = 'none'; button.innerHTML = 'Copied!'; setTimeout(() => { button.classList.remove('copied'); button.innerHTML = ` Copy `; }, 2000); }).catch(err => { console.error('Failed to copy text: ', err); }); } async highlightCode(code, language) { // Make sure the language is supported by your highlighting library const supportedLanguage = Prism.languages[language] ? language : 'plaintext'; return Prism.highlight(code, Prism.languages[supportedLanguage], supportedLanguage); } // Helper function to escape HTML special characters escapeHtml(unsafe) { if (typeof unsafe !== 'string') { console.log("Found unsafe string:", text) return ''; } return unsafe .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """) .replace(/'/g, "'"); } }