');
+ element.innerHTML = htmlWithTargetBlank;
+ } else {
+ element.innerHTML = `${content}
`;
+ }
+
+ this.setupSectionElement(element);
+ }
+
+ findSectionElement(sectionId) {
+ return this.container.querySelector(`[data-section-id="${sectionId}"]`);
+ }
+
+ setupSectionElement(element) {
+ element.className = 'ui-edit-section';
+ element.style.cssText = `
+ margin: 16px 0;
+ padding: 12px;
+ border-radius: 6px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+ border: 2px solid transparent;
+ `;
+
+ element.removeEventListener('mouseenter', element._mouseenterHandler);
+ element.removeEventListener('mouseleave', element._mouseleaveHandler);
+
+ element._mouseenterHandler = () => {
+ element.style.backgroundColor = 'rgba(0, 0, 0, 0.02)';
+ element.style.borderColor = 'rgba(0, 0, 0, 0.1)';
+ };
+
+ element._mouseleaveHandler = () => {
+ element.style.backgroundColor = '';
+ element.style.borderColor = 'transparent';
+ };
+
+ element.addEventListener('mouseenter', element._mouseenterHandler);
+ element.addEventListener('mouseleave', element._mouseleaveHandler);
+ }
+
+ /**
+ * Setup auto-resize for textarea
+ * @param {HTMLTextAreaElement} textarea - The textarea element
+ */
+ setupAutoResize(textarea) {
+ const autoResize = () => {
+ const transition = textarea.style.transition;
+ textarea.style.transition = 'none';
+
+ textarea.style.height = 'auto';
+ const contentHeight = textarea.scrollHeight;
+ const padding = 24;
+
+ const lineCount = textarea.value.split('\n').length;
+ const minHeight = Math.max(60, lineCount * 24 + padding);
+ const maxHeight = 360;
+
+ const newHeight = Math.max(60, Math.min(maxHeight, Math.max(minHeight, contentHeight + 4)));
+ textarea.style.height = newHeight + 'px';
+
+ textarea.style.transition = transition;
+ };
+
+ textarea.addEventListener('input', autoResize);
+ textarea.addEventListener('paste', () => setTimeout(autoResize, 10));
+
+ // Initial sizing
+ setTimeout(autoResize, 20);
+ }
+
+ /**
+ * Create status panel for real-time status display
+ * @returns {HTMLElement} Status panel element
+ */
+ createStatusPanel() {
+ const panel = document.createElement('div');
+ panel.className = 'ui-edit-status-panel';
+ panel.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: white;
+ border: 1px solid #ddd;
+ border-radius: 8px;
+ padding: 12px 16px;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
+ font-size: 14px;
+ z-index: 1000;
+ min-width: 180px;
+ `;
+
+ const statusText = document.createElement('div');
+ statusText.className = 'ui-edit-status-text';
+ statusText.textContent = 'Ready';
+
+ const detailsText = document.createElement('div');
+ detailsText.className = 'ui-edit-status-details';
+ detailsText.style.cssText = `
+ font-size: 12px;
+ color: #666;
+ margin-top: 4px;
+ `;
+
+ panel.appendChild(statusText);
+ panel.appendChild(detailsText);
+
+ return panel;
+ }
+
+ /**
+ * Update status display with current status information
+ * @param {Object} status - Status object from SectionManager
+ */
+ updateStatusDisplay(status) {
+ let statusPanel = document.querySelector('.ui-edit-status-panel');
+
+ if (!statusPanel) {
+ statusPanel = this.createStatusPanel();
+ document.body.appendChild(statusPanel);
+ }
+
+ const statusText = statusPanel.querySelector('.ui-edit-status-text');
+ const detailsText = statusPanel.querySelector('.ui-edit-status-details');
+
+ // Update status text and color based on state
+ switch (status.state) {
+ case 'ready':
+ statusText.textContent = 'β Ready';
+ statusText.style.color = '#28a745';
+ break;
+ case 'editing':
+ statusText.textContent = 'βοΈ Editing';
+ statusText.style.color = '#007bff';
+ break;
+ case 'modified':
+ statusText.textContent = 'β οΈ Modified';
+ statusText.style.color = '#ffc107';
+ break;
+ default:
+ statusText.textContent = 'Unknown';
+ statusText.style.color = '#6c757d';
+ }
+
+ // Update details
+ const details = [];
+ details.push(`${status.totalSections} sections`);
+
+ if (status.editingSections.length > 0) {
+ details.push(`${status.editingSections.length} editing`);
+ }
+
+ if (status.modifiedSections > 0) {
+ details.push(`${status.modifiedSections} modified`);
+ }
+
+ detailsText.textContent = details.join(' β’ ');
+
+ // Update timestamp
+ const now = new Date().toLocaleTimeString();
+ statusPanel.setAttribute('title', `Last update: ${now}`);
+ }
+}
+
+/**
+ * Main Editor Integration
+ */
+class MarkitectCleanEditor {
+ constructor(markdownContent, containerElement, options = {}) {
+ debug('10: MarkitectCleanEditor constructor called', 'EDITOR');
+
+ this.options = {
+ theme: 'github',
+ keyboardShortcuts: true,
+ autosave: false,
+ originalFilename: null,
+ ...options
+ };
+
+ debug('11: Creating SectionManager', 'EDITOR');
+ this.sectionManager = new SectionManager();
+
+ debug('12: Creating DOMRenderer', 'EDITOR');
+ this.domRenderer = new DOMRenderer(this.sectionManager, containerElement);
+ this.originalMarkdown = markdownContent;
+
+ this.cleanMarkdownContent = options.cleanMarkdownContent || markdownContent;
+ this.dogtagContent = options.dogtagContent || '';
+
+ debug('13: About to call initialize()', 'EDITOR');
+ this.initialize();
+ debug('14: initialize() completed', 'EDITOR');
+ }
+
+ initialize() {
+ try {
+ debug('15: Starting initialize() - markdown length: ' + this.originalMarkdown.length, 'INIT');
+
+ const sections = this.sectionManager.createSectionsFromMarkdown(this.originalMarkdown);
+ debug('16: Created ' + sections.length + ' sections', 'INIT');
+
+ // Mark the dogtag section as protected if we have a dogtag
+ if (this.dogtagContent) {
+ debug('17: Marking dogtag section', 'INIT');
+ this.markDogtagSection(sections);
+ }
+
+ // Mark base64 reference sections as protected
+ debug('18: Marking base64 reference sections', 'INIT');
+ this.markBase64ReferenceSections(sections);
+
+ console.log(`β Initialized clean editor with ${sections.length} sections`);
+
+ // Add global control panel
+ debug('19: Adding global controls', 'INIT');
+ this.addGlobalControls();
+
+ // Setup status tracking
+ debug('19.5: Setting up status tracking', 'INIT');
+ this.setupStatusTracking();
+
+ debug('20: Initialize completed successfully', 'INIT');
+ return true;
+ } catch (error) {
+ debug('ERROR in initialize: ' + error.message, 'ERROR');
+ console.error('Failed to initialize clean editor:', error);
+ return false;
+ }
+ }
+
+ markDogtagSection(sections) {
+ // Find the section that contains the dogtag content
+ const dogtagText = this.dogtagContent.trim();
+ if (!dogtagText) return;
+
+ for (const section of sections) {
+ if (section.currentMarkdown.includes(dogtagText)) {
+ section.isDogtagSection = true;
+ console.log('Marked dogtag section as protected:', section.id);
+ break;
+ }
+ }
+ }
+
+ markBase64ReferenceSections(sections) {
+ // Find sections that contain base64 image references
+ if (!window.markitectBase64References) return;
+
+ const refIds = Object.keys(window.markitectBase64References);
+ if (refIds.length === 0) return;
+
+ for (const section of sections) {
+ const markdown = section.currentMarkdown;
+
+ // Check if this section contains base64 reference syntax
+ for (const refId of refIds) {
+ if (markdown.includes(`[${refId}]:`)) {
+ section.isBase64RefSection = true;
+ console.log('Marked base64 reference section as protected:', section.id);
+ break;
+ }
+ }
+ }
+ }
+
+ addGlobalControls() {
+ // Create a floating control panel
+ const controlPanel = document.createElement('div');
+ controlPanel.id = 'markitect-global-controls';
+ controlPanel.style.cssText = `
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ background: rgba(248, 249, 250, 0.95);
+ border: 1px solid #dee2e6;
+ border-radius: 8px;
+ padding: 12px;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ z-index: 1000;
+ backdrop-filter: blur(8px);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ font-size: 14px;
+ min-width: 200px;
+ `;
+
+ const title = document.createElement('div');
+ title.style.cssText = `
+ font-weight: 600;
+ margin-bottom: 8px;
+ color: #495057;
+ border-bottom: 1px solid #dee2e6;
+ padding-bottom: 4px;
+ `;
+ title.textContent = 'Document Controls';
+
+ const buttonContainer = document.createElement('div');
+ buttonContainer.style.cssText = `
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ `;
+
+ // Save Document button
+ const saveButton = document.createElement('button');
+ saveButton.id = 'save-document';
+ saveButton.textContent = 'πΎ Save Document';
+ saveButton.style.cssText = `
+ background: #28a745;
+ color: white;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: background-color 0.2s;
+ `;
+
+ // Reset All button
+ const resetButton = document.createElement('button');
+ resetButton.id = 'reset-all';
+ resetButton.textContent = 'π Reset All';
+ resetButton.style.cssText = `
+ background: #ffc107;
+ color: #212529;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: background-color 0.2s;
+ `;
+
+ // Show Status button
+ const statusButton = document.createElement('button');
+ statusButton.id = 'show-status';
+ statusButton.textContent = 'π Show Status';
+ statusButton.style.cssText = `
+ background: #17a2b8;
+ color: white;
+ border: none;
+ padding: 8px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ font-weight: 500;
+ transition: background-color 0.2s;
+ `;
+
+ buttonContainer.appendChild(saveButton);
+ buttonContainer.appendChild(resetButton);
+ buttonContainer.appendChild(statusButton);
+
+ controlPanel.appendChild(title);
+ controlPanel.appendChild(buttonContainer);
+
+ document.body.appendChild(controlPanel);
+
+ // Add event listeners
+ document.getElementById('save-document').addEventListener('click', () => this.saveDocument());
+ document.getElementById('reset-all').addEventListener('click', () => this.resetAllSections());
+ document.getElementById('show-status').addEventListener('click', () => this.showStatus());
+ }
+
+ saveDocument() {
+ try {
+ const markdown = this.sectionManager.getDocumentMarkdown();
+
+ // Generate intelligent filename
+ const filename = this.generateSaveFilename();
+
+ // Create a download link
+ const blob = new Blob([markdown], { type: 'text/markdown' });
+ const url = URL.createObjectURL(blob);
+ const a = document.createElement('a');
+ a.href = url;
+ a.download = filename;
+ document.body.appendChild(a);
+ a.click();
+ document.body.removeChild(a);
+ URL.revokeObjectURL(url);
+
+ this.showMessage(`Document saved as ${filename}!`, 'success');
+ console.log('Document saved:', filename, markdown.length, 'characters');
+ } catch (error) {
+ this.showMessage('Failed to save document: ' + error.message, 'error');
+ console.error('Save failed:', error);
+ }
+ }
+
+ resetAllSections() {
+ if (!confirm('Reset all sections to their original content? This will lose all changes and cannot be undone.')) {
+ return;
+ }
+
+ try {
+ const sections = this.sectionManager.getAllSections();
+ sections.forEach(section => {
+ this.sectionManager.resetSection(section.id);
+ });
+
+ this.showMessage('All sections reset to original content', 'info');
+ console.log('All sections reset');
+ } catch (error) {
+ this.showMessage('Failed to reset sections: ' + error.message, 'error');
+ console.error('Reset failed:', error);
+ }
+ }
+
+ showStatus() {
+ const sections = this.sectionManager.getSectionStatus();
+ const totalSections = sections.length;
+ const editedSections = sections.filter(s => s.hasChanges).length;
+ const currentlyEditing = sections.filter(s => s.isEditing).length;
+
+ const statusHtml = `
+ Document Status
+ Total Sections: ${totalSections}
+ Modified Sections: ${editedSections}
+ Currently Editing: ${currentlyEditing}
+
+ Section Details:
+
+ ${sections.map(s => `
+ -
+ ${s.id} (${s.type})
+ - ${s.state}
+ ${s.hasChanges ? ' βοΈ' : ''}
+ ${s.isEditing ? ' ποΈ' : ''}
+
+ `).join('')}
+
+ `;
+
+ this.showModal('Document Status', statusHtml);
+ }
+
+ showMessage(message, type = 'info') {
+ const messageDiv = document.createElement('div');
+ messageDiv.style.cssText = `
+ position: fixed;
+ top: 20px;
+ left: 50%;
+ transform: translateX(-50%);
+ background: ${type === 'success' ? '#d4edda' : type === 'error' ? '#f8d7da' : '#d1ecf1'};
+ color: ${type === 'success' ? '#155724' : type === 'error' ? '#721c24' : '#0c5460'};
+ border: 1px solid ${type === 'success' ? '#c3e6cb' : type === 'error' ? '#f5c6cb' : '#bee5eb'};
+ border-radius: 6px;
+ padding: 12px 20px;
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ font-size: 14px;
+ z-index: 10001;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+ `;
+ messageDiv.textContent = message;
+
+ document.body.appendChild(messageDiv);
+
+ setTimeout(() => {
+ if (messageDiv.parentNode) {
+ messageDiv.parentNode.removeChild(messageDiv);
+ }
+ }, 3000);
+ }
+
+ showModal(title, content) {
+ // Remove existing modal if present
+ const existingModal = document.getElementById('markitect-modal');
+ if (existingModal) {
+ existingModal.remove();
+ }
+
+ const modalOverlay = document.createElement('div');
+ modalOverlay.id = 'markitect-modal';
+ modalOverlay.style.cssText = `
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.5);
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ z-index: 10000;
+ `;
+
+ const modal = document.createElement('div');
+ modal.style.cssText = `
+ background: white;
+ border-radius: 8px;
+ padding: 24px;
+ max-width: 600px;
+ max-height: 80vh;
+ overflow-y: auto;
+ box-shadow: 0 8px 32px rgba(0,0,0,0.3);
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ `;
+
+ const closeBtn = document.createElement('button');
+ closeBtn.textContent = 'Γ';
+ closeBtn.style.cssText = `
+ float: right;
+ background: none;
+ border: none;
+ font-size: 24px;
+ cursor: pointer;
+ color: #6c757d;
+ margin: -8px -8px 0 0;
+ `;
+
+ const modalContent = document.createElement('div');
+ modalContent.innerHTML = `${title}
${content}`;
+
+ function closeModal() {
+ modalOverlay.remove();
+ }
+
+ closeBtn.addEventListener('click', closeModal);
+ modalOverlay.addEventListener('click', (e) => {
+ if (e.target === modalOverlay) closeModal();
+ });
+
+ modal.appendChild(closeBtn);
+ modal.appendChild(modalContent);
+ modalOverlay.appendChild(modal);
+ document.body.appendChild(modalOverlay);
+ }
+
+ getDocumentMarkdown() {
+ return this.sectionManager.getDocumentMarkdown();
+ }
+
+ escapeRegex(str) {
+ return str.replace(/[.*+?^${}()|[]\]/g, '\\\\$&');
+ }
+
+ convertDataUrlToReference(markdown) {
+ if (!window.markitectBase64References) {
+ return markdown;
+ }
+
+ let convertedMarkdown = markdown;
+
+ Object.entries(window.markitectBase64References).forEach(([refId, refData]) => {
+ const dataUrl = refData.full_data_url;
+ const escapedDataUrl = this.escapeRegex(dataUrl);
+ const dataUrlPattern = new RegExp(`!\\[([^\\]]*)\\]\\(${escapedDataUrl}\\)`, 'g');
+ convertedMarkdown = convertedMarkdown.replace(dataUrlPattern, `![$1][${refId}]`);
+ });
+
+ return convertedMarkdown;
+ }
+
+ /**
+ * Setup real-time status tracking
+ */
+ setupStatusTracking() {
+ // Listen for status updates from SectionManager
+ this.sectionManager.on('status-updated', (status) => {
+ this.domRenderer.updateStatusDisplay(status);
+ });
+
+ // Start periodic status tracking
+ this.sectionManager.startStatusTracking(2000); // Update every 2 seconds
+
+ // Initial status display
+ this.sectionManager.updateGlobalStatus();
+
+ console.log('β Real-time status tracking initialized');
+ }
+
+ /**
+ * Generate intelligent save filename using 4-method fallback system
+ * @returns {string} Generated filename with .md extension
+ */
+ generateSaveFilename() {
+ // Method 1: Original filename from options
+ if (this.options.originalFilename) {
+ return this.sanitizeFilename(this.options.originalFilename);
+ }
+
+ // Method 2: Page title extraction
+ const titleFilename = this.extractFilenameFromTitle();
+ if (titleFilename) {
+ return this.sanitizeFilename(titleFilename + '.md');
+ }
+
+ // Method 3: URL pathname analysis
+ const urlFilename = this.extractFilenameFromUrl();
+ if (urlFilename) {
+ return this.sanitizeFilename(urlFilename + '.md');
+ }
+
+ // Method 4: First heading extraction
+ const headingFilename = this.extractFilenameFromHeading();
+ if (headingFilename) {
+ return this.sanitizeFilename(headingFilename + '.md');
+ }
+
+ // Method 5: Timestamp generation (fallback)
+ return this.generateTimestampFilename();
+ }
+
+ /**
+ * Sanitize filename to be filesystem-safe
+ * @param {string} filename - Raw filename
+ * @returns {string} Sanitized filename
+ */
+ sanitizeFilename(filename) {
+ if (!filename) return 'document.md';
+
+ // Remove or replace filesystem-unsafe characters
+ let sanitized = filename
+ .replace(/[\/\\:*?"<>|]/g, '-') // Replace unsafe chars with dashes
+ .replace(/\s+/g, '-') // Replace spaces with dashes
+ .replace(/-+/g, '-') // Replace multiple dashes with single dash
+ .replace(/^-|-$/g, '') // Remove leading/trailing dashes
+ .trim();
+
+ // Ensure it ends with .md
+ if (!sanitized.endsWith('.md')) {
+ sanitized += '.md';
+ }
+
+ // Ensure it's not empty
+ if (sanitized === '.md') {
+ sanitized = 'document.md';
+ }
+
+ return sanitized;
+ }
+
+ /**
+ * Extract filename from page title
+ * @returns {string|null} Filename or null if not suitable
+ */
+ extractFilenameFromTitle() {
+ if (typeof document === 'undefined' || !document.title) {
+ return null;
+ }
+
+ let title = document.title.trim();
+
+ // Remove common website suffixes
+ title = title
+ .split('|')[0] // Remove " | Website"
+ .split('-')[0] // Remove " - Website"
+ .split('β’')[0] // Remove " β’ Website"
+ .trim();
+
+ if (title.length < 3 || title.length > 100) {
+ return null;
+ }
+
+ return title;
+ }
+
+ /**
+ * Extract filename from URL pathname
+ * @returns {string|null} Filename or null if not suitable
+ */
+ extractFilenameFromUrl() {
+ if (typeof window === 'undefined' || !window.location) {
+ return null;
+ }
+
+ const pathname = window.location.pathname;
+ if (!pathname || pathname === '/') {
+ return null;
+ }
+
+ // Get the last segment of the path
+ const segments = pathname.split('/').filter(s => s.length > 0);
+ if (segments.length === 0) {
+ return null;
+ }
+
+ const lastSegment = segments[segments.length - 1];
+
+ // Remove file extensions
+ const filenameBase = lastSegment.replace(/\.[^.]*$/, '');
+
+ if (filenameBase.length < 3 || filenameBase.length > 100) {
+ return null;
+ }
+
+ return filenameBase;
+ }
+
+ /**
+ * Extract filename from first heading in markdown
+ * @returns {string|null} Filename or null if no heading found
+ */
+ extractFilenameFromHeading() {
+ if (!this.originalMarkdown) {
+ return null;
+ }
+
+ const lines = this.originalMarkdown.split('\n');
+
+ // Find first heading line
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (/^#{1,6}\s/.test(trimmed)) {
+ // Extract heading text (remove # symbols and trim)
+ const headingText = trimmed.replace(/^#{1,6}\s*/, '').trim();
+
+ if (headingText.length >= 3 && headingText.length <= 100) {
+ return headingText;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Generate timestamp-based filename as final fallback
+ * @returns {string} Timestamp-based filename
+ */
+ generateTimestampFilename() {
+ const now = new Date();
+ const timestamp = now.getFullYear().toString() +
+ (now.getMonth() + 1).toString().padStart(2, '0') +
+ now.getDate().toString().padStart(2, '0') + '-' +
+ now.getHours().toString().padStart(2, '0') +
+ now.getMinutes().toString().padStart(2, '0');
+
+ return `document-${timestamp}.md`;
+ }
+
+ /**
+ * Cleanup method to stop status tracking
+ */
+ destroy() {
+ if (this.sectionManager) {
+ this.sectionManager.stopStatusTracking();
+ }
+ }
+}
+
+// Initialize the clean editor system
+let markitectCleanEditor;
+
+function initializeCleanEditor() {
+ debug('1: initializeCleanEditor called', 'INIT');
+
+ const container = document.getElementById('markdown-content');
+ if (!container) {
+ debug('2: FAILED - Markdown content container not found', 'ERROR');
+ return;
+ }
+ debug('3: Container found', 'INIT');
+
+ if (typeof window.MarkitectEditor === 'undefined') {
+ debug('4: FAILED - MarkitectEditor not found', 'ERROR');
+ return;
+ }
+ debug('5: MarkitectEditor found', 'INIT');
+
+ debug('6: Creating editor with content length: ' + markdownContentWithDogtag.length, 'INIT');
+
+ try {
+ markitectCleanEditor = new window.MarkitectEditor.MarkitectCleanEditor(markdownContentWithDogtag, container, {
+ cleanMarkdownContent: markdownContent,
+ dogtagContent: dogtagContent
+ });
+ debug('7: Editor instance created', 'INIT');
+
+ window.markitectCleanEditor = markitectCleanEditor; // Make globally available
+ debug('8: Editor made globally available', 'INIT');
+
+ const contentAfterInit = container.innerHTML;
+ debug('9: Container has content: ' + (contentAfterInit.length > 0 ? 'YES (' + contentAfterInit.length + ' chars)' : 'NO'), 'INIT');
+
+ console.log('β
Clean section editor initialized successfully');
+ } catch (error) {
+ debug('ERROR in editor creation: ' + error.message, 'ERROR');
+ throw error;
+ }
+}
+
+// Document scroll indicators
+function initializeScrollIndicators() {
+ console.log('β
Document scroll indicators initialized');
+}
+
+// Export for module systems
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = { EditState, SectionType, Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
+ global.EditState = EditState;
+ global.SectionType = SectionType;
+ global.Section = Section;
+} else {
+ window.MarkitectEditor = { EditState, SectionType, Section, SectionManager, DOMRenderer, MarkitectCleanEditor };
+ window.EditState = EditState;
+ window.SectionType = SectionType;
+ window.Section = Section;
+}
\ No newline at end of file
diff --git a/test_filename_generation.js b/test_filename_generation.js
new file mode 100644
index 00000000..f7c796a0
--- /dev/null
+++ b/test_filename_generation.js
@@ -0,0 +1,161 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for Intelligent Save Filename Generation Recovery
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test intelligent filename generation functionality
+runner.describe('Intelligent Save Filename Generation System', () => {
+
+ runner.it('should have generateSaveFilename method in MarkitectCleanEditor', async () => {
+ // Load editor
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+ const hasGenerateSaveFilename = typeof editor.generateSaveFilename === 'function';
+ runner.expect(hasGenerateSaveFilename).toBeTruthy();
+ }
+ });
+
+ runner.it('should use original filename from options when available', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container, {
+ originalFilename: 'my-document.md'
+ });
+
+ const filename = editor.generateSaveFilename();
+ runner.expect(filename).toBe('my-document.md');
+ }
+ });
+
+ runner.it('should extract filename from page title when no original filename', async () => {
+ if (global.MarkitectCleanEditor) {
+ // Set a mock document title
+ const originalTitle = document.title;
+ document.title = 'My Amazing Document | Website';
+
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ const filename = editor.generateSaveFilename();
+ runner.expect(filename).toBe('My-Amazing-Document.md');
+
+ // Restore original title
+ document.title = originalTitle;
+ }
+ });
+
+ runner.it('should extract filename from URL pathname when no title', async () => {
+ if (global.MarkitectCleanEditor) {
+ // Mock window.location
+ const originalLocation = global.location;
+ global.location = { pathname: '/docs/user-guide/getting-started' };
+
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ const filename = editor.generateSaveFilename();
+ runner.expect(filename).toBe('getting-started.md');
+
+ // Restore original location
+ global.location = originalLocation;
+ }
+ });
+
+ runner.it('should extract filename from first heading when other methods fail', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const markdownContent = '# Advanced JavaScript Patterns\n\nThis is a guide to advanced patterns.';
+ const editor = new global.MarkitectCleanEditor(markdownContent, container);
+
+ const filename = editor.generateSaveFilename();
+ runner.expect(filename).toBe('Advanced-JavaScript-Patterns.md');
+ }
+ });
+
+ runner.it('should use timestamp when all other methods fail', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const markdownContent = 'Just some content without any headings or special info.';
+ const editor = new global.MarkitectCleanEditor(markdownContent, container);
+
+ const filename = editor.generateSaveFilename();
+ // Should start with 'document-' and end with '.md'
+ runner.expect(filename.startsWith('document-')).toBeTruthy();
+ runner.expect(filename.endsWith('.md')).toBeTruthy();
+
+ // Should contain timestamp
+ const timestampPart = filename.replace('document-', '').replace('.md', '');
+ runner.expect(timestampPart.length).toBeGreaterThan(8); // YYYYMMDD format or longer
+ }
+ });
+
+ runner.it('should sanitize filenames to be filesystem-safe', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const markdownContent = '# This/Has\\Bad:Characters*And?More\n\nContent';
+ const editor = new global.MarkitectCleanEditor(markdownContent, container);
+
+ const filename = editor.generateSaveFilename();
+ // Should not contain filesystem-unsafe characters
+ runner.expect(filename).not.toMatch(/[\/\\:*?"<>|]/);
+ runner.expect(filename).toBe('This-Has-Bad-Characters-And-More-Stuff.md');
+ }
+ });
+
+ runner.it('should handle edge cases like empty content gracefully', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('', container);
+
+ const filename = editor.generateSaveFilename();
+ runner.expect(filename.endsWith('.md')).toBeTruthy();
+ runner.expect(filename.length).toBeGreaterThan(3); // More than just '.md'
+ }
+ });
+
+ runner.it('should prefer higher priority methods over lower priority', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const markdownContent = '# Content Heading\n\nSome content';
+ const editor = new global.MarkitectCleanEditor(markdownContent, container, {
+ originalFilename: 'priority-test.md'
+ });
+
+ const filename = editor.generateSaveFilename();
+ // Should use original filename (method 1) over heading (method 4)
+ runner.expect(filename).toBe('priority-test.md');
+ }
+ });
+
+ runner.it('should have helper methods for each fallback strategy', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Test helper methods exist
+ runner.expect(typeof editor.sanitizeFilename).toBe('function');
+ runner.expect(typeof editor.extractFilenameFromTitle).toBe('function');
+ runner.expect(typeof editor.extractFilenameFromUrl).toBe('function');
+ runner.expect(typeof editor.extractFilenameFromHeading).toBe('function');
+ runner.expect(typeof editor.generateTimestampFilename).toBe('function');
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('πΎ Running TDD Tests for Intelligent Filename Generation Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now implement filename generation!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file
diff --git a/test_get_all_sections.js b/test_get_all_sections.js
new file mode 100755
index 00000000..f4a767a5
--- /dev/null
+++ b/test_get_all_sections.js
@@ -0,0 +1,82 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for getAllSections Method Recovery
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test getAllSections functionality
+runner.describe('SectionManager getAllSections method', () => {
+
+ runner.it('should have getAllSections method in SectionManager', async () => {
+ // Load editor
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasGetAllSections = typeof manager.getAllSections === 'function';
+ runner.expect(hasGetAllSections).toBeTruthy();
+ }
+ });
+
+ runner.it('should return array of all sections', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Create some test sections
+ const sections = manager.createSectionsFromMarkdown('# Test\n\nContent\n\n## Another\n\nMore content');
+
+ // getAllSections should return an array
+ const allSections = manager.getAllSections();
+ runner.expect(Array.isArray(allSections)).toBeTruthy();
+ runner.expect(allSections.length).toBe(sections.length);
+ }
+ });
+
+ runner.it('should return all sections from the sections Map', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Create sections
+ manager.createSectionsFromMarkdown('# Test\n\nContent');
+
+ const allSections = manager.getAllSections();
+ const mapSize = manager.sections.size;
+
+ runner.expect(allSections.length).toBe(mapSize);
+ }
+ });
+
+ runner.it('should return sections with proper properties', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Create sections
+ manager.createSectionsFromMarkdown('# Test\n\nContent');
+
+ const allSections = manager.getAllSections();
+
+ if (allSections.length > 0) {
+ const firstSection = allSections[0];
+ runner.expect(firstSection.id).toBeTruthy();
+ runner.expect(firstSection.currentMarkdown).toBeTruthy();
+ runner.expect(typeof firstSection.hasChanges).toBe('function');
+ runner.expect(typeof firstSection.isEditing).toBe('function');
+ runner.expect(typeof firstSection.getStatus).toBe('function');
+ }
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('π Running TDD Tests for getAllSections Method Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now implement getAllSections!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file
diff --git a/test_keyboard_shortcuts.js b/test_keyboard_shortcuts.js
new file mode 100755
index 00000000..125a7587
--- /dev/null
+++ b/test_keyboard_shortcuts.js
@@ -0,0 +1,103 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for Keyboard Shortcuts Recovery
+ */
+
+const { TestRunner, HTMLFileTester } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test keyboard shortcuts functionality
+runner.describe('Keyboard Shortcuts for Section Editing', () => {
+
+ runner.it('should have handleKeydown method in DOMRenderer', async () => {
+ // Clear cache and load editor
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ // Check if DOMRenderer has handleKeydown method
+ const DOMRenderer = global.DOMRenderer || require('/home/worsch/markitect_project/markitect/static/editor.js').DOMRenderer;
+
+ if (DOMRenderer) {
+ const renderer = new DOMRenderer({}, document.createElement('div'));
+ const hasHandleKeydown = typeof renderer.handleKeydown === 'function';
+ runner.expect(hasHandleKeydown).toBeTruthy();
+ }
+ });
+
+ runner.it('should bind keyboard handlers to textareas', async () => {
+ // This tests the integration - will check if textareas get keydown listeners
+ const { JSDOM } = require('jsdom');
+ const dom = new JSDOM(`
+
+ `);
+
+ global.document = dom.window.document;
+ global.window = dom.window;
+
+ // Load editor and create instances
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ if (global.DOMRenderer && global.SectionManager) {
+ const manager = new global.SectionManager();
+ const renderer = new global.DOMRenderer(manager, dom.window.document.getElementById('test-container'));
+
+ // The handleKeydown method should exist
+ runner.expect(typeof renderer.handleKeydown).toBe('function');
+ }
+ });
+
+ runner.it('should handle Ctrl+Enter for accepting changes', async () => {
+ // Mock event for Ctrl+Enter
+ const mockEvent = {
+ ctrlKey: true,
+ key: 'Enter',
+ preventDefault: () => {},
+ target: { closest: () => null }
+ };
+
+ // Test that the method exists and can be called
+ if (global.DOMRenderer) {
+ const renderer = new global.DOMRenderer({}, document.createElement('div'));
+
+ // Should not throw error when called
+ try {
+ renderer.handleKeydown(mockEvent);
+ runner.expect(true).toBeTruthy();
+ } catch (error) {
+ runner.expect(false).toBeTruthy();
+ }
+ }
+ });
+
+ runner.it('should handle Escape for canceling changes', async () => {
+ // Mock event for Escape
+ const mockEvent = {
+ key: 'Escape',
+ preventDefault: () => {},
+ target: { closest: () => null }
+ };
+
+ if (global.DOMRenderer) {
+ const renderer = new global.DOMRenderer({}, document.createElement('div'));
+
+ // Should not throw error when called
+ try {
+ renderer.handleKeydown(mockEvent);
+ runner.expect(true).toBeTruthy();
+ } catch (error) {
+ runner.expect(false).toBeTruthy();
+ }
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('β¨οΈ Running TDD Tests for Keyboard Shortcuts Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now implement keyboard shortcuts!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file
diff --git a/test_message_system.js b/test_message_system.js
new file mode 100644
index 00000000..fd49d9f4
--- /dev/null
+++ b/test_message_system.js
@@ -0,0 +1,214 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for Professional Message System Recovery
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test professional message system functionality
+runner.describe('Professional Message System with Color-coded Positioning', () => {
+
+ runner.it('should have showMessage method in MarkitectCleanEditor', async () => {
+ // Load editor
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+ const hasShowMessage = typeof editor.showMessage === 'function';
+ runner.expect(hasShowMessage).toBeTruthy();
+ }
+ });
+
+ runner.it('should support different message types (success, error, info, warning)', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Test that method can be called with different types
+ try {
+ editor.showMessage('Success message', 'success');
+ editor.showMessage('Error message', 'error');
+ editor.showMessage('Info message', 'info');
+ editor.showMessage('Warning message', 'warning');
+ runner.expect(true).toBeTruthy();
+ } catch (error) {
+ runner.expect(false).toBeTruthy();
+ }
+ }
+ });
+
+ runner.it('should create properly positioned message elements', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Show a message and check if it creates the right DOM element
+ editor.showMessage('Test message', 'info');
+
+ // Find the message element
+ const messageElements = Array.from(document.querySelectorAll('div')).filter(div =>
+ div.textContent === 'Test message' &&
+ div.style.position === 'fixed'
+ );
+
+ runner.expect(messageElements.length).toBeGreaterThan(0);
+
+ if (messageElements.length > 0) {
+ const messageDiv = messageElements[0];
+ runner.expect(messageDiv.style.position).toBe('fixed');
+ runner.expect(messageDiv.style.zIndex).toBeTruthy();
+ }
+ }
+ });
+
+ runner.it('should have proper color coding for different message types', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Test success message colors
+ editor.showMessage('Success', 'success');
+ let successElement = Array.from(document.querySelectorAll('div')).find(div =>
+ div.textContent === 'Success'
+ );
+ if (successElement) {
+ // Should have green-ish background
+ runner.expect(successElement.style.background.includes('#d4edda') ||
+ successElement.style.backgroundColor.includes('green') ||
+ successElement.style.background.includes('green')).toBeTruthy();
+ }
+
+ // Test error message colors
+ editor.showMessage('Error', 'error');
+ let errorElement = Array.from(document.querySelectorAll('div')).find(div =>
+ div.textContent === 'Error'
+ );
+ if (errorElement) {
+ // Should have red-ish background
+ runner.expect(errorElement.style.background.includes('#f8d7da') ||
+ errorElement.style.backgroundColor.includes('red') ||
+ errorElement.style.background.includes('red')).toBeTruthy();
+ }
+ }
+ });
+
+ runner.it('should auto-dismiss messages after timeout', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Show a message
+ editor.showMessage('Auto dismiss test', 'info');
+
+ // Check message exists
+ let messageElement = Array.from(document.querySelectorAll('div')).find(div =>
+ div.textContent === 'Auto dismiss test'
+ );
+ runner.expect(messageElement).toBeTruthy();
+
+ // Wait a short time and message should still be there
+ setTimeout(() => {
+ let stillThere = Array.from(document.querySelectorAll('div')).find(div =>
+ div.textContent === 'Auto dismiss test'
+ );
+ runner.expect(stillThere).toBeTruthy();
+ }, 1000);
+ }
+ });
+
+ runner.it('should have professional styling with shadows and typography', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ editor.showMessage('Styled message', 'info');
+
+ let messageElement = Array.from(document.querySelectorAll('div')).find(div =>
+ div.textContent === 'Styled message'
+ );
+
+ if (messageElement) {
+ // Should have box shadow
+ runner.expect(messageElement.style.boxShadow).toBeTruthy();
+
+ // Should have border radius
+ runner.expect(messageElement.style.borderRadius).toBeTruthy();
+
+ // Should have proper font family
+ runner.expect(messageElement.style.fontFamily.includes('system') ||
+ messageElement.style.fontFamily.includes('sans-serif')).toBeTruthy();
+
+ // Should have padding
+ runner.expect(messageElement.style.padding).toBeTruthy();
+ }
+ }
+ });
+
+ runner.it('should support advanced message types (warning, debug)', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Test warning and debug types
+ try {
+ editor.showMessage('Warning message', 'warning');
+ editor.showMessage('Debug message', 'debug');
+ runner.expect(true).toBeTruthy();
+ } catch (error) {
+ runner.expect(false).toBeTruthy();
+ }
+ }
+ });
+
+ runner.it('should handle multiple simultaneous messages gracefully', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Show multiple messages
+ editor.showMessage('Message 1', 'info');
+ editor.showMessage('Message 2', 'success');
+ editor.showMessage('Message 3', 'error');
+
+ // All messages should exist
+ const messageElements = Array.from(document.querySelectorAll('div')).filter(div =>
+ div.textContent.startsWith('Message ') && div.style.position === 'fixed'
+ );
+
+ runner.expect(messageElements.length).toBe(3);
+ }
+ });
+
+ runner.it('should have proper stacking order for multiple messages', async () => {
+ if (global.MarkitectCleanEditor) {
+ const container = document.createElement('div');
+ const editor = new global.MarkitectCleanEditor('# Test\n\nContent', container);
+
+ // Check if editor has stackMessages method for advanced positioning
+ const hasStackMessages = typeof editor.stackMessages === 'function';
+
+ // This is optional - if it doesn't exist, that's okay for basic functionality
+ // but we'll test it if it's implemented
+ if (hasStackMessages) {
+ runner.expect(hasStackMessages).toBeTruthy();
+ } else {
+ // Basic functionality is acceptable
+ runner.expect(true).toBeTruthy();
+ }
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('π¬ Running TDD Tests for Professional Message System Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now enhance message system!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file
diff --git a/test_runner.js b/test_runner.js
new file mode 100755
index 00000000..56555878
--- /dev/null
+++ b/test_runner.js
@@ -0,0 +1,249 @@
+#!/usr/bin/env node
+
+/**
+ * HTML Editor Test Runner
+ *
+ * This script provides a test environment for our HTML editor functionality
+ * using puppeteer for headless browser testing.
+ */
+
+const fs = require('fs');
+const path = require('path');
+
+// Simple test framework
+class TestRunner {
+ constructor() {
+ this.tests = [];
+ this.results = [];
+ this.currentTest = null;
+ }
+
+ describe(description, testFn) {
+ console.log(`\nπ Test Suite: ${description}`);
+ console.log('β'.repeat(50));
+ testFn();
+ }
+
+ it(description, testFn) {
+ this.tests.push({ description, testFn });
+ }
+
+ async run() {
+ console.log(`\nπ Running ${this.tests.length} tests...\n`);
+
+ for (const test of this.tests) {
+ this.currentTest = test;
+ try {
+ console.log(` π§ͺ ${test.description}`);
+ await test.testFn();
+ this.results.push({ ...test, status: 'PASS' });
+ console.log(` β
PASS`);
+ } catch (error) {
+ this.results.push({ ...test, status: 'FAIL', error });
+ console.log(` β FAIL: ${error.message}`);
+ }
+ }
+
+ this.printSummary();
+ }
+
+ printSummary() {
+ const passed = this.results.filter(r => r.status === 'PASS').length;
+ const failed = this.results.filter(r => r.status === 'FAIL').length;
+
+ console.log('\n' + 'β'.repeat(50));
+ console.log(`π Test Results: ${passed} passed, ${failed} failed`);
+
+ if (failed > 0) {
+ console.log('\nβ Failed Tests:');
+ this.results.filter(r => r.status === 'FAIL').forEach(test => {
+ console.log(` β’ ${test.description}: ${test.error.message}`);
+ });
+ }
+
+ console.log('β'.repeat(50));
+ }
+
+ expect(actual) {
+ return {
+ toBe: (expected) => {
+ if (actual !== expected) {
+ throw new Error(`Expected ${expected}, got ${actual}`);
+ }
+ },
+ toContain: (expected) => {
+ if (!actual.includes(expected)) {
+ throw new Error(`Expected "${actual}" to contain "${expected}"`);
+ }
+ },
+ toBeTruthy: () => {
+ if (!actual) {
+ throw new Error(`Expected truthy value, got ${actual}`);
+ }
+ },
+ toBeFalsy: () => {
+ if (actual) {
+ throw new Error(`Expected falsy value, got ${actual}`);
+ }
+ }
+ };
+ }
+}
+
+// HTML File Tester
+class HTMLFileTester {
+ constructor(htmlFilePath) {
+ this.htmlFilePath = htmlFilePath;
+ this.html = null;
+ this.jsdom = null;
+ this.window = null;
+ this.document = null;
+ }
+
+ async load() {
+ try {
+ // Try to use jsdom if available
+ const { JSDOM } = require('jsdom');
+ this.html = fs.readFileSync(this.htmlFilePath, 'utf8');
+
+ // Create a DOM environment
+ this.jsdom = new JSDOM(this.html, {
+ runScripts: "dangerously",
+ resources: "usable",
+ pretendToBeVisual: true
+ });
+
+ this.window = this.jsdom.window;
+ this.document = this.window.document;
+
+ // Wait for content to load
+ await new Promise(resolve => {
+ if (this.document.readyState === 'complete') {
+ resolve();
+ } else {
+ this.window.addEventListener('load', resolve);
+ }
+ });
+
+ return true;
+ } catch (error) {
+ // Fallback to simple HTML parsing
+ this.html = fs.readFileSync(this.htmlFilePath, 'utf8');
+ console.log('β οΈ Using fallback HTML parsing (install jsdom for full testing)');
+ return false;
+ }
+ }
+
+ hasElement(selector) {
+ if (this.document) {
+ return !!this.document.querySelector(selector);
+ }
+ // Fallback: simple text search
+ return this.html.includes(selector);
+ }
+
+ getElement(selector) {
+ if (this.document) {
+ return this.document.querySelector(selector);
+ }
+ return null;
+ }
+
+ hasJavaScript(functionName) {
+ return this.html.includes(functionName);
+ }
+
+ hasDebugMode() {
+ return this.html.includes('DEBUG_MODE');
+ }
+
+ getDebugMode() {
+ const match = this.html.match(/const DEBUG_MODE = ['"`](\w+)['"`];/);
+ return match ? match[1] : null;
+ }
+
+ simulate(action, selector) {
+ if (!this.document) {
+ throw new Error('Cannot simulate actions without DOM environment');
+ }
+
+ const element = this.document.querySelector(selector);
+ if (!element) {
+ throw new Error(`Element not found: ${selector}`);
+ }
+
+ switch (action) {
+ case 'click':
+ element.click();
+ break;
+ case 'focus':
+ element.focus();
+ break;
+ default:
+ throw new Error(`Unknown action: ${action}`);
+ }
+ }
+}
+
+// Main test runner instance
+const runner = new TestRunner();
+
+// Export for use
+module.exports = { TestRunner, HTMLFileTester, runner };
+
+// If run directly, run basic tests
+if (require.main === module) {
+ console.log('π§ͺ HTML Editor Test Runner');
+ console.log('Usage: node test_runner.js [html-file-path]');
+
+ const htmlFile = process.argv[2] || '/tmp/test_complete_functionality.html';
+
+ if (!fs.existsSync(htmlFile)) {
+ console.error(`β File not found: ${htmlFile}`);
+ process.exit(1);
+ }
+
+ // Basic structural tests
+ runner.describe('HTML Structure Tests', () => {
+ let tester;
+
+ runner.it('should load HTML file successfully', async () => {
+ tester = new HTMLFileTester(htmlFile);
+ const loaded = await tester.load();
+ runner.expect(loaded || tester.html).toBeTruthy();
+ });
+
+ runner.it('should have markdown content container', async () => {
+ runner.expect(tester.hasElement('#markdown-content')).toBeTruthy();
+ });
+
+ runner.it('should have debug system', async () => {
+ runner.expect(tester.hasDebugMode()).toBeTruthy();
+ });
+
+ runner.it('should use console debug mode', async () => {
+ runner.expect(tester.getDebugMode()).toBe('console');
+ });
+
+ runner.it('should have section editor functions', async () => {
+ runner.expect(tester.hasJavaScript('MarkitectCleanEditor')).toBeTruthy();
+ runner.expect(tester.hasJavaScript('showImageEditor')).toBeTruthy();
+ runner.expect(tester.hasJavaScript('setupAutoResize')).toBeTruthy();
+ });
+
+ runner.it('should have image manipulation functions', async () => {
+ runner.expect(tester.hasJavaScript('replaceImage')).toBeTruthy();
+ runner.expect(tester.hasJavaScript('resizeImage')).toBeTruthy();
+ runner.expect(tester.hasJavaScript('addImageCaption')).toBeTruthy();
+ runner.expect(tester.hasJavaScript('removeImage')).toBeTruthy();
+ });
+ });
+
+ // Run the tests
+ runner.run().then(() => {
+ console.log('\nπ Testing complete!');
+ }).catch(error => {
+ console.error('β Test runner failed:', error);
+ process.exit(1);
+ });
+}
\ No newline at end of file
diff --git a/test_section_splitting.js b/test_section_splitting.js
new file mode 100755
index 00000000..1e7ec81d
--- /dev/null
+++ b/test_section_splitting.js
@@ -0,0 +1,117 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for Section Splitting Functionality Recovery
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test section splitting functionality
+runner.describe('Section Splitting for Dynamic Heading Detection', () => {
+
+ runner.it('should have checkForSectionSplits method in SectionManager', async () => {
+ // Load editor
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasCheckForSectionSplits = typeof manager.checkForSectionSplits === 'function';
+ runner.expect(hasCheckForSectionSplits).toBeTruthy();
+ }
+ });
+
+ runner.it('should detect when new headings are added', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Original content without headings
+ const originalContent = 'Just some text';
+
+ // New content with a heading
+ const newContent = '# New Heading\n\nJust some text';
+
+ const shouldSplit = manager.checkForSectionSplits(newContent, originalContent);
+ runner.expect(shouldSplit).toBeTruthy();
+ }
+ });
+
+ runner.it('should detect when multiple headings are added', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Content with multiple headings
+ const content = '# First Heading\n\nContent\n\n## Second Heading\n\nMore content';
+
+ const shouldSplit = manager.checkForSectionSplits(content, '');
+ runner.expect(shouldSplit).toBeTruthy();
+ }
+ });
+
+ runner.it('should not split when no new headings are added', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Original and new content without headings
+ const originalContent = 'Some text';
+ const newContent = 'Some modified text';
+
+ const shouldSplit = manager.checkForSectionSplits(newContent, originalContent);
+ runner.expect(shouldSplit).toBeFalsy();
+ }
+ });
+
+ runner.it('should have handleSectionSplit method', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasHandleSectionSplit = typeof manager.handleSectionSplit === 'function';
+ runner.expect(hasHandleSectionSplit).toBeTruthy();
+ }
+ });
+
+ runner.it('should have createSectionsFromContent method', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasCreateSectionsFromContent = typeof manager.createSectionsFromContent === 'function';
+ runner.expect(hasCreateSectionsFromContent).toBeTruthy();
+ }
+ });
+
+ runner.it('should emit section-split event when sections are split', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ let eventEmitted = false;
+ manager.on('section-split', () => {
+ eventEmitted = true;
+ });
+
+ // This should emit the event if the method exists and works
+ if (typeof manager.handleSectionSplit === 'function') {
+ try {
+ // Create a test section first
+ manager.createSectionsFromMarkdown('# Test\n\nContent');
+ const sections = manager.getAllSections();
+ if (sections.length > 0) {
+ manager.handleSectionSplit(sections[0].id, '# First\n\nContent\n\n# Second\n\nMore');
+ runner.expect(eventEmitted).toBeTruthy();
+ }
+ } catch (error) {
+ // Method exists but might not be fully implemented yet
+ runner.expect(typeof manager.handleSectionSplit).toBe('function');
+ }
+ }
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('βοΈ Running TDD Tests for Section Splitting Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now implement section splitting!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file
diff --git a/test_state_management.js b/test_state_management.js
new file mode 100755
index 00000000..5ebe25b2
--- /dev/null
+++ b/test_state_management.js
@@ -0,0 +1,84 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for Advanced State Management Recovery
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test the advanced state management system
+runner.describe('Advanced State Management with EditState enum', () => {
+
+ runner.it('should have EditState enum with 4 states', async () => {
+ // Clear any existing definitions to avoid conflicts
+ delete global.EditState;
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+
+ // Load our editor.js to test
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ const hasEditState = global.EditState !== undefined;
+ runner.expect(hasEditState).toBeTruthy();
+
+ if (global.EditState) {
+ runner.expect(global.EditState.ORIGINAL).toBe('original');
+ runner.expect(global.EditState.EDITING).toBe('editing');
+ runner.expect(global.EditState.MODIFIED).toBe('modified');
+ runner.expect(global.EditState.SAVED).toBe('saved');
+ }
+ });
+
+ runner.it('should support pending changes in Section class', async () => {
+ // Editor.js already loaded above
+
+ if (global.Section) {
+ const section = new global.Section('test-id', 'original content');
+
+ // Should have pendingMarkdown property
+ runner.expect(section.pendingMarkdown).toBe(null);
+
+ // Should have proper state management
+ runner.expect(section.state).toBe('original');
+ }
+ });
+
+ runner.it('should implement stopEditing with state preservation', async () => {
+ if (global.Section) {
+ const section = new global.Section('test-id', 'original content');
+
+ // Start editing
+ section.startEdit();
+ section.updateContent('modified content');
+
+ // Stop editing should preserve changes
+ const result = section.stopEditing();
+
+ runner.expect(section.pendingMarkdown).toBe('modified content');
+ runner.expect(section.state).toBe('modified');
+ }
+ });
+
+ runner.it('should implement hasChanges detection', async () => {
+ if (global.Section) {
+ const section = new global.Section('test-id', 'original content');
+
+ // Initially no changes
+ runner.expect(section.hasChanges()).toBe(false);
+
+ // After modification should detect changes
+ section.currentMarkdown = 'modified content';
+ runner.expect(section.hasChanges()).toBe(true);
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('π§ͺ Running TDD Tests for State Management Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now implement the functionality!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file
diff --git a/test_status_tracking.js b/test_status_tracking.js
new file mode 100644
index 00000000..eff02278
--- /dev/null
+++ b/test_status_tracking.js
@@ -0,0 +1,158 @@
+#!/usr/bin/env node
+
+/**
+ * TDD Tests for Real-time Status Tracking Recovery
+ */
+
+const { TestRunner } = require('./test_runner.js');
+const runner = new TestRunner();
+
+// Test real-time status tracking functionality
+runner.describe('Real-time Status Tracking System', () => {
+
+ runner.it('should have updateGlobalStatus method in SectionManager', async () => {
+ // Load editor
+ delete require.cache[require.resolve('/home/worsch/markitect_project/markitect/static/editor.js')];
+ require('/home/worsch/markitect_project/markitect/static/editor.js');
+
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasUpdateGlobalStatus = typeof manager.updateGlobalStatus === 'function';
+ runner.expect(hasUpdateGlobalStatus).toBeTruthy();
+ }
+ });
+
+ runner.it('should have startStatusTracking method', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasStartStatusTracking = typeof manager.startStatusTracking === 'function';
+ runner.expect(hasStartStatusTracking).toBeTruthy();
+ }
+ });
+
+ runner.it('should have stopStatusTracking method', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+ const hasStopStatusTracking = typeof manager.stopStatusTracking === 'function';
+ runner.expect(hasStopStatusTracking).toBeTruthy();
+ }
+ });
+
+ runner.it('should track status changes when sections are modified', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ // Create a test section
+ manager.createSectionsFromMarkdown('# Test\n\nContent');
+ const sections = manager.getAllSections();
+
+ if (sections.length > 0) {
+ const sectionId = sections[0].id;
+
+ // Start editing
+ manager.startEditing(sectionId);
+ manager.updateContent(sectionId, '# Modified\n\nNew content');
+
+ // Status should reflect changes
+ const status = manager.getGlobalStatus();
+ runner.expect(status.hasModifications).toBeTruthy();
+ runner.expect(status.editingSections).toContain(sectionId);
+ }
+ }
+ });
+
+ runner.it('should provide global status information', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ const status = manager.getGlobalStatus();
+ runner.expect(status).toBeTruthy();
+ runner.expect(typeof status.totalSections).toBe('number');
+ runner.expect(typeof status.hasModifications).toBe('boolean');
+ runner.expect(Array.isArray(status.editingSections)).toBeTruthy();
+ runner.expect(typeof status.lastUpdate).toBe('string');
+ }
+ });
+
+ runner.it('should emit status-updated events periodically', async () => {
+ if (global.SectionManager) {
+ const manager = new global.SectionManager();
+
+ let eventEmitted = false;
+ manager.on('status-updated', (status) => {
+ eventEmitted = true;
+ runner.expect(status.totalSections).toBeDefined();
+ runner.expect(status.lastUpdate).toBeDefined();
+ });
+
+ // Start status tracking
+ if (typeof manager.startStatusTracking === 'function') {
+ manager.startStatusTracking();
+
+ // Trigger an update
+ if (typeof manager.updateGlobalStatus === 'function') {
+ manager.updateGlobalStatus();
+ runner.expect(eventEmitted).toBeTruthy();
+ }
+
+ // Stop tracking
+ if (typeof manager.stopStatusTracking === 'function') {
+ manager.stopStatusTracking();
+ }
+ }
+ }
+ });
+
+ runner.it('should have visual status indicators in DOMRenderer', async () => {
+ if (global.DOMRenderer) {
+ const manager = new global.SectionManager();
+ const renderer = new global.DOMRenderer(manager, document.createElement('div'));
+
+ const hasUpdateStatusDisplay = typeof renderer.updateStatusDisplay === 'function';
+ runner.expect(hasUpdateStatusDisplay).toBeTruthy();
+
+ const hasCreateStatusPanel = typeof renderer.createStatusPanel === 'function';
+ runner.expect(hasCreateStatusPanel).toBeTruthy();
+ }
+ });
+
+ runner.it('should display different status states (Ready, Modified, Editing)', async () => {
+ if (global.DOMRenderer && global.SectionManager) {
+ const manager = new global.SectionManager();
+ const renderer = new global.DOMRenderer(manager, document.createElement('div'));
+
+ // Test ready state
+ let status = { state: 'ready', totalSections: 0, hasModifications: false };
+ if (typeof renderer.updateStatusDisplay === 'function') {
+ // Should not throw error
+ try {
+ renderer.updateStatusDisplay(status);
+ runner.expect(true).toBeTruthy();
+ } catch (error) {
+ runner.expect(false).toBeTruthy();
+ }
+ }
+
+ // Test modified state
+ status = { state: 'modified', totalSections: 1, hasModifications: true };
+ if (typeof renderer.updateStatusDisplay === 'function') {
+ try {
+ renderer.updateStatusDisplay(status);
+ runner.expect(true).toBeTruthy();
+ } catch (error) {
+ runner.expect(false).toBeTruthy();
+ }
+ }
+ }
+ });
+});
+
+// Run the tests
+if (require.main === module) {
+ console.log('π Running TDD Tests for Real-time Status Tracking Recovery');
+ runner.run().then(() => {
+ console.log('β
Test run complete - now implement status tracking!');
+ });
+}
+
+module.exports = runner;
\ No newline at end of file