📜
active_file.js
Back
📝 Javascript ⚡ Executable Ctrl+S: Save • Ctrl+R: Run • Ctrl+F: Find
// active_file.js - Active File Display with Editable Scopes (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 Functions (Editable) --- 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 (replaces sidebar) 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> `; // 🔥 CONTENT AREA (unchanged) 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; // 🔥 Toggle functionality (same logic) 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; /* tighter external spacing */ overflow: hidden; `; wrapper.dataset.blockType = 'container'; wrapper.dataset.startLine = block.startLine; wrapper.dataset.endLine = block.endLine; // HEADER - tight, no extra padding const header = document.createElement('div'); header.style.cssText = ` background: #7c3aed; padding: 6px 10px; /* reduced from 14px 20px */ 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; /* tighter */ 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> `; // BODY - zero padding const body = document.createElement('div'); body.style.cssText = ` padding: 0; /* NO PADDING */ 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)); } } }); // Toggle 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 (FIXED) --- function saveBlocksAsVersion(activeFile, blocks, containerElement) { const originalLines = activeFile.content.split('\n'); const textareas = containerElement.querySelectorAll('.block-content'); // Create a map of line ranges to new content const lineUpdates = new Map(); textareas.forEach(ta => { const blockType = ta.dataset.blockType; const startLine = parseInt(ta.dataset.startLine); const endLine = parseInt(ta.dataset.endLine); if (blockType === 'scope') { // For scopes, keep the marker lines and update only the content lineUpdates.set(`${startLine}-${endLine}`, { type: 'scope', content: ta.value, startLine, endLine }); } else if (blockType === 'unmarked') { // For unmarked, replace the entire range lineUpdates.set(`${startLine}-${endLine}`, { type: 'unmarked', content: ta.value, startLine, endLine }); } }); // Reconstruct the file const newLines = []; let lineIndex = 0; while (lineIndex < originalLines.length) { let handled = false; // Check if this line is part of an update for (const [key, update] of lineUpdates) { if (lineIndex === update.startLine) { if (update.type === 'scope') { // Add opening marker newLines.push(originalLines[update.startLine]); // Add updated content newLines.push(update.content); // Add 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) { // Check if this line is a container marker const line = originalLines[lineIndex]; if (line.trim().startsWith('/*<CONTAINER') || line.trim().startsWith('<!--<CONTAINER')) { // Keep container markers as-is 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 [key, 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 Render Function --- 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 storage-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; } // Wrapper for the entire file context section const wrapper = document.createElement('div'); wrapper.style.cssText = ` background: #1a1a1a; border: 2px solid #2a2a2a; border-radius: 8px; margin-bottom: 20px; overflow: hidden; `; // File header (clickable to toggle) 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> `; // Content area 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') { // Skip empty unmarked blocks 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'; }); } } // --- Expose API --- window.ActiveFileDisplay = { render: renderActiveFileScopes }; console.log('[active_file] Active File Display module loaded'); })();