Skip to content

Commit bb3626d

Browse files
feat: implement theme-aware diagram preview rendering pipeline with offline SVG fallbacks
1 parent e91e5e7 commit bb3626d

2 files changed

Lines changed: 272 additions & 30 deletions

File tree

desktop-app/resources/js/script.js

Lines changed: 136 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5552,7 +5552,64 @@ document.addEventListener("DOMContentLoaded", async function () {
55525552
modal.addEventListener('keydown', onKey);
55535553
}
55545554

5555-
function openDiagramModal() {
5555+
function getCleanCode(templateCode) {
5556+
if (!templateCode) return '';
5557+
let clean = templateCode.trim();
5558+
if (clean.startsWith('```')) {
5559+
const firstNewLine = clean.indexOf('\n');
5560+
if (firstNewLine !== -1) {
5561+
clean = clean.substring(firstNewLine + 1);
5562+
}
5563+
}
5564+
if (clean.endsWith('```')) {
5565+
clean = clean.substring(0, clean.length - 3).trim();
5566+
}
5567+
return clean;
5568+
}
5569+
5570+
function getDiagramApiUrl(template, theme) {
5571+
if (typeof pako === 'undefined') {
5572+
return null;
5573+
}
5574+
try {
5575+
const cleanCode = getCleanCode(template.code);
5576+
if (template.category === 'Mermaid') {
5577+
const obj = {
5578+
code: cleanCode,
5579+
mermaid: {
5580+
theme: theme === 'dark' ? 'dark' : 'default'
5581+
}
5582+
};
5583+
const json = JSON.stringify(obj);
5584+
const encoded = encodeKrokiD2(json);
5585+
return `https://mermaid.ink/svg/pako:${encoded}`;
5586+
} else if (template.category === 'PlantUML') {
5587+
const encoded = encodePlantUML(cleanCode);
5588+
return `https://www.plantuml.com/plantuml/svg/${encoded}`;
5589+
} else if (template.category === 'D2') {
5590+
const encoded = encodeKrokiD2(cleanCode);
5591+
const themeParam = theme === 'dark' ? '?theme=200' : '';
5592+
return `https://kroki.io/d2/svg/${encoded}${themeParam}`;
5593+
} else {
5594+
let engine = '';
5595+
switch (template.category) {
5596+
case 'Graphviz': engine = 'graphviz'; break;
5597+
case 'Vega-Lite': engine = 'vegalite'; break;
5598+
case 'ABC Notation': engine = 'abc'; break;
5599+
case 'WaveDrom': engine = 'wavedrom'; break;
5600+
case 'Markmap': engine = 'markmap'; break;
5601+
default: engine = template.category.toLowerCase().replace(/\s+/g, '');
5602+
}
5603+
const encoded = encodeKrokiD2(cleanCode);
5604+
return `https://kroki.io/${engine}/svg/${encoded}`;
5605+
}
5606+
} catch (e) {
5607+
console.warn('Failed to encode diagram for URL:', e);
5608+
return null;
5609+
}
5610+
}
5611+
5612+
async function openDiagramModal() {
55565613
const modal = document.getElementById('diagram-modal');
55575614
const sidebar = modal.querySelector('.diagram-modal-sidebar');
55585615
const grid = document.getElementById('diagram-modal-grid');
@@ -5566,6 +5623,14 @@ document.addEventListener("DOMContentLoaded", async function () {
55665623

55675624
if (!modal || !sidebar || !grid || !emptyMessage || !searchInput || !previewContainer || !previewCode || !confirmBtn || !cancelBtn || !closeBtn) return;
55685625

5626+
if (typeof pako === 'undefined') {
5627+
try {
5628+
await loadScript(CDN.pako);
5629+
} catch (e) {
5630+
console.warn('Failed to load pako library for diagram previews:', e);
5631+
}
5632+
}
5633+
55695634
const start = markdownEditor.selectionStart;
55705635
const end = markdownEditor.selectionEnd;
55715636
modal.style.display = 'flex';
@@ -6320,20 +6385,44 @@ document.addEventListener("DOMContentLoaded", async function () {
63206385
const previewDiv = document.createElement('div');
63216386
previewDiv.className = 'diagram-card-preview';
63226387

6323-
let displayHtml = t.svg;
6324-
if (t.id === 'mermaid-sequence' || t.id === 'mermaid-er') {
6325-
displayHtml = `<div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
6326-
<div style="font-size:10px; font-weight:bold; color:#ff4081; margin-bottom:4px;">${t.title}</div>
6327-
<div style="flex:1; width:100%; display:flex; align-items:center; justify-content:center;">${t.svg}</div>
6328-
</div>`;
6329-
} else {
6330-
displayHtml = `<div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
6331-
<div style="font-size:10px; color:var(--text-color); margin-bottom:4px;">${t.title}</div>
6332-
<div style="flex:1; width:100%; display:flex; align-items:center; justify-content:center;">${t.svg}</div>
6333-
</div>`;
6334-
}
6388+
const isMermaidSpecial = (t.id === 'mermaid-sequence' || t.id === 'mermaid-er');
6389+
const titleColor = isMermaidSpecial ? '#ff4081' : 'var(--text-color)';
6390+
const titleWeight = isMermaidSpecial ? 'bold' : 'normal';
6391+
6392+
previewDiv.innerHTML = `
6393+
<div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
6394+
<div style="font-size:10px; font-weight:${titleWeight}; color:${titleColor}; margin-bottom:4px;">${t.title}</div>
6395+
<div class="diagram-svg-container" style="flex:1; width:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
6396+
${t.svg}
6397+
</div>
6398+
</div>
6399+
`;
63356400

6336-
previewDiv.innerHTML = displayHtml;
6401+
const theme = document.documentElement.getAttribute("data-theme") || "light";
6402+
const apiUrl = getDiagramApiUrl(t, theme);
6403+
6404+
if (apiUrl) {
6405+
const img = document.createElement('img');
6406+
img.style.display = 'none';
6407+
img.style.maxWidth = '100%';
6408+
img.style.maxHeight = '100%';
6409+
img.style.objectFit = 'contain';
6410+
6411+
img.onload = () => {
6412+
const svgContainer = previewDiv.querySelector('.diagram-svg-container');
6413+
if (svgContainer) {
6414+
svgContainer.textContent = '';
6415+
img.style.display = 'block';
6416+
svgContainer.appendChild(img);
6417+
}
6418+
};
6419+
6420+
img.onerror = () => {
6421+
console.warn(`Failed to load card preview from API for ${t.id}. Falling back to local SVG.`);
6422+
};
6423+
6424+
img.src = apiUrl;
6425+
}
63376426

63386427
const labelDiv = document.createElement('div');
63396428
labelDiv.className = 'diagram-card-label';
@@ -6349,8 +6438,40 @@ document.addEventListener("DOMContentLoaded", async function () {
63496438
card.classList.add('is-selected');
63506439

63516440
if (previewCode) previewCode.value = t.code.trim();
6352-
previewContainer.innerHTML = t.svg;
63536441
confirmBtn.disabled = false;
6442+
6443+
// Render bottom preview container with API image & fallback
6444+
previewContainer.innerHTML = `
6445+
<div class="diagram-svg-container" style="width:100%; height:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
6446+
${t.svg}
6447+
</div>
6448+
`;
6449+
6450+
const clickedTheme = document.documentElement.getAttribute("data-theme") || "light";
6451+
const clickedApiUrl = getDiagramApiUrl(t, clickedTheme);
6452+
6453+
if (clickedApiUrl) {
6454+
const previewImg = document.createElement('img');
6455+
previewImg.style.display = 'none';
6456+
previewImg.style.maxWidth = '100%';
6457+
previewImg.style.maxHeight = '100%';
6458+
previewImg.style.objectFit = 'contain';
6459+
6460+
previewImg.onload = () => {
6461+
const svgContainer = previewContainer.querySelector('.diagram-svg-container');
6462+
if (svgContainer) {
6463+
svgContainer.textContent = '';
6464+
previewImg.style.display = 'block';
6465+
svgContainer.appendChild(previewImg);
6466+
}
6467+
};
6468+
6469+
previewImg.onerror = () => {
6470+
console.warn(`Failed to load bottom preview from API for ${t.id}. Falling back to local SVG.`);
6471+
};
6472+
6473+
previewImg.src = clickedApiUrl;
6474+
}
63546475
});
63556476

63566477
grid.appendChild(card);

script.js

Lines changed: 136 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -5552,7 +5552,64 @@ document.addEventListener("DOMContentLoaded", async function () {
55525552
modal.addEventListener('keydown', onKey);
55535553
}
55545554

5555-
function openDiagramModal() {
5555+
function getCleanCode(templateCode) {
5556+
if (!templateCode) return '';
5557+
let clean = templateCode.trim();
5558+
if (clean.startsWith('```')) {
5559+
const firstNewLine = clean.indexOf('\n');
5560+
if (firstNewLine !== -1) {
5561+
clean = clean.substring(firstNewLine + 1);
5562+
}
5563+
}
5564+
if (clean.endsWith('```')) {
5565+
clean = clean.substring(0, clean.length - 3).trim();
5566+
}
5567+
return clean;
5568+
}
5569+
5570+
function getDiagramApiUrl(template, theme) {
5571+
if (typeof pako === 'undefined') {
5572+
return null;
5573+
}
5574+
try {
5575+
const cleanCode = getCleanCode(template.code);
5576+
if (template.category === 'Mermaid') {
5577+
const obj = {
5578+
code: cleanCode,
5579+
mermaid: {
5580+
theme: theme === 'dark' ? 'dark' : 'default'
5581+
}
5582+
};
5583+
const json = JSON.stringify(obj);
5584+
const encoded = encodeKrokiD2(json);
5585+
return `https://mermaid.ink/svg/pako:${encoded}`;
5586+
} else if (template.category === 'PlantUML') {
5587+
const encoded = encodePlantUML(cleanCode);
5588+
return `https://www.plantuml.com/plantuml/svg/${encoded}`;
5589+
} else if (template.category === 'D2') {
5590+
const encoded = encodeKrokiD2(cleanCode);
5591+
const themeParam = theme === 'dark' ? '?theme=200' : '';
5592+
return `https://kroki.io/d2/svg/${encoded}${themeParam}`;
5593+
} else {
5594+
let engine = '';
5595+
switch (template.category) {
5596+
case 'Graphviz': engine = 'graphviz'; break;
5597+
case 'Vega-Lite': engine = 'vegalite'; break;
5598+
case 'ABC Notation': engine = 'abc'; break;
5599+
case 'WaveDrom': engine = 'wavedrom'; break;
5600+
case 'Markmap': engine = 'markmap'; break;
5601+
default: engine = template.category.toLowerCase().replace(/\s+/g, '');
5602+
}
5603+
const encoded = encodeKrokiD2(cleanCode);
5604+
return `https://kroki.io/${engine}/svg/${encoded}`;
5605+
}
5606+
} catch (e) {
5607+
console.warn('Failed to encode diagram for URL:', e);
5608+
return null;
5609+
}
5610+
}
5611+
5612+
async function openDiagramModal() {
55565613
const modal = document.getElementById('diagram-modal');
55575614
const sidebar = modal.querySelector('.diagram-modal-sidebar');
55585615
const grid = document.getElementById('diagram-modal-grid');
@@ -5566,6 +5623,14 @@ document.addEventListener("DOMContentLoaded", async function () {
55665623

55675624
if (!modal || !sidebar || !grid || !emptyMessage || !searchInput || !previewContainer || !previewCode || !confirmBtn || !cancelBtn || !closeBtn) return;
55685625

5626+
if (typeof pako === 'undefined') {
5627+
try {
5628+
await loadScript(CDN.pako);
5629+
} catch (e) {
5630+
console.warn('Failed to load pako library for diagram previews:', e);
5631+
}
5632+
}
5633+
55695634
const start = markdownEditor.selectionStart;
55705635
const end = markdownEditor.selectionEnd;
55715636
modal.style.display = 'flex';
@@ -6320,20 +6385,44 @@ document.addEventListener("DOMContentLoaded", async function () {
63206385
const previewDiv = document.createElement('div');
63216386
previewDiv.className = 'diagram-card-preview';
63226387

6323-
let displayHtml = t.svg;
6324-
if (t.id === 'mermaid-sequence' || t.id === 'mermaid-er') {
6325-
displayHtml = `<div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
6326-
<div style="font-size:10px; font-weight:bold; color:#ff4081; margin-bottom:4px;">${t.title}</div>
6327-
<div style="flex:1; width:100%; display:flex; align-items:center; justify-content:center;">${t.svg}</div>
6328-
</div>`;
6329-
} else {
6330-
displayHtml = `<div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
6331-
<div style="font-size:10px; color:var(--text-color); margin-bottom:4px;">${t.title}</div>
6332-
<div style="flex:1; width:100%; display:flex; align-items:center; justify-content:center;">${t.svg}</div>
6333-
</div>`;
6334-
}
6388+
const isMermaidSpecial = (t.id === 'mermaid-sequence' || t.id === 'mermaid-er');
6389+
const titleColor = isMermaidSpecial ? '#ff4081' : 'var(--text-color)';
6390+
const titleWeight = isMermaidSpecial ? 'bold' : 'normal';
6391+
6392+
previewDiv.innerHTML = `
6393+
<div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
6394+
<div style="font-size:10px; font-weight:${titleWeight}; color:${titleColor}; margin-bottom:4px;">${t.title}</div>
6395+
<div class="diagram-svg-container" style="flex:1; width:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
6396+
${t.svg}
6397+
</div>
6398+
</div>
6399+
`;
63356400

6336-
previewDiv.innerHTML = displayHtml;
6401+
const theme = document.documentElement.getAttribute("data-theme") || "light";
6402+
const apiUrl = getDiagramApiUrl(t, theme);
6403+
6404+
if (apiUrl) {
6405+
const img = document.createElement('img');
6406+
img.style.display = 'none';
6407+
img.style.maxWidth = '100%';
6408+
img.style.maxHeight = '100%';
6409+
img.style.objectFit = 'contain';
6410+
6411+
img.onload = () => {
6412+
const svgContainer = previewDiv.querySelector('.diagram-svg-container');
6413+
if (svgContainer) {
6414+
svgContainer.textContent = '';
6415+
img.style.display = 'block';
6416+
svgContainer.appendChild(img);
6417+
}
6418+
};
6419+
6420+
img.onerror = () => {
6421+
console.warn(`Failed to load card preview from API for ${t.id}. Falling back to local SVG.`);
6422+
};
6423+
6424+
img.src = apiUrl;
6425+
}
63376426

63386427
const labelDiv = document.createElement('div');
63396428
labelDiv.className = 'diagram-card-label';
@@ -6349,8 +6438,40 @@ document.addEventListener("DOMContentLoaded", async function () {
63496438
card.classList.add('is-selected');
63506439

63516440
if (previewCode) previewCode.value = t.code.trim();
6352-
previewContainer.innerHTML = t.svg;
63536441
confirmBtn.disabled = false;
6442+
6443+
// Render bottom preview container with API image & fallback
6444+
previewContainer.innerHTML = `
6445+
<div class="diagram-svg-container" style="width:100%; height:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
6446+
${t.svg}
6447+
</div>
6448+
`;
6449+
6450+
const clickedTheme = document.documentElement.getAttribute("data-theme") || "light";
6451+
const clickedApiUrl = getDiagramApiUrl(t, clickedTheme);
6452+
6453+
if (clickedApiUrl) {
6454+
const previewImg = document.createElement('img');
6455+
previewImg.style.display = 'none';
6456+
previewImg.style.maxWidth = '100%';
6457+
previewImg.style.maxHeight = '100%';
6458+
previewImg.style.objectFit = 'contain';
6459+
6460+
previewImg.onload = () => {
6461+
const svgContainer = previewContainer.querySelector('.diagram-svg-container');
6462+
if (svgContainer) {
6463+
svgContainer.textContent = '';
6464+
previewImg.style.display = 'block';
6465+
svgContainer.appendChild(previewImg);
6466+
}
6467+
};
6468+
6469+
previewImg.onerror = () => {
6470+
console.warn(`Failed to load bottom preview from API for ${t.id}. Falling back to local SVG.`);
6471+
};
6472+
6473+
previewImg.src = clickedApiUrl;
6474+
}
63546475
});
63556476

63566477
grid.appendChild(card);

0 commit comments

Comments
 (0)