// 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');
})();