// editor.js
(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: "" };
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: `
`,
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 ===
let overlayEditorInstance = null;
let selectionRange = null;
let editHistory = [];
let currentEditIndex = 0;
function showOverlay(title, content, footer = null) {
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;
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';
// Clean up overlay editor if exists
if (overlayEditorInstance) {
overlayEditorInstance.destroy();
overlayEditorInstance = null;
}
// Clear edit history
editHistory = [];
currentEditIndex = 0;
}
// === EDIT SELECTION FUNCTIONS ===
function showEditSelectionOverlay() {
if (!globalEditorInstance) return;
const selected = globalEditorInstance.getSelectedText();
let hasSelection = selected && selected.trim().length > 0;
let finalContent = '';
// Store the selection range (or cursor position)
if (hasSelection) {
selectionRange = globalEditorInstance.getSelectionRange();
// Check if selection overlaps with any index items
const expandedContent = expandSelectionToIndexItems(selectionRange);
if (expandedContent) {
finalContent = expandedContent;
hasSelection = true;
} else {
finalContent = selected;
}
} else {
// No selection - get cursor position
const pos = globalEditorInstance.getCursorPosition();
const Range = ace.require('ace/range').Range;
selectionRange = new Range(pos.row, pos.column, pos.row, pos.column);
finalContent = '';
}
// Initialize edit history with current content
editHistory = [finalContent];
currentEditIndex = 0;
// Create editor container with navigation
const editorHtml = `
`;
const buttonText = hasSelection ? '✅ Replace Selection' : '➕ Add at Cursor';
const footerHtml = `
`;
const title = hasSelection ? 'Edit Selection' : 'Add Content';
showOverlay(title, editorHtml, footerHtml);
// Initialize Ace editor in overlay
setTimeout(() => {
initializeOverlayEditor(hasSelection);
setupEditNavigation(hasSelection);
}, 100);
}
function expandSelectionToIndexItems(range) {
if (!globalEditorInstance) return null;
const session = globalEditorInstance.getSession();
const Range = ace.require('ace/range').Range;
const startRow = range.start.row;
const endRow = range.end.row;
// Build index of ALL sections (markers + foldable)
const allSections = [];
// 1. Find all markers
const lineCount = session.getLength();
for (let row = 0; row < lineCount; row++) {
const line = session.getLine(row);
if (line.includes('<') && (line.includes(', /*anything<*/, //anything<, ///anything<
if (trimmed.includes('<') && (trimmed.includes('