/** * TestDrive-JSUI - JavaScript-First Markdown Editor * * Main entry point for the TestDrive-JSUI library. * This is a pure JavaScript library for interactive markdown editing. * Language adapters (Python, Ruby, Java) are optional integration helpers. * * @version 1.0.0 * @license MIT */ /** * TestDriveJSUI - Main library class * * Usage: * ```javascript * const editor = new TestDriveJSUI({ * container: '#editor', * markdown: '# Hello World\n\nEdit me!', * mode: 'edit', * theme: 'github', * autosave: false * }); * ``` */ class TestDriveJSUI { constructor(options = {}) { // Validate required options if (!options.container) { throw new Error('TestDriveJSUI: container option is required'); } // Configuration this.config = { container: options.container, markdown: options.markdown || '# Welcome\n\nStart editing...', mode: options.mode || 'edit', // 'edit' or 'view' theme: options.theme || 'github', autosave: options.autosave || false, shortcuts: options.shortcuts !== false, // default true sections: options.sections !== false, // default true debug: options.debug || false, // Advanced control panels (compass-positioned) controls: { editControl: options.controls?.editControl !== false, // default true statusControl: options.controls?.statusControl !== false, // default true contentsControl: options.controls?.contentsControl !== false, // default true debugControl: options.controls?.debugControl || false, // default false // Legacy simple control panel (top-right box) simpleControls: options.controls?.simpleControls || false // default false }, // Control positioning (compass: nw, ne, e, se, s, sw, w) controlPositions: { editControl: options.controlPositions?.editControl || 'ne', statusControl: options.controlPositions?.statusControl || 'e', contentsControl: options.controlPositions?.contentsControl || 'nw', debugControl: options.controlPositions?.debugControl || 'se' }, ...options }; // Internal state this.container = null; this.sectionManager = null; this.domRenderer = null; this.documentControls = null; // Advanced control panel instances this.editControl = null; this.statusControl = null; this.contentsControl = null; this.debugControl = null; this.isInitialized = false; this.listeners = new Map(); // Initialize this.init(); } /** * Initialize the editor */ init() { if (this.isInitialized) { console.warn('TestDriveJSUI: Already initialized'); return; } // Resolve container this.container = this.resolveContainer(this.config.container); if (!this.container) { throw new Error('TestDriveJSUI: Could not resolve container'); } // Check for marked.js dependency if (typeof marked === 'undefined') { console.warn('TestDriveJSUI: marked.js not loaded. Markdown rendering will be limited.'); } // Apply theme this.applyTheme(this.config.theme); // Initialize components based on mode if (this.config.mode === 'edit') { this.initEditMode(); } else { this.initViewMode(); } this.isInitialized = true; // Emit initialized event this.emit('initialized', { mode: this.config.mode, theme: this.config.theme, sections: this.sectionManager ? this.sectionManager.sections.size : 0 }); } /** * Initialize edit mode with full interactivity */ initEditMode() { // Load required components if (typeof SectionManager === 'undefined') { console.error('TestDriveJSUI: SectionManager not loaded'); return; } if (typeof DOMRenderer === 'undefined') { console.error('TestDriveJSUI: DOMRenderer not loaded'); return; } // Create section manager this.sectionManager = new SectionManager(); // Create DOM renderer this.domRenderer = new DOMRenderer(this.sectionManager, this.container); // Initialize control panels based on configuration if (this.config.controls.simpleControls) { // Legacy simple controls (top-right box) if (typeof DocumentControls === 'undefined') { console.warn('TestDriveJSUI: DocumentControls not loaded'); } else { this.documentControls = new DocumentControls(); this.documentControls.create(); this.setupControlHandlers(); } } else { // Advanced compass-positioned control panels this.initializeAdvancedControls(); } // Parse markdown into sections and render this.sectionManager.createSectionsFromMarkdown(this.config.markdown); // Setup keyboard shortcuts if enabled if (this.config.shortcuts) { this.setupKeyboardShortcuts(); } // Setup autosave if enabled if (this.config.autosave) { this.setupAutosave(); } } /** * Initialize view mode (read-only rendering) */ initViewMode() { // Clear container this.container.innerHTML = ''; // Render markdown directly using marked.js or simple renderer const html = this.renderMarkdown(this.config.markdown); // Create content wrapper const contentWrapper = document.createElement('div'); contentWrapper.className = 'testdrive-view-content'; contentWrapper.innerHTML = html; this.container.appendChild(contentWrapper); // Apply view mode styling contentWrapper.style.cssText = ` padding: 20px; max-width: 800px; margin: 0 auto; line-height: 1.6; `; } /** * Setup control panel event handlers */ setupControlHandlers() { this.documentControls.setEventHandlers({ 'save-document': () => { const markdown = this.getMarkdown(); this.emit('save', { markdown }); // If autosave is enabled and we have a save handler, call it if (this.listeners.has('save')) { // User has registered a save handler } else { // Default: save to localStorage this.saveToLocalStorage(markdown); alert('Document saved to browser localStorage'); } }, 'reset-all': () => { if (confirm('Reset all sections to original content?')) { this.resetAll(); } }, 'show-status': () => { const status = this.getStatus(); this.showStatus(status); }, 'toggle-debug': () => { this.toggleDebug(); } }); } /** * Initialize advanced compass-positioned control panels */ initializeAdvancedControls() { console.log('🎛️ Initializing advanced control panels...'); // ContentsControl (Table of Contents) if (this.config.controls.contentsControl && typeof ContentsControl !== 'undefined') { this.contentsControl = new ContentsControl(); this.contentsControl.config.position = this.config.controlPositions.contentsControl; this.contentsControl.show(); console.log(`✅ ContentsControl initialized (${this.config.controlPositions.contentsControl})`); } // StatusControl (Document Statistics) if (this.config.controls.statusControl && typeof StatusControl !== 'undefined') { this.statusControl = new StatusControl(); this.statusControl.config.position = this.config.controlPositions.statusControl; this.statusControl.show(); console.log(`✅ StatusControl initialized (${this.config.controlPositions.statusControl})`); } // DebugControl (Debug Logs) if (this.config.controls.debugControl && typeof DebugControl !== 'undefined') { this.debugControl = new DebugControl(); this.debugControl.config.position = this.config.controlPositions.debugControl; this.debugControl.show(); console.log(`✅ DebugControl initialized (${this.config.controlPositions.debugControl})`); } // EditControl (Document Actions) if (this.config.controls.editControl && typeof EditControl !== 'undefined') { this.editControl = new EditControl(); this.editControl.config.position = this.config.controlPositions.editControl; this.editControl.show(); console.log(`✅ EditControl initialized (${this.config.controlPositions.editControl})`); } // Setup keyboard shortcuts if enabled if (this.config.shortcuts) { this.setupKeyboardShortcuts(); } // Setup autosave if enabled if (this.config.autosave) { this.setupAutosave(); } } /** * Setup keyboard shortcuts */ setupKeyboardShortcuts() { document.addEventListener('keydown', (event) => { // Ctrl+S or Cmd+S - Save if ((event.ctrlKey || event.metaKey) && event.key === 's') { event.preventDefault(); this.save(); } // Escape - Close editor if (event.key === 'Escape') { if (this.domRenderer && this.domRenderer.currentFloatingMenu) { this.domRenderer.hideCurrentEditor(); } } }); } /** * Setup autosave functionality */ setupAutosave() { // Save every 30 seconds this.autosaveInterval = setInterval(() => { const markdown = this.getMarkdown(); this.saveToLocalStorage(markdown); this.emit('autosave', { markdown }); }, 30000); } /** * Resolve container from selector or element */ resolveContainer(container) { if (typeof container === 'string') { return document.querySelector(container); } else if (container instanceof HTMLElement) { return container; } return null; } /** * Render markdown to HTML */ renderMarkdown(markdown) { if (typeof marked !== 'undefined') { // Use marked.js for full markdown support marked.setOptions({ gfm: true, breaks: true, headerIds: true, mangle: false, sanitize: false }); return marked.parse(markdown); } else { // Fallback to simple rendering return this.simpleMarkdownRender(markdown); } } /** * Simple markdown renderer (fallback) */ simpleMarkdownRender(markdown) { return markdown .replace(/^# (.*$)/gim, '
$1')
.replace(/\n/gim, '