fix: implement fully functional reset buttons for text and image sections

- 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 <noreply@anthropic.com>
This commit is contained in:
2025-11-04 15:39:35 +01:00
parent 28584893d0
commit 85faf502c4

View File

@@ -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