This commit is contained in:
Saifeddine ALOUI 2024-08-27 23:09:18 +02:00
parent dff90d32a4
commit 0c1d255ebd
2 changed files with 97 additions and 89 deletions

View File

@ -37,97 +37,104 @@
class MarkdownRenderer { class MarkdownRenderer {
async renderMermaidDiagrams(text) { async renderMermaidDiagrams(text) {
const mermaidCodeRegex = /```mermaid\n([\s\S]*?)```/g; const mermaidCodeRegex = /```mermaid\n([\s\S]*?)```/g;
const matches = text.match(mermaidCodeRegex); const matches = text.match(mermaidCodeRegex);
if (!matches) return text; // Return original text if no Mermaid code found if (!matches) return text;
for (const match of matches) { for (const match of matches) {
const mermaidCode = match.replace(/```mermaid\n/, '').replace(/```$/, ''); const mermaidCode = match.replace(/```mermaid\n/, '').replace(/```$/, '');
const uniqueId = 'mermaid-' + Math.random().toString(36).substr(2, 9); const uniqueId = 'mermaid-' + Math.random().toString(36).substr(2, 9);
try { try {
const result = await mermaid.render(uniqueId, mermaidCode); const result = await mermaid.render(uniqueId, mermaidCode);
const htmlCode = ` const htmlCode = `
<div class="relative flex justify-center items-center mt-4 mb-4"> <div class="mermaid-container relative flex justify-center items-center mt-4 mb-4 w-full">
<div class="mermaid-diagram" id="${uniqueId}" style="transform-origin: center; transition: transform 0.3s;"> <div class="mermaid-diagram bg-white p-4 rounded-lg shadow-md overflow-auto w-full" style="max-height: 80vh;">
${result.svg} <div id="${uniqueId}" style="transform-origin: top left; transition: transform 0.3s;">
</div> ${result.svg}
<div class="absolute top-0 left-0 flex gap-1 p-1"> </div>
<button onclick="mr.zoomMermaid('${uniqueId}', 1.1)" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center"> </div>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 100 100"> <div class="absolute top-2 right-2 flex gap-1">
<circle cx="40" cy="40" r="25" stroke="black" stroke-width="5" fill="none" /> <button onclick="mr.zoomMermaid('${uniqueId}', 1.1)" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded">
<line x1="60" y1="60" x2="80" y2="80" stroke="black" stroke-width="5" /> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="50" y1="40" x2="30" y2="40" stroke="black" stroke-width="3" /> <circle cx="11" cy="11" r="8"></circle>
<line x1="40" y1="30" x2="40" y2="50" stroke="black" stroke-width="3" /> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</svg> <line x1="11" y1="8" x2="11" y2="14"></line>
</button> <line x1="8" y1="11" x2="14" y2="11"></line>
<button onclick="mr.zoomMermaid('${uniqueId}', 0.9)" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center"> </svg>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 100 100"> </button>
<circle cx="40" cy="40" r="25" stroke="black" stroke-width="5" fill="none" /> <button onclick="mr.zoomMermaid('${uniqueId}', 0.9)" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded">
<line x1="60" y1="60" x2="80" y2="80" stroke="black" stroke-width="5" /> <svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="50" y1="40" x2="30" y2="40" stroke="black" stroke-width="3" /> <circle cx="11" cy="11" r="8"></circle>
</svg> <line x1="21" y1="21" x2="16.65" y2="16.65"></line>
</button> <line x1="8" y1="11" x2="14" y2="11"></line>
<button onclick="mr.saveMermaidAsPNG('${uniqueId}')" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center"> </svg>
PNG </button>
</button> <button onclick="mr.saveMermaidAsPNG('${uniqueId}')" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded">
<button onclick="mr.saveMermaidAsSVG('${uniqueId}')" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center"> PNG
SVG </button>
</button> <button onclick="mr.saveMermaidAsSVG('${uniqueId}')" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded">
</div> SVG
</div> </button>
`; </div>
text = text.replace(match, htmlCode); </div>
} catch (error) { `;
console.error('Mermaid rendering failed:', error); text = text.replace(match, htmlCode);
text = text.replace(match, `<div class="mermaid-error">Failed to render diagram</div>`); } catch (error) {
} console.error('Mermaid rendering failed:', error);
} text = text.replace(match, `<div class="mermaid-error bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative" role="alert">Failed to render diagram</div>`);
}
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; return text;
}
const renderedText = await text.replace(codeBlockRegex, (match, language, code) => {
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'; language = language || 'plaintext';
if (!Prism.languages[language]) { if (!Prism.languages[language]) {
console.warn(`Language '${language}' is not supported by Prism. Falling back to plaintext.`); console.warn(`Language '${language}' is not supported by Prism. Falling back to plaintext.`);
language = 'plaintext'; language = 'plaintext';
} }
const highlightedCode = Prism.highlight(code.trim(), Prism.languages[language], language); const highlightedCode = Prism.highlight(code.trim(), Prism.languages[language], language);
const lines = highlightedCode.split(/\r?\n/); const lines = highlightedCode.split(/\r?\n/);
const numberedLines = lines.map((line, index) => const numberedLines = lines.map((line, index) =>
`<span class="code-line"><span class="line-number">${index + 1}</span><span class="line-content">${line}</span></span>` `<span class="code-line"><span class="line-number">${index + 1}</span><span class="line-content">${line}</span></span>`
).join('\n'); ).join('\n');
return `<div class="code-block-wrapper"> return `
<div class="code-block-header"> <div class="code-block-wrapper bg-gray-100 rounded-lg shadow-md overflow-hidden my-4">
<div class="language-label">${language}</div> <div class="code-block-header bg-gray-200 px-4 py-2 flex justify-between items-center">
<button class="copy-button" onclick="mr.copyCode(this)"> <div class="language-label font-semibold text-gray-700">${language}</div>
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <button class="copy-button bg-blue-500 hover:bg-blue-600 text-white font-bold py-1 px-3 rounded" onclick="mr.copyCode(this)">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="mr-1">
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
</svg> <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
Copy </svg>
</button> Copy
</div> </button>
<pre class="line-numbers"><code class="language-${language}">${numberedLines}</code></pre> </div>
<div class="code-content max-h-[500px] overflow-auto">
<pre class="line-numbers text-sm leading-tight"><code class="language-${language}">${numberedLines}</code></pre>
</div>
</div>`; </div>`;
}); });
return renderedText;
}
return renderedText;
}
handleInlineCode(text) { handleInlineCode(text) {
return text.replace(/`([^`]+)`/g, function(match, code) { return text.replace(/`([^`]+)`/g, function(match, code) {
@ -194,12 +201,14 @@ class MarkdownRenderer {
} }
handleHeaders(text) { handleHeaders(text) {
return text.replace(/^(#{1,6})\s+(.*?)$/gm, function(match, hashes, content) { return text.replace(/^(#{1,6})\s+(.*?)$/gm, function(match, hashes, content) {
const level = hashes.length; const level = hashes.length;
return '<h' + level + '>' + content + '</h' + level + '>'; const fontSize = 2.5 - (level * 0.3); // Decreasing font size for each level
}); return `<h${level} style="font-size: ${fontSize}em; font-weight: bold;">${content}</h${level}>`;
});
} }
handleBoldText(text) { handleBoldText(text) {
return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>'); return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
} }
@ -342,10 +351,10 @@ class MarkdownRenderer {
} }
zoomMermaid(id, factor) { zoomMermaid(id, factor) {
const diagram = document.getElementById(id); const svg = document.getElementById(id).firstElementChild;
const currentScale = diagram.style.transform ? parseFloat(diagram.style.transform.replace('scale(', '').replace(')', '')) : 1; const currentScale = svg.style.transform ? parseFloat(svg.style.transform.replace('scale(', '').replace(')', '')) : 1;
const newScale = currentScale * factor; const newScale = currentScale * factor;
diagram.style.transform = `scale(${newScale})`; svg.style.transform = `scale(${newScale})`;
} }
copyCode(button) { copyCode(button) {
@ -377,7 +386,6 @@ class MarkdownRenderer {
console.error('Failed to copy text: ', err); console.error('Failed to copy text: ', err);
}); });
} }
async highlightCode(code, language) { async highlightCode(code, language) {
// Make sure the language is supported by your highlighting library // Make sure the language is supported by your highlighting library
const supportedLanguage = Prism.languages[language] ? language : 'plaintext'; const supportedLanguage = Prism.languages[language] ? language : 'plaintext';

@ -1 +1 @@
Subproject commit 62d69436d4c9f760d1a36bad5b597bd421201662 Subproject commit 7d86b3e5f5c16ce3834e2791f8e619801fe7463f