Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desktop-app/resources/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,7 @@ <h5>Menu</h5>
<div class="find-replace-header" id="find-replace-drag-handle">
<span class="find-replace-title"><i class="bi bi-search me-1"></i> Find &amp; Replace</span>
<div class="find-replace-header-actions">
<button type="button" class="panel-icon-btn" id="find-replace-reset" title="Reset Position" aria-label="Reset Position"><i class="bi bi-arrow-counterclockwise"></i></button>
<button type="button" class="panel-icon-btn" id="find-replace-dock" title="Toggle Dock Mode" aria-label="Toggle Dock Mode"><i class="bi bi-layout-sidebar-reverse"></i></button>
<button type="button" class="panel-icon-btn" id="find-replace-close-icon" aria-label="Close find and replace panel"><i class="bi bi-x-lg"></i></button>
</div>
Expand Down
170 changes: 143 additions & 27 deletions desktop-app/resources/js/script.js
Original file line number Diff line number Diff line change
Expand Up @@ -3915,51 +3915,107 @@ This is a fully client-side application. Your content never leaves your browser
let isFrDocked = false;
let dragOffset = { x: 0, y: 0 };
let isPanelDragging = false;
let lastFloatingLeft = null;
let lastFloatingTop = null;
let lastFloatingRight = null;

function initFindReplacePanelDrag() {
const handle = document.getElementById('find-replace-drag-handle');
const panel = document.getElementById('find-replace-modal');
if (!handle || !panel) return;

handle.addEventListener('mousedown', (e) => {
if (isFrDocked) return;
if (e.target.closest('.find-replace-header-actions')) return;
const startDrag = (clientX, clientY) => {
isPanelDragging = true;
dragOffset.x = e.clientX - panel.offsetLeft;
dragOffset.y = e.clientY - panel.offsetTop;
dragOffset.x = clientX - panel.offsetLeft;
dragOffset.y = clientY - panel.offsetTop;
document.body.classList.add('resizing');
});
};

document.addEventListener('mousemove', (e) => {
if (!isPanelDragging || isFrDocked) return;
const x = e.clientX - dragOffset.x;
const y = e.clientY - dragOffset.y;
const moveDrag = (clientX, clientY) => {
const x = clientX - dragOffset.x;
const y = clientY - dragOffset.y;

// Keep panel inside viewport boundaries
const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;
panel.style.left = `${Math.max(0, Math.min(maxX, x))}px`;
panel.style.top = `${Math.max(0, Math.min(maxY, y))}px`;
const newLeft = `${Math.max(0, Math.min(maxX, x))}px`;
const newTop = `${Math.max(0, Math.min(maxY, y))}px`;
panel.style.left = newLeft;
panel.style.top = newTop;
panel.style.right = 'auto';
});

document.addEventListener('mouseup', () => {
lastFloatingLeft = newLeft;
lastFloatingTop = newTop;
lastFloatingRight = 'auto';
};

const stopDrag = () => {
if (isPanelDragging) {
isPanelDragging = false;
document.body.classList.remove('resizing');
}
};

// Mouse events
handle.addEventListener('mousedown', (e) => {
if (isFrDocked) return;
if (window.innerWidth < 768) return; // Do NOT allow dragging on mobile layouts
if (e.target.closest('.find-replace-header-actions')) return;
startDrag(e.clientX, e.clientY);
});

document.addEventListener('mousemove', (e) => {
if (!isPanelDragging || isFrDocked) return;
moveDrag(e.clientX, e.clientY);
});

document.addEventListener('mouseup', stopDrag);

// Touch events for tablets
handle.addEventListener('touchstart', (e) => {
if (isFrDocked) return;
if (window.innerWidth < 768) return; // Do NOT allow dragging on mobile layouts
if (e.target.closest('.find-replace-header-actions')) return;
if (e.touches && e.touches[0]) {
startDrag(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: true });

document.addEventListener('touchmove', (e) => {
if (!isPanelDragging || isFrDocked) return;
if (e.touches && e.touches[0]) {
moveDrag(e.touches[0].clientX, e.touches[0].clientY);
}
}, { passive: true });

document.addEventListener('touchend', stopDrag);
}

let frPreferredDocked = false;

function toggleFrDockMode(forceFloat = false) {
// If forceFloat is an Event (e.g. from click listener directly), treat as false
if (forceFloat instanceof Event || (forceFloat && typeof forceFloat === 'object')) {
forceFloat = false;
}

const panel = document.getElementById('find-replace-modal');
const dockBtn = document.getElementById('find-replace-dock');
const contentCont = document.querySelector('.content-container');
if (!panel || !dockBtn || !contentCont) return;

if (window.innerWidth <= 768 || forceFloat) {
// Save active element focus and selection before DOM movement
const activeEl = document.activeElement;
const activeId = activeEl ? activeEl.id : null;
const isInput = activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'SELECT' || activeEl.tagName === 'TEXTAREA');
let selStart = 0;
let selEnd = 0;
if (isInput && typeof activeEl.selectionStart === 'number') {
selStart = activeEl.selectionStart;
selEnd = activeEl.selectionEnd;
}

if (window.innerWidth < 1080 || forceFloat) {
isFrDocked = false;
panel.classList.remove('docked');
if (panel.parentElement !== document.body) {
Expand All @@ -3968,15 +4024,26 @@ This is a fully client-side application. Your content never leaves your browser
contentCont.classList.remove('fr-docked');
contentCont.style.setProperty('--dock-width', '0px');

panel.style.top = '';
panel.style.left = '';
panel.style.right = '';
panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';

dockBtn.innerHTML = '<i class="bi bi-layout-sidebar-reverse"></i>';
dockBtn.title = "Toggle Dock Mode";

panel.style.display = 'flex';
applyPaneWidths();

// Restore focus and selection
if (activeId) {
const el = document.getElementById(activeId);
if (el) {
el.focus();
if (isInput && typeof el.selectionStart === 'number') {
el.setSelectionRange(selStart, selEnd);
}
}
}
return;
}

Expand Down Expand Up @@ -4007,9 +4074,9 @@ This is a fully client-side application. Your content never leaves your browser
contentCont.classList.remove('fr-docked');
contentCont.style.setProperty('--dock-width', '0px');

panel.style.top = '';
panel.style.left = '';
panel.style.right = '';
panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';

dockBtn.innerHTML = '<i class="bi bi-layout-sidebar-reverse"></i>';
dockBtn.title = "Toggle Dock Mode";
Expand All @@ -4018,6 +4085,17 @@ This is a fully client-side application. Your content never leaves your browser
// Ensure display is flex and recalculate split panes
panel.style.display = 'flex';
applyPaneWidths();

// Restore focus and selection after layout change
if (activeId) {
const el = document.getElementById(activeId);
if (el) {
el.focus();
if (isInput && typeof el.selectionStart === 'number') {
el.setSelectionRange(selStart, selEnd);
}
}
}
}

function updateFindControls() {
Expand Down Expand Up @@ -4143,7 +4221,7 @@ This is a fully client-side application. Your content never leaves your browser
let wasDockedPref = localStorage.getItem('find-replace-docked') === 'true';

// Force floating-only mode on mobile/tablet viewports
if (window.innerWidth <= 768) {
if (window.innerWidth < 1080) {
wasDockedPref = false;
}

Expand Down Expand Up @@ -4380,10 +4458,23 @@ This is a fully client-side application. Your content never leaves your browser
});
}

// Reset position handler
const resetBtn = document.getElementById('find-replace-reset');
if (resetBtn) {
resetBtn.addEventListener('click', () => {
lastFloatingLeft = null;
lastFloatingTop = null;
lastFloatingRight = null;
modal.style.left = '';
modal.style.top = '';
modal.style.right = '';
});
}

// Dock toggle handler
const dockBtn = document.getElementById('find-replace-dock');
if (dockBtn) {
dockBtn.addEventListener('click', toggleFrDockMode);
dockBtn.addEventListener('click', () => toggleFrDockMode(false));
}

// Advanced Drawer Toggle
Expand Down Expand Up @@ -4619,7 +4710,7 @@ This is a fully client-side application. Your content never leaves your browser
}

function startResize(e) {
if (window.innerWidth <= 768) return;
if (window.innerWidth < 1080) return;
if (currentViewMode !== 'split') return;
e.preventDefault();
isResizing = true;
Expand All @@ -4628,7 +4719,7 @@ This is a fully client-side application. Your content never leaves your browser
}

function startResizeTouch(e) {
if (window.innerWidth <= 768) return;
if (window.innerWidth < 1080) return;
if (currentViewMode !== 'split') return;
e.preventDefault();
isResizing = true;
Expand Down Expand Up @@ -4675,7 +4766,7 @@ This is a fully client-side application. Your content never leaves your browser
}

function applyPaneWidths() {
if (window.innerWidth <= 768) {
if (window.innerWidth < 1080) {
resetPaneWidths();
return;
}
Expand Down Expand Up @@ -4787,11 +4878,36 @@ This is a fully client-side application. Your content never leaves your browser

// Initialize resizer - Story 1.3
initResizer();
function constrainFloatingPanelPosition() {
const panel = document.getElementById('find-replace-modal');
if (!panel || isFrDocked || panel.style.display === 'none') return;
if (window.innerWidth < 768) return; // Mobile layout forces fixed responsive positioning via CSS

// Only adjust if the inline style has custom dragged coordinates
if (!panel.style.left || panel.style.left === 'auto') return;

const leftVal = parseFloat(panel.style.left) || 0;
const topVal = parseFloat(panel.style.top) || 0;

const maxX = window.innerWidth - panel.offsetWidth;
const maxY = window.innerHeight - panel.offsetHeight;

const constrainedLeft = `${Math.max(0, Math.min(maxX, leftVal))}px`;
const constrainedTop = `${Math.max(0, Math.min(maxY, topVal))}px`;

panel.style.left = constrainedLeft;
panel.style.top = constrainedTop;

lastFloatingLeft = constrainedLeft;
lastFloatingTop = constrainedTop;
}

window.addEventListener('resize', () => {
scheduleLineNumberUpdate();
if (window.innerWidth <= 768 && isFrDocked && isFindModalOpen) {
if (window.innerWidth < 1080 && isFrDocked && isFindModalOpen) {
toggleFrDockMode(true);
}
constrainFloatingPanelPosition();
});

// View Mode Button Event Listeners - Story 1.1
Expand Down
12 changes: 9 additions & 3 deletions desktop-app/resources/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -3013,6 +3013,10 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
flex-shrink: 0;
}

.find-replace-panel.docked #find-replace-reset {
display: none !important;
}

.find-replace-header {
display: flex;
align-items: center;
Expand Down Expand Up @@ -3379,13 +3383,15 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
}

/* ========================================
MOBILE FIND PANEL RESPONSIVE FIXES
MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
======================================== */
@media (max-width: 768px) {
@media (max-width: 1079px) {
#find-replace-dock {
display: none !important;
}

}

@media (max-width: 768px) {
/* Prevent full screen expansion of floating panel on small mobile viewports */
.find-replace-panel {
width: calc(100% - 24px) !important;
Expand Down
1 change: 1 addition & 0 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,7 @@ <h5>Menu</h5>
<div class="find-replace-header" id="find-replace-drag-handle">
<span class="find-replace-title"><i class="bi bi-search me-1"></i> Find &amp; Replace</span>
<div class="find-replace-header-actions">
<button type="button" class="panel-icon-btn" id="find-replace-reset" title="Reset Position" aria-label="Reset Position"><i class="bi bi-arrow-counterclockwise"></i></button>
<button type="button" class="panel-icon-btn" id="find-replace-dock" title="Toggle Dock Mode" aria-label="Toggle Dock Mode"><i class="bi bi-layout-sidebar-reverse"></i></button>
<button type="button" class="panel-icon-btn" id="find-replace-close-icon" aria-label="Close find and replace panel"><i class="bi bi-x-lg"></i></button>
</div>
Expand Down
Loading
Loading