(function () {
try {
console.log("[editor.js] Loading HTML editor module...");
window.AppItems = window.AppItems || [];
// Store editor instance globally so close hook can access it
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 = [];
// Improved save function with proper error handling
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;
}
}
// Debounced save - saves 500ms after last keystroke
function debouncedSave(editorInstance) {
clearTimeout(saveTimeout);
saveTimeout = setTimeout(() => {
saveToLocalStorage(editorInstance);
}, 500);
}
// === SMART MARKER FUNCTIONS ===
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);
// Build wrapped text with proper comment syntax
let wrapped;
if (close) {
// Block comment style (HTML, CSS, PHP) - each marker closes on same line
wrapped = `${open}${markerName}<${close}\n${selected}\n${open}${markerName}>${close}`;
} else {
// Line comment style (JavaScript, PHP with //)
wrapped = `${open}${markerName}<\n${selected}\n${open}${markerName}>`;
}
// Temporarily enable editing to insert marker
const wasReadOnly = globalEditorInstance.getReadOnly();
globalEditorInstance.setReadOnly(false);
globalEditorInstance.session.replace(range, wrapped);
// Restore read-only state
globalEditorInstance.setReadOnly(wasReadOnly);
if (typeof showToast === 'function') {
showToast(`✅ Wrapped with marker: ${markerName}`, 'success');
}
console.log(`[editor.js] Wrapped selection with marker "${markerName}" using ${subLang} syntax`);
}
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>
</div>
<div class="ace-editor" id="ace-editor-placeholder"></div>
</div>
<!-- Fullscreen Multi-purpose Overlay -->
<div id="multiOverlay" style="
display: none;
position: fixed;
inset: 0;
background: #1e1e1e;
z-index: 999998;
flex-direction: column;
">
<!-- Header -->
<div style="
flex: 0 0 auto;
padding: 16px 20px;
border-bottom: 1px solid #333;
display: flex;
justify-content: space-between;
align-items: center;
">
<h3 id="overlayTitle" style="
margin: 0;
color: #e0e0e0;
font-size: 18px;
font-family: 'Segoe UI', sans-serif;
">Edit Content</h3>
<button id="closeOverlayBtn" style="
background: none;
border: none;
color: #888;
font-size: 28px;
cursor: pointer;
line-height: 1;
">×</button>
</div>
<!-- Scrollable editor area -->
<div id="overlayContent" style="
flex: 1;
overflow-y: auto;
padding: 20px;
display: flex;
flex-direction: column;
box-sizing: border-box;
margin-bottom: 70px; /* keep space for footer */
"></div>
<!-- Fixed Footer -->
<div id="overlayFooter" style="
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #111827;
border-top: 1px solid #333;
padding: 12px 20px;
display: flex;
justify-content: flex-end;
gap: 10px;
box-shadow: 0 -4px 12px rgba(0, 0, 0, 0.4);
">
<button id="applyBtn" style="
padding: 8px 16px;
background: #16a34a;
border: 1px solid #15803d;
border-radius: 4px;
color: #fff;
cursor: pointer;
font-size: 14px;
font-family: 'Segoe UI', sans-serif;
font-weight: 600;
">✅ Apply Changes</button>
<button id="cancelEditBtn" style="
padding: 8px 16px;
background: #3d3d3d;
border: 1px solid #555;
border-radius: 4px;
color: #e0e0e0;
cursor: pointer;
font-size: 14px;
font-family: 'Segoe UI', sans-serif;
">Cancel</button>
</div>
</div>
`,
onRender(el) {
console.log("[editor.js] onRender fired");
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 fitToOverlayBody() {
const body = container.closest('.app-dialog')?.querySelector('.app-dialog__body');
if (!body) return;
const bodyRect = body.getBoundingClientRect();
const topInBody = container.getBoundingClientRect().top - bodyRect.top;
const targetH = Math.max(200, Math.floor(bodyRect.height - topInBody - 6));
container.style.height = targetH + "px";
}
// === OVERLAY MANAGEMENT ===
function showOverlay(title, content, footer = null, forceReset = false) {
const overlay = el.querySelector('#multiOverlay');
const titleEl = el.querySelector('#overlayTitle');
const contentEl = el.querySelector('#overlayContent');
const footerEl = el.querySelector('#overlayFooter');
if (!overlay || !titleEl || !contentEl || !footerEl) return;
titleEl.textContent = title;
// 👇 Only rebuild content if forceReset = true
if (forceReset || contentEl.innerHTML.trim() === '') {
contentEl.innerHTML = content;
}
if (footer) {
footerEl.innerHTML = footer;
footerEl.style.display = 'flex';
} else {
footerEl.style.display = 'none';
}
overlay.style.display = 'flex';
}
function hideOverlay() {
const overlay = el.querySelector('#multiOverlay');
if (overlay) overlay.style.display = 'none';
// Call cleanup from overlay editor module
if (window.OverlayEditor) {
window.OverlayEditor.cleanup();
}
}
// === UTILITY FUNCTIONS ===
function escapeHtml(text) {
const div = document.createElement('div');
div.textContent = text;
return div.innerHTML;
}
// === SEARCH FUNCTIONS ===
function clearMarkers() {
if (!globalEditorInstance) return;
const session = globalEditorInstance.getSession();
searchState.markers.forEach(id => {
try { session.removeMarker(id); } catch (e) {}
});
searchState.markers = [];
}
function markMatches() {
if (!globalEditorInstance) return;
clearMarkers();
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 markerId = session.addMarker(r, cls, 'text');
searchState.markers.push(markerId);
});
}
function searchInEditor(query) {
if (!globalEditorInstance || !query) {
clearMarkers();
searchState = { matches: [], idx: -1, markers: [] };
updateMatchCounter();
return;
}
const session = globalEditorInstance.getSession();
const lines = session.getDocument().getAllLines();
const regex = new RegExp(query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi');
searchState.matches = [];
searchState.idx = -1;
clearMarkers();
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();
}
updateMatchCounter();
}
function gotoMatch() {
if (searchState.idx < 0 || !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();
}
function nextMatch() {
if (!searchState.matches.length) return;
searchState.idx = (searchState.idx + 1) % searchState.matches.length;
gotoMatch();
}
function prevMatch() {
if (!searchState.matches.length) return;
searchState.idx = (searchState.idx - 1 + searchState.matches.length) % searchState.matches.length;
gotoMatch();
}
function updateMatchCounter() {
const counter = el.querySelector('#matchCounter');
if (counter) {
if (searchState.matches.length > 0) {
counter.textContent = `${searchState.idx + 1} / ${searchState.matches.length}`;
} else {
counter.textContent = '';
}
}
}
// === FOLD/SCOPE SELECTION FUNCTIONS ===
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;
}
}
loadAce(() => {
console.log("[editor.js] Ace script loaded");
requestAnimationFrame(() => {
globalEditorInstance = ace.edit(container);
globalEditorInstance.setTheme("ace/theme/monokai");
globalEditorInstance.session.setMode("ace/mode/html");
let fileContent = '';
let fileName = 'Untitled';
let detectedMode = 'html';
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;
console.log(`[editor.js] Loaded content for ${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'
};
detectedMode = modeMap[ext] || 'html';
globalEditorInstance.session.setMode(`ace/mode/${detectedMode}`);
}
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
});
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 range = globalEditorInstance.getSession().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,
globalEditorInstance.getSession().getLine(range.end.row).length);
globalEditorInstance.selection.setRange(extended, false);
globalEditorInstance.scrollToLine(range.start.row, true, true, () => {});
globalEditorInstance.focus();
}
return true;
}
});
globalEditorInstance.getSession().on('change', () => {
cachedFolds = [];
lastCursorPos = null;
lastFoldIndex = -1;
debouncedSave(globalEditorInstance);
});
globalEditorInstance.on("blur", () => {
clearTimeout(saveTimeout);
saveToLocalStorage(globalEditorInstance);
});
// === INITIALIZE INDEX MODULE ===
if (window.EditorIndex) {
window.EditorIndex.init({
getGlobalEditor: () => globalEditorInstance,
getEl: () => el,
showOverlay: showOverlay,
hideOverlay: hideOverlay,
escapeHtml: escapeHtml,
showToast: typeof showToast === 'function' ? showToast : null
});
console.log("[editor.js] Editor index initialized");
} else {
console.warn("[editor.js] EditorIndex module not loaded");
}
// === INITIALIZE OVERLAY EDITOR MODULE ===
if (window.OverlayEditor) {
window.OverlayEditor.init({
getGlobalEditor: () => globalEditorInstance,
getEl: () => el,
showOverlay: showOverlay,
hideOverlay: hideOverlay,
generateDocumentIndex: window.EditorIndex ? window.EditorIndex.generateDocumentIndex : () => [],
findMarkerEnd: window.EditorIndex ? window.EditorIndex.findMarkerEnd : () => 0,
escapeHtml: escapeHtml,
showToast: typeof showToast === 'function' ? showToast : null
});
console.log("[editor.js] Overlay editor initialized");
} else {
console.warn("[editor.js] OverlayEditor module not loaded");
}
// === BUTTON EVENT LISTENERS ===
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');
const closeOverlayBtn = el.querySelector('#closeOverlayBtn');
const multiOverlay = el.querySelector('#multiOverlay');
if (indexBtn) {
indexBtn.addEventListener('click', () => {
if (window.EditorIndex) {
window.EditorIndex.showIndexOverlay();
} else {
console.error("[editor.js] EditorIndex module not available");
if (typeof showToast === 'function') {
showToast('⚠️ Index not available', 'error');
}
}
});
}
if (editSelectionBtn) {
editSelectionBtn.addEventListener('click', () => {
if (window.OverlayEditor) {
window.OverlayEditor.showEditSelectionOverlay();
} else {
console.error("[editor.js] OverlayEditor module not available");
if (typeof showToast === 'function') {
showToast('⚠️ Edit overlay not available', 'error');
}
}
});
}
if (closeOverlayBtn) {
closeOverlayBtn.addEventListener('click', hideOverlay);
}
if (multiOverlay) {
multiOverlay.addEventListener('click', (e) => {
if (e.target === multiOverlay) {
hideOverlay();
}
});
}
// === SEARCH EVENT LISTENERS ===
if (searchInput) {
searchInput.addEventListener('input', (e) => searchInEditor(e.target.value));
searchInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
e.preventDefault();
e.shiftKey ? prevMatch() : nextMatch();
} else if (e.key === 'Escape') {
searchInput.value = '';
searchInEditor('');
globalEditorInstance.focus();
}
});
}
if (prevBtn) prevBtn.addEventListener('click', () => { prevMatch(); globalEditorInstance.focus(); });
if (nextBtn) nextBtn.addEventListener('click', () => { nextMatch(); globalEditorInstance.focus(); });
// === KEYBOARD SHORTCUTS ===
globalEditorInstance.commands.addCommand({
name: 'focusSearch',
bindKey: {win: 'Ctrl-F', mac: 'Command-F'},
exec: () => { searchInput?.focus(); searchInput?.select(); }
});
globalEditorInstance.commands.addCommand({
name: 'showIndex',
bindKey: {win: 'Ctrl-I', mac: 'Command-I'},
exec: () => {
if (window.EditorIndex) {
window.EditorIndex.showIndexOverlay();
}
}
});
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
});
fitToOverlayBody();
globalEditorInstance.resize(true);
window.addEventListener("resize", () => { fitToOverlayBody(); globalEditorInstance.resize(true); });
});
});
}
};
window.AppItems.push(section);
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: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/html');
if (typeof showToast === 'function') showToast('✅ Switched to HTML', 'success');
}}},
{ label: "PHP", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/php');
if (typeof showToast === 'function') showToast('✅ Switched to PHP', 'success');
}}},
{ label: "JavaScript", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/javascript');
if (typeof showToast === 'function') showToast('✅ Switched to JavaScript', 'success');
}}},
{ label: "CSS", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/css');
if (typeof showToast === 'function') showToast('✅ Switched to CSS', 'success');
}}},
{ label: "JSON", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/json');
if (typeof showToast === 'function') showToast('✅ Switched to JSON', 'success');
}}},
{ label: "Markdown", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/markdown');
if (typeof showToast === 'function') showToast('✅ Switched to Markdown', 'success');
}}},
{ label: "Python", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/python');
if (typeof showToast === 'function') showToast('✅ Switched to Python', 'success');
}}},
{ label: "Plain Text", action: () => {
if (globalEditorInstance) {
globalEditorInstance.session.setMode('ace/mode/text');
if (typeof showToast === 'function') showToast('✅ Switched to Plain Text', 'success');
}}}
]
});
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);
}
})();