📜
editor_copy9.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// Storage Editor - Core Editor Component (editor.js) (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; } // 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 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: "" }; } // 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="openScopesBtn" 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> `; } // 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 openScopesBtn = containerEl.querySelector('#openScopesBtn'); 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 - calls external module if (openScopesBtn) { openScopesBtn.addEventListener('click', () => { if (window.StorageEditorScopes && typeof StorageEditorScopes.open === 'function') { StorageEditorScopes.open(); } else { alert('⚠️ Scopes module not loaded'); } }); } // Mark button handler - calls external module if (addMarkerBtn) { addMarkerBtn.addEventListener('click', () => { if (window.StorageEditorMark && typeof StorageEditorMark.addMarker === 'function') { StorageEditorMark.addMarker(editor); } else { alert('⚠️ Mark module not loaded'); } }); } // 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 Core 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 }]); } }, getActiveFile, loadActiveFiles, saveActiveFiles, detectSubLanguage, getCommentStyleFor, LANG_DEFS }; console.log('✅ Storage Editor Core initialized'); }); })();