📜
editor_copy7.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
(function () { try { console.log("[editor.js] Loading HTML editor module..."); window.AppItems = window.AppItems || []; let globalEditorInstance = null; let saveTimeout = null; // Search state let searchState = { matches: [], idx: -1, markers: [] }; // Fold selection state let lastCursorPos = null; let lastFoldIndex = -1; let cachedFolds = []; // Scope awareness markers let activeScopeMarkerIds = []; let activeSmartMarkerIds = []; // === SAVE HANDLERS === function saveToLocalStorage(editorInstance) { if (!editorInstance) return; try { const files = JSON.parse(localStorage.getItem("sftp_active_files") || "[]"); const active = files.find((f) => f.active); if (active) { active.content = editorInstance.getValue(); localStorage.setItem("sftp_active_files", JSON.stringify(files)); console.log(`[editor.js] ✓ Saved ${active.name}`); return true; } } catch (err) { console.error("[editor.js] Failed to save:", err); return false; } } function debouncedSave(editorInstance) { clearTimeout(saveTimeout); saveTimeout = setTimeout(() => { saveToLocalStorage(editorInstance); }, 500); } // === NAVIGATION SCOPE HANDLER === function checkNavigationScope() { if (!globalEditorInstance || !window.editorNavigationScope) return; const scope = window.editorNavigationScope; console.log("[editor.js] Checking navigation scope:", scope); // Get current file name let currentFileName = "Untitled"; try { const files = JSON.parse(localStorage.getItem("sftp_active_files") || "[]"); const active = files.find((f) => f.active); if (active) currentFileName = active.name; } catch {} // Verify this is for the current file if (scope.fileName !== currentFileName) { console.warn(`[editor.js] Navigation scope is for ${scope.fileName}, but current file is ${currentFileName}`); return; } const Range = ace.require('ace/range').Range; const session = globalEditorInstance.getSession(); // Navigate to the scope const range = new Range( scope.scope.startRow, 0, scope.scope.endRow, session.getLine(scope.scope.endRow).length ); if (scope.select) { globalEditorInstance.selection.setRange(range, false); } if (scope.highlight) { globalEditorInstance.scrollToLine(scope.scope.startRow, true, true, () => {}); } if (scope.cursorFallback && !scope.select) { globalEditorInstance.moveCursorTo(scope.scope.startRow, 0); } globalEditorInstance.focus(); console.log(`[editor.js] Navigated to ${scope.scope.label} (${scope.scope.type}) at lines ${scope.scope.startRow + 1}-${scope.scope.endRow + 1}`); if (typeof showToast === "function") { showToast(`📍 ${scope.scope.label}`, "success"); } // Clear the navigation scope after use delete window.editorNavigationScope; } // === SMART MARKERS (semantic regions) === function detectSubLanguage(editor) { const pos = editor.getCursorPosition(); const token = editor.session.getTokenAt(pos.row, pos.column); if (!token) return "php"; const t = token.type || ""; if (t.includes("php")) return "php"; if (t.includes("js")) return "javascript"; if (t.includes("css")) return "css"; if (t.includes("tag") || t.includes("attr")) return "html"; return "php"; } function getCommentStyleFor(lang) { switch (lang) { case "html": return { open: "<!--", close: "-->" }; case "css": return { open: "/*", close: "*/" }; case "javascript": return { open: "//", close: "" }; case "php": return { open: "/*", close: "*/" }; default: return { open: "//", close: "" }; } } function wrapSelectionWithSmartMarker(markerName) { if (!globalEditorInstance) return; const selected = globalEditorInstance.getSelectedText(); if (!selected) { if (typeof showToast === "function") showToast("⚠️ Select some text first!", "error"); return; } const range = globalEditorInstance.getSelectionRange(); const subLang = detectSubLanguage(globalEditorInstance); const { open, close } = getCommentStyleFor(subLang); let wrapped; if (close) { // Block comments wrapped = `${open}${markerName}<${close}\n${selected}\n${open}${markerName}>${close}`; } else { // Line comments wrapped = `${open}${markerName}<\n${selected}\n${open}${markerName}>`; } const wasReadOnly = globalEditorInstance.getReadOnly(); globalEditorInstance.setReadOnly(false); globalEditorInstance.session.replace(range, wrapped); globalEditorInstance.setReadOnly(wasReadOnly); if (typeof showToast === "function") showToast(`✅ Wrapped with marker: ${markerName}`, "success"); // Recompute smart marker visuals after insertion refreshSmartMarkerHighlights(); } // Parse /*name<*/ ... /*name>*/ pairs into ranges function getSmartMarkerRegions() { if (!globalEditorInstance) return []; const text = globalEditorInstance.getValue().split("\n"); // Support both /*name<*/ */ and <!--name--> styles is optional; here we detect /* */ const regexStart = /\/\*\s*([A-Za-z0-9_-]+)</; const regexEnd = /\/\*\s*([A-Za-z0-9_-]+)>/; const regions = []; for (let i = 0; i < text.length; i++) { const start = regexStart.exec(text[i]); if (start) { const name = start[1]; for (let j = i + 1; j < text.length; j++) { const end = regexEnd.exec(text[j]); if (end && end[1] === name) { regions.push({ name, startRow: i, startCol: 0, endRow: j, endCol: text[j].length, }); break; } } } } return regions; } function clearMarkerIds(ids) { if (!globalEditorInstance) return; const session = globalEditorInstance.getSession(); ids.forEach(id => { try { session.removeMarker(id); } catch {} }); ids.length = 0; } function refreshSmartMarkerHighlights() { if (!globalEditorInstance) return; const session = globalEditorInstance.getSession(); const Range = ace.require("ace/range").Range; clearMarkerIds(activeSmartMarkerIds); const regions = getSmartMarkerRegions(); regions.forEach(r => { const range = new Range(r.startRow, r.startCol, r.endRow, r.endCol); const id = session.addMarker(range, "smart-marker", "line", false); activeSmartMarkerIds.push(id); }); } // === SEARCH HELPERS === function clearSearchMarkers() { if (!globalEditorInstance) return; const session = globalEditorInstance.getSession(); searchState.markers.forEach(id => { try { session.removeMarker(id); } catch {} }); searchState.markers = []; } function updateMatchCounter(el) { const counter = el.querySelector("#matchCounter"); if (!counter) return; if (searchState.matches.length > 0) { counter.textContent = `${searchState.idx + 1} / ${searchState.matches.length}`; } else { counter.textContent = ""; } } function markMatches() { if (!globalEditorInstance) return; clearSearchMarkers(); const session = globalEditorInstance.getSession(); const Range = ace.require("ace/range").Range; searchState.matches.forEach((m, i) => { const r = new Range(m.r, m.s, m.r, m.e); const cls = i === searchState.idx ? "ace_selected-word" : "ace_selection"; const id = session.addMarker(r, cls, "text"); searchState.markers.push(id); }); } function gotoMatch(el) { if (!searchState.matches.length || !globalEditorInstance) return; const m = searchState.matches[searchState.idx]; const Range = ace.require("ace/range").Range; const r = new Range(m.r, m.s, m.r, m.e); globalEditorInstance.selection.setRange(r, false); globalEditorInstance.scrollToLine(m.r, true, true, () => {}); markMatches(); updateMatchCounter(el); } function nextMatch(el) { if (!searchState.matches.length) return; searchState.idx = (searchState.idx + 1) % searchState.matches.length; gotoMatch(el); } function prevMatch(el) { if (!searchState.matches.length) return; searchState.idx = (searchState.idx - 1 + searchState.matches.length) % searchState.matches.length; gotoMatch(el); } function searchInEditor(query, el) { if (!globalEditorInstance || !query) { clearSearchMarkers(); searchState = { matches: [], idx: -1, markers: [] }; updateMatchCounter(el); return; } const session = globalEditorInstance.getSession(); const lines = session.getDocument().getAllLines(); const regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "gi"); searchState.matches = []; searchState.idx = -1; clearSearchMarkers(); lines.forEach((line, r) => { let m; regex.lastIndex = 0; while ((m = regex.exec(line))) { searchState.matches.push({ r, s: m.index, e: m.index + m[0].length }); } }); if (searchState.matches.length) { searchState.idx = 0; gotoMatch(el); } updateMatchCounter(el); } // === FOLD/SCOPE SELECTION (token-aware { } pairing) === function getAllFoldsForRowTokenAware(targetRow) { if (!globalEditorInstance) return []; const session = globalEditorInstance.getSession(); const lineCount = session.getLength(); const stack = []; const allPairs = []; const isCodeToken = (type) => !/comment|string|regex/i.test(type); for (let row = 0; row < lineCount; row++) { const tokens = session.getTokens(row); let col = 0; for (const tok of tokens) { const { type, value } = tok; if (isCodeToken(type)) { for (let i = 0; i < value.length; i++) { const ch = value[i]; if (ch === "{") stack.push({ row, col: col + i }); else if (ch === "}") { const open = stack.pop(); if (open) { allPairs.push({ startRow: open.row, startCol: open.col, endRow: row, endCol: col + i, }); } } } } col += value.length; } } const cursor = globalEditorInstance.getCursorPosition(); const containsCursor = (p) => { if (cursor.row < p.startRow || cursor.row > p.endRow) return false; if (cursor.row === p.startRow && cursor.column <= p.startCol) return false; if (cursor.row === p.endRow && cursor.column >= p.endCol) return false; return true; }; const filtered = allPairs.filter(containsCursor); filtered.sort((a, b) => (a.endRow - a.startRow) - (b.endRow - b.startRow)); return filtered.map((p) => ({ start: p.startRow, end: p.endRow })); } function selectFold() { if (!globalEditorInstance) return; const pos = globalEditorInstance.getCursorPosition(); const session = globalEditorInstance.getSession(); const R = ace.require("ace/range").Range; if (!lastCursorPos || lastCursorPos.row !== pos.row) { cachedFolds = getAllFoldsForRowTokenAware(pos.row); lastFoldIndex = -1; lastCursorPos = { row: pos.row, column: pos.column }; } if (lastFoldIndex === -1) { const line = session.getLine(pos.row); const range = new R(pos.row, 0, pos.row, line.length); globalEditorInstance.selection.setRange(range, false); globalEditorInstance.focus(); lastFoldIndex = 0; return; } if (lastFoldIndex < cachedFolds.length) { const fold = cachedFolds[lastFoldIndex]; const range = new R( fold.start, 0, fold.end, session.getLine(fold.end).length ); globalEditorInstance.selection.setRange(range, false); globalEditorInstance.scrollToLine(fold.start, true, true, () => {}); globalEditorInstance.focus(); lastFoldIndex++; if (lastFoldIndex >= cachedFolds.length) lastFoldIndex = -1; } } // === UI SECTION === const section = { title: "HTML Editor", html: ` <div class="editor-section"> <div class="editor-toolbar" style=" display: flex; gap: 8px; padding: 8px 12px; background: #1e1e1e; border-bottom: 1px solid #333; align-items: center; "> <button id="indexBtn" title="Show document index" style=" padding: 6px 12px; background: #3d3d3d; border: 1px solid #555; border-radius: 4px; color: #e0e0e0; cursor: pointer; font-size: 13px; font-family: 'Segoe UI', sans-serif; ">📑 Index</button> <button id="editSelectionBtn" title="Edit selection in overlay" style=" padding: 6px 12px; background: #3d3d3d; border: 1px solid #555; border-radius: 4px; color: #e0e0e0; cursor: pointer; font-size: 13px; font-family: 'Segoe UI', sans-serif; ">✏️ Edit</button> <input type="text" id="editorSearchInput" placeholder="Find in file... (Ctrl+F)" style=" flex: 1; padding: 6px 10px; background: #2d2d2d; border: 1px solid #444; border-radius: 4px; color: #e0e0e0; font-size: 13px; font-family: 'Segoe UI', sans-serif; "/> <button id="searchPrevBtn" title="Previous match (Shift+Enter)" style=" padding: 6px 12px; background: #3d3d3d; border: 1px solid #555; border-radius: 4px; color: #e0e0e0; cursor: pointer; font-size: 16px; ">↑</button> <button id="searchNextBtn" title="Next match (Enter)" style=" padding: 6px 12px; background: #3d3d3d; border: 1px solid #555; border-radius: 4px; color: #e0e0e0; cursor: pointer; font-size: 16px; ">↓</button> <span id="matchCounter" style=" color: #888; font-size: 13px; font-family: 'Segoe UI', sans-serif; min-width: 60px; "></span> <span id="contextInfo" style=" margin-left: 12px; color: #9aa0a6; font-size: 12px; font-family: 'Segoe UI', sans-serif; opacity: 0.9; "></span> </div> <div class="ace-editor" id="ace-editor-placeholder"></div> </div> `, onRender(el) { console.log("[editor.js] onRender fired"); // Check for navigation scope when editor becomes visible setTimeout(() => { checkNavigationScope(); }, 100); const container = el.querySelector(".ace-editor"); if (!container) return console.warn("[editor.js] No .ace-editor found"); container.style.minHeight = "calc(70vh - 50px)"; container.style.display = "block"; function loadAce(cb) { if (window.ace) return cb(); const s = document.createElement("script"); s.src = "https://cdnjs.cloudflare.com/ajax/libs/ace/1.32.3/ace.js"; s.onload = cb; document.head.appendChild(s); } function updateContextInfo(text) { const elInfo = el.querySelector("#contextInfo"); if (elInfo) elInfo.textContent = text || ""; } // Dynamic scope highlighting (folds that contain cursor) function refreshScopeHighlights() { if (!globalEditorInstance) return; const session = globalEditorInstance.getSession(); const Range = ace.require("ace/range").Range; // Clear previous clearMarkerIds(activeScopeMarkerIds); const pos = globalEditorInstance.getCursorPosition(); // Use Ace folds (if any) + our token-aware folds const aceFolds = (session.getAllFolds?.() || []).map(f => f.range); const customFolds = getAllFoldsForRowTokenAware(pos.row).map(f => ({ start: { row: f.start, column: 0 }, end: { row: f.end, column: session.getLine(f.end).length } })); const ranges = []; // Normalize ace folds to similar shape aceFolds.forEach(r => ranges.push({ start: { row: r.start.row, column: r.start.column }, end: { row: r.end.row, column: r.end.column } })); // Add custom folds (ensures we still get scopes if mode has no folds) customFolds.forEach(r => ranges.push(r)); // Filter to only those containing cursor const containing = ranges.filter(r => { if (pos.row < r.start.row || pos.row > r.end.row) return false; if (pos.row === r.start.row && pos.column < r.start.column) return false; if (pos.row === r.end.row && pos.column > r.end.column) return false; return true; }); // Small-to-large (outermost last) containing.sort((a,b) => ((a.end.row - a.start.row) - (b.end.row - b.start.row))); containing.forEach(r => { const range = new Range(r.start.row, r.start.column, r.end.row, r.end.column); const id = session.addMarker(range, "scope-highlight", "line", false); activeScopeMarkerIds.push(id); }); // Context info line if (containing.length) { const top = containing[0]; updateContextInfo(`Inside scope: ${top.start.row + 1}–${top.end.row + 1}`); } else { updateContextInfo(""); } } function refreshCursorContext() { // Update scope highlights and smart marker highlights (they can change with cursor) refreshScopeHighlights(); // Show currently active smart marker (if any) const pos = globalEditorInstance.getCursorPosition(); const regions = getSmartMarkerRegions(); const inRegion = regions.find(r => { if (pos.row < r.startRow || pos.row > r.endRow) return false; if (pos.row === r.endRow && pos.column > r.endCol) return false; return true; }); const suffix = inRegion ? ` | Marker: ${inRegion.name}` : ""; const infoEl = el.querySelector("#contextInfo"); if (infoEl) infoEl.textContent += suffix; } // === MAIN LOAD === loadAce(() => { console.log("[editor.js] Ace script loaded"); requestAnimationFrame(() => { globalEditorInstance = ace.edit(container); globalEditorInstance.setTheme("ace/theme/monokai"); globalEditorInstance.session.setMode("ace/mode/html"); // Load file content/mode let fileContent = ""; let fileName = "Untitled"; try { const files = JSON.parse(localStorage.getItem("sftp_active_files") || "[]"); const active = files.find((f) => f.active); if (active && typeof active.content === "string") { fileContent = active.content; fileName = active.name; } } catch (err) { console.warn("[editor.js] Failed to load saved file content:", err); } if (fileName !== "Untitled") { const ext = fileName.split(".").pop().toLowerCase(); const modeMap = { php: "php", html: "html", htm: "html", js: "javascript", css: "css", json: "json", py: "python", md: "markdown", txt: "text" }; const mode = modeMap[ext] || "html"; globalEditorInstance.session.setMode(`ace/mode/${mode}`); } globalEditorInstance.setValue( fileContent.trim() !== "" ? fileContent : `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>Document</title> </head> <body> <h1>Hello!</h1> </body> </html>`, -1 ); globalEditorInstance.setOptions({ fontSize: "14px", wrap: true, showPrintMargin: false, useWorker: false, showFoldWidgets: true, foldStyle: "markbegin", enableAutoIndent: true, readOnly: true, highlightActiveLine: false, highlightGutterLine: false }); // --- Disable native folding behavior and convert to selection --- globalEditorInstance.commands.removeCommand("toggleFoldWidget"); globalEditorInstance.commands.removeCommand("toggleParentFoldWidget"); globalEditorInstance.getSession().addFold = () => false; globalEditorInstance.on("guttermousedown", function (e) { const target = e.domEvent.target; if (target.classList.contains("ace_fold-widget")) { const row = e.getDocumentPosition().row; const session = globalEditorInstance.getSession(); const range = session.getFoldWidgetRange(row); e.stop(); e.stopPropagation(); e.domEvent.stopPropagation(); e.domEvent.preventDefault(); if (range) { const Range = ace.require("ace/range").Range; const extended = new Range( range.start.row, 0, range.end.row, session.getLine(range.end.row).length ); globalEditorInstance.selection.setRange(extended, false); globalEditorInstance.scrollToLine(range.start.row, true, true, () => {}); globalEditorInstance.focus(); } return true; } }); // React to content and cursor changes globalEditorInstance.getSession().on("change", () => { debouncedSave(globalEditorInstance); refreshSmartMarkerHighlights(); refreshCursorContext(); }); globalEditorInstance.selection.on("changeCursor", () => { refreshCursorContext(); }); globalEditorInstance.on("blur", () => { clearTimeout(saveTimeout); saveToLocalStorage(globalEditorInstance); }); // === SEARCH WIRING === const searchInput = el.querySelector("#editorSearchInput"); const prevBtn = el.querySelector("#searchPrevBtn"); const nextBtn = el.querySelector("#searchNextBtn"); const indexBtn = el.querySelector("#indexBtn"); const editSelectionBtn = el.querySelector("#editSelectionBtn"); if (searchInput) { searchInput.addEventListener("input", (e) => searchInEditor(e.target.value, el)); searchInput.addEventListener("keydown", (e) => { if (e.key === "Enter") { e.preventDefault(); e.shiftKey ? prevMatch(el) : nextMatch(el); } else if (e.key === "Escape") { searchInput.value = ""; searchInEditor("", el); globalEditorInstance.focus(); } }); } if (prevBtn) prevBtn.addEventListener("click", () => { prevMatch(el); globalEditorInstance.focus(); }); if (nextBtn) nextBtn.addEventListener("click", () => { nextMatch(el); globalEditorInstance.focus(); }); if (indexBtn) { indexBtn.addEventListener("click", () => { console.log("[editor.js] Index button clicked"); // Check if EditorIndex is available if (typeof window.EditorIndex === "undefined" || !window.EditorIndex.showIndexOverlay) { console.error("[editor.js] EditorIndex not found"); if (typeof showToast === "function") { showToast("⚠️ Index module not loaded. Include editor_index.js", "error"); } return; } // Initialize EditorIndex with dependencies if not already done if (window.EditorIndex.init) { window.EditorIndex.init({ getGlobalEditor: () => globalEditorInstance, showToast: typeof showToast === "function" ? showToast : null }); } // Show the index overlay window.EditorIndex.showIndexOverlay(); }); } if (editSelectionBtn) { editSelectionBtn.addEventListener("click", () => { console.log("[editor.js] Edit button clicked"); if (!globalEditorInstance) { console.error("[editor.js] No global editor instance"); return; } // Function to actually open the overlay const openOverlay = () => { // Get selected text or use default message let selectedText = globalEditorInstance.getSelectedText(); console.log("[editor.js] Selected text length:", selectedText.length); if (!selectedText || selectedText.trim() === "") { selectedText = "<!-- No text selected -->\n\n"; } // Store the selection range for replacement const selectionRange = globalEditorInstance.getSelectionRange(); console.log("[editor.js] Opening overlay editor..."); // Open overlay editor with selected text window.OverlayEditor.open(selectedText, (editedText) => { console.log("[editor.js] Overlay editor saved"); // Callback when user saves const wasReadOnly = globalEditorInstance.getReadOnly(); // Temporarily enable editing if needed if (wasReadOnly) { globalEditorInstance.setReadOnly(false); } // Replace the selected text with edited text globalEditorInstance.session.replace(selectionRange, editedText); // Restore read-only state if (wasReadOnly) { globalEditorInstance.setReadOnly(true); } // Show success message if (typeof showToast === "function") { showToast("✅ Selection updated", "success"); } // Save and refresh saveToLocalStorage(globalEditorInstance); refreshSmartMarkerHighlights(); }); }; // Check if OverlayEditor is available and ready console.log("[editor.js] Checking for OverlayEditor:", typeof window.OverlayEditor); if (typeof window.OverlayEditor === "undefined") { console.error("[editor.js] OverlayEditor not found - waiting..."); // Wait a bit and try again (for deferred scripts) setTimeout(() => { if (typeof window.OverlayEditor !== "undefined" && window.OverlayEditor.open) { console.log("[editor.js] OverlayEditor now available"); openOverlay(); } else { console.error("[editor.js] OverlayEditor still not available"); if (typeof showToast === "function") { showToast("⚠️ Overlay editor not loaded. Check console.", "error"); } } }, 100); return; } if (!window.OverlayEditor.open) { console.error("[editor.js] OverlayEditor.open not found"); if (typeof showToast === "function") { showToast("⚠️ Overlay editor not initialized properly", "error"); } return; } // Use ready method if available (for deferred loading) if (window.OverlayEditor.ready) { window.OverlayEditor.ready(openOverlay); } else { openOverlay(); } }); } else { console.error("[editor.js] editSelectionBtn not found in DOM"); } // === KEYBOARD SHORTCUTS === globalEditorInstance.commands.addCommand({ name: "focusSearch", bindKey: { win: "Ctrl-F", mac: "Command-F" }, exec: () => { searchInput?.focus(); searchInput?.select(); } }); globalEditorInstance.commands.addCommand({ name: "selectScopeUp", bindKey: { win: "Alt-Up", mac: "Alt-Up" }, exec: selectFold }); globalEditorInstance.commands.addCommand({ name: "selectScopeDown", bindKey: { win: "Alt-Down", mac: "Alt-Down" }, exec: selectFold }); // Initial highlight pass refreshSmartMarkerHighlights(); refreshCursorContext(); // ===== CHECK FOR NAVIGATION SCOPE ===== // Look for window.editorNavigationScope set by filesystem_index or other external modules // Note: editor_index.js does direct navigation and doesn't use this console.log("[editor.js] Checking for navigation scope... exists?", !!window.editorNavigationScope); if (window.editorNavigationScope) { console.log("[editor.js] Found navigation scope (from filesystem or external module):", window.editorNavigationScope); const scope = window.editorNavigationScope; // Verify this is for the current file console.log(`[editor.js] Scope file: ${scope.fileName}, Current file: ${fileName}`); if (scope.fileName === fileName) { const Range = ace.require('ace/range').Range; const session = globalEditorInstance.getSession(); console.log(`[editor.js] Navigating to rows ${scope.scope.startRow}-${scope.scope.endRow}`); // Navigate to the scope const range = new Range( scope.scope.startRow, 0, scope.scope.endRow, session.getLine(scope.scope.endRow).length ); if (scope.select) { console.log("[editor.js] Selecting range"); globalEditorInstance.selection.setRange(range, false); } if (scope.highlight) { console.log("[editor.js] Scrolling to line"); // Scroll to the location globalEditorInstance.scrollToLine(scope.scope.startRow, true, true, () => {}); } if (scope.cursorFallback && !scope.select) { console.log("[editor.js] Moving cursor"); // Just move cursor without selecting globalEditorInstance.moveCursorTo(scope.scope.startRow, 0); } globalEditorInstance.focus(); console.log(`[editor.js] ✓ Navigated to ${scope.scope.label} (${scope.scope.type}) at lines ${scope.scope.startRow + 1}-${scope.scope.endRow + 1}`); if (typeof showToast === "function") { showToast(`📍 ${scope.scope.label}`, "success"); } } else { console.warn(`[editor.js] Navigation scope is for ${scope.fileName}, but current file is ${fileName}`); } // Clear the navigation scope after use console.log("[editor.js] Clearing navigation scope"); delete window.editorNavigationScope; } else { console.log("[editor.js] No navigation scope found (this is normal for editor_index.js)"); } }); }); } }; window.AppItems.push(section); // === CONTEXT MENU ITEMS === if (!window.AppOverlayMenuItems) window.AppOverlayMenuItems = []; window.AppOverlayMenuItems.push({ label: "Toggle Edit Mode", action: () => { if (!globalEditorInstance) return; const isReadOnly = globalEditorInstance.getReadOnly(); globalEditorInstance.setReadOnly(!isReadOnly); globalEditorInstance.setOptions({ highlightActiveLine: !isReadOnly, highlightGutterLine: !isReadOnly }); if (typeof showToast === "function") { showToast(isReadOnly ? "✏️ Editor now editable" : "🔒 Editor now read-only", "success"); } } }); window.AppOverlayMenuItems.push({ label: "Add Marker", action: () => { if (!globalEditorInstance) return; const selected = globalEditorInstance.getSelectedText(); if (!selected) { if (typeof showToast === "function") showToast("⚠️ Select some text first!", "error"); return; } const markerName = prompt("Enter marker name:"); if (markerName && markerName.trim()) wrapSelectionWithSmartMarker(markerName.trim()); } }); window.AppOverlayMenuItems.push({ label: "Language", submenu: [ { label: "HTML", action: () => { globalEditorInstance?.session.setMode("ace/mode/html"); if (typeof showToast === "function") showToast("✅ Switched to HTML", "success"); } }, { label: "PHP", action: () => { globalEditorInstance?.session.setMode("ace/mode/php"); if (typeof showToast === "function") showToast("✅ Switched to PHP", "success"); } }, { label: "JavaScript", action: () => { globalEditorInstance?.session.setMode("ace/mode/javascript"); if (typeof showToast === "function") showToast("✅ Switched to JavaScript", "success"); } }, { label: "CSS", action: () => { globalEditorInstance?.session.setMode("ace/mode/css"); if (typeof showToast === "function") showToast("✅ Switched to CSS", "success"); } }, { label: "JSON", action: () => { globalEditorInstance?.session.setMode("ace/mode/json"); if (typeof showToast === "function") showToast("✅ Switched to JSON", "success"); } }, { label: "Markdown", action: () => { globalEditorInstance?.session.setMode("ace/mode/markdown"); if (typeof showToast === "function") showToast("✅ Switched to Markdown", "success"); } }, { label: "Python", action: () => { globalEditorInstance?.session.setMode("ace/mode/python"); if (typeof showToast === "function") showToast("✅ Switched to Python", "success"); } }, { label: "Plain Text", action: () => { globalEditorInstance?.session.setMode("ace/mode/text"); if (typeof showToast === "function") showToast("✅ Switched to Plain Text", "success"); } } ] }); // Ensure save on overlay close (if host provides AppOverlay) if (window.AppOverlay && typeof window.AppOverlay.close === "function") { const originalClose = window.AppOverlay.close; window.AppOverlay.close = function (...args) { clearTimeout(saveTimeout); if (globalEditorInstance) { const saved = saveToLocalStorage(globalEditorInstance); if (saved && typeof showToast === "function") { const files = JSON.parse(localStorage.getItem("sftp_active_files") || "[]"); const active = files.find((f) => f.active); if (active) showToast(`💾 Saved ${active.name}`, "success"); } } return originalClose.apply(this, args); }; } } catch (err) { console.error("[editor.js] Fatal error:", err); } })();