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;
|
min-width: 300px;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
// Position the menu
|
// Smart positioning with viewport boundary detection
|
||||||
const rect = targetElement.getBoundingClientRect();
|
const rect = targetElement.getBoundingClientRect();
|
||||||
this.element.style.left = `${rect.left}px`;
|
const viewport = {
|
||||||
this.element.style.top = `${rect.bottom + 10}px`;
|
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
|
// Add content
|
||||||
if (contentElement) {
|
if (contentElement) {
|
||||||
@@ -72,7 +101,10 @@ class FloatingMenu {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: #666;
|
color: #666;
|
||||||
`;
|
`;
|
||||||
closeButton.addEventListener('click', () => this.hide());
|
closeButton.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.hide();
|
||||||
|
});
|
||||||
this.element.appendChild(closeButton);
|
this.element.appendChild(closeButton);
|
||||||
|
|
||||||
document.body.appendChild(this.element);
|
document.body.appendChild(this.element);
|
||||||
@@ -109,6 +141,8 @@ class DOMRenderer {
|
|||||||
this.editingSections = new Set();
|
this.editingSections = new Set();
|
||||||
this.currentFloatingMenu = null;
|
this.currentFloatingMenu = null;
|
||||||
this.eventListenersAttached = false;
|
this.eventListenersAttached = false;
|
||||||
|
this.lastClickTime = 0;
|
||||||
|
this.clickDebounceMs = 300; // Prevent rapid clicks
|
||||||
|
|
||||||
// Enhanced Event System - Track event types
|
// Enhanced Event System - Track event types
|
||||||
this.eventHistory = [];
|
this.eventHistory = [];
|
||||||
@@ -244,6 +278,14 @@ class DOMRenderer {
|
|||||||
handleSectionClick(event) {
|
handleSectionClick(event) {
|
||||||
debug('handleSectionClick: Click detected on target: ' + event.target.tagName + ' ' + (event.target.className || ''), 'CLICK');
|
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
|
// Don't handle clicks on form elements, buttons, or links
|
||||||
if (event.target.closest('textarea, button, input, a')) {
|
if (event.target.closest('textarea, button, input, a')) {
|
||||||
debug('handleSectionClick: Ignoring click on form element', 'CLICK');
|
debug('handleSectionClick: Ignoring click on form element', 'CLICK');
|
||||||
@@ -270,8 +312,15 @@ class DOMRenderer {
|
|||||||
debug('handleSectionClick: Found section object: ' + (section ? 'YES' : 'NO'), 'CLICK');
|
debug('handleSectionClick: Found section object: ' + (section ? 'YES' : 'NO'), 'CLICK');
|
||||||
|
|
||||||
if (section && section.isEditing()) {
|
if (section && section.isEditing()) {
|
||||||
debug('handleSectionClick: Section already being edited: ' + sectionId, 'CLICK');
|
debug('handleSectionClick: Section already being edited, checking if dialog is visible: ' + sectionId, 'CLICK');
|
||||||
return;
|
// 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');
|
debug('handleSectionClick: About to start editing for section: ' + sectionId, 'CLICK');
|
||||||
@@ -364,8 +413,20 @@ class DOMRenderer {
|
|||||||
cursor: pointer;
|
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(acceptButton);
|
||||||
controls.appendChild(cancelButton);
|
controls.appendChild(cancelButton);
|
||||||
|
controls.appendChild(resetButton);
|
||||||
|
|
||||||
editorContent.appendChild(textarea);
|
editorContent.appendChild(textarea);
|
||||||
editorContent.appendChild(controls);
|
editorContent.appendChild(controls);
|
||||||
@@ -391,6 +452,20 @@ class DOMRenderer {
|
|||||||
this.currentFloatingMenu = null; // Clear reference
|
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
|
// Auto-focus textarea
|
||||||
setTimeout(() => textarea.focus(), 100);
|
setTimeout(() => textarea.focus(), 100);
|
||||||
}
|
}
|
||||||
@@ -403,7 +478,7 @@ class DOMRenderer {
|
|||||||
|
|
||||||
// Track staging state for this editor
|
// Track staging state for this editor
|
||||||
const stagingState = {
|
const stagingState = {
|
||||||
originalMarkdown: section.currentMarkdown,
|
originalMarkdown: section.originalMarkdown,
|
||||||
currentAltText: '',
|
currentAltText: '',
|
||||||
currentImageSrc: '',
|
currentImageSrc: '',
|
||||||
stagedImageSrc: null,
|
stagedImageSrc: null,
|
||||||
@@ -711,6 +786,7 @@ class DOMRenderer {
|
|||||||
controls.appendChild(cancelBtn);
|
controls.appendChild(cancelBtn);
|
||||||
controls.appendChild(resetBtn);
|
controls.appendChild(resetBtn);
|
||||||
|
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
acceptBtn.addEventListener('click', () => {
|
acceptBtn.addEventListener('click', () => {
|
||||||
// Apply staged changes only when accept is clicked
|
// Apply staged changes only when accept is clicked
|
||||||
@@ -753,26 +829,46 @@ class DOMRenderer {
|
|||||||
this.currentFloatingMenu = null;
|
this.currentFloatingMenu = null;
|
||||||
});
|
});
|
||||||
|
|
||||||
resetBtn.addEventListener('click', () => {
|
resetBtn.addEventListener('click', (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
// Reset to original content
|
// Reset to original content
|
||||||
const originalImageMatch = stagingState.originalMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
|
const originalImageMatch = stagingState.originalMarkdown.match(/!\[(.*?)\]\((.*?)\)/);
|
||||||
|
|
||||||
if (originalImageMatch) {
|
if (originalImageMatch) {
|
||||||
const [, originalAltText, originalImageSrc] = originalImageMatch;
|
const [, originalAltText, originalImageSrc] = originalImageMatch;
|
||||||
|
|
||||||
|
// Update staging state to original values
|
||||||
stagingState.currentAltText = originalAltText;
|
stagingState.currentAltText = originalAltText;
|
||||||
stagingState.currentImageSrc = originalImageSrc;
|
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
|
// Create floating menu
|
||||||
|
|||||||
Reference in New Issue
Block a user