📜
editor_copy8.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Editor that syncs with localStorage for SFTP active files - ACE VERSION with Smart Markers (function() { const ACTIVE_FILES_KEY = 'sftp_active_files'; // === LANGUAGE HANDLING === const LANG_DEFS = { html: { mode: "ace/mode/html", exts: ["html", "htm"], comment: { open: "<!--", close: "-->" } }, css: { mode: "ace/mode/css", exts: ["css"], comment: { open: "/*", close: "*/" } }, javascript: { mode: "ace/mode/javascript", exts: ["js", "mjs", "cjs"], comment: { open: "//", close: "" } }, php: { mode: "ace/mode/php", exts: ["php", "phtml"], comment: { open: "//", close: "" } }, json: { mode: "ace/mode/json", exts: ["json"], comment: { open: "//", close: "" } }, markdown: { mode: "ace/mode/markdown", exts: ["md", "markdown"], comment: { open: "<!--", close: "-->" } }, python: { mode: "ace/mode/python", exts: ["py"], comment: { open: "#", close: "" } }, text: { mode: "ace/mode/text", exts: ["txt"], comment: { open: "//", close: "" } } }; // Choose Ace mode based on file extension function modeFromFileName(fileName = "") { const ext = (fileName.split(".").pop() || "").toLowerCase(); for (const [lang, def] of Object.entries(LANG_DEFS)) { if (def.exts.includes(ext)) return def.mode; } return LANG_DEFS.html.mode; } // Detect which sublanguage is active under the cursor function detectSubLanguage(editor) { const pos = editor.getCursorPosition(); const row = pos.row; const col = pos.column; // Get tokens on current line const tokens = editor.session.getTokens(row); // Find which token we're in let currentCol = 0; let activeToken = null; for (const token of tokens) { const tokenEnd = currentCol + token.value.length; if (col >= currentCol && col <= tokenEnd) { activeToken = token; break; } currentCol = tokenEnd; } if (!activeToken) { activeToken = tokens[tokens.length - 1] || { type: 'text' }; } const type = (activeToken.type || '').toLowerCase(); const currentMode = editor.session.getMode().$id || ""; // Check for JavaScript if (type.includes('javascript') || type.includes('js') || type.includes('script') || type === 'storage.type.js' || type === 'keyword.operator.js' || type.startsWith('source.js')) { return "javascript"; } // Check for CSS if (type.includes('css') || type.includes('style') || type === 'source.css' || type.startsWith('entity.name.tag.css') || type.startsWith('support.type.property-name')) { return "css"; } // Check for PHP if (type.includes('php') || type.includes('meta.tag.php') || type === 'source.php' || type.startsWith('keyword.control.php') || type.startsWith('support.function.php')) { return "php"; } // For HTML/PHP files, check context if (currentMode.includes("html") || currentMode.includes("php")) { let scriptDepth = 0; let styleDepth = 0; let phpDepth = 0; for (let i = 0; i <= row; i++) { const line = editor.session.getLine(i); scriptDepth += (line.match(/<script[^>]*>/gi) || []).length; scriptDepth -= (line.match(/<\/script>/gi) || []).length; styleDepth += (line.match(/<style[^>]*>/gi) || []).length; styleDepth -= (line.match(/<\/style>/gi) || []).length; phpDepth += (line.match(/<\?php/gi) || []).length; phpDepth -= (line.match(/\?>/gi) || []).length; } if (scriptDepth > 0) return "javascript"; if (styleDepth > 0) return "css"; if (phpDepth > 0) return "php"; if (type.includes('tag') || type.includes('attr') || type.includes('entity.name.tag')) { return "html"; } } // Fallback to mode if (currentMode.includes("javascript")) return "javascript"; if (currentMode.includes("css")) return "css"; if (currentMode.includes("python")) return "python"; if (currentMode.includes("markdown")) return "markdown"; if (currentMode.includes("json")) return "json"; if (currentMode.includes("php")) return "php"; if (currentMode.includes("html")) return "html"; return "text"; } // Return correct comment delimiters for the detected language function getCommentStyleFor(langKey) { return LANG_DEFS[langKey]?.comment || { open: "//", close: "" }; } // Get language name from mode function getLangNameFromMode(mode) { for (const [lang, def] of Object.entries(LANG_DEFS)) { if (def.mode === mode || mode.includes(lang)) return lang; } return 'text'; } // Detect language context for a line range function detectLanguageContext(lines, startLine, endLine) { let inScript = false; let inStyle = false; let inPhp = false; // Check context from beginning of file to start of scope for (let i = 0; i <= startLine; i++) { const line = lines[i]; // Track script tags if (/<script[^>]*>/i.test(line)) inScript = true; if (/<\/script>/i.test(line)) inScript = false; // Track style tags if (/<style[^>]*>/i.test(line)) inStyle = true; if (/<\/style>/i.test(line)) inStyle = false; // Track PHP tags if (/<\?php/i.test(line)) inPhp = true; if (/\?>/i.test(line)) inPhp = false; } // Determine language based on context if (inScript) return 'javascript'; if (inStyle) return 'css'; if (inPhp) return 'php'; // Check the actual content of the scope const scopeContent = lines.slice(startLine, endLine + 1).join('\n'); if (/(<\?php|<\?=)/i.test(scopeContent)) return 'php'; if (/<script[^>]*>/i.test(scopeContent)) return 'javascript'; if (/<style[^>]*>/i.test(scopeContent)) return 'css'; if (/<[a-z]+[^>]*>/i.test(scopeContent)) return 'html'; return 'text'; } // Parse scopes/markers from content and group by name function parseScopes(content) { if (!content) return []; const scopeInstances = []; const lines = content.split('\n'); // Patterns to match opening markers in different comment styles const patterns = [ { regex: /\/\/\s*([A-Z_]+)<\s*$/, style: 'js' }, { regex: /\/\*\s*([A-Z_]+)<\s*\*\//, style: 'css' }, { regex: /<!--\s*([A-Z_]+)<\s*-->/, style: 'html' }, { regex: /#\s*([A-Z_]+)<\s*$/, style: 'python' } ]; const scopeStack = []; lines.forEach((line, idx) => { // Check for opening marker for (const pattern of patterns) { const match = line.match(pattern.regex); if (match) { const newScope = { name: match[1], startLine: idx, preview: '', depth: scopeStack.length }; scopeStack.push(newScope); break; } } // Check for closing marker if (scopeStack.length > 0) { const currentScope = scopeStack[scopeStack.length - 1]; const closingPatterns = [ new RegExp(`\\/\\/\\s*${currentScope.name}>\\s*$`), new RegExp(`\\/\\*\\s*${currentScope.name}>\\s*\\*\\/`), new RegExp(`<!--\\s*${currentScope.name}>\\s*-->`), new RegExp(`#\\s*${currentScope.name}>\\s*$`) ]; for (const pattern of closingPatterns) { if (pattern.test(line)) { currentScope.endLine = idx; currentScope.lineCount = currentScope.endLine - currentScope.startLine + 1; // Detect language context currentScope.language = detectLanguageContext(lines, currentScope.startLine, currentScope.endLine); scopeInstances.push(currentScope); scopeStack.pop(); break; } } // Build preview (first non-marker line) if (currentScope && !currentScope.preview && idx > currentScope.startLine) { const trimmed = line.trim(); if (trimmed && !patterns.some(p => p.regex.test(trimmed))) { currentScope.preview = trimmed.substring(0, 60) + (trimmed.length > 60 ? '...' : ''); } } } }); // Close any unclosed scopes while (scopeStack.length > 0) { const currentScope = scopeStack.pop(); currentScope.endLine = lines.length - 1; currentScope.lineCount = currentScope.endLine - currentScope.startLine + 1; currentScope.language = detectLanguageContext(lines, currentScope.startLine, currentScope.endLine); scopeInstances.push(currentScope); } // Group by name const groupedScopes = {}; scopeInstances.forEach(scope => { if (!groupedScopes[scope.name]) { groupedScopes[scope.name] = { name: scope.name, instances: [] }; } groupedScopes[scope.name].instances.push(scope); }); // Convert to array and sort by first occurrence return Object.values(groupedScopes).sort((a, b) => { return a.instances[0].startLine - b.instances[0].startLine; }); } // Wrap selection with smart marker function wrapSelectionWithSmartMarker(editor, markerName) { if (!editor) { console.error('❌ No editor instance'); return; } const selected = editor.getSelectedText(); if (!selected) { alert("⚠️ Select some text first!"); return; } const range = editor.getSelectionRange(); const subLang = detectSubLanguage(editor); const { open, close } = getCommentStyleFor(subLang); let wrapped; if (close) { wrapped = `${open}${markerName}<${close}\n${selected}\n${open}${markerName}>${close}`; } else { wrapped = `${open}${markerName}<\n${selected}\n${open}${markerName}>`; } const wasReadOnly = editor.getReadOnly(); editor.setReadOnly(false); editor.session.replace(range, wrapped); editor.setReadOnly(wasReadOnly); if (typeof showToast === "function") { showToast(`✅ Marked: ${markerName} (${subLang})`, "success"); } } // Wait for ACE to be available function waitForAce(callback, attempts = 0) { if (typeof ace !== 'undefined') { console.log('✅ ACE Editor is available'); callback(); } else if (attempts < 50) { setTimeout(() => waitForAce(callback, attempts + 1), 100); } else { console.error('❌ ACE Editor failed to load after 5 seconds'); callback(); } } // Load active files from localStorage function loadActiveFiles() { try { const saved = localStorage.getItem(ACTIVE_FILES_KEY); return saved ? JSON.parse(saved) : []; } catch (e) { console.error('Failed to load active files:', e); return []; } } // Save active files to localStorage function saveActiveFiles(files) { try { localStorage.setItem(ACTIVE_FILES_KEY, JSON.stringify(files)); return true; } catch (e) { console.error('Failed to save active files:', e); return false; } } // Get the currently active file function getActiveFile() { const files = loadActiveFiles(); return files.find(f => f.active) || null; } // Update the active file content function updateActiveFileContent(content) { const files = loadActiveFiles(); const activeIndex = files.findIndex(f => f.active); if (activeIndex !== -1) { files[activeIndex].content = content; files[activeIndex].lastModified = new Date().toISOString(); saveActiveFiles(files); return true; } return false; } // Create the editor interface function createEditorHTML() { return ` <div style=" display: flex; flex-direction: column; height: 100%; background: #1e1e1e; "> <!-- File Info Bar --> <div id="editorFileInfo" style=" background: #252525; padding: 8px 16px; border-bottom: 1px solid #3a3a3a; display: flex; justify-content: space-between; align-items: center; color: #9aa4b2; font-size: 14px; flex-shrink: 0; "> <div style="display: flex; align-items: center; gap: 12px;"> <div> <span id="editorFileName" style="color: #e6edf3; font-weight: 600;">No file selected</span> <span id="editorFilePath" style="margin-left: 12px; color: #64748b; font-size: 12px;"></span> </div> <button id="openIndexBtn" class="editor-toolbar-btn" style=" background: #1a1a1a; border: 1px solid #2a2a2a; color: #fff; padding: 6px 16px; border-radius: 0; cursor: pointer; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; ">🎯 Scopes</button> <button id="addMarkerBtn" class="editor-toolbar-btn" style=" background: #1a1a1a; border: 1px solid #2a2a2a; color: #fff; padding: 6px 16px; border-radius: 0; cursor: pointer; font-size: 12px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; ">➕ Mark</button> </div> <div id="editorStatus" style="color: #10b981;"> ● Auto-save enabled </div> </div> <!-- ACE Editor Container --> <div id="aceEditorContainer" style=" flex:1; position:relative; width:100%; min-height:600px; "></div> <!-- Status Bar --> <div style=" background: #252525; padding: 6px 16px; border-top: 1px solid #3a3a3a; display: flex; justify-content: space-between; align-items: center; font-size: 12px; color: #64748b; flex-shrink: 0; "> <div id="editorStats"> Lines: <span id="lineCount">0</span> | Characters: <span id="charCount">0</span> | Mode: <span id="editorMode">text</span> | Cursor: <span id="cursorLang" style="color: #3b82f6; font-weight: 600;">-</span> </div> <div id="lastSaved">Last saved: <span id="lastSavedTime">Never</span></div> </div> </div> `; } // Get language color and icon function getLanguageStyle(lang) { const styles = { javascript: { color: '#f7df1e', bg: 'rgba(247, 223, 30, 0.1)', icon: '🟨' }, css: { color: '#264de4', bg: 'rgba(38, 77, 228, 0.1)', icon: '🟦' }, php: { color: '#8892bf', bg: 'rgba(136, 146, 191, 0.1)', icon: '🟪' }, html: { color: '#e34c26', bg: 'rgba(227, 76, 38, 0.1)', icon: '🟧' }, python: { color: '#3776ab', bg: 'rgba(55, 118, 171, 0.1)', icon: '🐍' }, text: { color: '#64748b', bg: 'rgba(100, 116, 139, 0.1)', icon: '📄' } }; return styles[lang] || styles.text; } // Render scope group with all instances function renderScopeGroup(group) { const instances = group.instances; const totalLines = instances.reduce((sum, inst) => sum + inst.lineCount, 0); // Get language distribution const langCounts = {}; instances.forEach(inst => { langCounts[inst.language] = (langCounts[inst.language] || 0) + 1; }); // Primary language is the most common const primaryLang = Object.entries(langCounts).sort((a, b) => b[1] - a[1])[0][0]; const langStyle = getLanguageStyle(primaryLang); let html = ` <div class="scope-group" style=" background: ${langStyle.bg}; border-left: 4px solid ${langStyle.color}; border-radius: 6px; padding: 12px; margin-bottom: 12px; cursor: pointer; transition: all 0.2s; "> <div style="display: flex; justify-content: space-between; align-items: start; margin-bottom: 10px;"> <div style="display: flex; align-items: center; gap: 10px;"> <span style="font-size: 20px;">${langStyle.icon}</span> <div> <div style="color: ${langStyle.color}; font-weight: 700; font-size: 17px; font-family: monospace;"> ${escapeHtml(group.name)} </div> <div style="color: #64748b; font-size: 11px; margin-top: 2px;"> ${instances.length} instance${instances.length !== 1 ? 's' : ''} • ${totalLines} total lines </div> </div> </div> <div style="display: flex; gap: 4px;"> `; // Show language badges Object.entries(langCounts).forEach(([lang, count]) => { const style = getLanguageStyle(lang); html += ` <span style=" background: ${style.bg}; border: 1px solid ${style.color}; color: ${style.color}; padding: 2px 8px; border-radius: 12px; font-size: 10px; font-weight: 700; text-transform: uppercase; ">${lang} ${count}</span> `; }); html += ` </div> </div> `; // Render each instance instances.forEach((instance, idx) => { const instStyle = getLanguageStyle(instance.language); html += ` <div class="scope-instance" data-start-line="${instance.startLine}" data-end-line="${instance.endLine}" style=" background: rgba(0, 0, 0, 0.2); border-left: 3px solid ${instStyle.color}; border-radius: 4px; padding: 8px 10px; margin-bottom: ${idx < instances.length - 1 ? '6px' : '0'}; transition: all 0.2s; "> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 4px;"> <div style="display: flex; align-items: center; gap: 6px;"> <span style=" background: ${instStyle.color}; color: #000; font-weight: 900; font-size: 10px; padding: 2px 6px; border-radius: 3px; font-family: monospace; ">#${idx + 1}</span> <span style="color: ${instStyle.color}; font-size: 11px; text-transform: uppercase; font-weight: 600;"> ${instance.language} </span> </div> <div style="color: #64748b; font-size: 11px;"> Lines ${instance.startLine + 1}-${instance.endLine + 1} (${instance.lineCount}) </div> </div> ${instance.preview ? ` <div style=" color: #9aa4b2; font-size: 11px; font-family: monospace; background: rgba(0,0,0,0.3); padding: 4px 6px; border-radius: 3px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; ">${escapeHtml(instance.preview)}</div> ` : ''} </div> `; }); html += '</div>'; return html; } // Create the Scopes Index overlay HTML function createScopesHTML() { const activeFile = getActiveFile(); let html = ` <div style="padding: 20px; height: 100%; overflow-y: auto; background: #000;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;"> <h2 style="margin: 0; color: #e6edf3; font-size: 18px;">📍 Code Scopes</h2> `; if (!activeFile) { html += ` </div> <div style=" text-align: center; padding: 60px 20px; color: #64748b; "> <div style="font-size: 48px; margin-bottom: 16px;">📄</div> <p style="margin: 0; font-size: 16px;">No file open</p> <p style="margin: 8px 0 0 0; font-size: 14px;">Open a file to see its scopes</p> </div> </div> `; return html; } const scopeGroups = parseScopes(activeFile.content); // Count total instances and languages let totalInstances = 0; const allLangCounts = {}; scopeGroups.forEach(group => { totalInstances += group.instances.length; group.instances.forEach(inst => { allLangCounts[inst.language] = (allLangCounts[inst.language] || 0) + 1; }); }); // Language filter buttons html += '<div style="display: flex; gap: 6px; flex-wrap: wrap;">'; const languages = Object.keys(allLangCounts).sort(); languages.forEach(lang => { const style = getLanguageStyle(lang); html += ` <button class="lang-filter" data-lang="${lang}" style=" background: ${style.bg}; border: 2px solid ${style.color}; color: ${style.color}; padding: 4px 12px; border-radius: 16px; cursor: pointer; font-size: 11px; font-weight: 700; text-transform: uppercase; transition: all 0.2s; ">${style.icon} ${lang} (${allLangCounts[lang]})</button> `; }); html += ` <button class="lang-filter active-filter" data-lang="all" style=" background: rgba(59, 130, 246, 0.2); border: 2px solid #3b82f6; color: #3b82f6; padding: 4px 12px; border-radius: 16px; cursor: pointer; font-size: 11px; font-weight: 700; text-transform: uppercase; transition: all 0.2s; opacity: 1; ">ALL</button> </div> </div> `; if (scopeGroups.length === 0) { html += ` <div style=" text-align: center; padding: 60px 20px; color: #64748b; "> <div style="font-size: 48px; margin-bottom: 16px;">🎯</div> <p style="margin: 0; font-size: 16px;">No scopes found</p> <p style="margin: 8px 0 0 0; font-size: 14px;">Use the "➕ Mark" button to add markers</p> </div> `; } else { html += `<div style="margin: 12px 0; color: #64748b; font-size: 13px;"> Found <strong style="color: #e6edf3;">${scopeGroups.length}</strong> unique scope${scopeGroups.length !== 1 ? 's' : ''} with <strong style="color: #10b981;">${totalInstances}</strong> total instance${totalInstances !== 1 ? 's' : ''} in <strong style="color: #e6edf3;">${escapeHtml(activeFile.name)}</strong> </div>`; html += '<div id="scopesList">'; scopeGroups.forEach(group => { html += renderScopeGroup(group); }); html += '</div>'; } html += '</div>'; return html; } // Setup Scopes overlay interactions function setupScopesInteractions(containerEl) { const activeFile = getActiveFile(); const scopeGroups = activeFile ? parseScopes(activeFile.content) : []; // Flatten all instances for easier lookup const allInstances = []; scopeGroups.forEach(group => { group.instances.forEach(inst => { allInstances.push(inst); }); }); // Language filter functionality let activeFilter = 'all'; const filterButtons = containerEl.querySelectorAll('.lang-filter'); const scopeInstances = containerEl.querySelectorAll('.scope-instance'); function applyFilter(lang) { activeFilter = lang; // Update button styles filterButtons.forEach(btn => { if (btn.dataset.lang === lang) { btn.style.opacity = '1'; btn.style.transform = 'scale(1.05)'; btn.classList.add('active-filter'); } else { btn.style.opacity = '0.5'; btn.style.transform = 'scale(1)'; btn.classList.remove('active-filter'); } }); // Filter scope instances allInstances.forEach((instance, index) => { const item = scopeInstances[index]; if (!item) return; if (lang === 'all' || instance.language === lang) { item.style.display = 'block'; } else { item.style.display = 'none'; } }); // Show/hide groups based on whether they have visible instances const scopeGroups = containerEl.querySelectorAll('.scope-group'); scopeGroups.forEach(groupEl => { const visibleInstances = Array.from(groupEl.querySelectorAll('.scope-instance')) .filter(inst => inst.style.display !== 'none'); if (visibleInstances.length > 0) { groupEl.style.display = 'block'; } else { groupEl.style.display = 'none'; } }); } filterButtons.forEach(btn => { btn.addEventListener('click', () => { applyFilter(btn.dataset.lang); }); }); // Initialize with 'all' filter active applyFilter('all'); // Scope instance interactions scopeInstances.forEach((item, index) => { const instance = allInstances[index]; if (!instance) return; const langStyle = getLanguageStyle(instance.language); item.addEventListener('mouseenter', () => { item.style.borderLeftColor = '#10b981'; item.style.borderLeftWidth = '4px'; item.style.background = 'rgba(16, 185, 129, 0.15)'; item.style.transform = 'translateX(4px)'; }); item.addEventListener('mouseleave', () => { item.style.borderLeftColor = langStyle.color; item.style.borderLeftWidth = '3px'; item.style.background = 'rgba(0, 0, 0, 0.2)'; item.style.transform = 'translateX(0)'; }); item.addEventListener('click', (e) => { e.stopPropagation(); // Prevent group click // Close overlay first if (window.AppOverlay && typeof AppOverlay.close === 'function') { AppOverlay.close(); } // Then select the scope in editor if (window._globalEditorInstance && instance) { const editor = window._globalEditorInstance; // Navigate to the start line editor.gotoLine(instance.startLine + 1, 0, true); // Select from start to end line const Range = ace.require('ace/range').Range; const selection = new Range( instance.startLine, 0, instance.endLine, editor.session.getLine(instance.endLine).length ); editor.selection.setRange(selection); editor.focus(); // Optional: Show confirmation if (typeof showToast === "function") { showToast(`✅ Selected ${instance.language} scope: ${instance.name} (${instance.lineCount} lines)`, "success"); } } }); }); } function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // Initialize ACE editor functionality function initializeEditor(containerEl) { console.log('🔧 Initializing ACE editor...'); if (typeof ace === 'undefined') { console.error('❌ ACE Editor is not available'); containerEl.innerHTML = ` <div style="padding: 40px; text-align: center; color: #ef4444;"> <h2>⚠️ ACE Editor Not Loaded</h2> <p>ACE Editor library is not available.</p> <button onclick="location.reload()" style=" margin-top: 20px; padding: 10px 20px; background: #3b82f6; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; ">🔄 Reload Page</button> </div> `; return; } const editorContainer = containerEl.querySelector('#aceEditorContainer'); const fileNameEl = containerEl.querySelector('#editorFileName'); const filePathEl = containerEl.querySelector('#editorFilePath'); const statusEl = containerEl.querySelector('#editorStatus'); const lineCountEl = containerEl.querySelector('#lineCount'); const charCountEl = containerEl.querySelector('#charCount'); const modeEl = containerEl.querySelector('#editorMode'); const cursorLangEl = containerEl.querySelector('#cursorLang'); const lastSavedTimeEl = containerEl.querySelector('#lastSavedTime'); const openIndexBtn = containerEl.querySelector('#openIndexBtn'); const addMarkerBtn = containerEl.querySelector('#addMarkerBtn'); console.log('🎨 Creating ACE editor instance...'); const editor = ace.edit(editorContainer); console.log('✅ ACE editor instance created'); editor.setTheme('ace/theme/monokai'); editor.session.setMode('ace/mode/text'); editor.setOptions({ fontSize: '14px', enableBasicAutocompletion: true, enableLiveAutocompletion: true, enableSnippets: true, showPrintMargin: false, highlightActiveLine: true, tabSize: 2, useSoftTabs: true, wrap: true }); let saveTimeout = null; let isInitialLoad = true; // Update cursor language indicator function updateCursorLang() { const lang = detectSubLanguage(editor); if (cursorLangEl) { cursorLangEl.textContent = lang; } } // Listen for cursor position changes editor.selection.on('changeCursor', updateCursorLang); // Toolbar button hover effects const toolbarBtns = containerEl.querySelectorAll('.editor-toolbar-btn'); toolbarBtns.forEach(btn => { btn.addEventListener('mouseenter', () => { btn.style.background = '#2a2a2a'; btn.style.borderColor = '#3a3a3a'; }); btn.addEventListener('mouseleave', () => { btn.style.background = '#1a1a1a'; btn.style.borderColor = '#2a2a2a'; }); }); // Scopes button handler if (openIndexBtn) { openIndexBtn.addEventListener('click', () => { if (window.AppOverlay && typeof AppOverlay.open === 'function') { AppOverlay.open([{ title: '🎯 Code Scopes', html: createScopesHTML(), onRender: setupScopesInteractions }]); } }); } // Mark button handler if (addMarkerBtn) { addMarkerBtn.addEventListener('click', () => { const selected = editor.getSelectedText(); if (!selected) { alert("⚠️ Select some text first!"); return; } const markerName = prompt("Enter marker name (e.g., SCOPE, TARGET, KEEP, NOTE):"); if (markerName && markerName.trim()) { wrapSelectionWithSmartMarker(editor, markerName.trim().toUpperCase()); } }); } // Load active file function loadFile() { const activeFile = getActiveFile(); if (activeFile) { editor.setValue(activeFile.content || '', -1); fileNameEl.textContent = activeFile.name; filePathEl.textContent = activeFile.path; const mode = modeFromFileName(activeFile.name); editor.session.setMode(mode); modeEl.textContent = getLangNameFromMode(mode); if (activeFile.lastModified) { const lastMod = new Date(activeFile.lastModified); lastSavedTimeEl.textContent = lastMod.toLocaleTimeString(); } } else { editor.setValue('', -1); fileNameEl.textContent = 'No file selected'; filePathEl.textContent = ''; lastSavedTimeEl.textContent = 'Never'; editor.session.setMode('ace/mode/text'); modeEl.textContent = 'text'; } updateStats(); updateCursorLang(); setTimeout(() => { isInitialLoad = false; }, 500); } // Update statistics function updateStats() { const content = editor.getValue(); const lines = editor.session.getLength(); const chars = content.length; lineCountEl.textContent = lines; charCountEl.textContent = chars; } // Auto-save function function autoSave() { if (isInitialLoad) return; const content = editor.getValue(); const success = updateActiveFileContent(content); if (success) { statusEl.style.color = '#10b981'; statusEl.textContent = '● Saved'; lastSavedTimeEl.textContent = new Date().toLocaleTimeString(); window.dispatchEvent(new CustomEvent('activeFileUpdated', { detail: { content } })); setTimeout(() => { statusEl.textContent = '● Auto-save enabled'; }, 2000); } else { statusEl.style.color = '#ef4444'; statusEl.textContent = '● Save failed'; } } // Listen for changes editor.session.on('change', () => { if (isInitialLoad) return; updateStats(); statusEl.style.color = '#f59e0b'; statusEl.textContent = '● Unsaved changes'; clearTimeout(saveTimeout); saveTimeout = setTimeout(autoSave, 500); }); // Listen for external file changes window.addEventListener('activeFilesUpdated', () => { loadFile(); }); // Initial load loadFile(); // Focus editor and force resize setTimeout(() => { editor.resize(true); editor.focus(); }, 100); // Resize on window resize window.addEventListener('resize', () => { editor.resize(); }); // Store editor instance for external access containerEl._aceEditor = editor; window._globalEditorInstance = editor; console.log('✅ ACE editor fully initialized'); } // Wait for ACE before initializing waitForAce(() => { console.log('🚀 Storage Editor initializing...'); if (window.AppItems) { window.AppItems.push({ title: 'Storage Editor', html: createEditorHTML(), onRender: initializeEditor }); } // Export API window.StorageEditor = { open: () => { if (window.AppOverlay && typeof AppOverlay.open === 'function') { AppOverlay.open([{ title: '📝 Storage Editor', html: createEditorHTML(), onRender: initializeEditor }]); } }, openScopes: () => { if (window.AppOverlay && typeof AppOverlay.open === 'function') { AppOverlay.open([{ title: '🎯 Code Scopes', html: createScopesHTML(), onRender: setupScopesInteractions }]); } }, mark: (markerName) => { if (window._globalEditorInstance) { wrapSelectionWithSmartMarker(window._globalEditorInstance, markerName || 'MARKER'); } }, getActiveFile, loadActiveFiles, saveActiveFiles }; console.log('✅ Storage Editor initialized'); }); })();