📜
editor_index_copy2.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
/** * Editor Index Module v2 * Hierarchical, language-aware document structure navigation */ (function() { 'use strict'; console.log("[editor_index.js v2] Loading editor index module..."); // ========================================================================= // DEPENDENCIES (injected from main editor) // ========================================================================= let deps = { getGlobalEditor: null, getEl: null, showOverlay: null, hideOverlay: null, escapeHtml: null, showToast: null }; // ========================================================================= // LANGUAGE DETECTION // ========================================================================= const LANGUAGE_INFO = { php: { icon: '📄', color: '#8892BF' }, js: { icon: '⚙️', color: '#F7DF1E' }, javascript: { icon: '⚙️', color: '#F7DF1E' }, css: { icon: '🎨', color: '#264DE4' }, html: { icon: '📦', color: '#E34F26' }, htm: { icon: '📦', color: '#E34F26' } }; function getLanguageInfo(lang) { const normalized = lang?.toLowerCase() || 'unknown'; return LANGUAGE_INFO[normalized] || { icon: '📝', color: '#888' }; } // ========================================================================= // MARKER PARSING // ========================================================================= /** * Parse marker name into components * Examples: * "buttons_css_1" -> { component: "buttons", language: "css", number: 1 } * "buttons_css" -> { component: "buttons", language: "css", number: null } * "buttons" -> { component: "buttons", language: null, number: null } */ function parseMarkerName(markerName) { // Remove brackets if present const cleaned = markerName.replace(/[\[\]]/g, '').trim(); const parts = cleaned.split('_'); if (parts.length === 1) { // Just component name: "buttons" return { component: parts[0], language: null, number: null, fullName: cleaned }; } if (parts.length === 2) { // Component + language: "buttons_css" return { component: parts[0], language: parts[1], number: null, fullName: cleaned }; } if (parts.length >= 3) { // Component + language + number: "buttons_css_1" const number = parseInt(parts[2]); return { component: parts[0], language: parts[1], number: isNaN(number) ? null : number, fullName: cleaned }; } return { component: cleaned, language: null, number: null, fullName: cleaned }; } // ========================================================================= // INDEX GENERATION // ========================================================================= function generateDocumentIndex() { const globalEditorInstance = deps.getGlobalEditor(); if (!globalEditorInstance) { console.error("[editor_index.js v2] Global editor instance not available"); return { components: {}, unmarked: [] }; } const session = globalEditorInstance.getSession(); const lineCount = session.getLength(); const components = {}; // Hierarchical structure const unmarked = []; // Items without proper markers for (let row = 0; row < lineCount; row++) { const line = session.getLine(row); const trimmed = line.trim(); if (!trimmed) continue; let match; // ===================================================================== // MARKERS (with language awareness) // ===================================================================== if (trimmed.includes('<') && (trimmed.includes('<!--') || trimmed.includes('/*') || trimmed.includes('//'))) { let markerMatch = trimmed.match(/(?:<!--|\/\*|\/\/\/|\/\/)\s*(.+?)</); if (markerMatch) { const markerName = markerMatch[1].trim(); const parsed = parseMarkerName(markerName); // Find closing marker const endRow = findMarkerEnd(row, `[${markerName}]`); const markerItem = { type: 'marker', row: row, endRow: endRow, label: markerName, parsed: parsed, preview: trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : '') }; if (parsed.language) { // Has language specification - add to components if (!components[parsed.component]) { components[parsed.component] = {}; } if (!components[parsed.component][parsed.language]) { components[parsed.component][parsed.language] = []; } components[parsed.component][parsed.language].push(markerItem); } else { // No language - add to unmarked unmarked.push(markerItem); } } } // ===================================================================== // LOOSE ITEMS (not in markers) // ===================================================================== // HTML tags else if ((match = trimmed.match(/^<(div|section|nav|header|footer|main|article|aside|button)[^>]*(?:id=["']([^"']+)["']|class=["']([^"']+)["'])?[^>]*>/i))) { const tag = match[1]; const id = match[2]; const className = match[3]; const label = id ? `#${id}` : (className ? `.${className.split(' ')[0]}` : `<${tag}>`); unmarked.push({ type: 'html', row: row, label: label, icon: '📦', preview: trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : '') }); } // JavaScript functions else if ((match = trimmed.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:function|\([^)]*\)\s*=>))/))) { const funcName = match[1] || match[2]; unmarked.push({ type: 'function', row: row, label: `${funcName}()`, icon: '⚙️', preview: trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : '') }); } // PHP functions else if ((match = trimmed.match(/(?:public|private|protected|static)?\s*function\s+(\w+)\s*\(/))) { unmarked.push({ type: 'function', row: row, label: `${match[1]}()`, icon: '🔧', preview: trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : '') }); } // CSS classes else if ((match = trimmed.match(/^\.([a-zA-Z0-9_-]+)\s*\{/))) { unmarked.push({ type: 'css', row: row, label: `.${match[1]}`, icon: '🎨', preview: trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : '') }); } // CSS element selectors else if ((match = trimmed.match(/^(body|html|header|footer|main|section|nav|article|aside|h[1-6]|p|div|span|a|button|input|form)\s*\{/i))) { unmarked.push({ type: 'css', row: row, label: match[1], icon: '🎨', preview: trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : '') }); } } return { components, unmarked }; } // ========================================================================= // MARKER UTILITIES // ========================================================================= function findMarkerEnd(startRow, markerName) { const globalEditorInstance = deps.getGlobalEditor(); if (!globalEditorInstance) return startRow; const session = globalEditorInstance.getSession(); const lineCount = session.getLength(); const cleanMarker = markerName.replace(/[\[\]]/g, '').trim(); for (let row = startRow + 1; row < lineCount; row++) { const line = session.getLine(row); if (line.includes('>')) { const closingMatch = line.match(/(?:<!--|\/\*|\/\/\/|\/\/)\s*(.+?)>/); if (closingMatch && closingMatch[1].trim() === cleanMarker) { return row; } } } return startRow; } // ========================================================================= // INDEX OVERLAY (HIERARCHICAL VIEW) // ========================================================================= function showIndexOverlay() { const { components, unmarked } = generateDocumentIndex(); const componentCount = Object.keys(components).length; const unmarkedCount = unmarked.length; const totalCount = componentCount + unmarkedCount; if (totalCount === 0) { deps.showOverlay( 'Document Index', '<div style="color: #888; text-align: center; padding: 40px;">No sections found in document</div>' ); return; } let indexHtml = ''; // ========================================================================= // COMPONENTS (Hierarchical) // ========================================================================= if (componentCount > 0) { indexHtml += `<div style="margin-bottom: 20px;">`; indexHtml += `<div style=" color: #888; font-size: 12px; font-weight: 600; text-transform: uppercase; margin-bottom: 8px; font-family: 'Segoe UI', sans-serif; ">📦 Components (${componentCount})</div>`; for (const [componentName, languages] of Object.entries(components)) { const languageCount = Object.keys(languages).length; const totalSections = Object.values(languages).reduce((sum, sections) => sum + sections.length, 0); indexHtml += ` <div class="component-group" style=" margin-bottom: 12px; border: 1px solid #444; border-radius: 6px; overflow: hidden; background: #2d2d2d; "> <div class="component-header" data-component="${deps.escapeHtml(componentName)}" style=" padding: 12px; background: #3d3d3d; cursor: pointer; display: flex; align-items: center; gap: 8px; font-family: 'Segoe UI', sans-serif; "> <span class="collapse-icon" style="font-size: 14px; transition: transform 0.2s;">▼</span> <span style="font-size: 16px;">📦</span> <span style="color: #e0e0e0; font-weight: 600; flex: 1;">${deps.escapeHtml(componentName)}</span> <span style="color: #888; font-size: 12px;">${languageCount} lang • ${totalSections} sections</span> </div> <div class="component-content" data-component="${deps.escapeHtml(componentName)}" style=" display: block; ">`; // Languages within component for (const [language, sections] of Object.entries(languages)) { const langInfo = getLanguageInfo(language); indexHtml += ` <div class="language-group" style=" border-top: 1px solid #444; "> <div class="language-header" data-language="${deps.escapeHtml(language)}" style=" padding: 10px 12px; background: #353535; cursor: pointer; display: flex; align-items: center; gap: 8px; font-family: 'Segoe UI', sans-serif; "> <span style="font-size: 12px; transition: transform 0.2s;">▼</span> <span style="font-size: 14px;">${langInfo.icon}</span> <span style="color: #e0e0e0; font-size: 14px; flex: 1;">${deps.escapeHtml(language)}</span> <span style="color: #888; font-size: 11px;">${sections.length} section${sections.length > 1 ? 's' : ''}</span> </div> <div class="language-content" style="display: block;">`; // Sections within language sections.forEach(section => { indexHtml += ` <div class="index-item" data-row="${section.row}" data-is-marker="true" data-label="${deps.escapeHtml(section.label)}" style=" padding: 10px 12px 10px 40px; background: #2d2d2d; border-top: 1px solid #3d3d3d; cursor: pointer; transition: background 0.15s; font-family: 'Segoe UI', monospace; "> <div style="display: flex; align-items: center; gap: 8px;"> <span style="font-size: 14px;">🏷️</span> <div style="flex: 1; min-width: 0;"> <div style="color: #e0e0e0; font-size: 13px;">${deps.escapeHtml(section.label)}</div> <div style="color: #888; font-size: 11px;">Line ${section.row + 1} - ${section.endRow + 1}</div> </div> </div> </div>`; }); indexHtml += ` </div> </div>`; } indexHtml += ` </div> </div>`; } indexHtml += `</div>`; } // ========================================================================= // UNMARKED SECTIONS // ========================================================================= if (unmarkedCount > 0) { indexHtml += `<div>`; indexHtml += `<div style=" color: #888; font-size: 12px; font-weight: 600; text-transform: uppercase; margin-bottom: 8px; font-family: 'Segoe UI', sans-serif; ">📂 Unmarked Sections (${unmarkedCount})</div>`; unmarked.forEach(item => { indexHtml += ` <div class="index-item" data-row="${item.row}" data-is-marker="${item.type === 'marker'}" data-label="${deps.escapeHtml(item.label)}" style=" padding: 10px 12px; margin: 4px 0; background: #3d3d3d; border-radius: 4px; cursor: pointer; transition: background 0.15s; font-family: 'Segoe UI', monospace; "> <div style="display: flex; align-items: center; gap: 8px;"> <span style="font-size: 16px;">${item.icon || '📝'}</span> <div style="flex: 1; min-width: 0;"> <div style="color: #e0e0e0; font-weight: 500; font-size: 14px;">${deps.escapeHtml(item.label)}</div> <div style="color: #888; font-size: 12px;">Line ${item.row + 1}</div> </div> </div> </div>`; }); indexHtml += `</div>`; } deps.showOverlay('Document Index', indexHtml); // ========================================================================= // EVENT LISTENERS // ========================================================================= setTimeout(() => { const el = deps.getEl(); const contentEl = el.querySelector('#overlayContent'); if (!contentEl) return; // Component collapse/expand contentEl.querySelectorAll('.component-header').forEach(header => { header.addEventListener('click', (e) => { const component = header.dataset.component; const content = contentEl.querySelector(`.component-content[data-component="${component}"]`); const icon = header.querySelector('.collapse-icon'); if (content.style.display === 'none') { content.style.display = 'block'; icon.textContent = '▼'; } else { content.style.display = 'none'; icon.textContent = '▶'; } }); }); // Language collapse/expand contentEl.querySelectorAll('.language-header').forEach(header => { header.addEventListener('click', (e) => { e.stopPropagation(); const content = header.nextElementSibling; const icon = header.querySelector('span:first-child'); if (content.style.display === 'none') { content.style.display = 'block'; icon.textContent = '▼'; } else { content.style.display = 'none'; icon.textContent = '▶'; } }); }); // Item click - navigate contentEl.querySelectorAll('.index-item').forEach(item => { item.addEventListener('mouseenter', () => { item.style.background = '#4a5568'; }); item.addEventListener('mouseleave', () => { item.style.background = item.closest('.language-content') ? '#2d2d2d' : '#3d3d3d'; }); item.addEventListener('click', () => { const row = parseInt(item.dataset.row); const isMarker = item.dataset.isMarker === 'true'; const label = item.dataset.label; if (isMarker) { navigateToMarker(row, label); } else { navigateToRow(row); } deps.hideOverlay(); }); }); }, 50); } // ========================================================================= // NAVIGATION // ========================================================================= function navigateToMarker(startRow, label) { const globalEditorInstance = deps.getGlobalEditor(); if (!globalEditorInstance) return; const session = globalEditorInstance.getSession(); const Range = ace.require('ace/range').Range; const endRow = findMarkerEnd(startRow, label); const range = new Range( startRow, 0, endRow, session.getLine(endRow).length ); globalEditorInstance.selection.setRange(range, false); globalEditorInstance.scrollToLine(startRow, true, true, () => {}); globalEditorInstance.focus(); } function navigateToRow(row) { const globalEditorInstance = deps.getGlobalEditor(); if (!globalEditorInstance) return; const session = globalEditorInstance.getSession(); const Range = ace.require('ace/range').Range; const foldRange = session.getFoldWidgetRange(row); if (foldRange) { const extended = new Range( foldRange.start.row, 0, foldRange.end.row, session.getLine(foldRange.end.row).length ); globalEditorInstance.selection.setRange(extended, false); } else { const line = session.getLine(row); const range = new Range(row, 0, row, line.length); globalEditorInstance.selection.setRange(range, false); } globalEditorInstance.scrollToLine(row, true, true, () => {}); globalEditorInstance.focus(); } // ========================================================================= // INITIALIZATION // ========================================================================= function init(dependencies) { deps = { ...deps, ...dependencies }; console.log("[editor_index.js v2] Initialized with dependencies"); } // ========================================================================= // PUBLIC API // ========================================================================= window.EditorIndex = { init: init, generateDocumentIndex: generateDocumentIndex, findMarkerEnd: findMarkerEnd, showIndexOverlay: showIndexOverlay, navigateToMarker: navigateToMarker, navigateToRow: navigateToRow, parseMarkerName: parseMarkerName // Export for use by other modules }; console.log("[editor_index.js v2] Module loaded successfully"); })();