📜
overlay_editor.js
← Back
📝 Javascript ⚡ Executable Ctrl+S: Save â€ĸ Ctrl+R: Run â€ĸ Ctrl+F: Find
(function () { try { console.log("[overlay_editor.js] Loading overlay editor module..."); // Create overlay editor namespace immediately window.OverlayEditor = window.OverlayEditor || { _ready: false, _readyCallbacks: [] }; let overlayEditorInstance = null; let overlayContainer = null; let onSaveCallback = null; let currentInstanceIndex = 0; let instances = []; let sourceFileName = ""; // LocalStorage key for instances const INSTANCES_KEY_PREFIX = "overlay_editor_instances_"; // Load instances from localStorage for a specific file function loadInstances(fileName) { try { const key = INSTANCES_KEY_PREFIX + fileName; const saved = localStorage.getItem(key); return saved ? JSON.parse(saved) : []; } catch (err) { console.error("[overlay_editor.js] Failed to load instances:", err); return []; } } // Save instances to localStorage for a specific file function saveInstances(fileName, instancesArray) { try { const key = INSTANCES_KEY_PREFIX + fileName; localStorage.setItem(key, JSON.stringify(instancesArray)); console.log(`[overlay_editor.js] Saved ${instancesArray.length} instances for ${fileName}`); } catch (err) { console.error("[overlay_editor.js] Failed to save instances:", err); } } // Get the current active file name function getActiveFileName() { try { const files = JSON.parse(localStorage.getItem("sftp_active_files") || "[]"); const active = files.find((f) => f.active); return active ? active.name : "default"; } catch { return "default"; } } // Update the counter display function updateCounter() { const counter = document.getElementById("overlayInstanceCounter"); if (counter) { if (instances.length === 0) { counter.textContent = "No instances"; } else { counter.textContent = `${currentInstanceIndex + 1} / ${instances.length}`; } } } // Update navigation buttons state function updateNavButtons() { const prevBtn = document.getElementById("overlayInstancePrev"); const nextBtn = document.getElementById("overlayInstanceNext"); const deleteBtn = document.getElementById("overlayInstanceDelete"); if (prevBtn) prevBtn.disabled = instances.length <= 1; if (nextBtn) nextBtn.disabled = instances.length <= 1; if (deleteBtn) deleteBtn.disabled = instances.length === 0; } // Load instance into editor function loadInstance(index) { if (index < 0 || index >= instances.length || !overlayEditorInstance) return; currentInstanceIndex = index; overlayEditorInstance.setValue(instances[index], -1); updateCounter(); updateNavButtons(); } // Navigate to previous instance function prevInstance() { if (instances.length <= 1) return; currentInstanceIndex = (currentInstanceIndex - 1 + instances.length) % instances.length; loadInstance(currentInstanceIndex); } // Navigate to next instance function nextInstance() { if (instances.length <= 1) return; currentInstanceIndex = (currentInstanceIndex + 1) % instances.length; loadInstance(currentInstanceIndex); } // Create new instance function createNewInstance() { const newContent = "<!-- New instance -->\n\n"; instances.push(newContent); currentInstanceIndex = instances.length - 1; loadInstance(currentInstanceIndex); saveInstances(sourceFileName, instances); if (typeof showToast === "function") { showToast("✅ New instance created", "success"); } } // Delete current instance function deleteCurrentInstance() { if (instances.length === 0) return; if (instances.length === 1) { // If it's the last one, just clear it instances[0] = "<!-- No text selected -->\n\n"; loadInstance(0); saveInstances(sourceFileName, instances); if (typeof showToast === "function") { showToast("đŸ—‘ī¸ Instance cleared", "info"); } return; } instances.splice(currentInstanceIndex, 1); if (currentInstanceIndex >= instances.length) { currentInstanceIndex = instances.length - 1; } loadInstance(currentInstanceIndex); saveInstances(sourceFileName, instances); if (typeof showToast === "function") { showToast("đŸ—‘ī¸ Instance deleted", "info"); } } // Clear all instances function clearAllInstances() { if (!confirm("Are you sure you want to delete all instances?")) return; instances = ["<!-- No text selected -->\n\n"]; currentInstanceIndex = 0; loadInstance(0); saveInstances(sourceFileName, instances); if (typeof showToast === "function") { showToast("đŸ—‘ī¸ All instances cleared", "info"); } } // ========================================================================= // TARGET DETECTION (Smart section matching) // ========================================================================= function calculateHeaderScore(item, overlayFirstLine, itemFirstLine) { const itemLabel = item.label?.toLowerCase() || ''; const overlayLower = overlayFirstLine.toLowerCase(); const itemLower = itemFirstLine.toLowerCase(); // Exact header match if (itemLower === overlayLower) return 1.0; // Label appears in overlay if (itemLabel && overlayLower.includes(itemLabel)) return 0.9; // Similar function/class names const overlayWords = overlayLower.match(/\w+/g) || []; const itemWords = itemLower.match(/\w+/g) || []; const commonWords = overlayWords.filter(w => itemWords.includes(w) && w.length > 3); if (commonWords.length > 0) { return 0.7 + (commonWords.length * 0.1); } return 0.3; } function calculateContentScore(overlayLines, itemLines) { let exactMatches = 0; let partialMatches = 0; for (const overlayLine of overlayLines) { const overlayLower = overlayLine.toLowerCase(); for (const itemLine of itemLines) { const itemLower = itemLine.toLowerCase(); if (itemLower === overlayLower) { exactMatches++; break; } else if (itemLower.includes(overlayLower) || overlayLower.includes(itemLower)) { partialMatches++; break; } } } const score = (exactMatches * 1.0 + partialMatches * 0.5) / overlayLines.length; return { score: Math.min(score, 1.0), exactMatches, partialMatches }; } function detectTargetSection() { console.log("[overlay_editor.js] Detecting target section..."); if (!overlayEditorInstance) { console.error("[overlay_editor.js] No overlay editor instance"); if (typeof showToast === "function") { showToast("âš ī¸ Editor not initialized", "error"); } return; } const content = overlayEditorInstance.getValue(); if (!content || !content.trim()) { if (typeof showToast === "function") { showToast("âš ī¸ No content to analyze", "error"); } return; } // Get the global editor instance if (typeof window.AppItems === "undefined" || !window.AppItems.length) { if (typeof showToast === "function") { showToast("âš ī¸ Main editor not available", "error"); } return; } // Find the editor AppItem const editorItem = window.AppItems.find(item => item.title === "HTML Editor"); if (!editorItem || !editorItem.getGlobalEditor) { if (typeof showToast === "function") { showToast("âš ī¸ Could not access main editor", "error"); } return; } const globalEditorInstance = editorItem.getGlobalEditor(); if (!globalEditorInstance) { if (typeof showToast === "function") { showToast("âš ī¸ Main editor not initialized", "error"); } return; } // Get index from EditorIndex module if (typeof window.EditorIndex === "undefined" || !window.EditorIndex.generateDocumentIndex) { if (typeof showToast === "function") { showToast("âš ī¸ Index module not loaded", "error"); } return; } // Initialize EditorIndex if needed if (window.EditorIndex.init) { window.EditorIndex.init({ getGlobalEditor: () => globalEditorInstance, showToast: typeof showToast === "function" ? showToast : null }); } const indexResult = window.EditorIndex.generateDocumentIndex(); let flatIndex = []; // Flatten the hierarchical structure const { components, unmarked } = indexResult; for (const [componentName, languages] of Object.entries(components)) { for (const [language, sections] of Object.entries(languages)) { sections.forEach(section => { flatIndex.push({ ...section, isMarker: true }); }); } } flatIndex = flatIndex.concat(unmarked); if (flatIndex.length === 0) { if (typeof showToast === "function") { showToast("â„šī¸ No sections found in document", "info"); } return; } const session = globalEditorInstance.getSession(); const Range = ace.require('ace/range').Range; let candidates = []; // Get lines from overlay content const contentLines = content.split('\n').map(l => l.trim()).filter(l => l.length > 0); const firstContentLine = contentLines[0] || ''; console.log(`[overlay_editor.js] Analyzing ${flatIndex.length} sections against overlay content`); // Score each section in the document for (const item of flatIndex) { let itemStartRow, itemEndRow; if (item.type === 'marker') { itemStartRow = item.row; itemEndRow = item.endRow || window.EditorIndex.findMarkerEnd(item.row, item.label); } else { const foldRange = session.getFoldWidgetRange(item.row); if (foldRange) { itemStartRow = foldRange.start.row; itemEndRow = foldRange.end.row; } else { itemStartRow = itemEndRow = item.row; } } // Get all lines from this section const itemLines = []; for (let row = itemStartRow; row <= itemEndRow; row++) { const line = session.getLine(row).trim(); if (line.length > 0) { itemLines.push(line); } } // Calculate scores const headerScore = calculateHeaderScore(item, firstContentLine, itemLines[0] || ''); const contentScore = calculateContentScore(contentLines, itemLines); const finalScore = (headerScore * 0.7) + (contentScore.score * 0.3); candidates.push({ ...item, startRow: itemStartRow, endRow: itemEndRow, score: finalScore, headerScore: headerScore, contentScore: contentScore.score, exactMatches: contentScore.exactMatches, partialMatches: contentScore.partialMatches, totalLines: contentLines.length, size: itemEndRow - itemStartRow }); } // Sort by score (descending) candidates.sort((a, b) => { if (Math.abs(a.score - b.score) < 0.05) { return a.size - b.size; // Prefer smaller sections for ties } return b.score - a.score; }); const bestMatch = candidates[0]; console.log(`[overlay_editor.js] Best match: ${bestMatch.label} (score: ${(bestMatch.score * 100).toFixed(1)}%)`); // Navigate to best match in main editor const range = new Range( bestMatch.startRow, 0, bestMatch.endRow, session.getLine(bestMatch.endRow).length ); globalEditorInstance.selection.setRange(range, false); globalEditorInstance.scrollToLine(bestMatch.startRow, true, true, () => {}); globalEditorInstance.focus(); if (typeof showToast === "function") { const quality = bestMatch.score >= 0.9 ? "Excellent" : bestMatch.score >= 0.7 ? "Good" : "Fair"; showToast(`đŸŽ¯ ${bestMatch.label} (${quality} - ${(bestMatch.score * 100).toFixed(0)}%)`, "success"); } console.log("[overlay_editor.js] Target detection complete"); } // Create the overlay HTML function createOverlayHTML() { return ` <div id="overlayEditorContainer" style=" position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.8); z-index: 2147483647; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(4px); "> <div style=" background: #1e1e1e; border: 1px solid #3a3a3a; border-radius: 8px; width: 90%; max-width: 1200px; height: 85%; display: flex; flex-direction: column; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.5); position: relative; "> <!-- Header --> <div style=" padding: 12px 16px; background: #2d2d2d; border-bottom: 1px solid #3a3a3a; border-radius: 8px 8px 0 0; display: flex; flex-direction: column; gap: 8px; "> <!-- First Row: Title and Primary Actions --> <div style=" display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; "> <h3 style=" margin: 0; color: #e0e0e0; font-size: 16px; font-weight: 500; flex-shrink: 0; ">Edit Selection</h3> <div style="display: flex; gap: 8px; flex-wrap: wrap;"> <button id="overlayEditorSave" style=" background: #007acc; color: white; border: none; padding: 6px 16px; border-radius: 4px; cursor: pointer; font-size: 13px; font-weight: 500; ">Save</button> <button id="overlayEditorCancel" style=" background: #3a3a3a; color: #e0e0e0; border: none; padding: 6px 16px; border-radius: 4px; cursor: pointer; font-size: 13px; ">Cancel</button> </div> </div> <!-- Second Row: Navigation and Management --> <div style=" display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; "> <!-- Navigation Controls --> <div style="display: flex; align-items: center; gap: 8px;"> <button id="overlayInstancePrev" title="Previous instance (Alt+Left)" style=" background: #3a3a3a; color: #e0e0e0; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 16px; ">←</button> <span id="overlayInstanceCounter" style=" color: #e0e0e0; font-size: 13px; min-width: 60px; text-align: center; ">1 / 1</span> <button id="overlayInstanceNext" title="Next instance (Alt+Right)" style=" background: #3a3a3a; color: #e0e0e0; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 16px; ">→</button> </div> <!-- Management Buttons --> <div style="display: flex; gap: 6px; flex-wrap: wrap;"> <button id="overlayInstanceNew" title="Create new instance (Ctrl+N)" style=" background: #2e7d32; color: white; border: none; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; ">+ New</button> <button id="overlayInstanceDelete" title="Delete current instance (Ctrl+D)" style=" background: #c62828; color: white; border: none; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 13px; ">đŸ—‘ī¸</button> <button id="overlayInstanceClearAll" title="Clear all instances" style=" background: #d32f2f; color: white; border: none; padding: 6px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; ">Clear All</button> <button id="overlayDetectTarget" title="Find best matching section in document" style=" background: #3a7ca5; color: white; border: none; padding: 6px 12px; border-radius: 4px; cursor: pointer; font-size: 12px; margin-left: 8px; ">đŸŽ¯ Find Target</button> </div> </div> </div> <!-- Editor Container --> <div id="overlayEditorDiv" style=" flex: 1; position: relative; overflow: hidden; "></div> </div> </div> `; } // Open the overlay editor with initial text window.OverlayEditor.open = function (initialText, callback) { console.log("[overlay_editor.js] open() called"); console.log("[overlay_editor.js] initialText length:", initialText?.length || 0); if (overlayContainer) { console.warn("[overlay_editor.js] Overlay already open"); return; } onSaveCallback = callback; sourceFileName = getActiveFileName(); // Load existing instances for this file instances = loadInstances(sourceFileName); // If no instances exist, create one with the initial text if (instances.length === 0) { instances = [initialText || "<!-- No text selected -->\n\n"]; } else { // Add the new selection as a new instance if it's not empty if (initialText && initialText.trim() !== "" && initialText !== "<!-- No text selected -->\n\n") { instances.push(initialText); currentInstanceIndex = instances.length - 1; } else { currentInstanceIndex = instances.length - 1; // Load the last instance } } // Save the updated instances saveInstances(sourceFileName, instances); // Create and append overlay overlayContainer = document.createElement("div"); overlayContainer.innerHTML = createOverlayHTML(); // CRITICAL: Append to body (not inside any other container) and ensure it's the last element if (document.body.lastElementChild !== overlayContainer) { document.body.appendChild(overlayContainer); } console.log("[overlay_editor.js] Overlay container appended to body"); // Wait for DOM insertion setTimeout(() => { console.log("[overlay_editor.js] Initializing Ace editor..."); const editorDiv = document.getElementById("overlayEditorDiv"); if (!editorDiv) { console.error("[overlay_editor.js] Editor div not found"); return; } // Check if Ace is available if (typeof ace === 'undefined') { console.error("[overlay_editor.js] Ace editor not loaded!"); if (typeof showToast === "function") { showToast("âš ī¸ Ace editor not loaded yet", "error"); } window.OverlayEditor.close(); return; } // Initialize Ace editor overlayEditorInstance = ace.edit(editorDiv); overlayEditorInstance.setTheme("ace/theme/monokai"); overlayEditorInstance.session.setMode("ace/mode/html"); overlayEditorInstance.setOptions({ fontSize: "14px", wrap: true, showPrintMargin: false, useWorker: false, enableAutoIndent: true, highlightActiveLine: true, highlightGutterLine: true }); // Load the current instance loadInstance(currentInstanceIndex); console.log("[overlay_editor.js] Ace editor initialized"); // Focus the editor overlayEditorInstance.focus(); // Wire up buttons const saveBtn = document.getElementById("overlayEditorSave"); const cancelBtn = document.getElementById("overlayEditorCancel"); const newBtn = document.getElementById("overlayInstanceNew"); const deleteBtn = document.getElementById("overlayInstanceDelete"); const clearAllBtn = document.getElementById("overlayInstanceClearAll"); const prevBtn = document.getElementById("overlayInstancePrev"); const nextBtn = document.getElementById("overlayInstanceNext"); if (saveBtn) { saveBtn.addEventListener("click", () => { console.log("[overlay_editor.js] Save button clicked"); // Update current instance with edited content instances[currentInstanceIndex] = overlayEditorInstance.getValue(); saveInstances(sourceFileName, instances); const editedText = overlayEditorInstance.getValue(); window.OverlayEditor.close(); if (onSaveCallback) { onSaveCallback(editedText); } }); } if (cancelBtn) { cancelBtn.addEventListener("click", () => { console.log("[overlay_editor.js] Cancel button clicked"); window.OverlayEditor.close(); }); } if (newBtn) { newBtn.addEventListener("click", createNewInstance); } if (deleteBtn) { deleteBtn.addEventListener("click", deleteCurrentInstance); } if (clearAllBtn) { clearAllBtn.addEventListener("click", clearAllInstances); } if (prevBtn) { prevBtn.addEventListener("click", prevInstance); } if (nextBtn) { nextBtn.addEventListener("click", nextInstance); } // Find Target button const targetBtn = document.getElementById("overlayDetectTarget"); if (targetBtn) { targetBtn.addEventListener("click", () => { console.log("[overlay_editor.js] Find Target button clicked"); detectTargetSection(); }); } // Auto-save current instance when switching overlayEditorInstance.getSession().on("change", () => { // Update the current instance in memory instances[currentInstanceIndex] = overlayEditorInstance.getValue(); }); // Keyboard shortcuts overlayEditorInstance.commands.addCommand({ name: "closeOverlay", bindKey: { win: "Esc", mac: "Esc" }, exec: () => { console.log("[overlay_editor.js] ESC pressed"); window.OverlayEditor.close(); } }); overlayEditorInstance.commands.addCommand({ name: "saveOverlay", bindKey: { win: "Ctrl-S", mac: "Command-S" }, exec: () => { console.log("[overlay_editor.js] Save shortcut pressed"); instances[currentInstanceIndex] = overlayEditorInstance.getValue(); saveInstances(sourceFileName, instances); const editedText = overlayEditorInstance.getValue(); window.OverlayEditor.close(); if (onSaveCallback) { onSaveCallback(editedText); } } }); overlayEditorInstance.commands.addCommand({ name: "newInstance", bindKey: { win: "Ctrl-N", mac: "Command-N" }, exec: createNewInstance }); overlayEditorInstance.commands.addCommand({ name: "deleteInstance", bindKey: { win: "Ctrl-D", mac: "Command-D" }, exec: deleteCurrentInstance }); overlayEditorInstance.commands.addCommand({ name: "prevInstance", bindKey: { win: "Alt-Left", mac: "Alt-Left" }, exec: prevInstance }); overlayEditorInstance.commands.addCommand({ name: "nextInstance", bindKey: { win: "Alt-Right", mac: "Alt-Right" }, exec: nextInstance }); }, 50); }; // Close the overlay editor window.OverlayEditor.close = function () { // Save all instances before closing if (instances.length > 0 && sourceFileName) { saveInstances(sourceFileName, instances); } if (overlayEditorInstance) { overlayEditorInstance.destroy(); overlayEditorInstance = null; } if (overlayContainer) { overlayContainer.remove(); overlayContainer = null; } onSaveCallback = null; instances = []; currentInstanceIndex = 0; sourceFileName = ""; }; // Check if ready method exists, if not create it window.OverlayEditor.ready = function(callback) { if (window.OverlayEditor._ready) { callback(); } else { window.OverlayEditor._readyCallbacks.push(callback); } }; // Get current editor value (for target detection) window.OverlayEditor.getValue = function() { if (overlayEditorInstance) { return overlayEditorInstance.getValue(); } return ""; }; // Mark as ready window.OverlayEditor._ready = true; // Execute any pending callbacks if (window.OverlayEditor._readyCallbacks.length > 0) { console.log("[overlay_editor.js] Executing", window.OverlayEditor._readyCallbacks.length, "pending callbacks"); window.OverlayEditor._readyCallbacks.forEach(cb => cb()); window.OverlayEditor._readyCallbacks = []; } console.log("[overlay_editor.js] ✓ Module loaded successfully"); } catch (err) { console.error("[overlay_editor.js] Fatal error:", err); } })();