// 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: `

Overlay

`, 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('