📜
active_file_copy2.js
← Back
📝 Javascript ⚡ Executable Ctrl+S: Save â€ĸ Ctrl+R: Run â€ĸ Ctrl+F: Find
// active_file.js - Active File Display + Chat Snippet Rendering (ALL LAYOUT LIVES HERE) (function() { console.log("[active_file] Loading Active File Display module..."); // --- Utility Functions --- function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // --- Version Management --- function addVersionToFile(fileName, content) { if (!window.FilesManager) { console.error('[active_file] FilesManager not available'); return; } const files = window.FilesManager.getFiles(); const file = files.find(f => f.name === fileName); if (!file) return; // Initialize versions array if it doesn't exist if (!file.versions) { file.versions = []; } // Add new version with timestamp file.versions.push({ content: content, timestamp: Date.now(), label: `v${file.versions.length + 1}` }); // Update current content file.content = content; window.FilesManager.saveFiles(files); // Trigger update event window.dispatchEvent(new Event('activeFilesUpdated')); } // ----------------------------------------------------------------------------- // BLOCK RENDERING (EDITABLE – ACTIVE FILE) // ----------------------------------------------------------------------------- function renderScopeBlockEditable(block, blockId) { const style = window.StorageEditorScopes.getLanguageStyle(block.data.language); const lineCount = block.content.split("\n").length; // More minimum height const minHeight = Math.max(180, lineCount * 28); const wrapper = document.createElement("div"); wrapper.style.cssText = ` border: 2px solid ${style.color}; border-radius: 8px; margin-bottom: 14px; background: #0f0f0f; /* DARK BG */ overflow: hidden; `; // HEADER (DARK THEME) const header = document.createElement("div"); header.style.cssText = ` padding: 8px 12px; background: #1a1a1a; /* DARK HEADER */ border-bottom: 2px solid ${style.color}; display: flex; justify-content: space-between; align-items: center; font-family: monospace; cursor: pointer; user-select: none; `; header.innerHTML = ` <div style="display:flex; align-items:center; gap:10px;"> <span style="font-size:18px; color:${style.color};">${style.icon}</span> <span style=" font-size:15px; font-weight:800; color:#ffffff; /* white name */ ">${escapeHtml(block.data.name)}</span> </div> <div style="display:flex; align-items:center; gap:8px;"> <span style=" border: 1px solid ${style.color}; padding: 2px 6px; border-radius: 3px; font-size: 11px; font-weight: 800; color: ${style.color}; background: #0f0f0f; ">${style.label}</span> <span style=" border: 1px solid #555; padding: 2px 6px; border-radius: 3px; font-size: 10px; font-weight: 700; color: #fff; background: #2a2a2a; ">${lineCount} lines</span> <span class="toggle-icon" style=" font-size: 14px; color: #ffffff; margin-left: 4px; " >â–ŧ</span> </div> `; // CONTENT AREA (DARK TEXTAREA) const content = document.createElement("textarea"); content.className = "block-content"; content.dataset.blockId = blockId; content.dataset.blockType = "scope"; content.dataset.startLine = block.startLine; content.dataset.endLine = block.endLine; content.style.cssText = ` width: 100%; background: #0d0d0d; /* DARK */ color: #ffffff; /* WHITE TEXT */ border: none; padding: 10px 12px; font-family: Consolas, monospace; font-size: 14px; line-height: 1.55; resize: vertical; outline: none; box-sizing: border-box; min-height: ${minHeight}px; transition: max-height 0.25s ease, padding 0.25s ease; `; content.value = block.content; // Collapse state let isExpanded = true; header.addEventListener("click", () => { isExpanded = !isExpanded; const icon = header.querySelector(".toggle-icon"); if (isExpanded) { content.style.maxHeight = minHeight + "px"; content.style.padding = "10px 12px"; icon.textContent = "â–ŧ"; } else { content.style.maxHeight = "0px"; content.style.padding = "0 12px"; icon.textContent = "â–ļ"; } }); wrapper.appendChild(header); wrapper.appendChild(content); return wrapper; } function renderUnmarkedBlockEditable(block, blockId) { const lineCount = block.endLine - block.startLine + 1; const textareaHeight = Math.max(60, lineCount * 20 + 24); const wrapper = document.createElement('div'); wrapper.style.cssText = ` margin-bottom: 12px; border-radius: 8px; overflow: hidden; background: rgba(55,65,81,0.05); opacity: 0.7; border: 2px dashed #374151; `; // HEADER BAR const header = document.createElement('div'); header.className = 'scope-toggle'; header.style.cssText = ` width: 100%; background: #374151; color: #fff; padding: 6px 10px; display: flex; align-items: center; gap: 10px; cursor: pointer; user-select: none; font-size: 13px; font-weight: 600; font-family: monospace; `; const containerLabel = block.container ? `<span style="color:#8b5cf6; font-size:11px; font-weight:600;">(${escapeHtml(block.container)})</span>` : ''; header.innerHTML = ` <span class="toggle-icon" style="font-size:12px;">â–ŧ</span> <span style="font-size:18px;">📝</span> <span style="letter-spacing:0.5px;">UNMARKED</span> ${containerLabel} <span style="margin-left:auto; font-size:11px; color:#d1d5db;">${lineCount}L</span> `; const content = document.createElement('textarea'); content.className = 'block-content'; content.dataset.blockId = blockId; content.dataset.blockType = 'unmarked'; content.dataset.startLine = block.startLine; content.dataset.endLine = block.endLine; content.style.cssText = ` width: 100%; height: ${textareaHeight}px; background: #1a1a1a; color: #9ca3af; border: none; border-top: 2px dashed #374151; padding: 12px; font-family: 'Consolas', 'Monaco', monospace; font-size: 12px; line-height: 1.5; resize: none; outline: none; overflow: hidden; transition: max-height 0.3s ease, padding 0.3s ease; box-sizing: border-box; `; content.value = block.content; let isExpanded = true; header.addEventListener('click', () => { isExpanded = !isExpanded; const toggle = header.querySelector('.toggle-icon'); if (isExpanded) { content.style.maxHeight = 'none'; content.style.padding = '12px'; toggle.textContent = 'â–ŧ'; } else { content.style.maxHeight = '0'; content.style.padding = '0 12px'; toggle.textContent = 'â–ļ'; } }); wrapper.appendChild(header); wrapper.appendChild(content); return wrapper; } function renderContainerBlockEditable(block, blockId) { const lineRange = `Lines ${block.startLine + 1}-${block.endLine + 1}`; const wrapper = document.createElement('div'); wrapper.style.cssText = ` background: rgba(139,92,246,0.05); border: 3px solid #8b5cf6; border-radius: 10px; margin-bottom: 14px; overflow: hidden; `; wrapper.dataset.blockType = 'container'; wrapper.dataset.startLine = block.startLine; wrapper.dataset.endLine = block.endLine; // HEADER const header = document.createElement('div'); header.style.cssText = ` background: #7c3aed; padding: 6px 10px; display: flex; justify-content: space-between; align-items: center; cursor: pointer; user-select: none; `; header.innerHTML = ` <div style="font-weight: 700; color: #fff; display: flex; align-items: center; gap: 8px; font-size: 15px;"> <span class="container-toggle">â–ŧ</span> <span style="font-size: 18px;">đŸ“Ļ</span> <span style="font-family: monospace; text-transform: uppercase; letter-spacing: .5px;"> ${escapeHtml(block.data.name)} </span> <span style=" background: rgba(255,255,255,0.25); padding: 2px 6px; border-radius: 3px; font-size: 10px; ">${block.children.length} blocks</span> </div> <div style="font-size: 10px; color: rgba(255,255,255,0.85); font-weight: 600;"> ${lineRange} </div> `; const body = document.createElement('div'); body.style.cssText = ` padding: 0; transition: max-height .25s ease; overflow: hidden; `; block.children.forEach((child, idx) => { const childId = `${blockId}-child-${idx}`; if (child.type === 'scope') { body.appendChild(renderScopeBlockEditable(child, childId)); } else if (child.type === 'unmarked') { const trimmed = child.content.trim(); if (trimmed.length > 0) { body.appendChild(renderUnmarkedBlockEditable(child, childId)); } } }); let isExpanded = true; header.addEventListener('click', () => { const toggle = header.querySelector('.container-toggle'); if (isExpanded) { body.style.maxHeight = '0'; toggle.textContent = 'â–ļ'; } else { body.style.maxHeight = 'none'; toggle.textContent = 'â–ŧ'; } isExpanded = !isExpanded; }); wrapper.appendChild(header); wrapper.appendChild(body); return wrapper; } // ----------------------------------------------------------------------------- // SAVE BLOCKS AS VERSION (EDITABLE ACTIVE FILE) // ----------------------------------------------------------------------------- function injectMetadataIntoScopeBlock(originalLines, update, blockLookup) { const openLine = originalLines[update.startLine]; const metadataLines = []; // Timestamp metadataLines.push(`@updatedAt:${Date.now()}@`); // User (static for now) metadataLines.push(`@updatedBy:ai@`); // Container + Position (if known) if (blockLookup && blockLookup.container) { metadataLines.push(`@container:${blockLookup.container}@`); } if (blockLookup && typeof blockLookup.position === "number") { metadataLines.push(`@position:${blockLookup.position}@`); } // Placeholder for related scopes metadataLines.push(`@relatedScopes:@`); return [ openLine, ...metadataLines, update.content, originalLines[update.endLine] ]; } function buildMetadataLines(blockInfo) { const metaLines = []; // Required basics metaLines.push(`@updatedAt:${Date.now()}@`); metaLines.push(`@updatedBy:ai@`); // Container + position, if we know them if (blockInfo && blockInfo.container) { metaLines.push(`@container:${blockInfo.container}@`); } if (blockInfo && typeof blockInfo.position === "number") { metaLines.push(`@position:${blockInfo.position}@`); } // Placeholder for future AI wiring metaLines.push(`@relatedScopes:@`); return metaLines; } function stripExistingMetadata(rawContent) { if (!rawContent) return ""; const lines = rawContent.split("\n"); const cleaned = []; let skipping = true; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // Metadata lines look like: @key:value@ const trimmed = line.trim(); const isMeta = trimmed.startsWith("@") && trimmed.endsWith("@") && trimmed.includes(":"); if (skipping && isMeta) { // skip this line continue; } else { skipping = false; cleaned.push(line); } } return cleaned.join("\n"); } function saveBlocksAsVersion(activeFile, blocks, containerElement) { const originalLines = activeFile.content.split('\n'); const textareas = containerElement.querySelectorAll('.block-content'); // Build scope metadata map from blocks: // key: "startLine-endLine" → { container, position } const scopeMeta = new Map(); // First pass: collect info from block structure blocks.forEach(topBlock => { if (topBlock.type === 'container') { const containerName = topBlock.data?.name || null; const scopeChildren = topBlock.children.filter(c => c.type === 'scope'); scopeChildren.forEach((childScope, idx) => { const key = `${childScope.startLine}-${childScope.endLine}`; scopeMeta.set(key, { container: containerName, position: idx + 1 }); }); } else if (topBlock.type === 'scope') { // Top-level scope (no container) const key = `${topBlock.startLine}-${topBlock.endLine}`; scopeMeta.set(key, { container: null, position: null }); } }); // Create a map of line ranges to new content (as before) const lineUpdates = new Map(); textareas.forEach(ta => { const blockType = ta.dataset.blockType; const startLine = parseInt(ta.dataset.startLine, 10); const endLine = parseInt(ta.dataset.endLine, 10); if (blockType === 'scope') { // We'll strip any old metadata from the textarea content const cleanedContent = stripExistingMetadata(ta.value); lineUpdates.set(`${startLine}-${endLine}`, { type: 'scope', content: cleanedContent, startLine, endLine }); } else if (blockType === 'unmarked') { lineUpdates.set(`${startLine}-${endLine}`, { type: 'unmarked', content: ta.value, startLine, endLine }); } }); // Reconstruct the file (preserve your existing logic) const newLines = []; let lineIndex = 0; while (lineIndex < originalLines.length) { let handled = false; for (const [key, update] of lineUpdates) { if (lineIndex === update.startLine) { if (update.type === 'scope') { // Get metadata for this scope (container + position) const blockInfo = scopeMeta.get(key) || null; // Opening marker newLines.push(originalLines[update.startLine]); // Metadata lines (below marker) const metaLines = buildMetadataLines(blockInfo); metaLines.forEach(line => newLines.push(line)); // Updated content newLines.push(update.content); // Closing marker newLines.push(originalLines[update.endLine]); lineIndex = update.endLine + 1; } else if (update.type === 'unmarked') { // Replace entire unmarked block newLines.push(update.content); lineIndex = update.endLine + 1; } handled = true; break; } } if (!handled) { const line = originalLines[lineIndex]; // Preserve container markers exactly as-is if ( line.trim().startsWith('/*<CONTAINER') || line.trim().startsWith('<!--<CONTAINER') ) { newLines.push(line); lineIndex++; } else if ( line.trim().startsWith('</CONTAINER>') || line.trim().startsWith('<!--</CONTAINER>') ) { newLines.push(line); lineIndex++; } else { // Skip lines that are inside blocks we've already processed let insideProcessedBlock = false; for (const [, update] of lineUpdates) { if (lineIndex > update.startLine && lineIndex <= update.endLine) { insideProcessedBlock = true; break; } } if (!insideProcessedBlock) { newLines.push(line); } lineIndex++; } } } const newContent = newLines.join('\n'); // Add version to file addVersionToFile(activeFile.name, newContent); // Show success message const btn = containerElement.querySelector('#makeVersionBtn'); if (btn) { const originalText = btn.textContent; btn.textContent = '✅ VERSION SAVED'; btn.style.background = '#10b981'; setTimeout(() => { btn.textContent = originalText; btn.style.background = '#3b82f6'; }, 2000); } } // ----------------------------------------------------------------------------- // MAIN ACTIVE FILE RENDER FUNCTION (EDITABLE) // ----------------------------------------------------------------------------- function renderActiveFileScopes(container) { if (!window.FilesManager) { console.error('[active_file] FilesManager not available'); return; } const files = window.FilesManager.getFiles(); const activeFile = files.find(f => f.active); container.innerHTML = ''; if (!activeFile) { const emptyMsg = document.createElement('div'); emptyMsg.style.cssText = ` color: #666; text-align: center; padding: 40px; font-size: 14px; `; emptyMsg.textContent = '📄 No active file selected'; container.appendChild(emptyMsg); return; } // Check if StorageEditorScopes is available if (!window.StorageEditorScopes || typeof StorageEditorScopes.buildBlockStructure !== 'function') { const errorMsg = document.createElement('div'); errorMsg.style.cssText = ` color: #ef4444; text-align: center; padding: 40px; font-size: 14px; `; errorMsg.innerHTML = 'âš ī¸ Scopes module not loaded<br><small style="color: #888;">Load scopes.js to see block structure</small>'; container.appendChild(errorMsg); return; } // Build block structure const blocks = StorageEditorScopes.buildBlockStructure(activeFile.content); if (!blocks || blocks.length === 0) { const noScopesMsg = document.createElement('div'); noScopesMsg.style.cssText = ` color: #666; text-align: center; padding: 40px; font-size: 14px; `; noScopesMsg.innerHTML = ` <div style="font-size: 32px; margin-bottom: 12px;">📄</div> <div><strong>${activeFile.name}</strong></div> <div style="margin-top: 8px; font-size: 12px;">No scopes found in this file</div> `; container.appendChild(noScopesMsg); return; } const wrapper = document.createElement('div'); wrapper.style.cssText = ` background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; margin-bottom: 20px; overflow: hidden; `; const header = document.createElement('div'); header.style.cssText = ` background: #1a1a1a; padding: 12px 16px; display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #2a2a2a; `; header.innerHTML = ` <div> <div style="display: flex; align-items: center; gap: 8px;"> <span style="color: #16a34a; font-weight: 700; font-size: 16px;"> 📄 ${activeFile.name} </span> <span style="color: #64748b; font-size: 12px;"> ${blocks.length} blocks </span> </div> <div style="color: #64748b; font-size: 11px; margin-top: 4px;"> Active file context (editable) </div> </div> <button id="makeVersionBtn" style=" padding: 8px 16px; background: #3b82f6; border: 1px solid #2563eb; border-radius: 4px; color: #fff; cursor: pointer; font-size: 12px; font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; ">💾 Make Version</button> `; const contentArea = document.createElement('div'); contentArea.style.cssText = ` overflow: hidden; background: #0a0a0a; padding: 16px; `; // Render blocks blocks.forEach((block, idx) => { const blockId = `ai-chat-block-${idx}`; if (block.type === 'container') { contentArea.appendChild(renderContainerBlockEditable(block, blockId)); } else if (block.type === 'scope') { contentArea.appendChild(renderScopeBlockEditable(block, blockId)); } else if (block.type === 'unmarked') { const trimmed = block.content.trim(); if (trimmed.length > 0) { contentArea.appendChild(renderUnmarkedBlockEditable(block, blockId)); } } }); wrapper.appendChild(header); wrapper.appendChild(contentArea); container.appendChild(wrapper); // Make Version button handler const makeVersionBtn = header.querySelector('#makeVersionBtn'); if (makeVersionBtn) { makeVersionBtn.addEventListener('click', () => { saveBlocksAsVersion(activeFile, blocks, container); }); makeVersionBtn.addEventListener('mouseenter', () => { makeVersionBtn.style.background = '#2563eb'; }); makeVersionBtn.addEventListener('mouseleave', () => { makeVersionBtn.style.background = '#3b82f6'; }); } } // ----------------------------------------------------------------------------- // CHAT SNIPPET RENDERING (READ-ONLY) – ALL LAYOUT HERE // ----------------------------------------------------------------------------- // A read-only scope block (for Chat) – HTML string function renderScopeBlockChat(block, blockId, isInChat) { const style = window.StorageEditorScopes.getLanguageStyle(block.data.language); const lineRange = `Lines ${block.startLine + 1}-${block.endLine + 1}`; // Build metadata badges let metadataBadges = ''; if (block.data.header) { const h = block.data.header; const actionIcon = h.action === 'new' ? '✨' : 'âœī¸'; metadataBadges += ` <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Container:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${escapeHtml(h.container)}</span> </div> <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Position:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">#${h.position}</span> </div> <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Action:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${actionIcon} ${h.action.toUpperCase()}</span> </div> `; } else if (block.data.container) { metadataBadges += ` <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Container:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${escapeHtml(block.data.container)}</span> </div> `; } // Language metadataBadges += ` <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Language:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${style.label}</span> </div> `; // Attributes if (block.data.attributes) { const attrs = block.data.attributes; if (attrs.editedBy) { metadataBadges += ` <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Edited By:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${escapeHtml(attrs.editedBy)}</span> </div> `; } if (attrs.editedAt) { const timestamp = attrs.editedAt; let displayTime = timestamp; try { const date = new Date(parseInt(timestamp)); if (!isNaN(date.getTime())) { displayTime = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'}); } } catch (e) {} metadataBadges += ` <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Edited At:</span> <span style="color: #111827; font-size: 11px;">${escapeHtml(displayTime)}</span> </div> `; } Object.keys(attrs).forEach(key => { if (key !== 'editedBy' && key !== 'editedAt') { metadataBadges += ` <div style="padding: 8px 12px; border-bottom: 1px solid #e5e7eb; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">${escapeHtml(key)}:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${escapeHtml(attrs[key])}</span> </div> `; } }); } if (!isInChat) { metadataBadges += ` <div style="padding: 8px 12px; display: flex; justify-content: space-between;"> <span style="color: #6b7280; font-size: 11px; font-weight: 600;">Lines:</span> <span style="color: #111827; font-size: 11px; font-family: monospace;">${lineRange}</span> </div> `; } // Chat action footer let actionFooter = ''; if (isInChat && block.data.header) { const h = block.data.header; const btnText = h.action === 'new' ? '✨ INSERT INTO FILE' : 'âœī¸ REPLACE IN FILE'; const btnId = `action-btn-${blockId}`; actionFooter = ` <div style="padding: 12px; background: #ffffff; border-top: 2px solid ${style.color};"> <button id="${btnId}" class="scope-action-btn" style=" background: #ffffff; color: #111827; border: 2px solid #111827; padding: 12px 24px; border-radius: 6px; cursor: pointer; font-weight: 700; font-size: 12px; width: 100%; text-transform: uppercase; letter-spacing: 0.5px; transition: all 0.2s; " data-action="${h.action}" data-container="${escapeHtml(h.container)}" data-position="${h.position}" data-scope-name="${escapeHtml(block.data.name)}" data-language="${escapeHtml(h.language)}" data-block-id="${blockId}" onmouseover="this.style.background='#111827'; this.style.color='#ffffff'" onmouseout="this.style.background='#ffffff'; this.style.color='#111827'" >${btnText}</button> </div> `; } const metadataMenuId = `metadata-menu-${blockId}`; return ` <div class="block-scope" data-block-id="${blockId}" style=" position: relative; display: flex; flex-direction: column; margin-bottom: 16px; border-radius: 6px; overflow: hidden; border: 2px solid ${style.color}; background: #ffffff; box-shadow: 0 1px 3px rgba(0,0,0,0.1); "> <!-- Header --> <div style=" background: #ffffff; padding: 10px 16px; display: flex; align-items: center; justify-content: space-between; border-bottom: 2px solid ${style.color}; "> <div style="display: flex; align-items: center; gap: 10px;"> <span style="font-size: 18px;">${style.icon}</span> <div style=" font-weight: 700; color: #111827; font-family: monospace; font-size: 13px; letter-spacing: 0.3px; ">${escapeHtml(block.data.name)}</div> <div style=" background: ${style.bg}; border: 1px solid ${style.color}; padding: 2px 8px; border-radius: 3px; font-size: 10px; font-weight: 700; color: #111827; text-transform: uppercase; ">${style.label}</div> </div> <button onclick="(function(e) { e.stopPropagation(); const menu = document.getElementById('${metadataMenuId}'); const isVisible = menu.style.display === 'block'; menu.style.display = isVisible ? 'none' : 'block'; })(event)" style=" background: #ffffff; border: 1px solid #111827; color: #111827; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 11px; font-weight: 600; transition: all 0.2s; " onmouseover="this.style.background='#f3f4f6'" onmouseout="this.style.background='#ffffff'" >â„šī¸ Info</button> </div> <!-- Pull-up Metadata Menu --> <div id="${metadataMenuId}" style=" display: none; position: fixed; bottom: ${isInChat ? '100px' : '20px'}; left: 50%; transform: translateX(-50%); width: 90%; max-width: 500px; background: #ffffff; border: 2px solid ${style.color}; border-radius: 8px; box-shadow: 0 20px 50px rgba(0,0,0,0.3); z-index: 2147483647; max-height: 400px; overflow-y: auto; "> ${metadataBadges} </div> <!-- Content Area --> <textarea class="block-content" data-block-id="${blockId}" style=" width: 100%; height: 300px; background: #ffffff; color: #111827; border: none; padding: 16px; font-family: 'Consolas', 'Monaco', monospace; font-size: 13px; line-height: 1.6; resize: none; outline: none; overflow-y: auto; box-sizing: border-box; pointer-events: none; user-select: text; ">${escapeHtml(block.content)}</textarea> ${actionFooter} </div> `; } // Universal snippet renderer used by Chat – ALL LAYOUT NOW LIVES HERE function renderChatAnswer(answerText, parentElement) { if (!answerText) return "<pre></pre>"; const parseScopes = window.StorageEditorScopes && window.StorageEditorScopes.parseScopes; // Only parse if markers likely exist and parser is present if (parseScopes && (answerText.includes("<!--") || answerText.includes("//") || answerText.includes("/*"))) { try { const parsed = parseScopes(answerText); if (parsed.scopes && parsed.scopes.length > 0) { let html = ""; const lines = answerText.split('\n'); parsed.scopes.forEach((scope, idx) => { const scopeContent = lines.slice(scope.startLine + 1, scope.endLine).join('\n'); html += renderScopeBlockChat( { data: { language: scope.language, name: scope.name, header: scope.header, container: scope.container, attributes: scope.attributes }, startLine: scope.startLine, endLine: scope.endLine, content: scopeContent }, "scope-render-" + Date.now() + "-" + idx, true // isInChat ); }); if (parentElement) { parentElement.innerHTML = html; // Setup action buttons (insert / replace into file) setTimeout(() => { parentElement.querySelectorAll('.scope-action-btn').forEach(btn => { btn.addEventListener('click', function() { const action = this.dataset.action; const container = this.dataset.container; const position = parseInt(this.dataset.position); const scopeName = this.dataset.scopeName; const language = this.dataset.language; const blockId = this.dataset.blockId; const textarea = parentElement.querySelector(`.block-content[data-block-id="${blockId}"]`); const content = textarea ? textarea.value : ''; try { const attributes = { editedBy: 'ai', editedAt: Date.now().toString() }; let result; if (action === 'new') { result = window.StorageEditorScopes.insertAt(container, position, { name: scopeName, language: language, content: content, attributes: attributes }); } else if (action === 'edit') { result = window.StorageEditorScopes.replace(container, position, scopeName, content, attributes); } this.style.background = '#10b981'; this.textContent = '✅ APPLIED'; this.disabled = true; this.style.cursor = 'not-allowed'; console.log('[ScopeAction]', result); setTimeout(() => { this.style.transition = 'opacity 0.3s'; this.style.opacity = '0'; setTimeout(() => this.remove(), 300); }, 2000); } catch (error) { console.error('[ScopeAction] Error:', error); this.style.background = '#ef4444'; this.textContent = '❌ ERROR'; alert('Error: ' + error.message); } }); }); }, 100); // Clean visual markers out of textareas setTimeout(() => { parentElement.querySelectorAll("textarea.block-content").forEach(area => { area.value = area.value // HTML markers <!-- name< --> OR <!-- name> --> .replace(/<!--\s*[a-z0-9_-]+<\s*-->/gi, "") .replace(/<!--\s*[a-z0-9_-]+>\s*-->/gi, "") // CSS markers /* name< */ /* name> */ .replace(/\/\*\s*[a-z0-9_-]+<\s*\*\//gi, "") .replace(/\/\*\s*[a-z0-9_-]+>\s*\*\//gi, "") // JS markers // name< // name> .replace(/\/\/\s*[a-z0-9_-]+</gi, "") .replace(/\/\/\s*[a-z0-9_-]+>/gi, "") .trim(); }); }, 120); } return html; } } catch (err) { console.error("Snippet parsing failed:", err); return `<pre>${escapeHtml(answerText)}</pre>`; } } // Not a scope-formatted snippet — return plain if (parentElement) { parentElement.innerHTML = `<pre>${escapeHtml(answerText)}</pre>`; } return `<pre>${escapeHtml(answerText)}</pre>`; } // ----------------------------------------------------------------------------- // EXPOSE API // ----------------------------------------------------------------------------- window.ActiveFileDisplay = { // Editable active file view render: renderActiveFileScopes, // Chat snippet renderer (replaces StorageEditorScopes.renderAnswer) renderAnswer: renderChatAnswer }; console.log('[active_file] Active File Display module loaded'); })();