From 81584fa5d77bf5789100332d1e78c8f187be8214 Mon Sep 17 00:00:00 2001 From: Baivab Sarkar Date: Wed, 27 May 2026 15:02:23 +0530 Subject: [PATCH] fix(editor): fix global shortcut interception, scroll view centering, floating position resets, and split pane docking reflow --- desktop-app/resources/js/script.js | 123 ++++++++++++++++++++++------- desktop-app/resources/styles.css | 6 +- script.js | 123 ++++++++++++++++++++++------- styles.css | 6 +- 4 files changed, 200 insertions(+), 58 deletions(-) diff --git a/desktop-app/resources/js/script.js b/desktop-app/resources/js/script.js index 5562b70..c25bbb9 100644 --- a/desktop-app/resources/js/script.js +++ b/desktop-app/resources/js/script.js @@ -3951,32 +3951,52 @@ This is a fully client-side application. Your content never leaves your browser }); } + let frPreferredDocked = false; + function toggleFrDockMode() { const panel = document.getElementById('find-replace-modal'); const dockBtn = document.getElementById('find-replace-dock'); - const editorWrapper = document.querySelector('.editor-dock-wrapper') || document.querySelector('.content-container'); - if (!panel || !dockBtn || !editorWrapper) return; + const contentCont = document.querySelector('.content-container'); + if (!panel || !dockBtn || !contentCont) return; isFrDocked = !isFrDocked; + + // Save preference to localStorage + frPreferredDocked = isFrDocked; + localStorage.setItem('find-replace-docked', frPreferredDocked ? 'true' : 'false'); + if (isFrDocked) { panel.classList.add('docked'); panel.style.left = 'auto'; panel.style.top = 'auto'; panel.style.right = 'auto'; - // Append panel to dock container - editorWrapper.appendChild(panel); + + // Append panel to dock container (.content-container) + contentCont.appendChild(panel); + contentCont.classList.add('fr-docked'); + contentCont.style.setProperty('--dock-width', '340px'); + dockBtn.innerHTML = ''; dockBtn.title = "Toggle Floating Mode"; } else { panel.classList.remove('docked'); + // Reset position and float on body document.body.appendChild(panel); - panel.style.top = '100px'; - panel.style.right = '20px'; - panel.style.left = 'auto'; + contentCont.classList.remove('fr-docked'); + contentCont.style.setProperty('--dock-width', '0px'); + + panel.style.top = ''; + panel.style.left = ''; + panel.style.right = ''; + dockBtn.innerHTML = ''; dockBtn.title = "Toggle Dock Mode"; } + + // Ensure display is flex and recalculate split panes + panel.style.display = 'flex'; + applyPaneWidths(); } function updateFindControls() { @@ -4059,16 +4079,21 @@ This is a fully client-side application. Your content never leaves your browser markdownEditor.focus(); markdownEditor.setSelectionRange(match.start, match.end); - // Auto-scroll logic if editor cursor is near find-replace panel - requestAnimationFrame(() => { - const panel = document.getElementById('find-replace-modal'); - if (!isFrDocked && panel && panel.style.display !== 'none') { - const panelRect = panel.getBoundingClientRect(); - // Check if selection coords intersect - // Simplification: if panel is right-aligned, push editor view left if needed, - // or just let native setSelectionRange scroll to cursor naturally. - } - }); + // Explicitly scroll editor to center active match in viewport + try { + const styles = window.getComputedStyle(markdownEditor); + const lineHeight = parseFloat(styles.lineHeight) || 21; + const textBefore = markdownEditor.value.slice(0, match.start); + const lineIndex = textBefore.split('\n').length - 1; + const editorHeight = markdownEditor.clientHeight; + const targetScrollTop = Math.max(0, (lineIndex * lineHeight) - (editorHeight / 2) + (lineHeight / 2)); + + markdownEditor.scrollTop = targetScrollTop; + syncHighlightScroll(); + syncLineNumberScroll(); + } catch (e) { + console.warn("Viewport centering scroll failed:", e); + } } function cycleFindMatch(direction) { @@ -4093,6 +4118,17 @@ This is a fully client-side application. Your content never leaves your browser findReplaceInput.value = selected; } + // Restore docked/floating mode preference + const wasDockedPref = localStorage.getItem('find-replace-docked') === 'true'; + + if (wasDockedPref) { + isFrDocked = false; // Set false so toggleFrDockMode() turns it to true + toggleFrDockMode(); + } else { + isFrDocked = true; // Set true so toggleFrDockMode() turns it to false + toggleFrDockMode(); + } + findReplaceModal.style.display = 'flex'; requestAnimationFrame(function() { @@ -4109,11 +4145,16 @@ This is a fully client-side application. Your content never leaves your browser function closeFindReplaceModal() { isFindModalOpen = false; const panel = document.getElementById('find-replace-modal'); + const contentCont = document.querySelector('.content-container'); if (panel) { panel.style.display = 'none'; if (isFrDocked) { - // Undock and return to body - toggleFrDockMode(); + // Reset split layout styles when closed + if (contentCont) { + contentCont.classList.remove('fr-docked'); + contentCont.style.setProperty('--dock-width', '0px'); + applyPaneWidths(); + } } } findMatches = []; @@ -4587,8 +4628,8 @@ This is a fully client-side application. Your content never leaves your browser if (currentViewMode !== 'split') return; const previewPercent = 100 - editorWidthPercent; - editorPaneElement.style.flex = `0 0 calc(${editorWidthPercent}% - 4px)`; - previewPaneElement.style.flex = `0 0 calc(${previewPercent}% - 4px)`; + editorPaneElement.style.flex = `0 0 calc((100% - var(--dock-width, 0px)) * ${editorWidthPercent / 100} - 4px)`; + previewPaneElement.style.flex = `0 0 calc((100% - var(--dock-width, 0px)) * ${previewPercent / 100} - 4px)`; scheduleLineNumberUpdate(); } @@ -4731,11 +4772,6 @@ This is a fully client-side application. Your content never leaves your browser // Editor key handlers for list continuation and indentation markdownEditor.addEventListener("keydown", function(e) { - if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'f') { - e.preventDefault(); - openFindReplaceModal(); - return; - } if (handleListEnter(e)) { return; } @@ -5954,7 +5990,40 @@ This is a fully client-side application. Your content never leaves your browser if (e.target === shareModal) closeShareModal(); }); document.addEventListener('keydown', function (e) { - if (e.key === 'Escape' && shareModal.classList.contains('is-visible')) closeShareModal(); + if (e.key === 'Escape' && shareModal.classList.contains('is-visible')) { + closeShareModal(); + } + + // Global Ctrl+F / Cmd+F interception + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'f') { + e.preventDefault(); + openFindReplaceModal(); + const findInput = document.getElementById('find-replace-input'); + if (findInput) { + findInput.focus(); + findInput.select(); + } + return; + } + + // Global Ctrl+H / Cmd+H interception + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'h') { + e.preventDefault(); + openFindReplaceModal(); + const replaceInput = document.getElementById('find-replace-with'); + if (replaceInput) { + replaceInput.focus(); + replaceInput.select(); + } + return; + } + + // Global Escape dismissal for find-replace panel + if (e.key === 'Escape' && isFindModalOpen) { + e.preventDefault(); + closeFindReplaceModal(); + return; + } }); shareButton.addEventListener('click', openShareModal); diff --git a/desktop-app/resources/styles.css b/desktop-app/resources/styles.css index 2e47509..e9fc5b3 100644 --- a/desktop-app/resources/styles.css +++ b/desktop-app/resources/styles.css @@ -573,12 +573,14 @@ body { } .find-highlight { - background-color: rgba(255, 196, 0, 0.35); + background-color: var(--fr-match-highlight, rgba(255, 223, 93, 0.4)) !important; border-radius: 2px; + color: transparent !important; } .find-highlight.active { - background-color: rgba(255, 196, 0, 0.65); + background-color: var(--fr-match-active, #ff9b30) !important; + color: transparent !important; } /* Dropdown improvements */ diff --git a/script.js b/script.js index 5562b70..c25bbb9 100644 --- a/script.js +++ b/script.js @@ -3951,32 +3951,52 @@ This is a fully client-side application. Your content never leaves your browser }); } + let frPreferredDocked = false; + function toggleFrDockMode() { const panel = document.getElementById('find-replace-modal'); const dockBtn = document.getElementById('find-replace-dock'); - const editorWrapper = document.querySelector('.editor-dock-wrapper') || document.querySelector('.content-container'); - if (!panel || !dockBtn || !editorWrapper) return; + const contentCont = document.querySelector('.content-container'); + if (!panel || !dockBtn || !contentCont) return; isFrDocked = !isFrDocked; + + // Save preference to localStorage + frPreferredDocked = isFrDocked; + localStorage.setItem('find-replace-docked', frPreferredDocked ? 'true' : 'false'); + if (isFrDocked) { panel.classList.add('docked'); panel.style.left = 'auto'; panel.style.top = 'auto'; panel.style.right = 'auto'; - // Append panel to dock container - editorWrapper.appendChild(panel); + + // Append panel to dock container (.content-container) + contentCont.appendChild(panel); + contentCont.classList.add('fr-docked'); + contentCont.style.setProperty('--dock-width', '340px'); + dockBtn.innerHTML = ''; dockBtn.title = "Toggle Floating Mode"; } else { panel.classList.remove('docked'); + // Reset position and float on body document.body.appendChild(panel); - panel.style.top = '100px'; - panel.style.right = '20px'; - panel.style.left = 'auto'; + contentCont.classList.remove('fr-docked'); + contentCont.style.setProperty('--dock-width', '0px'); + + panel.style.top = ''; + panel.style.left = ''; + panel.style.right = ''; + dockBtn.innerHTML = ''; dockBtn.title = "Toggle Dock Mode"; } + + // Ensure display is flex and recalculate split panes + panel.style.display = 'flex'; + applyPaneWidths(); } function updateFindControls() { @@ -4059,16 +4079,21 @@ This is a fully client-side application. Your content never leaves your browser markdownEditor.focus(); markdownEditor.setSelectionRange(match.start, match.end); - // Auto-scroll logic if editor cursor is near find-replace panel - requestAnimationFrame(() => { - const panel = document.getElementById('find-replace-modal'); - if (!isFrDocked && panel && panel.style.display !== 'none') { - const panelRect = panel.getBoundingClientRect(); - // Check if selection coords intersect - // Simplification: if panel is right-aligned, push editor view left if needed, - // or just let native setSelectionRange scroll to cursor naturally. - } - }); + // Explicitly scroll editor to center active match in viewport + try { + const styles = window.getComputedStyle(markdownEditor); + const lineHeight = parseFloat(styles.lineHeight) || 21; + const textBefore = markdownEditor.value.slice(0, match.start); + const lineIndex = textBefore.split('\n').length - 1; + const editorHeight = markdownEditor.clientHeight; + const targetScrollTop = Math.max(0, (lineIndex * lineHeight) - (editorHeight / 2) + (lineHeight / 2)); + + markdownEditor.scrollTop = targetScrollTop; + syncHighlightScroll(); + syncLineNumberScroll(); + } catch (e) { + console.warn("Viewport centering scroll failed:", e); + } } function cycleFindMatch(direction) { @@ -4093,6 +4118,17 @@ This is a fully client-side application. Your content never leaves your browser findReplaceInput.value = selected; } + // Restore docked/floating mode preference + const wasDockedPref = localStorage.getItem('find-replace-docked') === 'true'; + + if (wasDockedPref) { + isFrDocked = false; // Set false so toggleFrDockMode() turns it to true + toggleFrDockMode(); + } else { + isFrDocked = true; // Set true so toggleFrDockMode() turns it to false + toggleFrDockMode(); + } + findReplaceModal.style.display = 'flex'; requestAnimationFrame(function() { @@ -4109,11 +4145,16 @@ This is a fully client-side application. Your content never leaves your browser function closeFindReplaceModal() { isFindModalOpen = false; const panel = document.getElementById('find-replace-modal'); + const contentCont = document.querySelector('.content-container'); if (panel) { panel.style.display = 'none'; if (isFrDocked) { - // Undock and return to body - toggleFrDockMode(); + // Reset split layout styles when closed + if (contentCont) { + contentCont.classList.remove('fr-docked'); + contentCont.style.setProperty('--dock-width', '0px'); + applyPaneWidths(); + } } } findMatches = []; @@ -4587,8 +4628,8 @@ This is a fully client-side application. Your content never leaves your browser if (currentViewMode !== 'split') return; const previewPercent = 100 - editorWidthPercent; - editorPaneElement.style.flex = `0 0 calc(${editorWidthPercent}% - 4px)`; - previewPaneElement.style.flex = `0 0 calc(${previewPercent}% - 4px)`; + editorPaneElement.style.flex = `0 0 calc((100% - var(--dock-width, 0px)) * ${editorWidthPercent / 100} - 4px)`; + previewPaneElement.style.flex = `0 0 calc((100% - var(--dock-width, 0px)) * ${previewPercent / 100} - 4px)`; scheduleLineNumberUpdate(); } @@ -4731,11 +4772,6 @@ This is a fully client-side application. Your content never leaves your browser // Editor key handlers for list continuation and indentation markdownEditor.addEventListener("keydown", function(e) { - if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'f') { - e.preventDefault(); - openFindReplaceModal(); - return; - } if (handleListEnter(e)) { return; } @@ -5954,7 +5990,40 @@ This is a fully client-side application. Your content never leaves your browser if (e.target === shareModal) closeShareModal(); }); document.addEventListener('keydown', function (e) { - if (e.key === 'Escape' && shareModal.classList.contains('is-visible')) closeShareModal(); + if (e.key === 'Escape' && shareModal.classList.contains('is-visible')) { + closeShareModal(); + } + + // Global Ctrl+F / Cmd+F interception + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'f') { + e.preventDefault(); + openFindReplaceModal(); + const findInput = document.getElementById('find-replace-input'); + if (findInput) { + findInput.focus(); + findInput.select(); + } + return; + } + + // Global Ctrl+H / Cmd+H interception + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'h') { + e.preventDefault(); + openFindReplaceModal(); + const replaceInput = document.getElementById('find-replace-with'); + if (replaceInput) { + replaceInput.focus(); + replaceInput.select(); + } + return; + } + + // Global Escape dismissal for find-replace panel + if (e.key === 'Escape' && isFindModalOpen) { + e.preventDefault(); + closeFindReplaceModal(); + return; + } }); shareButton.addEventListener('click', openShareModal); diff --git a/styles.css b/styles.css index 2e47509..e9fc5b3 100644 --- a/styles.css +++ b/styles.css @@ -573,12 +573,14 @@ body { } .find-highlight { - background-color: rgba(255, 196, 0, 0.35); + background-color: var(--fr-match-highlight, rgba(255, 223, 93, 0.4)) !important; border-radius: 2px; + color: transparent !important; } .find-highlight.active { - background-color: rgba(255, 196, 0, 0.65); + background-color: var(--fr-match-active, #ff9b30) !important; + color: transparent !important; } /* Dropdown improvements */