diff --git a/UserInterfaceFramework.md b/UserInterfaceFramework.md index 4a1b0050..417e6992 100644 --- a/UserInterfaceFramework.md +++ b/UserInterfaceFramework.md @@ -208,23 +208,43 @@ Provides detailed information about the current editing session, including versi ### Description Provides user confirmation for potentially destructive operations that cannot be easily undone. -### Current Implementation -- **Method**: Browser native `confirm()` (temporary solution) +### Current Implementation ✅ COMPLETED +- **Method**: Custom theme-aware modal dialog - **Trigger**: "🔄 Reset All" button in floating action panel -- **Message**: "Reset all content to original markdown? This will lose all edits and remove split sections." +- **Message**: "Reset all content to original markdown?" +- **Warning**: "This will permanently lose all edits and remove any split sections. This action cannot be undone." + +### Features Implemented +- **Theme-Aware Styling**: Adapts to all UI themes (standard, greyscale, electric, psychedelic) +- **Clear Action Buttons**: + - Primary action: "Reset Document" (red danger button) + - Secondary action: "Keep Changes" (grey cancel button) +- **Enhanced UX**: + - Detailed consequence explanation with warning styling + - Professional modal overlay with smooth animations + - Proper focus management and accessibility +- **Keyboard Support**: + - ESC key to cancel + - Enter key to confirm + - Tab navigation between buttons ### Use Cases - **Reset All Sections**: Complete document reset to original state -- **Future**: Delete operations, bulk changes, file operations +- **Future**: Extensible for delete operations, bulk changes, file operations -### Future Enhancement Plan -**Target**: Replace browser confirm with custom modal dialog -- **Styling**: Theme-aware modal with clear action buttons -- **Features**: - - Clear primary/secondary action buttons - - Detailed consequence explanation - - Optional "Don't ask again" for non-critical confirmations -- **Accessibility**: Proper focus management, keyboard support +### Technical Implementation +**CSS Classes**: +- `.ui-edit-confirmation-modal` - Modal container +- `.ui-edit-confirmation-content` - Main message +- `.ui-edit-confirmation-warning` - Warning section +- `.ui-edit-confirmation-buttons` - Button container +- `.ui-edit-button-confirm` - Danger action button +- `.ui-edit-button-cancel` - Cancel action button + +**JavaScript Method**: `showConfirmation(message, confirmText, cancelText, warningText)` +- Returns Promise for async/await support +- Theme-consistent styling via layered theme system +- Proper event cleanup and accessibility features --- diff --git a/markitect/clean_document_manager.py b/markitect/clean_document_manager.py index 2f04ab57..ca13b88a 100644 --- a/markitect/clean_document_manager.py +++ b/markitect/clean_document_manager.py @@ -481,8 +481,145 @@ class CleanDocumentManager: border-top: 1px solid {props.get('editor_panel_border', '#dee2e6')}; text-align: right; }} - outline: none; - }}""" + + /* Confirmation Dialog Styles */ + .markitect-edit-mode .ui-edit-confirmation-modal {{ + max-width: 500px; + }} + .markitect-edit-mode .ui-edit-confirmation-content {{ + font-size: 16px; + line-height: 1.5; + margin-bottom: 24px; + color: {props.get('editor_text_color', '#212529')}; + }} + .markitect-edit-mode .ui-edit-confirmation-warning {{ + background: {props.get('editor_warning_bg', '#fff3cd')}; + border: 1px solid {props.get('editor_warning_border', '#ffeaa7')}; + color: {props.get('editor_warning_text', '#856404')}; + padding: 12px 16px; + border-radius: 6px; + margin: 16px 0; + font-size: 14px; + }} + .markitect-edit-mode .ui-edit-confirmation-buttons {{ + display: flex; + gap: 12px; + justify-content: flex-end; + }} + .markitect-edit-mode .ui-edit-button-confirm {{ + background: {props.get('editor_danger_button', '#dc3545')}; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s, transform 0.1s; + }} + .markitect-edit-mode .ui-edit-button-confirm:hover {{ + background: {props.get('editor_danger_button_hover', '#c82333')}; + transform: translateY(-1px); + }} + .markitect-edit-mode .ui-edit-button-confirm:active {{ + transform: translateY(0); + }} + .markitect-edit-mode .ui-edit-button-confirm:focus {{ + outline: 2px solid {props.get('editor_focus_color', '#007bff')}; + outline-offset: 2px; + }} + .markitect-edit-mode .ui-edit-button-cancel {{ + background: {props.get('editor_secondary_button', '#6c757d')}; + color: white; + border: none; + padding: 10px 20px; + border-radius: 6px; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: background-color 0.2s, transform 0.1s; + }} + .markitect-edit-mode .ui-edit-button-cancel:hover {{ + background: {props.get('editor_secondary_button_hover', '#545b62')}; + transform: translateY(-1px); + }} + .markitect-edit-mode .ui-edit-button-cancel:active {{ + transform: translateY(0); + }} + .markitect-edit-mode .ui-edit-button-cancel:focus {{ + outline: 2px solid {props.get('editor_focus_color', '#007bff')}; + outline-offset: 2px; + }} + + /* Document Scroll Indicators */ + .ui-scroll-indicator {{ + position: fixed; + left: 50%; + transform: translateX(-50%); + width: 60px; + height: 30px; + background: {props.get('editor_panel_bg', '#f8f9fa')}; + border: 1px solid {props.get('editor_panel_border', '#dee2e6')}; + border-radius: 15px; + display: flex; + align-items: center; + justify-content: center; + cursor: pointer; + opacity: 0; + visibility: hidden; + transition: opacity 0.3s ease, visibility 0.3s ease, transform 0.2s ease, background-color 0.2s ease; + z-index: 1000; + box-shadow: 0 4px 12px {props.get('editor_shadow', 'rgba(0,0,0,0.15)')}; + }} + .ui-scroll-indicator:hover {{ + transform: translateX(-50%) scale(1.05); + }} + .ui-scroll-indicator:not(.disabled):hover {{ + background: {props.get('editor_button_hover', '#e9ecef')}; + }} + .ui-scroll-indicator.active {{ + opacity: 0.9; + visibility: visible; + }} + .ui-scroll-indicator.disabled {{ + background: {props.get('editor_button_active', '#dee2e6')}; + cursor: not-allowed; + opacity: 0.6; + }} + .ui-scroll-indicator.disabled:hover {{ + transform: translateX(-50%); + background: {props.get('editor_button_active', '#dee2e6')}; + }} + .ui-scroll-indicator-up {{ + top: 20px; + }} + .ui-scroll-indicator-down {{ + bottom: 20px; + }} + .ui-scroll-indicator::before {{ + content: ''; + width: 0; + height: 0; + border-style: solid; + transition: border-color 0.2s ease; + }} + .ui-scroll-indicator-up::before {{ + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-bottom: 12px solid {props.get('editor_text_color', '#212529')}; + }} + .ui-scroll-indicator-down::before {{ + border-left: 8px solid transparent; + border-right: 8px solid transparent; + border-top: 12px solid {props.get('editor_text_color', '#212529')}; + }} + .ui-scroll-indicator.disabled.ui-scroll-indicator-up::before {{ + border-bottom-color: {props.get('editor_secondary_button', '#6c757d')}; + }} + .ui-scroll-indicator.disabled.ui-scroll-indicator-down::before {{ + border-top-color: {props.get('editor_secondary_button', '#6c757d')}; + }} + """ return f"" @@ -667,6 +804,13 @@ class CleanDocumentManager: console.error("Clean edit mode failed to initialize:", error); }} }} + + // Step 3: Initialize document scroll indicators (always available) + try {{ + initializeScrollIndicators(); + }} catch (error) {{ + console.error("Scroll indicators failed to initialize:", error); + }} }}); // Handle CDN loading errors @@ -1675,8 +1819,143 @@ class MarkitectCleanEditor { } } - resetAllSections() { - if (confirm('Reset all content to original markdown? This will lose all edits and remove split sections.')) { + /** + * Show custom confirmation dialog with theme-consistent styling + * @param {string} message - The confirmation message + * @param {string} confirmText - Text for confirm button (default: "Confirm") + * @param {string} cancelText - Text for cancel button (default: "Cancel") + * @param {string} warningText - Optional warning text to highlight consequences + * @returns {Promise} - True if confirmed, false if cancelled + */ + showConfirmation(message, confirmText = "Confirm", cancelText = "Cancel", warningText = null) { + return new Promise((resolve) => { + // Remove any existing modal + const existingModal = document.querySelector('.ui-edit-modal-overlay'); + if (existingModal) { + existingModal.remove(); + } + + // Create modal overlay + const overlay = document.createElement('div'); + overlay.className = 'ui-edit-modal-overlay'; + + // Create modal content + const modal = document.createElement('div'); + modal.className = 'ui-edit-modal ui-edit-confirmation-modal'; + + // Create header + const header = document.createElement('div'); + header.className = 'ui-edit-modal-header'; + + const title = document.createElement('h3'); + title.className = 'ui-edit-modal-title'; + title.textContent = 'Confirm Action'; + + const closeBtn = document.createElement('button'); + closeBtn.className = 'ui-edit-modal-close'; + closeBtn.innerHTML = '×'; + closeBtn.setAttribute('aria-label', 'Close'); + + header.appendChild(title); + header.appendChild(closeBtn); + + // Create body + const body = document.createElement('div'); + body.className = 'ui-edit-modal-body'; + + const content = document.createElement('div'); + content.className = 'ui-edit-confirmation-content'; + content.textContent = message; + body.appendChild(content); + + // Add warning section if provided + if (warningText) { + const warning = document.createElement('div'); + warning.className = 'ui-edit-confirmation-warning'; + warning.textContent = warningText; + body.appendChild(warning); + } + + // Create footer with action buttons + const footer = document.createElement('div'); + footer.className = 'ui-edit-modal-footer'; + + const buttonContainer = document.createElement('div'); + buttonContainer.className = 'ui-edit-confirmation-buttons'; + + const cancelBtn = document.createElement('button'); + cancelBtn.className = 'ui-edit-button-cancel'; + cancelBtn.textContent = cancelText; + + const confirmBtn = document.createElement('button'); + confirmBtn.className = 'ui-edit-button-confirm'; + confirmBtn.textContent = confirmText; + + buttonContainer.appendChild(cancelBtn); + buttonContainer.appendChild(confirmBtn); + footer.appendChild(buttonContainer); + + // Assemble modal + modal.appendChild(header); + modal.appendChild(body); + modal.appendChild(footer); + overlay.appendChild(modal); + document.body.appendChild(overlay); + + // Function to close modal and resolve + const closeModal = (result) => { + overlay.remove(); + resolve(result); + }; + + // Event listeners + closeBtn.addEventListener('click', () => closeModal(false)); + cancelBtn.addEventListener('click', () => closeModal(false)); + confirmBtn.addEventListener('click', () => closeModal(true)); + + // Close on overlay click + overlay.addEventListener('click', (e) => { + if (e.target === overlay) { + closeModal(false); + } + }); + + // Keyboard support + const handleKeyDown = (e) => { + if (e.key === 'Escape') { + closeModal(false); + } else if (e.key === 'Enter') { + closeModal(true); + } + }; + + document.addEventListener('keydown', handleKeyDown); + + // Clean up event listener when modal is closed + const originalResolve = resolve; + resolve = (result) => { + document.removeEventListener('keydown', handleKeyDown); + originalResolve(result); + }; + + // Show modal with animation + setTimeout(() => { + overlay.classList.add('active'); + // Focus the confirm button for accessibility + confirmBtn.focus(); + }, 10); + }); + } + + async resetAllSections() { + const confirmed = await this.showConfirmation( + 'Reset all content to original markdown?', + 'Reset Document', + 'Keep Changes', + 'This will permanently lose all edits and remove any split sections. This action cannot be undone.' + ); + + if (confirmed) { // Clear the section manager completely this.sectionManager.sections.clear(); // Note: No longer tracking single editingSection @@ -1912,6 +2191,136 @@ function initializeCleanEditor() { console.log('✅ Clean section editor initialized successfully'); } +// Document scroll indicators +function initializeScrollIndicators() { + // Create scroll indicators + const scrollUpIndicator = document.createElement('div'); + scrollUpIndicator.className = 'ui-scroll-indicator ui-scroll-indicator-up'; + scrollUpIndicator.setAttribute('aria-label', 'Scroll to top'); + scrollUpIndicator.setAttribute('title', 'Scroll up'); + + const scrollDownIndicator = document.createElement('div'); + scrollDownIndicator.className = 'ui-scroll-indicator ui-scroll-indicator-down'; + scrollDownIndicator.setAttribute('aria-label', 'Scroll to bottom'); + scrollDownIndicator.setAttribute('title', 'Scroll down'); + + document.body.appendChild(scrollUpIndicator); + document.body.appendChild(scrollDownIndicator); + + let scrollIndicatorTimeout = null; + + // Function to show/hide indicators based on scroll position and mouse position + function updateScrollIndicators(mouseY = null) { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + const windowHeight = window.innerHeight; + const documentHeight = document.documentElement.scrollHeight; + + // Determine if scrolling is possible in each direction + const canScrollUp = scrollTop > 0; + const canScrollDown = scrollTop < documentHeight - windowHeight; + + // Only show indicators if there's any scroll possibility or if document is short + let showUp = false; + let showDown = false; + + // Show indicators on mouseover near top/bottom of viewport + if (mouseY !== null) { + const topZone = 100; // pixels from top + const bottomZone = windowHeight - 100; // pixels from bottom + + if (mouseY <= topZone) { + showUp = true; + } + if (mouseY >= bottomZone) { + showDown = true; + } + } + + // Update indicator visibility and state + if (showUp) { + scrollUpIndicator.classList.add('active'); + if (canScrollUp) { + scrollUpIndicator.classList.remove('disabled'); + } else { + scrollUpIndicator.classList.add('disabled'); + } + } else { + scrollUpIndicator.classList.remove('active'); + } + + if (showDown) { + scrollDownIndicator.classList.add('active'); + if (canScrollDown) { + scrollDownIndicator.classList.remove('disabled'); + } else { + scrollDownIndicator.classList.add('disabled'); + } + } else { + scrollDownIndicator.classList.remove('active'); + } + + // Auto-hide after a delay when mouse moves away + if (scrollIndicatorTimeout) { + clearTimeout(scrollIndicatorTimeout); + } + scrollIndicatorTimeout = setTimeout(() => { + scrollUpIndicator.classList.remove('active'); + scrollDownIndicator.classList.remove('active'); + }, 2000); + } + + // Mouse move handler + function handleMouseMove(e) { + updateScrollIndicators(e.clientY); + } + + // Smooth scroll function + function smoothScroll(targetY, duration = 500) { + const startY = window.pageYOffset; + const difference = targetY - startY; + const startTime = performance.now(); + + function step(currentTime) { + const elapsed = currentTime - startTime; + const progress = Math.min(elapsed / duration, 1); + + // Easing function (ease-out) + const easeOut = 1 - Math.pow(1 - progress, 3); + + window.scrollTo(0, startY + difference * easeOut); + + if (progress < 1) { + requestAnimationFrame(step); + } + } + + requestAnimationFrame(step); + } + + // Click handlers for smooth scrolling + scrollUpIndicator.addEventListener('click', () => { + const currentScroll = window.pageYOffset; + const targetScroll = Math.max(0, currentScroll - window.innerHeight * 0.8); + smoothScroll(targetScroll); + }); + + scrollDownIndicator.addEventListener('click', () => { + const currentScroll = window.pageYOffset; + const maxScroll = document.documentElement.scrollHeight - window.innerHeight; + const targetScroll = Math.min(maxScroll, currentScroll + window.innerHeight * 0.8); + smoothScroll(targetScroll); + }); + + // Event listeners + document.addEventListener('mousemove', handleMouseMove); + document.addEventListener('scroll', () => updateScrollIndicators()); + + // Initial check + updateScrollIndicators(); + + console.log('✅ Document scroll indicators initialized'); +} + // Export for testing and usage if (typeof module !== 'undefined' && module.exports) { module.exports = { Section, SectionManager, DOMRenderer, MarkitectCleanEditor }; diff --git a/markitect/plugins/builtin/markdown_commands.py b/markitect/plugins/builtin/markdown_commands.py index 44782657..8ea94cd3 100644 --- a/markitect/plugins/builtin/markdown_commands.py +++ b/markitect/plugins/builtin/markdown_commands.py @@ -75,7 +75,14 @@ LAYERED_THEMES = { 'editor_button_active': '#dee2e6', 'editor_text_color': '#212529', 'editor_focus_color': '#0066cc', - 'editor_shadow': 'rgba(0,0,0,0.1)' + 'editor_shadow': 'rgba(0,0,0,0.1)', + 'editor_danger_button': '#dc3545', + 'editor_danger_button_hover': '#c82333', + 'editor_secondary_button': '#6c757d', + 'editor_secondary_button_hover': '#545b62', + 'editor_warning_bg': '#fff3cd', + 'editor_warning_border': '#ffeaa7', + 'editor_warning_text': '#856404' } }, 'greyscale': { @@ -93,10 +100,13 @@ LAYERED_THEMES = { 'editor_accept_hover': '#777777', 'editor_cancel_bg': '#999999', 'editor_cancel_hover': '#808080', - 'editor_reset_bg': '#aaaaaa', - 'editor_reset_hover': '#999999', - 'editor_secondary_bg': '#bbbbbb', - 'editor_secondary_hover': '#aaaaaa' + 'editor_danger_button': '#8b0000', + 'editor_danger_button_hover': '#700000', + 'editor_secondary_button': '#666666', + 'editor_secondary_button_hover': '#555555', + 'editor_warning_bg': '#f0f0f0', + 'editor_warning_border': '#cccccc', + 'editor_warning_text': '#555555' } }, 'electric': { @@ -109,7 +119,14 @@ LAYERED_THEMES = { 'editor_button_active': '#0099ff', 'editor_text_color': '#00ffff', 'editor_focus_color': '#ffff00', - 'editor_shadow': '0 0 20px rgba(0,255,255,0.5), 0 0 40px rgba(255,255,0,0.2)' + 'editor_shadow': '0 0 20px rgba(0,255,255,0.5), 0 0 40px rgba(255,255,0,0.2)', + 'editor_danger_button': '#ff3366', + 'editor_danger_button_hover': '#ff0033', + 'editor_secondary_button': '#006699', + 'editor_secondary_button_hover': '#004d73', + 'editor_warning_bg': '#003366', + 'editor_warning_border': '#00ffff', + 'editor_warning_text': '#ffff00' } }, 'psychedelic': { @@ -122,7 +139,14 @@ LAYERED_THEMES = { 'editor_button_active': 'rgba(255,20,147,0.5)', 'editor_text_color': '#ffffff', 'editor_focus_color': '#ff1493', - 'editor_shadow': 'rgba(255,20,147,0.4)' + 'editor_shadow': 'rgba(255,20,147,0.4)', + 'editor_danger_button': 'linear-gradient(45deg, #ff0066, #cc0044)', + 'editor_danger_button_hover': 'linear-gradient(45deg, #ff3388, #dd1155)', + 'editor_secondary_button': 'linear-gradient(45deg, #8a2be2, #4b0082)', + 'editor_secondary_button_hover': 'linear-gradient(45deg, #9932cc, #6a1a9a)', + 'editor_warning_bg': 'linear-gradient(45deg, #ffa500, #ff8c00)', + 'editor_warning_border': '#ff1493', + 'editor_warning_text': '#ffffff' } },