From b8eb52202b5804fe12ee4a1ecab941dedfdccbe6 Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:42:59 +0530 Subject: [PATCH 1/7] fix(mermaid): prevent diagrams from breaking on theme change --- script.js | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/script.js b/script.js index a74971a..6989da9 100644 --- a/script.js +++ b/script.js @@ -696,7 +696,7 @@ document.addEventListener("DOMContentLoaded", function () { .replace(/&/g, "&") .replace(//g, ">"); - return `
${escapedCode}
`; + return `
${escapedCode}
`; } const validLanguage = hljs.getLanguage(language) ? language : "plaintext"; @@ -1564,7 +1564,7 @@ document.addEventListener("DOMContentLoaded", function () { const html = tableHtml + marked.parse(referenceData.cleanedMarkdown); const sanitizedHtml = DOMPurify.sanitize(html, { ADD_TAGS: ['mjx-container', 'input'], - ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled'], + ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled', 'data-original-code'], ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel|blob):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i }); markdownPreview.innerHTML = sanitizedHtml; @@ -5534,9 +5534,22 @@ document.addEventListener("DOMContentLoaded", function () { if (mermaidNodes.length > 0) { // Clear existing rendered Mermaid SVGs and re-render with new theme mermaidNodes.forEach(function(node) { + const originalCode = node.getAttribute('data-original-code'); + if (originalCode) { + const decodedCode = decodeURIComponent(originalCode); + const escapedCode = decodedCode + .replace(/&/g, "&") + .replace(//g, ">"); + node.innerHTML = escapedCode; + } node.removeAttribute('data-processed'); const container = node.closest('.mermaid-container'); - if (container) container.classList.add('is-loading'); + if (container) { + container.classList.add('is-loading'); + const oldToolbar = container.querySelector('.mermaid-toolbar'); + if (oldToolbar) oldToolbar.remove(); + } }); Promise.resolve(mermaid.init(undefined, mermaidNodes)) .then(function() { @@ -5700,7 +5713,7 @@ document.addEventListener("DOMContentLoaded", function () { const html = tableHtml + marked.parse(referenceData.cleanedMarkdown); const sanitizedHtml = DOMPurify.sanitize(html, { ADD_TAGS: ['mjx-container', 'input'], - ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled'] + ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled', 'data-original-code'] }); const tempContainer = document.createElement("div"); tempContainer.innerHTML = sanitizedHtml; @@ -6400,7 +6413,7 @@ document.addEventListener("DOMContentLoaded", function () { const html = marked.parse(markdown); const sanitizedHtml = DOMPurify.sanitize(html, { ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'], - ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled'] + ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code'] }); const tempElement = document.createElement("div"); From 83dcff5511fab75a10f43a3a1b316bbdf4eff1f1 Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:43:53 +0530 Subject: [PATCH 2/7] chore(desktop): sync desktop-app resources with web-app fixes --- desktop-app/resources/index.html | 13 +- desktop-app/resources/js/script.js | 396 ++++++++++++++++++++++++++--- desktop-app/resources/styles.css | 7 + 3 files changed, 373 insertions(+), 43 deletions(-) diff --git a/desktop-app/resources/index.html b/desktop-app/resources/index.html index b02882e..873b4ac 100644 --- a/desktop-app/resources/index.html +++ b/desktop-app/resources/index.html @@ -212,9 +212,6 @@

Menu

Share -
@@ -317,15 +314,15 @@

Menu

diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 598c105..6989da9 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -56,6 +56,16 @@ document.addEventListener("DOMContentLoaded", function () { let activeFindIndex = -1; let lastFindQuery = ''; + // Custom Editor History State Manager variables + const tabHistories = {}; + let currentHistoryTabId = null; + let lastPushedValue = ''; + let typingTimeout = null; + let lastInputType = null; // 'insert', 'delete', 'programmatic', or null + let lastCursorStart = 0; + let lastCursorEnd = 0; + let pendingState = null; + const markdownEditor = document.getElementById("markdown-editor"); const markdownPreview = document.getElementById("markdown-preview"); const markdownFormatToolbar = document.getElementById("markdown-format-toolbar"); @@ -106,7 +116,6 @@ document.addEventListener("DOMContentLoaded", function () { const mobileExportPdf = document.getElementById("mobile-export-pdf"); const mobileCopyMarkdown = document.getElementById("mobile-copy-markdown"); const mobileThemeToggle = document.getElementById("mobile-theme-toggle"); - const mobileDirectionToggle = document.getElementById("mobile-direction-toggle"); const shareButton = document.getElementById("share-button"); const mobileShareButton = document.getElementById("mobile-share-button"); const githubImportModal = document.getElementById("github-import-modal"); @@ -264,12 +273,6 @@ document.addEventListener("DOMContentLoaded", function () { directionToggle.setAttribute("aria-label", toggleLabel); directionToggle.setAttribute("aria-pressed", isRtl.toString()); } - if (mobileDirectionToggle) { - const icon = isRtl - ? '' - : ''; - mobileDirectionToggle.innerHTML = `${icon} ${toggleLabel}`; - } } const savedDirection = loadGlobalState().direction; @@ -693,7 +696,7 @@ document.addEventListener("DOMContentLoaded", function () { .replace(/&/g, "&") .replace(//g, ">"); - return `
${escapedCode}
`; + return `
${escapedCode}
`; } const validLanguage = hljs.getLanguage(language) ? language : "plaintext"; @@ -1277,11 +1280,26 @@ document.addEventListener("DOMContentLoaded", function () { function switchTab(tabId) { if (tabId === activeTabId) return; saveCurrentTabState(); + + // Clear typing timeout and reset tracking for the new tab + if (typingTimeout) { + clearTimeout(typingTimeout); + typingTimeout = null; + } + lastInputType = null; + pendingState = null; + activeTabId = tabId; saveActiveTabId(activeTabId); const tab = tabs.find(function(t) { return t.id === tabId; }); if (!tab) return; markdownEditor.value = tab.content; + + initTabHistory(tabId, tab.content); + lastPushedValue = tab.content; + currentHistoryTabId = tabId; + updateUndoRedoButtons(); + restoreViewMode(tab.viewMode); renderMarkdown(); requestAnimationFrame(function() { @@ -1306,6 +1324,12 @@ document.addEventListener("DOMContentLoaded", function () { function closeTab(tabId) { const idx = tabs.findIndex(function(t) { return t.id === tabId; }); if (idx === -1) return; + + // Clean up history of the closed tab + if (tabHistories[tabId]) { + delete tabHistories[tabId]; + } + tabs.splice(idx, 1); if (tabs.length === 0) { // Auto-create new "Untitled" when last tab is deleted @@ -1472,6 +1496,8 @@ document.addEventListener("DOMContentLoaded", function () { } const activeTab = tabs.find(function(t) { return t.id === activeTabId; }); markdownEditor.value = activeTab.content; + initTabHistory(activeTabId, activeTab.content); + updateUndoRedoButtons(); restoreViewMode(activeTab.viewMode); renderMarkdown(); const editorPane = document.querySelector('.editor-pane'); @@ -1538,7 +1564,7 @@ document.addEventListener("DOMContentLoaded", function () { const html = tableHtml + marked.parse(referenceData.cleanedMarkdown); const sanitizedHtml = DOMPurify.sanitize(html, { ADD_TAGS: ['mjx-container', 'input'], - ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled'], + ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled', 'data-original-code'], ALLOWED_URI_REGEXP: /^(?:(?:https?|mailto|tel|blob):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i }); markdownPreview.innerHTML = sanitizedHtml; @@ -2389,12 +2415,15 @@ document.addEventListener("DOMContentLoaded", function () { } function replaceEditorRange(start, end, replacement, selectStart, selectEnd) { + pushProgrammaticHistoryState(); markdownEditor.focus(); markdownEditor.setRangeText(replacement, start, end, 'end'); const nextStart = typeof selectStart === 'number' ? selectStart : start + replacement.length; const nextEnd = typeof selectEnd === 'number' ? selectEnd : nextStart; markdownEditor.setSelectionRange(nextStart, nextEnd); markdownEditor.dispatchEvent(new Event('input', { bubbles: true })); + lastPushedValue = markdownEditor.value; + lastInputType = 'programmatic'; } function wrapEditorSelection(prefix, suffix, placeholder) { @@ -2598,6 +2627,286 @@ document.addEventListener("DOMContentLoaded", function () { .replace(/`([^`]+)`/g, '$1'); } + function getOrCreateTabHistory(tabId) { + if (!tabId) return { undoStack: [], redoStack: [] }; + if (!tabHistories[tabId]) { + tabHistories[tabId] = { + undoStack: [], + redoStack: [] + }; + } + return tabHistories[tabId]; + } + + function initTabHistory(tabId, initialValue) { + const hist = getOrCreateTabHistory(tabId); + if (hist.undoStack.length === 0) { + hist.undoStack.push({ + value: initialValue || '', + selectionStart: 0, + selectionEnd: 0 + }); + lastPushedValue = initialValue || ''; + currentHistoryTabId = tabId; + pendingState = null; + } + } + + function pushProgrammaticHistoryState() { + if (typingTimeout) { + clearTimeout(typingTimeout); + typingTimeout = null; + } + + const tabId = activeTabId; + const hist = getOrCreateTabHistory(tabId); + const currentValue = markdownEditor.value; + + if (pendingState) { + hist.undoStack.push(pendingState); + if (hist.undoStack.length > 200) { + hist.undoStack.shift(); + } + hist.redoStack.length = 0; + pendingState = null; + lastPushedValue = currentValue; + } else if (currentValue !== lastPushedValue) { + hist.undoStack.push({ + value: currentValue, + selectionStart: markdownEditor.selectionStart, + selectionEnd: markdownEditor.selectionEnd + }); + if (hist.undoStack.length > 200) { + hist.undoStack.shift(); + } + hist.redoStack.length = 0; + lastPushedValue = currentValue; + } + updateUndoRedoButtons(); + } + + function commitPendingState() { + if (typingTimeout) { + clearTimeout(typingTimeout); + typingTimeout = null; + } + if (!pendingState) return; + + const tabId = activeTabId; + const hist = getOrCreateTabHistory(tabId); + + hist.undoStack.push(pendingState); + if (hist.undoStack.length > 200) { + hist.undoStack.shift(); + } + + hist.redoStack.length = 0; + lastPushedValue = markdownEditor.value; + pendingState = null; + updateUndoRedoButtons(); + } + + function handleKeystrokeHistory(e) { + const currentValue = markdownEditor.value; + if (currentValue === lastPushedValue) return; + + const inputType = e ? e.inputType : ''; + + if (!pendingState) { + pendingState = { + value: lastPushedValue, + selectionStart: lastCursorStart, + selectionEnd: lastCursorEnd + }; + } + + let shouldCommit = false; + + if (inputType === 'insertLineBreak' || inputType === 'insertParagraph' || inputType === 'insertFromPaste' || lastInputType === 'programmatic') { + shouldCommit = true; + } else if (e && e.data === ' ') { + shouldCommit = true; + } else { + const isDelete = inputType.startsWith('delete'); + const wasDelete = lastInputType === 'delete'; + const isInsert = inputType.startsWith('insert'); + const wasInsert = lastInputType === 'insert'; + + if ((isDelete && wasInsert) || (isInsert && wasDelete)) { + shouldCommit = true; + } + } + + if (shouldCommit) { + commitPendingState(); + } + + if (typingTimeout) { + clearTimeout(typingTimeout); + } + typingTimeout = setTimeout(function() { + commitPendingState(); + }, 1000); + + if (inputType.startsWith('delete')) { + lastInputType = 'delete'; + } else if (inputType.startsWith('insert')) { + lastInputType = 'insert'; + } else { + lastInputType = 'other'; + } + } + + function updateLastCursor() { + if (markdownEditor) { + lastCursorStart = markdownEditor.selectionStart; + lastCursorEnd = markdownEditor.selectionEnd; + } + } + + function updateUndoRedoButtons() { + const undoBtn = document.querySelector('[data-md-action="undo"]'); + const redoBtn = document.querySelector('[data-md-action="redo"]'); + if (!undoBtn || !redoBtn) return; + + const tabId = activeTabId; + const hist = getOrCreateTabHistory(tabId); + + const canUndo = hist.undoStack.length > 0 || pendingState !== null; + const canRedo = hist.redoStack.length > 0; + + undoBtn.disabled = !canUndo; + undoBtn.classList.toggle('disabled', !canUndo); + + redoBtn.disabled = !canRedo; + redoBtn.classList.toggle('disabled', !canRedo); + } + + function executeUndo() { + if (typingTimeout) { + clearTimeout(typingTimeout); + typingTimeout = null; + } + + const tabId = activeTabId; + const hist = getOrCreateTabHistory(tabId); + const currentValue = markdownEditor.value; + + let stateToRestore = null; + + if (pendingState) { + stateToRestore = pendingState; + pendingState = null; + + hist.redoStack.push({ + value: currentValue, + selectionStart: markdownEditor.selectionStart, + selectionEnd: markdownEditor.selectionEnd + }); + if (hist.redoStack.length > 200) { + hist.redoStack.shift(); + } + } else if (hist.undoStack.length > 0) { + const topState = hist.undoStack.pop(); + if (topState) { + stateToRestore = topState; + + hist.redoStack.push({ + value: currentValue, + selectionStart: markdownEditor.selectionStart, + selectionEnd: markdownEditor.selectionEnd + }); + if (hist.redoStack.length > 200) { + hist.redoStack.shift(); + } + } + } + + if (stateToRestore) { + markdownEditor.value = stateToRestore.value; + markdownEditor.setSelectionRange(stateToRestore.selectionStart, stateToRestore.selectionEnd); + lastPushedValue = stateToRestore.value; + lastInputType = null; + + markdownEditor.dispatchEvent(new Event('input', { bubbles: true })); + saveCurrentTabState(); + } + + updateUndoRedoButtons(); + } + + function executeRedo() { + if (typingTimeout) { + clearTimeout(typingTimeout); + typingTimeout = null; + } + + const tabId = activeTabId; + const hist = getOrCreateTabHistory(tabId); + const currentValue = markdownEditor.value; + + if (hist.redoStack.length > 0) { + const stateToRestore = hist.redoStack.pop(); + + hist.undoStack.push({ + value: currentValue, + selectionStart: markdownEditor.selectionStart, + selectionEnd: markdownEditor.selectionEnd + }); + if (hist.undoStack.length > 200) { + hist.undoStack.shift(); + } + + markdownEditor.value = stateToRestore.value; + markdownEditor.setSelectionRange(stateToRestore.selectionStart, stateToRestore.selectionEnd); + lastPushedValue = stateToRestore.value; + lastInputType = null; + pendingState = null; + + markdownEditor.dispatchEvent(new Event('input', { bubbles: true })); + saveCurrentTabState(); + } + + updateUndoRedoButtons(); + } + + function stripMarkdownFormatting(text) { + if (!text) return ''; + return text + // Remove fenced code block syntax + .replace(/^```[a-zA-Z0-9-]*\r?\n?/gm, '') + .replace(/```\r?$/gm, '') + // Remove reference link definitions (e.g., [id]: url "title") + .replace(/^\[[^\]]+\]:\s*\S+(?:\s+(?:"[^"]*"|'[^']*'|\([^)]*\)))?\s*$/gm, '') + // Strip basic markdown constructs (headers, blockquotes, lists, bold, italic, strikethrough, code) + .replace(/^#{1,6}\s+/gm, '') + .replace(/^>\s?/gm, '') + .replace(/^(\s*)([-*+]|\d+\.)\s+/gm, '$1') + .replace(/!\[([^\]]*)\]\([^)]*\)/g, '$1') + .replace(/\[([^\]]+)\]\([^)]*\)/g, '$1') + // HTML alignment tags or custom tags (strip the tags, keep inner text) + .replace(/<[^>]+>/g, '') + // Bold, Italic, Strikethrough, Inline code + .replace(/(\*\*|__)(.*?)\1/g, '$2') + .replace(/(\*|_)(.*?)\1/g, '$2') + .replace(/~~(.*?)~~/g, '$1') + .replace(/`([^`]+)`/g, '$1') + // Remove horizontal rules + .replace(/^\s*[-*_]{3,}\s*$/gm, ''); + } + + function applyClearFormatting() { + const fullText = markdownEditor.value; + pushProgrammaticHistoryState(); + replaceEditorRange(0, fullText.length, '', 0, 0); + + // Force immediate visual rendering and gutter update + renderMarkdown(); + updateLineNumbers(); + updateFindHighlights(); + saveCurrentTabState(); + } + function toTitleCase(text) { return text.toLowerCase().replace(/\b\w/g, function(letter) { return letter.toUpperCase(); @@ -4795,10 +5104,12 @@ document.addEventListener("DOMContentLoaded", function () { } function runMarkdownTool(action, button) { - if (action === 'undo' || action === 'redo') { - markdownEditor.focus(); - document.execCommand(action); - markdownEditor.dispatchEvent(new Event('input', { bubbles: true })); + if (action === 'undo') { + executeUndo(); + return; + } + if (action === 'redo') { + executeRedo(); return; } @@ -5037,19 +5348,6 @@ document.addEventListener("DOMContentLoaded", function () { mobileExportHtml.addEventListener("click", () => exportHtml.click()); mobileExportPdf.addEventListener("click", () => exportPdf.click()); mobileCopyMarkdown.addEventListener("click", () => copyMarkdownButton.click()); - if (mobileDirectionToggle) { - mobileDirectionToggle.addEventListener("click", () => { - if (directionToggle) { - directionToggle.click(); - } else { - const currentDir = markdownEditor ? markdownEditor.getAttribute("dir") : "ltr"; - const direction = currentDir === "rtl" ? "ltr" : "rtl"; - applyDirectionToContent(direction); - saveGlobalState({ direction }); - updateDirectionToggleUI(direction); - } - }); - } mobileThemeToggle.addEventListener("click", () => { themeToggle.click(); mobileThemeToggle.innerHTML = themeToggle.innerHTML + " Toggle Dark Mode"; @@ -5143,7 +5441,8 @@ document.addEventListener("DOMContentLoaded", function () { }); }); - markdownEditor.addEventListener("input", function() { + markdownEditor.addEventListener("input", function(e) { + handleKeystrokeHistory(e); debouncedRender(); clearTimeout(saveTabStateTimeout); saveTabStateTimeout = setTimeout(saveCurrentTabState, 500); @@ -5155,6 +5454,12 @@ document.addEventListener("DOMContentLoaded", function () { scheduleLineNumberUpdate(); }); + markdownEditor.addEventListener('keydown', updateLastCursor); + markdownEditor.addEventListener('keyup', updateLastCursor); + markdownEditor.addEventListener('mousedown', updateLastCursor); + markdownEditor.addEventListener('mouseup', updateLastCursor); + markdownEditor.addEventListener('focus', updateLastCursor); + initMarkdownFormatToolbar(); initFindReplaceModal(); initAppModals(); @@ -5229,9 +5534,22 @@ document.addEventListener("DOMContentLoaded", function () { if (mermaidNodes.length > 0) { // Clear existing rendered Mermaid SVGs and re-render with new theme mermaidNodes.forEach(function(node) { + const originalCode = node.getAttribute('data-original-code'); + if (originalCode) { + const decodedCode = decodeURIComponent(originalCode); + const escapedCode = decodedCode + .replace(/&/g, "&") + .replace(//g, ">"); + node.innerHTML = escapedCode; + } node.removeAttribute('data-processed'); const container = node.closest('.mermaid-container'); - if (container) container.classList.add('is-loading'); + if (container) { + container.classList.add('is-loading'); + const oldToolbar = container.querySelector('.mermaid-toolbar'); + if (oldToolbar) oldToolbar.remove(); + } }); Promise.resolve(mermaid.init(undefined, mermaidNodes)) .then(function() { @@ -5395,7 +5713,7 @@ document.addEventListener("DOMContentLoaded", function () { const html = tableHtml + marked.parse(referenceData.cleanedMarkdown); const sanitizedHtml = DOMPurify.sanitize(html, { ADD_TAGS: ['mjx-container', 'input'], - ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled'] + ADD_ATTR: ['id', 'class', 'style', 'align', 'type', 'checked', 'disabled', 'data-original-code'] }); const tempContainer = document.createElement("div"); tempContainer.innerHTML = sanitizedHtml; @@ -6095,7 +6413,7 @@ document.addEventListener("DOMContentLoaded", function () { const html = marked.parse(markdown); const sanitizedHtml = DOMPurify.sanitize(html, { ADD_TAGS: ['mjx-container', 'svg', 'path', 'g', 'marker', 'defs', 'pattern', 'clipPath', 'input'], - ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled'] + ADD_ATTR: ['id', 'class', 'style', 'align', 'viewBox', 'd', 'fill', 'stroke', 'transform', 'marker-end', 'marker-start', 'type', 'checked', 'disabled', 'data-original-code'] }); const tempElement = document.createElement("div"); @@ -6571,6 +6889,19 @@ document.addEventListener("DOMContentLoaded", function () { } document.addEventListener("keydown", function (e) { + if (document.activeElement === markdownEditor) { + const isCmdOrCtrl = e.ctrlKey || e.metaKey; + if (isCmdOrCtrl && !e.shiftKey && e.key.toLowerCase() === 'z') { + e.preventDefault(); + executeUndo(); + return; + } else if ((isCmdOrCtrl && e.shiftKey && e.key.toLowerCase() === 'z') || (isCmdOrCtrl && e.key.toLowerCase() === 'y')) { + e.preventDefault(); + executeRedo(); + return; + } + } + if ((e.ctrlKey || e.metaKey) && e.key === "s") { e.preventDefault(); exportMd.click(); @@ -7243,11 +7574,6 @@ document.addEventListener("DOMContentLoaded", function () { const isRtl = document.body.style.direction === 'rtl'; dirToggle.title = isRtl ? dict.switchLtr : dict.switchRtl; } - const mDirToggle = document.getElementById('mobile-direction-toggle'); - if (mDirToggle) { - const isRtl = document.body.style.direction === 'rtl'; - mDirToggle.innerHTML = ` ${isRtl ? dict.switchLtr : dict.switchRtl}`; - } // Modal Titles const modalHelpTitle = document.getElementById('help-modal-title'); diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css index fb88af6..b4814fa 100644 --- a/desktop-app/resources/styles.css +++ b/desktop-app/resources/styles.css @@ -693,6 +693,13 @@ body { background-color: var(--button-active); } +.markdown-tool-btn:disabled, +.markdown-tool-btn.disabled { + opacity: 0.4; + cursor: not-allowed; + pointer-events: none; +} + .markdown-tool-btn i { font-size: 15px; } From 47dab155ac800f8e92ed2749b0c62663eef2efdf Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:46:39 +0530 Subject: [PATCH 3/7] perf(mermaid): defer diagram re-rendering to keep theme transition smooth --- desktop-app/resources/js/script.js | 82 ++++++++++++++++-------------- script.js | 82 ++++++++++++++++-------------- 2 files changed, 88 insertions(+), 76 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 6989da9..e5e5a4a 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -286,6 +286,7 @@ document.addEventListener("DOMContentLoaded", function () { // Track last Mermaid theme to avoid redundant re-initialization (PERF-005) let _lastMermaidTheme = null; + let _mermaidThemeReinitTimeout = null; const initMermaid = (forceReinit) => { if (typeof mermaid === 'undefined') return; // PERF-002: Not loaded yet const currentTheme = document.documentElement.getAttribute("data-theme"); @@ -5528,46 +5529,51 @@ document.addEventListener("DOMContentLoaded", function () { // CSS custom properties handle all other theme transitions automatically. // PERF-002: Guard mermaid re-render — skip if not loaded yet if (typeof mermaid !== 'undefined') { - initMermaid(true); // Force re-init with new theme - try { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); - if (mermaidNodes.length > 0) { - // Clear existing rendered Mermaid SVGs and re-render with new theme - mermaidNodes.forEach(function(node) { - const originalCode = node.getAttribute('data-original-code'); - if (originalCode) { - const decodedCode = decodeURIComponent(originalCode); - const escapedCode = decodedCode - .replace(/&/g, "&") - .replace(//g, ">"); - node.innerHTML = escapedCode; - } - node.removeAttribute('data-processed'); - const container = node.closest('.mermaid-container'); - if (container) { - container.classList.add('is-loading'); - const oldToolbar = container.querySelector('.mermaid-toolbar'); - if (oldToolbar) oldToolbar.remove(); - } - }); - Promise.resolve(mermaid.init(undefined, mermaidNodes)) - .then(function() { - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); - }); - addMermaidToolbars(); - }) - .catch(function(e) { - console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); - }); + if (_mermaidThemeReinitTimeout) { + clearTimeout(_mermaidThemeReinitTimeout); + } + _mermaidThemeReinitTimeout = setTimeout(function() { + initMermaid(true); // Force re-init with new theme + try { + const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); + if (mermaidNodes.length > 0) { + // Clear existing rendered Mermaid SVGs and re-render with new theme + mermaidNodes.forEach(function(node) { + const originalCode = node.getAttribute('data-original-code'); + if (originalCode) { + const decodedCode = decodeURIComponent(originalCode); + const escapedCode = decodedCode + .replace(/&/g, "&") + .replace(//g, ">"); + node.innerHTML = escapedCode; + } + node.removeAttribute('data-processed'); + const container = node.closest('.mermaid-container'); + if (container) { + container.classList.add('is-loading'); + const oldToolbar = container.querySelector('.mermaid-toolbar'); + if (oldToolbar) oldToolbar.remove(); + } }); + Promise.resolve(mermaid.init(undefined, mermaidNodes)) + .then(function() { + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + addMermaidToolbars(); + }) + .catch(function(e) { + console.warn('Mermaid theme re-render failed:', e); + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + }); + } + } catch (e) { + console.warn('Mermaid theme re-render failed:', e); } - } catch (e) { - console.warn('Mermaid theme re-render failed:', e); - } + }, 150); } }); diff --git a/script.js b/script.js index 6989da9..e5e5a4a 100644 --- a/script.js +++ b/script.js @@ -286,6 +286,7 @@ document.addEventListener("DOMContentLoaded", function () { // Track last Mermaid theme to avoid redundant re-initialization (PERF-005) let _lastMermaidTheme = null; + let _mermaidThemeReinitTimeout = null; const initMermaid = (forceReinit) => { if (typeof mermaid === 'undefined') return; // PERF-002: Not loaded yet const currentTheme = document.documentElement.getAttribute("data-theme"); @@ -5528,46 +5529,51 @@ document.addEventListener("DOMContentLoaded", function () { // CSS custom properties handle all other theme transitions automatically. // PERF-002: Guard mermaid re-render — skip if not loaded yet if (typeof mermaid !== 'undefined') { - initMermaid(true); // Force re-init with new theme - try { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); - if (mermaidNodes.length > 0) { - // Clear existing rendered Mermaid SVGs and re-render with new theme - mermaidNodes.forEach(function(node) { - const originalCode = node.getAttribute('data-original-code'); - if (originalCode) { - const decodedCode = decodeURIComponent(originalCode); - const escapedCode = decodedCode - .replace(/&/g, "&") - .replace(//g, ">"); - node.innerHTML = escapedCode; - } - node.removeAttribute('data-processed'); - const container = node.closest('.mermaid-container'); - if (container) { - container.classList.add('is-loading'); - const oldToolbar = container.querySelector('.mermaid-toolbar'); - if (oldToolbar) oldToolbar.remove(); - } - }); - Promise.resolve(mermaid.init(undefined, mermaidNodes)) - .then(function() { - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); - }); - addMermaidToolbars(); - }) - .catch(function(e) { - console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); - }); + if (_mermaidThemeReinitTimeout) { + clearTimeout(_mermaidThemeReinitTimeout); + } + _mermaidThemeReinitTimeout = setTimeout(function() { + initMermaid(true); // Force re-init with new theme + try { + const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); + if (mermaidNodes.length > 0) { + // Clear existing rendered Mermaid SVGs and re-render with new theme + mermaidNodes.forEach(function(node) { + const originalCode = node.getAttribute('data-original-code'); + if (originalCode) { + const decodedCode = decodeURIComponent(originalCode); + const escapedCode = decodedCode + .replace(/&/g, "&") + .replace(//g, ">"); + node.innerHTML = escapedCode; + } + node.removeAttribute('data-processed'); + const container = node.closest('.mermaid-container'); + if (container) { + container.classList.add('is-loading'); + const oldToolbar = container.querySelector('.mermaid-toolbar'); + if (oldToolbar) oldToolbar.remove(); + } }); + Promise.resolve(mermaid.init(undefined, mermaidNodes)) + .then(function() { + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + addMermaidToolbars(); + }) + .catch(function(e) { + console.warn('Mermaid theme re-render failed:', e); + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + }); + } + } catch (e) { + console.warn('Mermaid theme re-render failed:', e); } - } catch (e) { - console.warn('Mermaid theme re-render failed:', e); - } + }, 150); } }); From 676323429edce7ac19d31a960351af5bd6c7f0ca Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:48:46 +0530 Subject: [PATCH 4/7] style(mermaid): implement smooth synchronized fade transitions on theme switch --- desktop-app/resources/js/script.js | 31 ++++++++++++++++++++++-------- desktop-app/resources/styles.css | 13 +++++++++++++ script.js | 31 ++++++++++++++++++++++-------- styles.css | 13 +++++++++++++ 4 files changed, 72 insertions(+), 16 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index e5e5a4a..d23fa1a 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -5529,15 +5529,27 @@ document.addEventListener("DOMContentLoaded", function () { // CSS custom properties handle all other theme transitions automatically. // PERF-002: Guard mermaid re-render — skip if not loaded yet if (typeof mermaid !== 'undefined') { + const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); + if (mermaidNodes.length > 0) { + // Smoothly fade out all diagrams before starting the re-render process + mermaidNodes.forEach(function(node) { + const container = node.closest('.mermaid-container'); + if (container) { + container.classList.add('theme-switching'); + } + }); + } + if (_mermaidThemeReinitTimeout) { clearTimeout(_mermaidThemeReinitTimeout); } + + // Wait 200ms for the fade-out animation to complete, then re-render in the background _mermaidThemeReinitTimeout = setTimeout(function() { initMermaid(true); // Force re-init with new theme try { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); if (mermaidNodes.length > 0) { - // Clear existing rendered Mermaid SVGs and re-render with new theme + // Restore original diagram code to prevent parsing already-rendered SVG as source mermaidNodes.forEach(function(node) { const originalCode = node.getAttribute('data-original-code'); if (originalCode) { @@ -5551,29 +5563,32 @@ document.addEventListener("DOMContentLoaded", function () { node.removeAttribute('data-processed'); const container = node.closest('.mermaid-container'); if (container) { - container.classList.add('is-loading'); const oldToolbar = container.querySelector('.mermaid-toolbar'); if (oldToolbar) oldToolbar.remove(); } }); Promise.resolve(mermaid.init(undefined, mermaidNodes)) .then(function() { - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); + // Smoothly fade the new diagrams back in + markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { + c.classList.remove('theme-switching'); }); addMermaidToolbars(); }) .catch(function(e) { console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); + markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { + c.classList.remove('theme-switching'); }); }); } } catch (e) { console.warn('Mermaid theme re-render failed:', e); + markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { + c.classList.remove('theme-switching'); + }); } - }, 150); + }, 200); // 200ms delay perfectly aligns with our CSS fade transition! } }); diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css index b4814fa..6be0195 100644 --- a/desktop-app/resources/styles.css +++ b/desktop-app/resources/styles.css @@ -3633,8 +3633,21 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } +/* Smooth fade transitions for Mermaid theme switching */ +.mermaid-container .mermaid { + transition: opacity 0.2s ease-in-out; + opacity: 1; +} + +.mermaid-container.theme-switching .mermaid { + opacity: 0 !important; +} + /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { + .mermaid-container .mermaid { + transition: none; + } .skeleton-placeholder, .skeleton-placeholder::after, .mermaid-container.is-loading, diff --git a/script.js b/script.js index e5e5a4a..d23fa1a 100644 --- a/script.js +++ b/script.js @@ -5529,15 +5529,27 @@ document.addEventListener("DOMContentLoaded", function () { // CSS custom properties handle all other theme transitions automatically. // PERF-002: Guard mermaid re-render — skip if not loaded yet if (typeof mermaid !== 'undefined') { + const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); + if (mermaidNodes.length > 0) { + // Smoothly fade out all diagrams before starting the re-render process + mermaidNodes.forEach(function(node) { + const container = node.closest('.mermaid-container'); + if (container) { + container.classList.add('theme-switching'); + } + }); + } + if (_mermaidThemeReinitTimeout) { clearTimeout(_mermaidThemeReinitTimeout); } + + // Wait 200ms for the fade-out animation to complete, then re-render in the background _mermaidThemeReinitTimeout = setTimeout(function() { initMermaid(true); // Force re-init with new theme try { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); if (mermaidNodes.length > 0) { - // Clear existing rendered Mermaid SVGs and re-render with new theme + // Restore original diagram code to prevent parsing already-rendered SVG as source mermaidNodes.forEach(function(node) { const originalCode = node.getAttribute('data-original-code'); if (originalCode) { @@ -5551,29 +5563,32 @@ document.addEventListener("DOMContentLoaded", function () { node.removeAttribute('data-processed'); const container = node.closest('.mermaid-container'); if (container) { - container.classList.add('is-loading'); const oldToolbar = container.querySelector('.mermaid-toolbar'); if (oldToolbar) oldToolbar.remove(); } }); Promise.resolve(mermaid.init(undefined, mermaidNodes)) .then(function() { - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); + // Smoothly fade the new diagrams back in + markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { + c.classList.remove('theme-switching'); }); addMermaidToolbars(); }) .catch(function(e) { console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { - c.classList.remove('is-loading'); + markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { + c.classList.remove('theme-switching'); }); }); } } catch (e) { console.warn('Mermaid theme re-render failed:', e); + markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { + c.classList.remove('theme-switching'); + }); } - }, 150); + }, 200); // 200ms delay perfectly aligns with our CSS fade transition! } }); diff --git a/styles.css b/styles.css index b4814fa..6be0195 100644 --- a/styles.css +++ b/styles.css @@ -3633,8 +3633,21 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } +/* Smooth fade transitions for Mermaid theme switching */ +.mermaid-container .mermaid { + transition: opacity 0.2s ease-in-out; + opacity: 1; +} + +.mermaid-container.theme-switching .mermaid { + opacity: 0 !important; +} + /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { + .mermaid-container .mermaid { + transition: none; + } .skeleton-placeholder, .skeleton-placeholder::after, .mermaid-container.is-loading, From 79c17b86875d05dd75f086a3ada84830b765ccf0 Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:51:31 +0530 Subject: [PATCH 5/7] style(theme): implement unified 300ms full-page theme switch transition --- desktop-app/resources/js/script.js | 16 ++++++++++++++-- desktop-app/resources/styles.css | 12 +++++++++++- script.js | 16 ++++++++++++++-- styles.css | 12 +++++++++++- 4 files changed, 50 insertions(+), 6 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index d23fa1a..525e9bf 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -287,6 +287,7 @@ document.addEventListener("DOMContentLoaded", function () { // Track last Mermaid theme to avoid redundant re-initialization (PERF-005) let _lastMermaidTheme = null; let _mermaidThemeReinitTimeout = null; + let _themeTransitionTimeout = null; const initMermaid = (forceReinit) => { if (typeof mermaid === 'undefined') return; // PERF-002: Not loaded yet const currentTheme = document.documentElement.getAttribute("data-theme"); @@ -5512,6 +5513,13 @@ document.addEventListener("DOMContentLoaded", function () { } themeToggle.addEventListener("click", function () { _lastRenderedContent = null; + + if (_themeTransitionTimeout) { + clearTimeout(_themeTransitionTimeout); + } + // Smoothly transition all background, border, text, fill, and stroke colors on the entire page + document.documentElement.classList.add("theme-transitioning"); + const theme = document.documentElement.getAttribute("data-theme") === "dark" ? "light" @@ -5524,6 +5532,10 @@ document.addEventListener("DOMContentLoaded", function () { } else { themeToggle.innerHTML = ''; } + + _themeTransitionTimeout = setTimeout(function() { + document.documentElement.classList.remove("theme-transitioning"); + }, 300); // PERF-004: Only re-render Mermaid diagrams on theme change instead of full renderMarkdown() // CSS custom properties handle all other theme transitions automatically. @@ -5544,7 +5556,7 @@ document.addEventListener("DOMContentLoaded", function () { clearTimeout(_mermaidThemeReinitTimeout); } - // Wait 200ms for the fade-out animation to complete, then re-render in the background + // Wait 150ms for the fade-out animation to complete, then re-render in the background _mermaidThemeReinitTimeout = setTimeout(function() { initMermaid(true); // Force re-init with new theme try { @@ -5588,7 +5600,7 @@ document.addEventListener("DOMContentLoaded", function () { c.classList.remove('theme-switching'); }); } - }, 200); // 200ms delay perfectly aligns with our CSS fade transition! + }, 150); // 150ms delay perfectly aligns with our 150ms CSS fade transition! } }); diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css index 6be0195..afb141d 100644 --- a/desktop-app/resources/styles.css +++ b/desktop-app/resources/styles.css @@ -3633,9 +3633,15 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } +/* Full-page unified theme transition */ +html.theme-transitioning, +html.theme-transitioning * { + transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease, fill 0.3s ease, stroke 0.3s ease, box-shadow 0.3s ease !important; +} + /* Smooth fade transitions for Mermaid theme switching */ .mermaid-container .mermaid { - transition: opacity 0.2s ease-in-out; + transition: opacity 0.15s ease-in-out; opacity: 1; } @@ -3645,6 +3651,10 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { + html.theme-transitioning, + html.theme-transitioning * { + transition: none !important; + } .mermaid-container .mermaid { transition: none; } diff --git a/script.js b/script.js index d23fa1a..525e9bf 100644 --- a/script.js +++ b/script.js @@ -287,6 +287,7 @@ document.addEventListener("DOMContentLoaded", function () { // Track last Mermaid theme to avoid redundant re-initialization (PERF-005) let _lastMermaidTheme = null; let _mermaidThemeReinitTimeout = null; + let _themeTransitionTimeout = null; const initMermaid = (forceReinit) => { if (typeof mermaid === 'undefined') return; // PERF-002: Not loaded yet const currentTheme = document.documentElement.getAttribute("data-theme"); @@ -5512,6 +5513,13 @@ document.addEventListener("DOMContentLoaded", function () { } themeToggle.addEventListener("click", function () { _lastRenderedContent = null; + + if (_themeTransitionTimeout) { + clearTimeout(_themeTransitionTimeout); + } + // Smoothly transition all background, border, text, fill, and stroke colors on the entire page + document.documentElement.classList.add("theme-transitioning"); + const theme = document.documentElement.getAttribute("data-theme") === "dark" ? "light" @@ -5524,6 +5532,10 @@ document.addEventListener("DOMContentLoaded", function () { } else { themeToggle.innerHTML = ''; } + + _themeTransitionTimeout = setTimeout(function() { + document.documentElement.classList.remove("theme-transitioning"); + }, 300); // PERF-004: Only re-render Mermaid diagrams on theme change instead of full renderMarkdown() // CSS custom properties handle all other theme transitions automatically. @@ -5544,7 +5556,7 @@ document.addEventListener("DOMContentLoaded", function () { clearTimeout(_mermaidThemeReinitTimeout); } - // Wait 200ms for the fade-out animation to complete, then re-render in the background + // Wait 150ms for the fade-out animation to complete, then re-render in the background _mermaidThemeReinitTimeout = setTimeout(function() { initMermaid(true); // Force re-init with new theme try { @@ -5588,7 +5600,7 @@ document.addEventListener("DOMContentLoaded", function () { c.classList.remove('theme-switching'); }); } - }, 200); // 200ms delay perfectly aligns with our CSS fade transition! + }, 150); // 150ms delay perfectly aligns with our 150ms CSS fade transition! } }); diff --git a/styles.css b/styles.css index 6be0195..afb141d 100644 --- a/styles.css +++ b/styles.css @@ -3633,9 +3633,15 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } +/* Full-page unified theme transition */ +html.theme-transitioning, +html.theme-transitioning * { + transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease, fill 0.3s ease, stroke 0.3s ease, box-shadow 0.3s ease !important; +} + /* Smooth fade transitions for Mermaid theme switching */ .mermaid-container .mermaid { - transition: opacity 0.2s ease-in-out; + transition: opacity 0.15s ease-in-out; opacity: 1; } @@ -3645,6 +3651,10 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { + html.theme-transitioning, + html.theme-transitioning * { + transition: none !important; + } .mermaid-container .mermaid { transition: none; } From 2257f22619801c85c34ca69bf8503f7604d1cf3b Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:54:15 +0530 Subject: [PATCH 6/7] perf(theme): revert full-page transitions to restore fast, lightweight color switches --- desktop-app/resources/js/script.js | 15 ++------------- desktop-app/resources/styles.css | 12 +----------- script.js | 15 ++------------- styles.css | 12 +----------- 4 files changed, 6 insertions(+), 48 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 525e9bf..0b4617d 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -5513,13 +5513,6 @@ document.addEventListener("DOMContentLoaded", function () { } themeToggle.addEventListener("click", function () { _lastRenderedContent = null; - - if (_themeTransitionTimeout) { - clearTimeout(_themeTransitionTimeout); - } - // Smoothly transition all background, border, text, fill, and stroke colors on the entire page - document.documentElement.classList.add("theme-transitioning"); - const theme = document.documentElement.getAttribute("data-theme") === "dark" ? "light" @@ -5532,10 +5525,6 @@ document.addEventListener("DOMContentLoaded", function () { } else { themeToggle.innerHTML = ''; } - - _themeTransitionTimeout = setTimeout(function() { - document.documentElement.classList.remove("theme-transitioning"); - }, 300); // PERF-004: Only re-render Mermaid diagrams on theme change instead of full renderMarkdown() // CSS custom properties handle all other theme transitions automatically. @@ -5556,7 +5545,7 @@ document.addEventListener("DOMContentLoaded", function () { clearTimeout(_mermaidThemeReinitTimeout); } - // Wait 150ms for the fade-out animation to complete, then re-render in the background + // Wait 200ms for the fade-out animation to complete, then re-render in the background _mermaidThemeReinitTimeout = setTimeout(function() { initMermaid(true); // Force re-init with new theme try { @@ -5600,7 +5589,7 @@ document.addEventListener("DOMContentLoaded", function () { c.classList.remove('theme-switching'); }); } - }, 150); // 150ms delay perfectly aligns with our 150ms CSS fade transition! + }, 200); // 200ms delay perfectly aligns with our CSS fade transition! } }); diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css index afb141d..6be0195 100644 --- a/desktop-app/resources/styles.css +++ b/desktop-app/resources/styles.css @@ -3633,15 +3633,9 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } -/* Full-page unified theme transition */ -html.theme-transitioning, -html.theme-transitioning * { - transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease, fill 0.3s ease, stroke 0.3s ease, box-shadow 0.3s ease !important; -} - /* Smooth fade transitions for Mermaid theme switching */ .mermaid-container .mermaid { - transition: opacity 0.15s ease-in-out; + transition: opacity 0.2s ease-in-out; opacity: 1; } @@ -3651,10 +3645,6 @@ html.theme-transitioning * { /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { - html.theme-transitioning, - html.theme-transitioning * { - transition: none !important; - } .mermaid-container .mermaid { transition: none; } diff --git a/script.js b/script.js index 525e9bf..0b4617d 100644 --- a/script.js +++ b/script.js @@ -5513,13 +5513,6 @@ document.addEventListener("DOMContentLoaded", function () { } themeToggle.addEventListener("click", function () { _lastRenderedContent = null; - - if (_themeTransitionTimeout) { - clearTimeout(_themeTransitionTimeout); - } - // Smoothly transition all background, border, text, fill, and stroke colors on the entire page - document.documentElement.classList.add("theme-transitioning"); - const theme = document.documentElement.getAttribute("data-theme") === "dark" ? "light" @@ -5532,10 +5525,6 @@ document.addEventListener("DOMContentLoaded", function () { } else { themeToggle.innerHTML = ''; } - - _themeTransitionTimeout = setTimeout(function() { - document.documentElement.classList.remove("theme-transitioning"); - }, 300); // PERF-004: Only re-render Mermaid diagrams on theme change instead of full renderMarkdown() // CSS custom properties handle all other theme transitions automatically. @@ -5556,7 +5545,7 @@ document.addEventListener("DOMContentLoaded", function () { clearTimeout(_mermaidThemeReinitTimeout); } - // Wait 150ms for the fade-out animation to complete, then re-render in the background + // Wait 200ms for the fade-out animation to complete, then re-render in the background _mermaidThemeReinitTimeout = setTimeout(function() { initMermaid(true); // Force re-init with new theme try { @@ -5600,7 +5589,7 @@ document.addEventListener("DOMContentLoaded", function () { c.classList.remove('theme-switching'); }); } - }, 150); // 150ms delay perfectly aligns with our 150ms CSS fade transition! + }, 200); // 200ms delay perfectly aligns with our CSS fade transition! } }); diff --git a/styles.css b/styles.css index afb141d..6be0195 100644 --- a/styles.css +++ b/styles.css @@ -3633,15 +3633,9 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } -/* Full-page unified theme transition */ -html.theme-transitioning, -html.theme-transitioning * { - transition: background-color 0.3s ease, border-color 0.3s ease, color 0.3s ease, fill 0.3s ease, stroke 0.3s ease, box-shadow 0.3s ease !important; -} - /* Smooth fade transitions for Mermaid theme switching */ .mermaid-container .mermaid { - transition: opacity 0.15s ease-in-out; + transition: opacity 0.2s ease-in-out; opacity: 1; } @@ -3651,10 +3645,6 @@ html.theme-transitioning * { /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { - html.theme-transitioning, - html.theme-transitioning * { - transition: none !important; - } .mermaid-container .mermaid { transition: none; } From 533e03b8f15a73ed576bfefb8ee2897b3be632c3 Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Tue, 2 Jun 2026 11:56:48 +0530 Subject: [PATCH 7/7] perf(theme): restore original main branch instant theme changing code with raw source preservation --- desktop-app/resources/js/script.js | 95 ++++++++++++------------------ desktop-app/resources/styles.css | 13 ---- script.js | 95 ++++++++++++------------------ styles.css | 13 ---- 4 files changed, 76 insertions(+), 140 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 0b4617d..3d21538 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -5530,66 +5530,47 @@ document.addEventListener("DOMContentLoaded", function () { // CSS custom properties handle all other theme transitions automatically. // PERF-002: Guard mermaid re-render — skip if not loaded yet if (typeof mermaid !== 'undefined') { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); - if (mermaidNodes.length > 0) { - // Smoothly fade out all diagrams before starting the re-render process - mermaidNodes.forEach(function(node) { - const container = node.closest('.mermaid-container'); - if (container) { - container.classList.add('theme-switching'); - } - }); - } - - if (_mermaidThemeReinitTimeout) { - clearTimeout(_mermaidThemeReinitTimeout); - } - - // Wait 200ms for the fade-out animation to complete, then re-render in the background - _mermaidThemeReinitTimeout = setTimeout(function() { - initMermaid(true); // Force re-init with new theme - try { - if (mermaidNodes.length > 0) { + initMermaid(true); // Force re-init with new theme + try { + const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); + if (mermaidNodes.length > 0) { + // Clear existing rendered Mermaid SVGs and re-render with new theme + mermaidNodes.forEach(function(node) { // Restore original diagram code to prevent parsing already-rendered SVG as source - mermaidNodes.forEach(function(node) { - const originalCode = node.getAttribute('data-original-code'); - if (originalCode) { - const decodedCode = decodeURIComponent(originalCode); - const escapedCode = decodedCode - .replace(/&/g, "&") - .replace(//g, ">"); - node.innerHTML = escapedCode; - } - node.removeAttribute('data-processed'); - const container = node.closest('.mermaid-container'); - if (container) { - const oldToolbar = container.querySelector('.mermaid-toolbar'); - if (oldToolbar) oldToolbar.remove(); - } - }); - Promise.resolve(mermaid.init(undefined, mermaidNodes)) - .then(function() { - // Smoothly fade the new diagrams back in - markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { - c.classList.remove('theme-switching'); - }); - addMermaidToolbars(); - }) - .catch(function(e) { - console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { - c.classList.remove('theme-switching'); - }); - }); - } - } catch (e) { - console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { - c.classList.remove('theme-switching'); + const originalCode = node.getAttribute('data-original-code'); + if (originalCode) { + const decodedCode = decodeURIComponent(originalCode); + const escapedCode = decodedCode + .replace(/&/g, "&") + .replace(//g, ">"); + node.innerHTML = escapedCode; + } + node.removeAttribute('data-processed'); + const container = node.closest('.mermaid-container'); + if (container) { + container.classList.add('is-loading'); + const oldToolbar = container.querySelector('.mermaid-toolbar'); + if (oldToolbar) oldToolbar.remove(); + } }); + Promise.resolve(mermaid.init(undefined, mermaidNodes)) + .then(function() { + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + addMermaidToolbars(); + }) + .catch(function(e) { + console.warn('Mermaid theme re-render failed:', e); + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + }); } - }, 200); // 200ms delay perfectly aligns with our CSS fade transition! + } catch (e) { + console.warn('Mermaid theme re-render failed:', e); + } } }); diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css index 6be0195..b4814fa 100644 --- a/desktop-app/resources/styles.css +++ b/desktop-app/resources/styles.css @@ -3633,21 +3633,8 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } -/* Smooth fade transitions for Mermaid theme switching */ -.mermaid-container .mermaid { - transition: opacity 0.2s ease-in-out; - opacity: 1; -} - -.mermaid-container.theme-switching .mermaid { - opacity: 0 !important; -} - /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { - .mermaid-container .mermaid { - transition: none; - } .skeleton-placeholder, .skeleton-placeholder::after, .mermaid-container.is-loading, diff --git a/script.js b/script.js index 0b4617d..3d21538 100644 --- a/script.js +++ b/script.js @@ -5530,66 +5530,47 @@ document.addEventListener("DOMContentLoaded", function () { // CSS custom properties handle all other theme transitions automatically. // PERF-002: Guard mermaid re-render — skip if not loaded yet if (typeof mermaid !== 'undefined') { - const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); - if (mermaidNodes.length > 0) { - // Smoothly fade out all diagrams before starting the re-render process - mermaidNodes.forEach(function(node) { - const container = node.closest('.mermaid-container'); - if (container) { - container.classList.add('theme-switching'); - } - }); - } - - if (_mermaidThemeReinitTimeout) { - clearTimeout(_mermaidThemeReinitTimeout); - } - - // Wait 200ms for the fade-out animation to complete, then re-render in the background - _mermaidThemeReinitTimeout = setTimeout(function() { - initMermaid(true); // Force re-init with new theme - try { - if (mermaidNodes.length > 0) { + initMermaid(true); // Force re-init with new theme + try { + const mermaidNodes = markdownPreview.querySelectorAll('.mermaid'); + if (mermaidNodes.length > 0) { + // Clear existing rendered Mermaid SVGs and re-render with new theme + mermaidNodes.forEach(function(node) { // Restore original diagram code to prevent parsing already-rendered SVG as source - mermaidNodes.forEach(function(node) { - const originalCode = node.getAttribute('data-original-code'); - if (originalCode) { - const decodedCode = decodeURIComponent(originalCode); - const escapedCode = decodedCode - .replace(/&/g, "&") - .replace(//g, ">"); - node.innerHTML = escapedCode; - } - node.removeAttribute('data-processed'); - const container = node.closest('.mermaid-container'); - if (container) { - const oldToolbar = container.querySelector('.mermaid-toolbar'); - if (oldToolbar) oldToolbar.remove(); - } - }); - Promise.resolve(mermaid.init(undefined, mermaidNodes)) - .then(function() { - // Smoothly fade the new diagrams back in - markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { - c.classList.remove('theme-switching'); - }); - addMermaidToolbars(); - }) - .catch(function(e) { - console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { - c.classList.remove('theme-switching'); - }); - }); - } - } catch (e) { - console.warn('Mermaid theme re-render failed:', e); - markdownPreview.querySelectorAll('.mermaid-container.theme-switching').forEach(function(c) { - c.classList.remove('theme-switching'); + const originalCode = node.getAttribute('data-original-code'); + if (originalCode) { + const decodedCode = decodeURIComponent(originalCode); + const escapedCode = decodedCode + .replace(/&/g, "&") + .replace(//g, ">"); + node.innerHTML = escapedCode; + } + node.removeAttribute('data-processed'); + const container = node.closest('.mermaid-container'); + if (container) { + container.classList.add('is-loading'); + const oldToolbar = container.querySelector('.mermaid-toolbar'); + if (oldToolbar) oldToolbar.remove(); + } }); + Promise.resolve(mermaid.init(undefined, mermaidNodes)) + .then(function() { + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + addMermaidToolbars(); + }) + .catch(function(e) { + console.warn('Mermaid theme re-render failed:', e); + markdownPreview.querySelectorAll('.mermaid-container.is-loading').forEach(function(c) { + c.classList.remove('is-loading'); + }); + }); } - }, 200); // 200ms delay perfectly aligns with our CSS fade transition! + } catch (e) { + console.warn('Mermaid theme re-render failed:', e); + } } }); diff --git a/styles.css b/styles.css index 6be0195..b4814fa 100644 --- a/styles.css +++ b/styles.css @@ -3633,21 +3633,8 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang= animation: skeleton-shimmer 1.6s cubic-bezier(0.4, 0, 0.2, 1) infinite; } -/* Smooth fade transitions for Mermaid theme switching */ -.mermaid-container .mermaid { - transition: opacity 0.2s ease-in-out; - opacity: 1; -} - -.mermaid-container.theme-switching .mermaid { - opacity: 0 !important; -} - /* Accessibility: respect user's motion preferences */ @media (prefers-reduced-motion: reduce) { - .mermaid-container .mermaid { - transition: none; - } .skeleton-placeholder, .skeleton-placeholder::after, .mermaid-container.is-loading,