Skip to content

Commit 970f473

Browse files
Merge pull request #128 from ThisIs-Developer/fix-dock-dragging
fix(editor): preserve find & replace floating panel position and add reset button
2 parents 3d90d0f + 9fb9bc9 commit 970f473

6 files changed

Lines changed: 196 additions & 40 deletions

File tree

desktop-app/resources/index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,7 @@ <h5>Menu</h5>
414414
<div class="find-replace-header" id="find-replace-drag-handle">
415415
<span class="find-replace-title"><i class="bi bi-search me-1"></i> Find &amp; Replace</span>
416416
<div class="find-replace-header-actions">
417+
<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>
417418
<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>
418419
<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>
419420
</div>

desktop-app/resources/js/script.js

Lines changed: 143 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3915,51 +3915,107 @@ This is a fully client-side application. Your content never leaves your browser
39153915
let isFrDocked = false;
39163916
let dragOffset = { x: 0, y: 0 };
39173917
let isPanelDragging = false;
3918+
let lastFloatingLeft = null;
3919+
let lastFloatingTop = null;
3920+
let lastFloatingRight = null;
39183921

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

3924-
handle.addEventListener('mousedown', (e) => {
3925-
if (isFrDocked) return;
3926-
if (e.target.closest('.find-replace-header-actions')) return;
3927+
const startDrag = (clientX, clientY) => {
39273928
isPanelDragging = true;
3928-
dragOffset.x = e.clientX - panel.offsetLeft;
3929-
dragOffset.y = e.clientY - panel.offsetTop;
3929+
dragOffset.x = clientX - panel.offsetLeft;
3930+
dragOffset.y = clientY - panel.offsetTop;
39303931
document.body.classList.add('resizing');
3931-
});
3932+
};
39323933

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

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

3946-
document.addEventListener('mouseup', () => {
3947+
lastFloatingLeft = newLeft;
3948+
lastFloatingTop = newTop;
3949+
lastFloatingRight = 'auto';
3950+
};
3951+
3952+
const stopDrag = () => {
39473953
if (isPanelDragging) {
39483954
isPanelDragging = false;
39493955
document.body.classList.remove('resizing');
39503956
}
3957+
};
3958+
3959+
// Mouse events
3960+
handle.addEventListener('mousedown', (e) => {
3961+
if (isFrDocked) return;
3962+
if (window.innerWidth < 768) return; // Do NOT allow dragging on mobile layouts
3963+
if (e.target.closest('.find-replace-header-actions')) return;
3964+
startDrag(e.clientX, e.clientY);
39513965
});
3966+
3967+
document.addEventListener('mousemove', (e) => {
3968+
if (!isPanelDragging || isFrDocked) return;
3969+
moveDrag(e.clientX, e.clientY);
3970+
});
3971+
3972+
document.addEventListener('mouseup', stopDrag);
3973+
3974+
// Touch events for tablets
3975+
handle.addEventListener('touchstart', (e) => {
3976+
if (isFrDocked) return;
3977+
if (window.innerWidth < 768) return; // Do NOT allow dragging on mobile layouts
3978+
if (e.target.closest('.find-replace-header-actions')) return;
3979+
if (e.touches && e.touches[0]) {
3980+
startDrag(e.touches[0].clientX, e.touches[0].clientY);
3981+
}
3982+
}, { passive: true });
3983+
3984+
document.addEventListener('touchmove', (e) => {
3985+
if (!isPanelDragging || isFrDocked) return;
3986+
if (e.touches && e.touches[0]) {
3987+
moveDrag(e.touches[0].clientX, e.touches[0].clientY);
3988+
}
3989+
}, { passive: true });
3990+
3991+
document.addEventListener('touchend', stopDrag);
39523992
}
39533993

39543994
let frPreferredDocked = false;
39553995

39563996
function toggleFrDockMode(forceFloat = false) {
3997+
// If forceFloat is an Event (e.g. from click listener directly), treat as false
3998+
if (forceFloat instanceof Event || (forceFloat && typeof forceFloat === 'object')) {
3999+
forceFloat = false;
4000+
}
4001+
39574002
const panel = document.getElementById('find-replace-modal');
39584003
const dockBtn = document.getElementById('find-replace-dock');
39594004
const contentCont = document.querySelector('.content-container');
39604005
if (!panel || !dockBtn || !contentCont) return;
39614006

3962-
if (window.innerWidth <= 768 || forceFloat) {
4007+
// Save active element focus and selection before DOM movement
4008+
const activeEl = document.activeElement;
4009+
const activeId = activeEl ? activeEl.id : null;
4010+
const isInput = activeEl && (activeEl.tagName === 'INPUT' || activeEl.tagName === 'SELECT' || activeEl.tagName === 'TEXTAREA');
4011+
let selStart = 0;
4012+
let selEnd = 0;
4013+
if (isInput && typeof activeEl.selectionStart === 'number') {
4014+
selStart = activeEl.selectionStart;
4015+
selEnd = activeEl.selectionEnd;
4016+
}
4017+
4018+
if (window.innerWidth < 1080 || forceFloat) {
39634019
isFrDocked = false;
39644020
panel.classList.remove('docked');
39654021
if (panel.parentElement !== document.body) {
@@ -3968,15 +4024,26 @@ This is a fully client-side application. Your content never leaves your browser
39684024
contentCont.classList.remove('fr-docked');
39694025
contentCont.style.setProperty('--dock-width', '0px');
39704026

3971-
panel.style.top = '';
3972-
panel.style.left = '';
3973-
panel.style.right = '';
4027+
panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
4028+
panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
4029+
panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';
39744030

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

39784034
panel.style.display = 'flex';
39794035
applyPaneWidths();
4036+
4037+
// Restore focus and selection
4038+
if (activeId) {
4039+
const el = document.getElementById(activeId);
4040+
if (el) {
4041+
el.focus();
4042+
if (isInput && typeof el.selectionStart === 'number') {
4043+
el.setSelectionRange(selStart, selEnd);
4044+
}
4045+
}
4046+
}
39804047
return;
39814048
}
39824049

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

4010-
panel.style.top = '';
4011-
panel.style.left = '';
4012-
panel.style.right = '';
4077+
panel.style.left = lastFloatingLeft !== null ? lastFloatingLeft : '';
4078+
panel.style.top = lastFloatingTop !== null ? lastFloatingTop : '';
4079+
panel.style.right = lastFloatingRight !== null ? lastFloatingRight : '';
40134080

40144081
dockBtn.innerHTML = '<i class="bi bi-layout-sidebar-reverse"></i>';
40154082
dockBtn.title = "Toggle Dock Mode";
@@ -4018,6 +4085,17 @@ This is a fully client-side application. Your content never leaves your browser
40184085
// Ensure display is flex and recalculate split panes
40194086
panel.style.display = 'flex';
40204087
applyPaneWidths();
4088+
4089+
// Restore focus and selection after layout change
4090+
if (activeId) {
4091+
const el = document.getElementById(activeId);
4092+
if (el) {
4093+
el.focus();
4094+
if (isInput && typeof el.selectionStart === 'number') {
4095+
el.setSelectionRange(selStart, selEnd);
4096+
}
4097+
}
4098+
}
40214099
}
40224100

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

41454223
// Force floating-only mode on mobile/tablet viewports
4146-
if (window.innerWidth <= 768) {
4224+
if (window.innerWidth < 1080) {
41474225
wasDockedPref = false;
41484226
}
41494227

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

4461+
// Reset position handler
4462+
const resetBtn = document.getElementById('find-replace-reset');
4463+
if (resetBtn) {
4464+
resetBtn.addEventListener('click', () => {
4465+
lastFloatingLeft = null;
4466+
lastFloatingTop = null;
4467+
lastFloatingRight = null;
4468+
modal.style.left = '';
4469+
modal.style.top = '';
4470+
modal.style.right = '';
4471+
});
4472+
}
4473+
43834474
// Dock toggle handler
43844475
const dockBtn = document.getElementById('find-replace-dock');
43854476
if (dockBtn) {
4386-
dockBtn.addEventListener('click', toggleFrDockMode);
4477+
dockBtn.addEventListener('click', () => toggleFrDockMode(false));
43874478
}
43884479

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

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

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

46774768
function applyPaneWidths() {
4678-
if (window.innerWidth <= 768) {
4769+
if (window.innerWidth < 1080) {
46794770
resetPaneWidths();
46804771
return;
46814772
}
@@ -4787,11 +4878,36 @@ This is a fully client-side application. Your content never leaves your browser
47874878

47884879
// Initialize resizer - Story 1.3
47894880
initResizer();
4881+
function constrainFloatingPanelPosition() {
4882+
const panel = document.getElementById('find-replace-modal');
4883+
if (!panel || isFrDocked || panel.style.display === 'none') return;
4884+
if (window.innerWidth < 768) return; // Mobile layout forces fixed responsive positioning via CSS
4885+
4886+
// Only adjust if the inline style has custom dragged coordinates
4887+
if (!panel.style.left || panel.style.left === 'auto') return;
4888+
4889+
const leftVal = parseFloat(panel.style.left) || 0;
4890+
const topVal = parseFloat(panel.style.top) || 0;
4891+
4892+
const maxX = window.innerWidth - panel.offsetWidth;
4893+
const maxY = window.innerHeight - panel.offsetHeight;
4894+
4895+
const constrainedLeft = `${Math.max(0, Math.min(maxX, leftVal))}px`;
4896+
const constrainedTop = `${Math.max(0, Math.min(maxY, topVal))}px`;
4897+
4898+
panel.style.left = constrainedLeft;
4899+
panel.style.top = constrainedTop;
4900+
4901+
lastFloatingLeft = constrainedLeft;
4902+
lastFloatingTop = constrainedTop;
4903+
}
4904+
47904905
window.addEventListener('resize', () => {
47914906
scheduleLineNumberUpdate();
4792-
if (window.innerWidth <= 768 && isFrDocked && isFindModalOpen) {
4907+
if (window.innerWidth < 1080 && isFrDocked && isFindModalOpen) {
47934908
toggleFrDockMode(true);
47944909
}
4910+
constrainFloatingPanelPosition();
47954911
});
47964912

47974913
// View Mode Button Event Listeners - Story 1.1

desktop-app/resources/styles.css

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3013,6 +3013,10 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
30133013
flex-shrink: 0;
30143014
}
30153015

3016+
.find-replace-panel.docked #find-replace-reset {
3017+
display: none !important;
3018+
}
3019+
30163020
.find-replace-header {
30173021
display: flex;
30183022
align-items: center;
@@ -3379,13 +3383,15 @@ html[lang="ko"] .markdown-body h1, html[lang="ko"] .markdown-body h2, html[lang=
33793383
}
33803384

33813385
/* ========================================
3382-
MOBILE FIND PANEL RESPONSIVE FIXES
3386+
MOBILE & TABLET FIND PANEL RESPONSIVE FIXES
33833387
======================================== */
3384-
@media (max-width: 768px) {
3388+
@media (max-width: 1079px) {
33853389
#find-replace-dock {
33863390
display: none !important;
33873391
}
3388-
3392+
}
3393+
3394+
@media (max-width: 768px) {
33893395
/* Prevent full screen expansion of floating panel on small mobile viewports */
33903396
.find-replace-panel {
33913397
width: calc(100% - 24px) !important;

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ <h5>Menu</h5>
411411
<div class="find-replace-header" id="find-replace-drag-handle">
412412
<span class="find-replace-title"><i class="bi bi-search me-1"></i> Find &amp; Replace</span>
413413
<div class="find-replace-header-actions">
414+
<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>
414415
<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>
415416
<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>
416417
</div>

0 commit comments

Comments
 (0)