UNPKG

advanced-live-server-installer

Version:

Auto-installer for Advanced Live Server VS Code Extension

451 lines (419 loc) 16.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.DOMInspector = void 0; const vscode = __importStar(require("vscode")); const path = __importStar(require("path")); class DOMInspector { constructor(context) { this.context = context; } async openInspector() { if (this.panel) { this.panel.reveal(); return; } this.panel = vscode.window.createWebviewPanel('domInspector', 'DOM Inspector', vscode.ViewColumn.Two, { enableScripts: true, retainContextWhenHidden: true, localResourceRoots: [ vscode.Uri.file(path.join(this.context.extensionPath, 'media')), ], }); this.panel.webview.html = this.getWebviewContent(); this.panel.webview.onDidReceiveMessage(this.handleMessage.bind(this)); this.panel.onDidDispose(() => { this.panel = undefined; }); } async analyzeDOM(html) { // Parse HTML and create DOM tree const nodes = this.parseHTML(html); const issues = this.analyzeIssues(nodes); const performance = this.calculatePerformanceMetrics(nodes); const accessibility = this.calculateAccessibilityMetrics(nodes); this.currentAnalysis = { nodes, issues, performance, accessibility, }; // Send analysis to webview if open if (this.panel) { this.panel.webview.postMessage({ type: 'analysis', data: this.currentAnalysis, }); } return this.currentAnalysis; } parseHTML(html) { // Simple HTML parser - in a real implementation, you'd use a proper HTML parser const nodes = []; const lines = html.split('\n'); let nodeId = 0; for (const line of lines) { const trimmed = line.trim(); if (trimmed.startsWith('<') && !trimmed.startsWith('</') && !trimmed.endsWith('/>')) { const tagMatch = trimmed.match(/<(\w+)/); if (tagMatch) { const tagName = tagMatch[1]; const attributes = this.extractAttributes(trimmed); const className = attributes.class || ''; nodes.push({ id: `node-${nodeId++}`, tagName, className, textContent: '', attributes, children: [], styles: {}, computedStyles: {}, boundingBox: { x: 0, y: 0, width: 0, height: 0 }, isVisible: true, zIndex: 0, }); } } } return nodes; } extractAttributes(line) { const attributes = {}; const attrRegex = /(\w+)=["']([^"']*)["']/g; let match; while ((match = attrRegex.exec(line)) !== null) { attributes[match[1]] = match[2]; } return attributes; } analyzeIssues(nodes) { const issues = []; for (const node of nodes) { // Check for missing alt attributes on images if (node.tagName.toLowerCase() === 'img' && !node.attributes.alt) { issues.push({ type: 'warning', severity: 'medium', message: 'Image missing alt attribute', nodeId: node.id, suggestion: 'Add alt="description" to improve accessibility', }); } // Check for missing aria labels on interactive elements if (['button', 'input', 'select', 'textarea'].includes(node.tagName.toLowerCase())) { const hasLabel = node.attributes['aria-label'] || node.attributes['aria-labelledby'] || node.attributes.title; if (!hasLabel && !node.attributes.id) { issues.push({ type: 'warning', severity: 'medium', message: `${node.tagName} missing accessibility label`, nodeId: node.id, suggestion: 'Add aria-label, aria-labelledby, or title attribute', }); } } // Check for inline styles (performance issue) if (node.attributes.style) { issues.push({ type: 'info', severity: 'low', message: 'Element uses inline styles', nodeId: node.id, suggestion: 'Consider moving styles to external CSS for better performance', }); } } return issues; } calculatePerformanceMetrics(nodes) { const depth = this.calculateMaxDepth(nodes); return { totalNodes: nodes.length, depth, renderTime: nodes.length * 0.1, memoryUsage: nodes.length * 0.5, repaints: Math.floor(nodes.length * 0.3), reflows: Math.floor(nodes.length * 0.1), }; } calculateAccessibilityMetrics(nodes) { let missingAltText = 0; let missingAriaLabels = 0; let headingStructure = 0; let colorContrast = 0; let keyboardNavigation = 0; let screenReaderCompatible = 0; for (const node of nodes) { if (node.tagName.toLowerCase() === 'img' && !node.attributes.alt) { missingAltText++; } if (['button', 'input', 'select', 'textarea'].includes(node.tagName.toLowerCase())) { const hasLabel = node.attributes['aria-label'] || node.attributes['aria-labelledby'] || node.attributes.title; if (!hasLabel && !node.attributes.id) { missingAriaLabels++; } } if (/^h[1-6]$/.test(node.tagName.toLowerCase())) { headingStructure++; } if (node.attributes.tabindex) { keyboardNavigation++; } if (node.attributes['aria-label'] || node.attributes['aria-describedby']) { screenReaderCompatible++; } } return { missingAltText, missingAriaLabels, headingStructure, colorContrast, keyboardNavigation, screenReaderCompatible, }; } calculateMaxDepth(nodes) { // Simplified depth calculation return Math.floor(Math.log2(nodes.length)) + 1; } getWebviewContent() { return ` <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>DOM Inspector</title> <style> body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; margin: 0; padding: 20px; background: var(--vscode-editor-background); color: var(--vscode-editor-foreground); } .container { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; height: calc(100vh - 40px); } .panel { background: var(--vscode-panel-background); border: 1px solid var(--vscode-panel-border); border-radius: 6px; padding: 16px; overflow: auto; } .panel h2 { margin-top: 0; color: var(--vscode-editor-foreground); border-bottom: 1px solid var(--vscode-panel-border); padding-bottom: 8px; } .tree-node { margin: 4px 0; padding: 4px 8px; border-radius: 4px; cursor: pointer; transition: background-color 0.2s; } .tree-node:hover { background: var(--vscode-list-hoverBackground); } .tree-node.selected { background: var(--vscode-list-activeSelectionBackground); color: var(--vscode-list-activeSelectionForeground); } .issue { margin: 8px 0; padding: 8px; border-radius: 4px; border-left: 4px solid; } .issue.error { background: rgba(255, 0, 0, 0.1); border-left-color: #ff0000; } .issue.warning { background: rgba(255, 165, 0, 0.1); border-left-color: #ffa500; } .issue.info { background: rgba(0, 123, 255, 0.1); border-left-color: #007bff; } .metrics { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; } .metric { background: var(--vscode-editor-background); padding: 12px; border-radius: 4px; text-align: center; } .metric-value { font-size: 24px; font-weight: bold; color: var(--vscode-textLink-foreground); } .metric-label { font-size: 12px; color: var(--vscode-descriptionForeground); margin-top: 4px; } </style> </head> <body> <div class="container"> <div class="panel"> <h2>DOM Tree</h2> <div id="dom-tree"></div> </div> <div class="panel"> <h2>Analysis</h2> <div id="issues"></div> <h2>Performance Metrics</h2> <div id="performance" class="metrics"></div> <h2>Accessibility Metrics</h2> <div id="accessibility" class="metrics"></div> </div> </div> <script> const vscode = acquireVsCodeApi(); window.addEventListener('message', event => { const message = event.data; switch (message.type) { case 'analysis': displayAnalysis(message.data); break; } }); function displayAnalysis(analysis) { displayDOMTree(analysis.nodes); displayIssues(analysis.issues); displayPerformanceMetrics(analysis.performance); displayAccessibilityMetrics(analysis.accessibility); } function displayDOMTree(nodes) { const container = document.getElementById('dom-tree'); container.innerHTML = ''; nodes.forEach(node => { const element = document.createElement('div'); element.className = 'tree-node'; element.innerHTML = \`<strong>\${node.tagName}</strong>\${node.className ? ' .' + node.className.split(' ').join('.') : ''}\`; element.onclick = () => selectNode(node); container.appendChild(element); }); } function displayIssues(issues) { const container = document.getElementById('issues'); container.innerHTML = ''; issues.forEach(issue => { const element = document.createElement('div'); element.className = \`issue \${issue.type}\`; element.innerHTML = \` <strong>\${issue.message}</strong> \${issue.suggestion ? '<br><small>' + issue.suggestion + '</small>' : ''} \`; container.appendChild(element); }); } function displayPerformanceMetrics(metrics) { const container = document.getElementById('performance'); container.innerHTML = ''; Object.entries(metrics).forEach(([key, value]) => { const element = document.createElement('div'); element.className = 'metric'; element.innerHTML = \` <div class="metric-value">\${value}</div> <div class="metric-label">\${key.replace(/([A-Z])/g, ' $1').toLowerCase()}</div> \`; container.appendChild(element); }); } function displayAccessibilityMetrics(metrics) { const container = document.getElementById('accessibility'); container.innerHTML = ''; Object.entries(metrics).forEach(([key, value]) => { const element = document.createElement('div'); element.className = 'metric'; element.innerHTML = \` <div class="metric-value">\${value}</div> <div class="metric-label">\${key.replace(/([A-Z])/g, ' $1').toLowerCase()}</div> \`; container.appendChild(element); }); } function selectNode(node) { // Remove previous selection document.querySelectorAll('.tree-node.selected').forEach(el => { el.classList.remove('selected'); }); // Add selection to clicked node event.target.closest('.tree-node').classList.add('selected'); // Send message to extension vscode.postMessage({ type: 'nodeSelected', nodeId: node.id }); } </script> </body> </html> `; } handleMessage(message) { switch (message.type) { case 'nodeSelected': this.onNodeSelected(message.nodeId); break; } } onNodeSelected(nodeId) { if (this.currentAnalysis) { const node = this.currentAnalysis.nodes.find(n => n.id === nodeId); if (node) { // Highlight the selected node in the editor vscode.commands.executeCommand('advancedLiveServer.highlightNode', node); } } } getCurrentAnalysis() { return this.currentAnalysis; } } exports.DOMInspector = DOMInspector; //# sourceMappingURL=dom-inspector.js.map