๐Ÿ“œ
blocks_copy.js
โ† Back
๐Ÿ“ Javascript โšก Executable Ctrl+S: Save โ€ข Ctrl+R: Run โ€ข Ctrl+F: Find
// blocks.js - Scope Parser and Block Management (function() { console.log("[blocks] Loading Scope Parser module..."); // --- Utility Functions --- function escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } // --- Attribute Parser --- function parseAttributes(line) { const attributes = {}; // Pattern: @key:value@ (repeating) // Works with both /* @attr:val@ */ and <!-- @attr:val@ --> const attrPattern = /@([a-zA-Z0-9_-]+):([^@]+)@/g; let match; while ((match = attrPattern.exec(line)) !== null) { const key = match[1]; const value = match[2].trim(); attributes[key] = value; } return Object.keys(attributes).length > 0 ? attributes : null; } // --- Check if line is attribute-only --- function isAttributeOnlyLine(line) { const trimmed = line.trim(); const attrs = parseAttributes(trimmed); if (!attrs) return false; // Check if line is ONLY attributes (in comments) // CSS/JS: /* @...@ */ // HTML: <!-- @...@ --> const cssAttrOnly = /^\s*\/\*\s*@[^*]*\*\/\s*$/; const htmlAttrOnly = /^\s*<!--\s*@[^-]*-->\s*$/; return cssAttrOnly.test(trimmed) || htmlAttrOnly.test(trimmed); } // --- Scope Parser --- function parseScopeMarkers(content) { const lines = content.split('\n'); const result = { sections: [] // Single ordered array maintaining file order }; const stack = []; // Track nested containers let currentUnmarked = []; let lineNumber = 0; let containerCount = 0; // Track container numbers globally // Regex patterns for markers const containerOpenPattern = /\/\*\s*([a-zA-Z0-9_-]+):\s*container<\s*\*\//; const containerClosePattern = /\/\*\s*([a-zA-Z0-9_-]+):\s*container>\s*\*\//; const sectionOpenPattern = /\/\*\s*([a-zA-Z0-9_-]+)<\s*\*\//; const sectionClosePattern = /\/\*\s*([a-zA-Z0-9_-]+)>\s*\*\//; // HTML comment patterns const htmlContainerOpenPattern = /<!--\s*([a-zA-Z0-9_-]+):\s*container<\s*-->/; const htmlContainerClosePattern = /<!--\s*([a-zA-Z0-9_-]+):\s*container>\s*-->/; const htmlSectionOpenPattern = /<!--\s*([a-zA-Z0-9_-]+)<\s*-->/; const htmlSectionClosePattern = /<!--\s*([a-zA-Z0-9_-]+)>\s*-->/; for (const line of lines) { lineNumber++; const trimmed = line.trim(); // Check for attributes in current line const attrs = parseAttributes(trimmed); // Check for container open let match = trimmed.match(containerOpenPattern) || trimmed.match(htmlContainerOpenPattern); if (match) { // Save any unmarked content before this container if (currentUnmarked.length > 0) { result.sections.push({ type: 'unmarked', content: currentUnmarked.join('\n'), startLine: lineNumber - currentUnmarked.length, endLine: lineNumber - 1 }); currentUnmarked = []; } containerCount++; const baseName = match[1].split(':')[0]; // Get base name without ": container" stack.push({ name: match[1], autoName: `${baseName}-c${containerCount}`, type: 'container', sections: [], startLine: lineNumber, content: [], containerNumber: containerCount, sectionCount: 0, // Track sections within this container attributes: attrs // Add attributes if found }); continue; } // Check for container close match = trimmed.match(containerClosePattern) || trimmed.match(htmlContainerClosePattern); if (match) { if (stack.length > 0) { const container = stack.pop(); container.endLine = lineNumber; container.fullContent = container.content.join('\n'); delete container.content; // Clean up temp array // Check for attributes on closing tag if (attrs && !container.attributes) { container.attributes = attrs; } if (stack.length === 0) { // Top-level container - add to sections in order result.sections.push(container); } else { // Nested container - add to parent stack[stack.length - 1].sections.push(container); } } continue; } // Check for section open match = trimmed.match(sectionOpenPattern) || trimmed.match(htmlSectionOpenPattern); if (match) { if (stack.length > 0) { const parent = stack[stack.length - 1]; parent.sectionCount++; const baseName = match[1]; const autoName = `${parent.autoName}s${parent.sectionCount}`; parent.sections.push({ name: match[1], autoName: autoName, type: 'section', content: [], startLine: lineNumber, sectionNumber: parent.sectionCount, attributes: attrs // Add attributes if found }); } continue; } // Check for section close match = trimmed.match(sectionClosePattern) || trimmed.match(htmlSectionClosePattern); if (match) { if (stack.length > 0) { const parent = stack[stack.length - 1]; const lastSection = parent.sections[parent.sections.length - 1]; if (lastSection && lastSection.type === 'section') { lastSection.endLine = lineNumber; lastSection.fullContent = lastSection.content.join('\n'); delete lastSection.content; // Clean up temp array // Check for attributes on closing tag if (attrs && !lastSection.attributes) { lastSection.attributes = attrs; } } } continue; } // Regular content line if (stack.length > 0) { const parent = stack[stack.length - 1]; // Add to current section if one is open const lastSection = parent.sections[parent.sections.length - 1]; if (lastSection && lastSection.type === 'section' && !lastSection.fullContent) { lastSection.content.push(line); } // Always add to container content parent.content.push(line); } else { // Outside any container - unmarked content currentUnmarked.push(line); } } // Handle any remaining unmarked content if (currentUnmarked.length > 0) { result.sections.push({ type: 'unmarked', content: currentUnmarked.join('\n'), startLine: lineNumber - currentUnmarked.length + 1, endLine: lineNumber }); } return result; } // --- Reconstruct File from Blocks --- function reconstructFile(parsed) { const lines = []; parsed.sections.forEach(section => { if (section.type === 'unmarked') { lines.push(section.content); } else if (section.type === 'container') { lines.push(section.fullContent); } }); return lines.join('\n'); } // --- View Parsed Structure (Text-based) --- function showParsedJSON(fileName, fileContent) { const parsed = parseScopeMarkers(fileContent); // Create modal const overlay = document.createElement('div'); overlay.style.cssText = ` position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.9); display: flex; align-items: center; justify-content: center; z-index: 2147483647; padding: 20px; `; const dialog = document.createElement('div'); dialog.style.cssText = ` background: #1a1a1a; border: 2px solid #3a3a3a; border-radius: 12px; padding: 24px; max-width: 1200px; width: 95%; max-height: 90vh; display: flex; flex-direction: column; `; const header = document.createElement('div'); header.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 2px solid #2a2a2a; `; header.innerHTML = ` <div> <h2 style="margin: 0; color: #e6edf3; font-size: 20px; font-weight: 700;"> ๐Ÿ“‹ Document Structure </h2> <p style="margin: 4px 0 0 0; color: #64748b; font-size: 13px; font-family: monospace;"> ${escapeHtml(fileName)} </p> </div> <div style="display: flex; gap: 12px;"> <button id="toggleViewBtn" style=" padding: 8px 16px; background: #1e40af; border: 1px solid #2563eb; border-radius: 6px; color: #e0e0e0; cursor: pointer; font-size: 13px; font-weight: 600; ">View JSON</button> <button id="closeBtn" style=" padding: 8px 16px; background: #374151; border: 1px solid #4b5563; border-radius: 6px; color: #e0e0e0; cursor: pointer; font-size: 13px; font-weight: 600; ">Close</button> </div> `; // Text-based structure view const structureContainer = document.createElement('div'); structureContainer.style.cssText = ` background: #0a0a0a; border: 1px solid #2a2a2a; border-radius: 6px; padding: 20px; color: #e0e0e0; font-size: 13px; font-family: 'Courier New', monospace; overflow: auto; flex: 1; line-height: 1.6; `; // Build text representation let textRepresentation = ''; let blockNumber = 1; parsed.sections.forEach((section, idx) => { if (section.type === 'unmarked') { const lineCount = section.content.split('\n').length; textRepresentation += `<div style="margin-bottom: 20px; padding: 12px; background: rgba(100, 116, 139, 0.1); border-left: 3px solid #64748b; border-radius: 4px;">`; textRepresentation += `<div style="color: #94a3b8; font-weight: 600; margin-bottom: 6px;">Block ${blockNumber++} ยท Unmarked Content</div>`; textRepresentation += `<div style="color: #64748b; font-size: 12px;">Lines ${section.startLine}โ€“${section.endLine} (${lineCount} line${lineCount !== 1 ? 's' : ''})</div>`; textRepresentation += `<div style="margin-top: 8px; padding: 8px; background: rgba(0,0,0,0.3); border-radius: 3px; color: #9ca3af; font-size: 11px; max-height: 60px; overflow: hidden;">`; textRepresentation += escapeHtml(section.content.substring(0, 200)); if (section.content.length > 200) textRepresentation += '...'; textRepresentation += `</div></div>`; } else if (section.type === 'container') { const lineCount = section.endLine - section.startLine + 1; const hasAttrs = section.attributes && Object.keys(section.attributes).length > 0; const containerId = `container-${idx}`; textRepresentation += `<div style="margin-bottom: 20px; padding: 12px; background: rgba(59, 130, 246, 0.1); border-left: 3px solid #3b82f6; border-radius: 4px;">`; // Header with attributes button textRepresentation += `<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 6px;">`; textRepresentation += `<div style="color: #60a5fa; font-weight: 600;">Block ${blockNumber++} ยท Container: <span style="color: #93c5fd;">${escapeHtml(section.autoName)}</span></div>`; if (hasAttrs) { textRepresentation += `<button onclick="document.getElementById('${containerId}-attrs').style.display = document.getElementById('${containerId}-attrs').style.display === 'none' ? 'block' : 'none'" style="padding: 4px 8px; background: rgba(168, 85, 247, 0.2); border: 1px solid rgba(168, 85, 247, 0.4); border-radius: 4px; color: #c084fc; cursor: pointer; font-size: 10px; font-weight: 600;">๐Ÿ“Œ ${Object.keys(section.attributes).length} Attr${Object.keys(section.attributes).length !== 1 ? 's' : ''}</button>`; } textRepresentation += `</div>`; textRepresentation += `<div style="color: #64748b; font-size: 11px; font-style: italic; margin-bottom: 4px;">Original: ${escapeHtml(section.name)}</div>`; // Display attributes (hidden by default) if (hasAttrs) { textRepresentation += `<div id="${containerId}-attrs" style="display: none; margin: 8px 0; padding: 8px; background: rgba(168, 85, 247, 0.15); border: 1px solid rgba(168, 85, 247, 0.3); border-radius: 4px;">`; textRepresentation += `<div style="color: #c084fc; font-size: 11px; font-weight: 600; margin-bottom: 4px;">๐Ÿ“Œ Attributes:</div>`; for (const [key, value] of Object.entries(section.attributes)) { textRepresentation += `<div style="color: #e9d5ff; font-size: 11px; margin-left: 8px;">`; textRepresentation += `<span style="color: #d8b4fe; font-weight: 600;">${escapeHtml(key)}:</span> `; textRepresentation += `<span style="color: #f3e8ff;">${escapeHtml(value)}</span>`; textRepresentation += `</div>`; } textRepresentation += `</div>`; } textRepresentation += `<div style="color: #64748b; font-size: 12px;">Lines ${section.startLine}โ€“${section.endLine} (${lineCount} line${lineCount !== 1 ? 's' : ''})</div>`; if (section.sections && section.sections.length > 0) { textRepresentation += `<div style="margin-top: 12px; margin-left: 16px; padding-left: 12px; border-left: 2px solid rgba(59, 130, 246, 0.3);">`; section.sections.forEach((subsection, subIdx) => { const subLineCount = subsection.endLine - subsection.startLine + 1; const subHasAttrs = subsection.attributes && Object.keys(subsection.attributes).length > 0; const sectionId = `section-${idx}-${subIdx}`; textRepresentation += `<div style="margin-bottom: 10px; padding: 8px; background: rgba(16, 185, 129, 0.1); border-left: 2px solid #10b981; border-radius: 3px;">`; // Section header with attributes button textRepresentation += `<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 3px;">`; textRepresentation += `<div style="color: #34d399; font-weight: 600; font-size: 12px;">Section: <span style="color: #6ee7b7;">${escapeHtml(subsection.autoName)}</span></div>`; if (subHasAttrs) { textRepresentation += `<button onclick="document.getElementById('${sectionId}-attrs').style.display = document.getElementById('${sectionId}-attrs').style.display === 'none' ? 'block' : 'none'" style="padding: 3px 6px; background: rgba(168, 85, 247, 0.2); border: 1px solid rgba(168, 85, 247, 0.4); border-radius: 3px; color: #c084fc; cursor: pointer; font-size: 9px; font-weight: 600;">๐Ÿ“Œ ${Object.keys(subsection.attributes).length}</button>`; } textRepresentation += `</div>`; textRepresentation += `<div style="color: #64748b; font-size: 10px; font-style: italic; margin-bottom: 3px;">Original: ${escapeHtml(subsection.name)}</div>`; // Display attributes (hidden by default) if (subHasAttrs) { textRepresentation += `<div id="${sectionId}-attrs" style="display: none; margin: 6px 0; padding: 6px; background: rgba(168, 85, 247, 0.15); border: 1px solid rgba(168, 85, 247, 0.3); border-radius: 3px;">`; textRepresentation += `<div style="color: #c084fc; font-size: 10px; font-weight: 600; margin-bottom: 3px;">๐Ÿ“Œ Attributes:</div>`; for (const [key, value] of Object.entries(subsection.attributes)) { textRepresentation += `<div style="color: #e9d5ff; font-size: 10px; margin-left: 6px;">`; textRepresentation += `<span style="color: #d8b4fe; font-weight: 600;">${escapeHtml(key)}:</span> `; textRepresentation += `<span style="color: #f3e8ff;">${escapeHtml(value)}</span>`; textRepresentation += `</div>`; } textRepresentation += `</div>`; } textRepresentation += `<div style="color: #64748b; font-size: 11px;">Lines ${subsection.startLine}โ€“${subsection.endLine} (${subLineCount} line${subLineCount !== 1 ? 's' : ''})</div>`; if (subsection.fullContent) { textRepresentation += `<div style="margin-top: 6px; padding: 6px; background: rgba(0,0,0,0.3); border-radius: 3px; color: #9ca3af; font-size: 10px; max-height: 50px; overflow: hidden;">`; textRepresentation += escapeHtml(subsection.fullContent.substring(0, 150)); if (subsection.fullContent.length > 150) textRepresentation += '...'; textRepresentation += `</div>`; } textRepresentation += `</div>`; }); textRepresentation += `</div>`; } textRepresentation += `</div>`; } }); structureContainer.innerHTML = textRepresentation; // JSON view (hidden by default) const jsonContainer = document.createElement('pre'); jsonContainer.style.cssText = ` background: #0a0a0a; border: 1px solid #2a2a2a; border-radius: 6px; padding: 16px; color: #e0e0e0; font-size: 12px; font-family: 'Courier New', monospace; overflow: auto; flex: 1; white-space: pre-wrap; word-wrap: break-word; display: none; `; jsonContainer.textContent = JSON.stringify(parsed, null, 2); const stats = document.createElement('div'); stats.style.cssText = ` margin-top: 16px; padding: 12px; background: #0a0a0a; border: 1px solid #2a2a2a; border-radius: 6px; display: flex; gap: 20px; font-size: 13px; flex-wrap: wrap; `; // Count containers and unmarked sections const containers = parsed.sections.filter(s => s.type === 'container'); const unmarked = parsed.sections.filter(s => s.type === 'unmarked'); const totalSections = containers.reduce((sum, c) => sum + c.sections.length, 0); stats.innerHTML = ` <div style="color: #3b82f6;"> <strong>๐Ÿ“ฆ Containers:</strong> ${containers.length} </div> <div style="color: #10b981;"> <strong>๐Ÿ“„ Sections:</strong> ${totalSections} </div> <div style="color: #64748b;"> <strong>โšช Unmarked:</strong> ${unmarked.length} </div> <div style="color: #8b5cf6;"> <strong>๐Ÿ“‹ Total Blocks:</strong> ${parsed.sections.length} </div> `; dialog.appendChild(header); dialog.appendChild(structureContainer); dialog.appendChild(jsonContainer); dialog.appendChild(stats); overlay.appendChild(dialog); document.body.appendChild(overlay); // Toggle between structure and JSON view let showingJSON = false; const toggleBtn = header.querySelector('#toggleViewBtn'); toggleBtn.addEventListener('click', () => { showingJSON = !showingJSON; if (showingJSON) { structureContainer.style.display = 'none'; jsonContainer.style.display = 'block'; toggleBtn.textContent = 'View Structure'; } else { structureContainer.style.display = 'block'; jsonContainer.style.display = 'none'; toggleBtn.textContent = 'View JSON'; } }); // Close handlers header.querySelector('#closeBtn').addEventListener('click', () => { document.body.removeChild(overlay); }); overlay.addEventListener('click', (e) => { if (e.target === overlay) { document.body.removeChild(overlay); } }); } // --- Expose API --- window.BlocksManager = { parseScopes: parseScopeMarkers, showParsedJSON: showParsedJSON, reconstructFile: reconstructFile, activeFileParsed: null // Will hold parsed JSON of active file }; // --- Auto-parse active file --- function updateActiveFileParsed() { // Check if FilesManager is available if (!window.FilesManager) { window.BlocksManager.activeFileParsed = null; return; } const files = window.FilesManager.getFiles(); const activeFile = files.find(f => f.active); if (activeFile && activeFile.content) { window.BlocksManager.activeFileParsed = parseScopeMarkers(activeFile.content); console.log('[blocks] Active file parsed:', activeFile.name); console.log('[blocks] Access via: window.BlocksManager.activeFileParsed'); } else { window.BlocksManager.activeFileParsed = null; console.log('[blocks] No active file'); } } // Listen for file updates window.addEventListener('activeFilesUpdated', updateActiveFileParsed); // Initial parse on load if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', updateActiveFileParsed); } else { // DOM already loaded, run immediately setTimeout(updateActiveFileParsed, 100); } console.log('[blocks] Scope Parser module loaded'); })();