Skip to content

Commit eb46e6b

Browse files
feat: render dynamic diagram previews in modal using main-thread rendering pipeline
1 parent 06f96f9 commit eb46e6b

1 file changed

Lines changed: 195 additions & 1 deletion

File tree

script.js

Lines changed: 195 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5834,14 +5834,208 @@ document.addEventListener("DOMContentLoaded", async function () {
58345834
cards.forEach(c => c.classList.remove('is-selected'));
58355835
card.classList.add('is-selected');
58365836

5837-
previewContainer.innerHTML = t.svg;
58385837
if (previewCode) previewCode.value = t.code.trim();
5838+
renderSelectedDiagramPreview(previewContainer, t.code);
58395839
confirmBtn.disabled = false;
58405840
});
58415841

58425842
grid.appendChild(card);
58435843
});
58445844
}
5845+
5846+
function renderSelectedDiagramPreview(container, code) {
5847+
const html = marked.parse(code);
5848+
const sanitized = sanitizePreviewHtml(html);
5849+
container.innerHTML = sanitized;
5850+
5851+
const roots = [container];
5852+
5853+
try {
5854+
const mermaidNodes = queryPreviewRoots(roots, '.mermaid');
5855+
if (mermaidNodes.length > 0) {
5856+
const renderMermaidNodes = function() {
5857+
initMermaid(false);
5858+
Promise.resolve(mermaid.init(undefined, mermaidNodes))
5859+
.then(() => {
5860+
addMermaidToolbars();
5861+
})
5862+
.catch((e) => {
5863+
console.warn("Mermaid rendering failed in modal:", e);
5864+
addMermaidToolbars();
5865+
});
5866+
};
5867+
if (typeof mermaid === 'undefined') {
5868+
loadScript(CDN.mermaid).then(function() {
5869+
initMermaid(true);
5870+
renderMermaidNodes();
5871+
}).catch(function(e) { console.warn('Failed to load mermaid:', e); });
5872+
} else {
5873+
renderMermaidNodes();
5874+
}
5875+
}
5876+
} catch (e) {
5877+
console.warn("Mermaid rendering in modal failed:", e);
5878+
}
5879+
5880+
try {
5881+
const abcNodes = queryPreviewRoots(roots, '.abc-notation');
5882+
if (abcNodes.length > 0) {
5883+
abcNodes.forEach(node => {
5884+
const originalCode = node.getAttribute('data-original-code');
5885+
if (!originalCode) return;
5886+
const decodedCode = decodeURIComponent(originalCode);
5887+
try {
5888+
node.innerHTML = '';
5889+
ABCJS.renderAbc(node.id, decodedCode, {
5890+
responsive: "resize",
5891+
add_classes: true
5892+
});
5893+
node.innerHTML = DOMPurify.sanitize(node.innerHTML, PREVIEW_SANITIZE_OPTIONS);
5894+
} catch (err) {
5895+
console.warn("ABC notation rendering failed:", err);
5896+
}
5897+
});
5898+
}
5899+
} catch (e) {
5900+
console.warn("ABC rendering in modal failed:", e);
5901+
}
5902+
5903+
try {
5904+
const plantumlNodes = queryPreviewRoots(roots, '.plantuml-diagram');
5905+
if (plantumlNodes.length > 0) {
5906+
plantumlNodes.forEach(node => {
5907+
const containerEl = node.closest('.plantuml-container');
5908+
const originalCode = node.getAttribute('data-original-code');
5909+
if (!originalCode) return;
5910+
const decodedCode = decodeURIComponent(originalCode);
5911+
try {
5912+
let modifiedCode = decodedCode;
5913+
if (!modifiedCode.toLowerCase().includes('backgroundcolor')) {
5914+
const lines = modifiedCode.split('\n');
5915+
let inserted = false;
5916+
for (let i = 0; i < lines.length; i++) {
5917+
const trimmed = lines[i].trim();
5918+
if (trimmed.startsWith('@start')) {
5919+
lines.splice(i + 1, 0, 'skinparam backgroundColor transparent');
5920+
inserted = true;
5921+
break;
5922+
}
5923+
}
5924+
if (!inserted) {
5925+
modifiedCode = 'skinparam backgroundColor transparent\n' + modifiedCode;
5926+
} else {
5927+
modifiedCode = lines.join('\n');
5928+
}
5929+
}
5930+
const encoded = encodePlantUML(modifiedCode);
5931+
const url = 'https://www.plantuml.com/plantuml/svg/' + encoded;
5932+
5933+
node.innerHTML = '';
5934+
const img = document.createElement('img');
5935+
img.crossOrigin = 'anonymous';
5936+
img.src = url;
5937+
img.alt = 'PlantUML Diagram';
5938+
img.className = 'plantuml-img';
5939+
img.draggable = false;
5940+
img.addEventListener('dragstart', e => e.preventDefault());
5941+
img.onload = function() {
5942+
if (containerEl) containerEl.classList.remove('is-loading');
5943+
addPlantumlToolbars();
5944+
};
5945+
img.onerror = function() {
5946+
node.innerHTML = `<div class="render-error-msg" style="padding: 1.5em; text-align: center; color: var(--text-color);"><i class="bi bi-wifi-off me-2"></i>Offline or unable to connect to PlantUML server</div>`;
5947+
if (containerEl) containerEl.classList.remove('is-loading');
5948+
};
5949+
node.appendChild(img);
5950+
} catch (err) {
5951+
console.error("PlantUML encoding failed:", err);
5952+
}
5953+
});
5954+
}
5955+
} catch (e) {
5956+
console.warn("PlantUML in modal failed:", e);
5957+
}
5958+
5959+
try {
5960+
const d2Nodes = queryPreviewRoots(roots, '.d2-diagram');
5961+
if (d2Nodes.length > 0) {
5962+
d2Nodes.forEach(node => {
5963+
const containerEl = node.closest('.d2-container');
5964+
const originalCode = node.getAttribute('data-original-code');
5965+
if (!originalCode) return;
5966+
const decodedCode = decodeURIComponent(originalCode);
5967+
try {
5968+
let modifiedCode = decodedCode;
5969+
if (!modifiedCode.includes('style.fill') && !/style\s*:\s*\{[^}]*fill/.test(modifiedCode)) {
5970+
modifiedCode = `style.fill: transparent\n${modifiedCode}`;
5971+
}
5972+
const encoded = encodeKrokiD2(modifiedCode);
5973+
const url = 'https://kroki.io/d2/svg/' + encoded;
5974+
5975+
node.innerHTML = '';
5976+
const img = document.createElement('img');
5977+
img.crossOrigin = 'anonymous';
5978+
img.src = url;
5979+
img.alt = 'D2 Diagram';
5980+
img.className = 'd2-img';
5981+
img.draggable = false;
5982+
img.addEventListener('dragstart', e => e.preventDefault());
5983+
img.onload = function() {
5984+
if (containerEl) containerEl.classList.remove('is-loading');
5985+
addD2Toolbars();
5986+
};
5987+
img.onerror = function() {
5988+
node.innerHTML = `<div class="render-error-msg" style="padding: 1.5em; text-align: center; color: var(--text-color);"><i class="bi bi-wifi-off me-2"></i>Offline or unable to connect to Kroki server</div>`;
5989+
if (containerEl) containerEl.classList.remove('is-loading');
5990+
};
5991+
node.appendChild(img);
5992+
} catch (err) {
5993+
console.error("D2 encoding failed:", err);
5994+
}
5995+
});
5996+
}
5997+
} catch (e) {
5998+
console.warn("D2 in modal failed:", e);
5999+
}
6000+
6001+
try {
6002+
const graphvizNodes = queryPreviewRoots(roots, '.graphviz-diagram');
6003+
if (graphvizNodes.length > 0) {
6004+
graphvizNodes.forEach(node => {
6005+
const containerEl = node.closest('.graphviz-container');
6006+
const originalCode = node.getAttribute('data-original-code');
6007+
if (!originalCode) return;
6008+
const decodedCode = decodeURIComponent(originalCode);
6009+
try {
6010+
const encoded = encodeKrokiGraphviz(decodedCode);
6011+
const url = 'https://kroki.io/graphviz/svg/' + encoded;
6012+
6013+
node.innerHTML = '';
6014+
const img = document.createElement('img');
6015+
img.crossOrigin = 'anonymous';
6016+
img.src = url;
6017+
img.alt = 'Graphviz Diagram';
6018+
img.className = 'graphviz-img';
6019+
img.draggable = false;
6020+
img.addEventListener('dragstart', e => e.preventDefault());
6021+
img.onload = function() {
6022+
if (containerEl) containerEl.classList.remove('is-loading');
6023+
addGraphvizToolbars();
6024+
};
6025+
img.onerror = function() {
6026+
node.innerHTML = `<div class="render-error-msg" style="padding: 1.5em; text-align: center; color: var(--text-color);"><i class="bi bi-wifi-off me-2"></i>Offline or unable to connect to Kroki server</div>`;
6027+
if (containerEl) containerEl.classList.remove('is-loading');
6028+
};
6029+
node.appendChild(img);
6030+
} catch (err) {
6031+
console.error("Graphviz encoding failed:", err);
6032+
}
6033+
});
6034+
}
6035+
} catch (e) {
6036+
console.warn("Graphviz in modal failed:", e);
6037+
}
6038+
}
58456039

58466040
renderSidebar();
58476041
renderGrid();

0 commit comments

Comments
 (0)