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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user