/** * 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, ...options }; // Internal state this.container = null; this.sectionManager = null; this.domRenderer = null; this.documentControls = 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; } if (typeof DocumentControls === 'undefined') { console.error('TestDriveJSUI: DocumentControls not loaded'); return; } // Create section manager this.sectionManager = new SectionManager(); // Create DOM renderer this.domRenderer = new DOMRenderer(this.sectionManager, this.container); // Create document controls this.documentControls = new DocumentControls(); this.documentControls.create(); // Set up event handlers for controls this.setupControlHandlers(); // 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(); } }); } /** * 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, '