// Requires importing: // // // // // // // // // // // // // // // // // // // // // // // When served with lollms, just use // // // // // // // Don't forget to get the css too class MarkdownRenderer { constructor() { this.svgState = {}; this.initDiagramZoomPan(); } initDiagramZoomPan = (id, type) => { if (!this.diagramState) { this.diagramState = {}; } this.diagramState[id] = { scale: 1, translateX: 0, translateY: 0, isDragging: false, startX: 0, startY: 0, type: type }; setTimeout(() => { const container = document.getElementById(id); if (!container) return; container.addEventListener('wheel', (e) => this.handleDiagramWheel(e, id)); container.addEventListener('mousedown', (e) => this.handleDiagramMouseDown(e, id)); container.addEventListener('mousemove', (e) => this.handleDiagramMouseMove(e, id)); container.addEventListener('mouseup', () => this.handleDiagramMouseUp(id)); container.addEventListener('mouseleave', () => this.handleDiagramMouseUp(id)); }, 100); } handleDiagramWheel = (e, id) => { e.preventDefault(); const delta = e.deltaY > 0 ? 0.9 : 1.1; this.zoomDiagram(id, delta); } handleDiagramMouseDown = (e, id) => { const state = this.diagramState[id]; if (!state) return; state.isDragging = true; state.startX = e.clientX - state.translateX; state.startY = e.clientY - state.translateY; } handleDiagramMouseMove = (e, id) => { const state = this.diagramState[id]; if (!state || !state.isDragging) return; state.translateX = e.clientX - state.startX; state.translateY = e.clientY - state.startY; this.updateDiagramTransform(id); } handleDiagramMouseUp = (id) => { if (this.diagramState[id]) { this.diagramState[id].isDragging = false; } } zoomDiagram = (id, delta) => { const state = this.diagramState[id]; if (!state) return; state.scale *= delta; this.updateDiagramTransform(id); } resetDiagramZoomPan = (id) => { const type = this.diagramState[id]?.type; this.diagramState[id] = { scale: 1, translateX: 0, translateY: 0, isDragging: false, startX: 0, startY: 0, type: type }; this.updateDiagramTransform(id); } updateDiagramTransform = (id) => { const container = document.getElementById(id); if (!container) return; const state = this.diagramState[id]; container.style.transform = `translate(${state.translateX}px, ${state.translateY}px) scale(${state.scale})`; } saveDiagramAsPNG(id) { console.log('Starting saveDiagramAsPNG function'); const container = document.getElementById(id); if (!container) { console.error('Container element not found'); alert('Container element not found'); return; } console.log('Container element found:', container); // Find the SVG element within the container const svgElement = container.querySelector('svg'); if (!svgElement) { console.error('SVG element not found within the container'); alert('SVG element not found within the container'); return; } console.log('SVG element found:', svgElement); console.log('SVG element outerHTML:', svgElement.outerHTML); try { // Get SVG data const svgData = new XMLSerializer().serializeToString(svgElement); console.log('Serialized SVG data:', svgData); // Create a data URI const svgDataUri = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData))); console.log('SVG Data URI created'); // Create image const img = new Image(); img.onload = function() { console.log('Image loaded successfully'); console.log('Image dimensions:', img.width, 'x', img.height); const promptResolution = prompt("Enter the desired scaling factor (e.g., 1.5 for 150% resolution):"); if (promptResolution) { const scaleFactor = parseFloat(promptResolution); if (!isNaN(scaleFactor) && scaleFactor > 0) { console.log('Scale factor:', scaleFactor); const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); canvas.width = img.width * scaleFactor; canvas.height = img.height * scaleFactor; console.log('Canvas dimensions:', canvas.width, 'x', canvas.height); ctx.fillStyle = 'white'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.drawImage(img, 0, 0, canvas.width, canvas.height); // Convert canvas to blob and download canvas.toBlob(function(blob) { if (blob) { console.log('Blob created successfully'); const link = document.createElement('a'); link.download = 'graph.png'; link.href = URL.createObjectURL(blob); link.click(); URL.revokeObjectURL(link.href); console.log('Download link clicked'); } else { console.error('Failed to create blob'); alert('Failed to create image. Please try again.'); } }, 'image/png'); } else { console.error('Invalid scale factor:', promptResolution); alert("Invalid scaling factor. Please enter a positive number."); } } else { console.log('User cancelled the prompt'); } }; img.onerror = function() { console.error('Error loading image'); console.log('SVG Data URI:', svgDataUri); alert('Failed to load SVG image. Please check the console for more details.'); }; console.log('Setting image source'); img.src = svgDataUri; } catch (error) { console.error('Error saving diagram as PNG:', error); alert('An error occurred while saving the diagram. Please try again.'); } } saveDiagramAsSVG = (id) => { const svgElement = document.getElementById(id); if (!svgElement) { console.error('SVG element not found'); return; } try { const svgData = new XMLSerializer().serializeToString(svgElement); const svgBlob = new Blob([svgData], {type: 'image/svg+xml;charset=utf-8'}); const downloadLink = document.createElement('a'); downloadLink.href = URL.createObjectURL(svgBlob); downloadLink.download = `diagram-${id}.svg`; document.body.appendChild(downloadLink); downloadLink.click(); document.body.removeChild(downloadLink); URL.revokeObjectURL(downloadLink.href); } catch (error) { console.error('Error saving diagram as SVG:', error); } } async renderMermaidDiagrams(text) { const mermaidCodeRegex = /```mermaid\n([\s\S]*?)```/g; let match; let lastIndex = 0; let result = ''; while ((match = mermaidCodeRegex.exec(text)) !== null) { const mermaidCode = match[1]; const uniqueId = 'mermaid-' + Math.random().toString(36).substr(2, 9); // Add the text before the Mermaid diagram result += text.slice(lastIndex, match.index); try { const renderResult = await mermaid.render(uniqueId, mermaidCode); const htmlCode = `
${numberedLines}
$1'); } handleHorizontalRules(text) { return text.replace(/^(-{3,}|_{3,}|\*{3,})$/gm, '
' + currentParagraph + '
'); currentParagraph = ''; } inCodeBlock = !inCodeBlock; result.push(line); continue; } // If we're in a code block, don't process the line if (inCodeBlock) { result.push(line); continue; } // Check for list items if (trimmedLine.match(/^[-*+]\s/) || trimmedLine.match(/^\d+\.\s/)) { if (currentParagraph) { result.push('' + currentParagraph + '
'); currentParagraph = ''; } if (!inList) { result.push('' + currentParagraph + '
'); currentParagraph = ''; } let level = trimmedLine.match(/^#+/)[0].length; result.push(`' + currentParagraph + '
'); currentParagraph = ''; } result.push('' + currentParagraph + '
'); currentParagraph = ''; } result.push('' + currentParagraph + '
'); } return result.join('\n'); } initMathJax() { // Configure MathJax window.MathJax = { tex: { inlineMath: [['$', '$']], displayMath: [['$$', '$$'], ['\\[', '\\]']] }, svg: { fontCache: 'global' }, startup: { ready: () => { MathJax.startup.defaultReady(); MathJax.startup.promise.then(() => { console.log('MathJax is loaded and ready'); // You can add any post-initialization logic here }); } } }; // Load MathJax if (!window.MathJax) { const script = document.createElement('script'); script.src = 'https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-svg.js'; script.async = true; document.head.appendChild(script); } } async renderMarkdown(text) { // Handle Mermaid graphs first text = await this.renderMermaidDiagrams(text); text = await this.renderGraphvizDiagrams(text); // Handle SVG graphs first text = await this.renderSVG(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.handleLatexEquations(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})`; } 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, "'"); } } // Make sure there is a global variable called mr that instanciate MarkdownRenderer mr = new MarkdownRenderer()