From 85faf502c48a404281c99e2ab556680ee4b655cb Mon Sep 17 00:00:00 2001 From: tegwick Date: Tue, 4 Nov 2025 15:39:35 +0100 Subject: [PATCH] fix: implement fully functional reset buttons for text and image sections MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add missing reset button to text section editors alongside Accept/Cancel - Fix image reset button by using section.originalMarkdown instead of currentMarkdown - Implement complete reset workflow that updates section content and accepts changes automatically - Add smart dialog positioning with viewport boundary detection to prevent off-screen dialogs - Add click debouncing to prevent rapid-fire interaction issues - Allow re-opening sections already marked as editing when dialog is not visible - Reset buttons now provide one-click restoration to original content with automatic editor closure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- .../static/js/components/dom-renderer.js | 136 +++++++++++++++--- 1 file changed, 116 insertions(+), 20 deletions(-) diff --git a/markitect/static/js/components/dom-renderer.js b/markitect/static/js/components/dom-renderer.js index c302f80f..5156a64d 100644 --- a/markitect/static/js/components/dom-renderer.js +++ b/markitect/static/js/components/dom-renderer.js @@ -46,10 +46,39 @@ class FloatingMenu { min-width: 300px; `; - // Position the menu + // Smart positioning with viewport boundary detection const rect = targetElement.getBoundingClientRect(); - this.element.style.left = `${rect.left}px`; - this.element.style.top = `${rect.bottom + 10}px`; + const viewport = { + width: window.innerWidth, + height: window.innerHeight + }; + + // Calculate initial position (below the section) + let left = rect.left; + let top = rect.bottom + 10; + + // Adjust horizontal position if menu would go off-screen + const menuWidth = 350; // Estimated menu width + if (left + menuWidth > viewport.width) { + left = viewport.width - menuWidth - 20; // 20px margin from edge + } + if (left < 10) { + left = 10; // Minimum margin from left edge + } + + // Adjust vertical position if menu would go off-screen + const menuHeight = 300; // Estimated menu height + if (top + menuHeight > viewport.height) { + // Position above the section instead + top = rect.top - menuHeight - 10; + if (top < 10) { + // If still off-screen, position at viewport top + top = 10; + } + } + + this.element.style.left = `${left}px`; + this.element.style.top = `${top}px`; // Add content if (contentElement) { @@ -72,7 +101,10 @@ class FloatingMenu { cursor: pointer; color: #666; `; - closeButton.addEventListener('click', () => this.hide()); + closeButton.addEventListener('click', (event) => { + event.stopPropagation(); + this.hide(); + }); this.element.appendChild(closeButton); document.body.appendChild(this.element); @@ -109,6 +141,8 @@ class DOMRenderer { this.editingSections = new Set(); this.currentFloatingMenu = null; this.eventListenersAttached = false; + this.lastClickTime = 0; + this.clickDebounceMs = 300; // Prevent rapid clicks // Enhanced Event System - Track event types this.eventHistory = []; @@ -244,6 +278,14 @@ class DOMRenderer { handleSectionClick(event) { debug('handleSectionClick: Click detected on target: ' + event.target.tagName + ' ' + (event.target.className || ''), 'CLICK'); + // Debounce rapid clicks + const now = Date.now(); + if (now - this.lastClickTime < this.clickDebounceMs) { + debug('handleSectionClick: Click debounced (too rapid)', 'CLICK'); + return; + } + this.lastClickTime = now; + // Don't handle clicks on form elements, buttons, or links if (event.target.closest('textarea, button, input, a')) { debug('handleSectionClick: Ignoring click on form element', 'CLICK'); @@ -270,8 +312,15 @@ class DOMRenderer { debug('handleSectionClick: Found section object: ' + (section ? 'YES' : 'NO'), 'CLICK'); if (section && section.isEditing()) { - debug('handleSectionClick: Section already being edited: ' + sectionId, 'CLICK'); - return; + debug('handleSectionClick: Section already being edited, checking if dialog is visible: ' + sectionId, 'CLICK'); + // If section is editing but no dialog is visible, allow re-opening + const existingDialog = document.querySelector('.ui-edit-floating-menu'); + if (existingDialog) { + debug('handleSectionClick: Dialog already visible, ignoring click', 'CLICK'); + return; + } else { + debug('handleSectionClick: Section editing but no dialog visible, proceeding to show editor', 'CLICK'); + } } debug('handleSectionClick: About to start editing for section: ' + sectionId, 'CLICK'); @@ -364,8 +413,20 @@ class DOMRenderer { cursor: pointer; `; + const resetButton = document.createElement('button'); + resetButton.textContent = '↺ Reset'; + resetButton.style.cssText = ` + background: #fd7e14; + color: white; + border: none; + padding: 8px 16px; + border-radius: 4px; + cursor: pointer; + `; + controls.appendChild(acceptButton); controls.appendChild(cancelButton); + controls.appendChild(resetButton); editorContent.appendChild(textarea); editorContent.appendChild(controls); @@ -391,6 +452,20 @@ class DOMRenderer { this.currentFloatingMenu = null; // Clear reference }); + resetButton.addEventListener('click', () => { + // Reset textarea to original content and apply the change + const section = this.sectionManager.sections.get(sectionId); + if (section) { + textarea.value = section.originalMarkdown; + // Actually update the section content to original and accept the changes + this.sectionManager.updateContent(sectionId, section.originalMarkdown); + this.sectionManager.acceptChanges(sectionId); + // Close the editor + floatingMenu.hide(); + this.currentFloatingMenu = null; + } + }); + // Auto-focus textarea setTimeout(() => textarea.focus(), 100); } @@ -403,7 +478,7 @@ class DOMRenderer { // Track staging state for this editor const stagingState = { - originalMarkdown: section.currentMarkdown, + originalMarkdown: section.originalMarkdown, currentAltText: '', currentImageSrc: '', stagedImageSrc: null, @@ -711,6 +786,7 @@ class DOMRenderer { controls.appendChild(cancelBtn); controls.appendChild(resetBtn); + // Event handlers acceptBtn.addEventListener('click', () => { // Apply staged changes only when accept is clicked @@ -753,26 +829,46 @@ class DOMRenderer { this.currentFloatingMenu = null; }); - resetBtn.addEventListener('click', () => { + resetBtn.addEventListener('click', (event) => { + event.preventDefault(); + event.stopPropagation(); + // Reset to original content const originalImageMatch = stagingState.originalMarkdown.match(/!\[(.*?)\]\((.*?)\)/); + if (originalImageMatch) { const [, originalAltText, originalImageSrc] = originalImageMatch; + + // Update staging state to original values stagingState.currentAltText = originalAltText; stagingState.currentImageSrc = originalImageSrc; + + // Clear any staged changes + stagingState.stagedImageSrc = null; + stagingState.stagedAltText = null; + stagingState.hasChanges = false; + + // Reset alt text input to original + altTextInput.value = originalAltText; + + // Trigger input event to ensure UI consistency + const inputEvent = new Event('input', { bubbles: true, cancelable: true }); + altTextInput.dispatchEvent(inputEvent); + + // Reset preview to original image + updateImagePreview(originalImageSrc, originalAltText); + + // Update change indicator + updateChangeIndicator(); + + // Actually update the section content to original and accept the changes + this.sectionManager.updateContent(sectionId, stagingState.originalMarkdown); + this.sectionManager.acceptChanges(sectionId); + + // Close the editor + this.currentFloatingMenu.hide(); + this.currentFloatingMenu = null; } - - // Clear any staged changes - stagingState.stagedImageSrc = null; - stagingState.stagedAltText = null; - stagingState.hasChanges = false; - - // Reset alt text input to original - altTextInput.value = stagingState.currentAltText; - - // Reset preview to original - updateImagePreview(stagingState.currentImageSrc, stagingState.currentAltText); - updateChangeIndicator(); }); // Create floating menu