@@ -3163,15 +3163,17 @@ document.addEventListener("DOMContentLoaded", async function () {
31633163 try {
31643164 const plantumlNodes = queryPreviewRoots(roots, '.plantuml-diagram');
31653165 if (plantumlNodes.length > 0) {
3166- const renderPlantumlNodes = function() {
3166+ const renderPlantumlNodes = async function() {
31673167 if (context.renderId !== previewRenderGeneration) return;
31683168
3169- plantumlNodes.forEach( node => {
3169+ for (const node of plantumlNodes) {
31703170 const container = node.closest('.plantuml-container');
31713171 const originalCode = node.getAttribute('data-original-code');
3172- if (!originalCode) return ;
3172+ if (!originalCode) continue ;
31733173 const decodedCode = decodeURIComponent(originalCode);
31743174
3175+ if (container) container.classList.add('is-loading');
3176+
31753177 try {
31763178 let modifiedCode = decodedCode;
31773179 if (!modifiedCode.toLowerCase().includes('backgroundcolor')) {
@@ -3191,6 +3193,18 @@ document.addEventListener("DOMContentLoaded", async function () {
31913193 modifiedCode = lines.join('\n');
31923194 }
31933195 }
3196+
3197+ // Try local compile first if in Neutralino
3198+ if (typeof Neutralino !== 'undefined') {
3199+ const localSvg = await compileDiagramLocally('plantuml', modifiedCode);
3200+ if (localSvg) {
3201+ node.innerHTML = localSvg;
3202+ if (container) container.classList.remove('is-loading');
3203+ addPlantumlToolbars();
3204+ continue;
3205+ }
3206+ }
3207+
31943208 const encoded = encodePlantUML(modifiedCode);
31953209 const url = 'https://www.plantuml.com/plantuml/svg/' + encoded;
31963210
@@ -3219,7 +3233,7 @@ document.addEventListener("DOMContentLoaded", async function () {
32193233 node.innerHTML = `<div class="render-error-msg" style="padding: 1.5em; text-align: center; color: var(--text-color);">Error encoding diagram: ${escapeHtml(err.message)}</div>`;
32203234 if (container) container.classList.remove('is-loading');
32213235 }
3222- });
3236+ }
32233237 };
32243238
32253239 if (typeof pako === 'undefined') {
@@ -3244,7 +3258,7 @@ document.addEventListener("DOMContentLoaded", async function () {
32443258 try {
32453259 const d2Nodes = queryPreviewRoots(roots, '.d2-diagram');
32463260 if (d2Nodes.length > 0) {
3247- const renderSingleD2Node = function(node) {
3261+ const renderSingleD2Node = async function(node) {
32483262 const container = node.closest('.d2-container');
32493263 const originalCode = node.getAttribute('data-original-code');
32503264 if (!originalCode) return;
@@ -3257,6 +3271,18 @@ document.addEventListener("DOMContentLoaded", async function () {
32573271 if (!modifiedCode.includes('style.fill') && !/style\s*:\s*\{[^}]*fill/.test(modifiedCode)) {
32583272 modifiedCode = `style.fill: transparent\n${modifiedCode}`;
32593273 }
3274+
3275+ // Try local compile first if in Neutralino
3276+ if (typeof Neutralino !== 'undefined') {
3277+ const localSvg = await compileDiagramLocally('d2', modifiedCode);
3278+ if (localSvg) {
3279+ node.innerHTML = localSvg;
3280+ if (container) container.classList.remove('is-loading');
3281+ addD2Toolbars();
3282+ return;
3283+ }
3284+ }
3285+
32603286 const encoded = encodeKrokiD2(modifiedCode);
32613287 const url = 'https://kroki.io/d2/svg/' + encoded;
32623288
@@ -5609,6 +5635,83 @@ document.addEventListener("DOMContentLoaded", async function () {
56095635 }
56105636 }
56115637
5638+ async function compileDiagramLocally(engine, code) {
5639+ if (typeof Neutralino === 'undefined') return null;
5640+ try {
5641+ if (engine === 'd2') {
5642+ const result = await Neutralino.os.execCommand('d2 - -', { stdIn: code });
5643+ if (result && result.exitCode === 0 && result.stdOut) {
5644+ return result.stdOut;
5645+ }
5646+ } else if (engine === 'plantuml') {
5647+ try {
5648+ const result = await Neutralino.os.execCommand('plantuml -pipe -tsvg', { stdIn: code });
5649+ if (result && result.exitCode === 0 && result.stdOut) {
5650+ return result.stdOut;
5651+ }
5652+ } catch (e) {
5653+ const result = await Neutralino.os.execCommand('java -jar plantuml.jar -pipe -tsvg', { stdIn: code });
5654+ if (result && result.exitCode === 0 && result.stdOut) {
5655+ return result.stdOut;
5656+ }
5657+ }
5658+ }
5659+ } catch (e) {
5660+ console.warn(`Local execution for ${engine} failed:`, e);
5661+ }
5662+ return null;
5663+ }
5664+
5665+ async function fetchDiagramPreview(apiUrl) {
5666+ if (typeof caches === 'undefined') {
5667+ const response = await fetch(apiUrl);
5668+ if (!response.ok) throw new Error('Failed to fetch');
5669+ return await response.text();
5670+ }
5671+ const cache = await caches.open('diagram-previews');
5672+ const cachedResponse = await cache.match(apiUrl);
5673+ if (cachedResponse) {
5674+ return await cachedResponse.text();
5675+ }
5676+ const response = await fetch(apiUrl);
5677+ if (!response.ok) throw new Error('Failed to fetch');
5678+ await cache.put(apiUrl, response.clone());
5679+ return await response.text();
5680+ }
5681+
5682+ async function getOrRenderDiagramPreview(template, theme, callback) {
5683+ const cleanCode = getCleanCode(template.code);
5684+
5685+ if (typeof Neutralino !== 'undefined') {
5686+ if (template.category === 'D2') {
5687+ const localSvg = await compileDiagramLocally('d2', cleanCode);
5688+ if (localSvg) {
5689+ callback(localSvg);
5690+ return;
5691+ }
5692+ } else if (template.category === 'PlantUML') {
5693+ const localSvg = await compileDiagramLocally('plantuml', cleanCode);
5694+ if (localSvg) {
5695+ callback(localSvg);
5696+ return;
5697+ }
5698+ }
5699+ }
5700+
5701+ const apiUrl = getDiagramApiUrl(template, theme);
5702+ if (!apiUrl) {
5703+ callback(null);
5704+ return;
5705+ }
5706+
5707+ try {
5708+ const svgText = await fetchDiagramPreview(apiUrl);
5709+ callback(svgText);
5710+ } catch (e) {
5711+ callback(null);
5712+ }
5713+ }
5714+
56125715 async function openDiagramModal() {
56135716 const modal = document.getElementById('diagram-modal');
56145717 const sidebar = modal.querySelector('.diagram-modal-sidebar');
@@ -6388,41 +6491,34 @@ document.addEventListener("DOMContentLoaded", async function () {
63886491 const isMermaidSpecial = (t.id === 'mermaid-sequence' || t.id === 'mermaid-er');
63896492 const titleColor = isMermaidSpecial ? '#ff4081' : 'var(--text-color)';
63906493 const titleWeight = isMermaidSpecial ? 'bold' : 'normal';
6494+ const catClass = t.category.toLowerCase().replace(/\s+/g, '');
63916495
63926496 previewDiv.innerHTML = `
63936497 <div style="display:flex; flex-direction:column; align-items:center; width:100%; height:100%;">
63946498 <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;">
6499+ <div class="diagram-svg-container diagram-svg-${catClass} " style="flex:1; width:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
63966500 ${t.svg}
63976501 </div>
63986502 </div>
63996503 `;
64006504
64016505 const theme = document.documentElement.getAttribute("data-theme") || "light";
6402- const apiUrl = getDiagramApiUrl(t, theme);
64036506
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 = () => {
6507+ getOrRenderDiagramPreview(t, theme, svgText => {
6508+ if (svgText) {
64126509 const svgContainer = previewDiv.querySelector('.diagram-svg-container');
64136510 if (svgContainer) {
6414- svgContainer.textContent = '';
6415- img.style.display = 'block';
6416- svgContainer.appendChild(img);
6511+ svgContainer.innerHTML = svgText;
6512+ const svgEl = svgContainer.querySelector('svg');
6513+ if (svgEl) {
6514+ svgEl.style.maxWidth = '100%';
6515+ svgEl.style.maxHeight = '100%';
6516+ svgEl.style.width = 'auto';
6517+ svgEl.style.height = 'auto';
6518+ }
64176519 }
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- }
6520+ }
6521+ });
64266522
64276523 const labelDiv = document.createElement('div');
64286524 labelDiv.className = 'diagram-card-label';
@@ -6440,38 +6536,31 @@ document.addEventListener("DOMContentLoaded", async function () {
64406536 if (previewCode) previewCode.value = t.code.trim();
64416537 confirmBtn.disabled = false;
64426538
6539+ const catClass = t.category.toLowerCase().replace(/\s+/g, '');
64436540 // Render bottom preview container with API image & fallback
64446541 previewContainer.innerHTML = `
6445- <div class="diagram-svg-container" style="width:100%; height:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
6542+ <div class="diagram-svg-container diagram-svg-${catClass} " style="width:100%; height:100%; display:flex; align-items:center; justify-content:center; overflow:hidden;">
64466543 ${t.svg}
64476544 </div>
64486545 `;
64496546
64506547 const clickedTheme = document.documentElement.getAttribute("data-theme") || "light";
6451- const clickedApiUrl = getDiagramApiUrl(t, clickedTheme);
64526548
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 = () => {
6549+ getOrRenderDiagramPreview(t, clickedTheme, svgText => {
6550+ if (svgText) {
64616551 const svgContainer = previewContainer.querySelector('.diagram-svg-container');
64626552 if (svgContainer) {
6463- svgContainer.textContent = '';
6464- previewImg.style.display = 'block';
6465- svgContainer.appendChild(previewImg);
6553+ svgContainer.innerHTML = svgText;
6554+ const svgEl = svgContainer.querySelector('svg');
6555+ if (svgEl) {
6556+ svgEl.style.maxWidth = '100%';
6557+ svgEl.style.maxHeight = '100%';
6558+ svgEl.style.width = 'auto';
6559+ svgEl.style.height = 'auto';
6560+ }
64666561 }
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- }
6562+ }
6563+ });
64756564 });
64766565
64776566 grid.appendChild(card);
0 commit comments