(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);
}
})();