/** * EditControl - Document Editing Tools and Actions Control * * Provides a comprehensive set of document editing tools including text formatting, * document actions (print, save, export), navigation helpers, and editing modes. * Designed to enhance the writing and editing experience within the TestDrive-JSUI * environment. * * Features: * - Document actions (print, save, export to various formats) * - Text formatting tools (bold, italic, headers) * - Navigation helpers (scroll to top/bottom, go to line) * - Word processing features (find/replace, word count) * - Accessibility tools (font size, contrast adjustment) * - Markdown formatting shortcuts * * Dependencies: * - ControlBase (base control functionality) */ /** * EditControl - Comprehensive document editing control * * This control provides writers and editors with essential tools for document * creation and modification. It includes both basic text operations and * advanced features for content management and formatting. */ class EditControl extends ControlBase { constructor() { super(); // Configure for editing functionality this.config = { icon: '✏️', title: 'Edit', className: 'edit-control', defaultContent: 'Document editing tools loading...', ariaLabel: 'Document Edit Control', position: 'e' // East positioning }; // Edit control state this.editingMode = 'view'; // 'view', 'edit', 'preview' this.fontSize = 16; this.lastSaveTime = null; this.unsavedChanges = false; this.shortcuts = new Map(); this.initializeShortcuts(); } /** * Initialize keyboard shortcuts for editing */ initializeShortcuts() { this.shortcuts.set('Ctrl+S', () => this.saveDocument()); this.shortcuts.set('Ctrl+P', () => this.printDocument()); this.shortcuts.set('Ctrl+F', () => this.showFindDialog()); this.shortcuts.set('Ctrl+B', () => this.toggleBold()); this.shortcuts.set('Ctrl+I', () => this.toggleItalic()); this.shortcuts.set('Escape', () => this.exitEditMode()); } /** * Generate the main editing tools HTML */ generateEditToolsHTML() { return this.safeOperation(() => { return `

Edit Tools

Document Actions
Text Tools
Markdown Tools
Mode: ${this.editingMode}
Font: ${this.fontSize}px
${this.lastSaveTime ? `
Saved: ${new Date(this.lastSaveTime).toLocaleTimeString()}
` : ''} ${this.unsavedChanges ? '
⚠️ Unsaved changes
' : ''}
`; }, '

Error generating edit tools

', 'generateEditToolsHTML'); } /** * Print the document */ printDocument() { return this.safeOperation(() => { window.print(); // Show feedback this.showActionFeedback('πŸ–¨οΈ Print dialog opened', '#28a745'); }, null, 'printDocument'); } /** * Save document (placeholder - would integrate with actual save system) */ saveDocument() { return this.safeOperation(() => { // In a real implementation, this would save to a backend this.lastSaveTime = Date.now(); this.unsavedChanges = false; // Update display this.buildContent(); // Show feedback this.showActionFeedback('πŸ’Ύ Document saved', '#007bff'); }, null, 'saveDocument'); } /** * Export document to various formats */ exportDocument() { return this.safeOperation(() => { const contentArea = document.querySelector('#markitect-content') || document.body; const htmlContent = contentArea.innerHTML; const textContent = contentArea.textContent; // Create export menu const exportMenu = document.createElement('div'); exportMenu.style.cssText = ` position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; border: 2px solid #007bff; border-radius: 8px; padding: 1rem; z-index: 10000; box-shadow: 0 4px 20px rgba(0,0,0,0.3); `; exportMenu.innerHTML = `

Export Document

`; // Add export functions exportMenu.exportAsHTML = () => { this.downloadFile(htmlContent, 'document.html', 'text/html'); document.body.removeChild(exportMenu); }; exportMenu.exportAsText = () => { this.downloadFile(textContent, 'document.txt', 'text/plain'); document.body.removeChild(exportMenu); }; exportMenu.exportAsMarkdown = () => { // Simple HTML to Markdown conversion (basic) let markdown = htmlContent .replace(/]*>(.*?)<\/h1>/gi, '# $1\n\n') .replace(/]*>(.*?)<\/h2>/gi, '## $1\n\n') .replace(/]*>(.*?)<\/h3>/gi, '### $1\n\n') .replace(/]*>(.*?)<\/p>/gi, '$1\n\n') .replace(/]*>(.*?)<\/strong>/gi, '**$1**') .replace(/]*>(.*?)<\/em>/gi, '*$1*') .replace(/<[^>]*>/g, ''); // Remove remaining HTML tags this.downloadFile(markdown, 'document.md', 'text/markdown'); document.body.removeChild(exportMenu); }; document.body.appendChild(exportMenu); }, null, 'exportDocument'); } /** * Download a file with given content */ downloadFile(content, filename, mimeType) { const blob = new Blob([content], { type: mimeType }); const url = URL.createObjectURL(blob); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.appendChild(link); link.click(); document.body.removeChild(link); URL.revokeObjectURL(url); } /** * Reset all changes and restore document to original state */ resetAll() { return this.safeOperation(() => { // Show confirmation dialog const confirmed = window.confirm( 'Reset all changes?\n\nThis will:\n' + 'β€’ Restore document to original state\n' + 'β€’ Clear all unsaved changes\n' + 'β€’ Reset font size and other settings\n\n' + 'This action cannot be undone.' ); if (!confirmed) { this.showActionFeedback('🚫 Reset cancelled', '#6c757d'); return; } // Reset edit control state this.fontSize = 16; this.editingMode = 'view'; this.unsavedChanges = false; this.lastSaveTime = null; // Reset font size this.applyFontSize(); // Clear any highlights document.querySelectorAll('.edit-highlight').forEach(el => { el.outerHTML = el.innerHTML; }); // Try to reset sections if SectionManager is available if (window.sectionManager && typeof window.sectionManager.resetAllSections === 'function') { window.sectionManager.resetAllSections(); } // Try to reset document controls if available if (window.documentControls && typeof window.documentControls.resetAllChanges === 'function') { window.documentControls.resetAllChanges(); } // Clear any debug messages if debug control is available if (window.debugControl && typeof window.debugControl.clearMessages === 'function') { window.debugControl.clearMessages(); } // Reload the page as ultimate fallback if (window.confirm('Reload page to complete reset?')) { window.location.reload(); return; } // Update the control display this.buildContent(); // Show feedback this.showActionFeedback('πŸ”„ All changes reset', '#ffc107', '#212529'); }, null, 'resetAll'); } /** * Scroll to top of document */ scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); this.showActionFeedback('⬆️ Scrolled to top', '#6c757d'); } /** * Scroll to bottom of document */ scrollToBottom() { window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' }); this.showActionFeedback('⬇️ Scrolled to bottom', '#6c757d'); } /** * Show go to line dialog */ showGoToLine() { const lineNumber = prompt('Go to line number:'); if (lineNumber && !isNaN(lineNumber)) { // Simple implementation - scroll to approximate position const totalHeight = document.body.scrollHeight; const approximatePosition = (parseInt(lineNumber) / 100) * totalHeight; window.scrollTo({ top: approximatePosition, behavior: 'smooth' }); this.showActionFeedback(`🎯 Went to line ${lineNumber}`, '#6c757d'); } } /** * Show find and replace dialog */ showFindReplace() { const searchTerm = prompt('Find text:'); if (searchTerm) { // Simple highlight implementation this.highlightText(searchTerm); this.showActionFeedback(`πŸ” Highlighted "${searchTerm}"`, '#ffc107', '#000'); } } /** * Highlight text in the document */ highlightText(searchTerm) { return this.safeOperation(() => { // Remove previous highlights document.querySelectorAll('.edit-highlight').forEach(el => { el.outerHTML = el.innerHTML; }); // Add new highlights const contentArea = document.querySelector('#markitect-content') || document.body; const walker = document.createTreeWalker( contentArea, NodeFilter.SHOW_TEXT, null, false ); const textNodes = []; let node; while (node = walker.nextNode()) { textNodes.push(node); } textNodes.forEach(textNode => { const parent = textNode.parentNode; const text = textNode.textContent; if (text.toLowerCase().includes(searchTerm.toLowerCase())) { const regex = new RegExp(`(${searchTerm})`, 'gi'); const highlightedHTML = text.replace(regex, '$1'); const wrapper = document.createElement('div'); wrapper.innerHTML = highlightedHTML; while (wrapper.firstChild) { parent.insertBefore(wrapper.firstChild, textNode); } parent.removeChild(textNode); } }); }, null, 'highlightText'); } /** * Increase font size */ increaseFontSize() { this.fontSize = Math.min(this.fontSize + 2, 24); this.applyFontSize(); this.buildContent(); } /** * Decrease font size */ decreaseFontSize() { this.fontSize = Math.max(this.fontSize - 2, 12); this.applyFontSize(); this.buildContent(); } /** * Apply font size to document */ applyFontSize() { const contentArea = document.querySelector('#markitect-content') || document.body; contentArea.style.fontSize = `${this.fontSize}px`; } /** * Copy page link to clipboard */ copyLink() { return this.safeOperation(() => { const url = window.location.href; if (navigator.clipboard) { navigator.clipboard.writeText(url).then(() => { this.showActionFeedback('πŸ“‹ Link copied to clipboard', '#fd7e14'); }); } else { // Fallback for older browsers prompt('Copy this link:', url); this.showActionFeedback('πŸ“‹ Link displayed for copying', '#fd7e14'); } }, null, 'copyLink'); } /** * Insert markdown formatting */ insertMarkdown(prefix, suffix, placeholder) { // This would integrate with an actual text editor // For now, just show what would be inserted const text = `${prefix}${placeholder}${suffix}`; if (navigator.clipboard) { navigator.clipboard.writeText(text); this.showActionFeedback(`πŸ“‹ Copied: ${text}`, '#495057'); } else { prompt('Markdown to copy:', text); } } /** * Show action feedback message */ showActionFeedback(message, backgroundColor, color = 'white') { const feedback = document.createElement('div'); feedback.style.cssText = ` position: fixed; top: 20px; right: 20px; background: ${backgroundColor}; color: ${color}; padding: 0.5rem 1rem; border-radius: 4px; z-index: 9999; font-size: 0.8rem; box-shadow: 0 2px 10px rgba(0,0,0,0.2); `; feedback.textContent = message; document.body.appendChild(feedback); setTimeout(() => { if (feedback.parentNode) { document.body.removeChild(feedback); } }, 3000); } /** * Build the control content * Override of base class method to provide edit-specific functionality */ buildContent() { return this.safeOperation(() => { const content = this.element?.querySelector('.control-content'); if (content) { content.innerHTML = this.generateEditToolsHTML(); // Store reference to this control for onclick handlers this.element.editControl = this; } }, null, 'buildContent'); } /** * Exit edit mode */ exitEditMode() { this.editingMode = 'view'; this.buildContent(); } } // Export for module systems or attach to global for direct usage if (typeof module !== 'undefined' && module.exports) { module.exports = EditControl; } else { window.EditControl = EditControl; }