// Enhanced Diff Viewer Component class DiffViewer { constructor(options = {}) { this.renderStrategy = options.renderStrategy || 'side-by-side'; this.syntaxHighlighters = new Map(); this.commentThreads = []; this.container = null; this.performance = { startTime: null, endTime: null }; } initialize(containerId) { this.container = document.getElementById(containerId); if (!this.container) { throw new Error(`Container element with id "${containerId}" not found`); } this.setupSyntaxHighlighters(); } setupSyntaxHighlighters() { // Set up Prism.js or similar syntax highlighting library this.syntaxHighlighters.set('text', this.plainTextHighlighter); this.syntaxHighlighters.set('json', this.jsonHighlighter); this.syntaxHighlighters.set('python', this.pythonHighlighter); } async render(diffData) { this.performance.startTime = performance.now(); const { changes, metadata, navigation } = diffData; const content = this.renderStrategy === 'side-by-side' ? this.renderSideBySide(changes) : this.renderInline(changes); this.container.innerHTML = `
${this.renderMetadata(metadata)} ${this.renderControls()}
${content}
${this.renderNavigation(navigation)}
`; this.attachEventListeners(); await this.highlightSyntax(); this.performance.endTime = performance.now(); this.updatePerformanceMetrics(); } renderSideBySide(changes) { return Object.entries(changes).map(([field, change]) => `
${field} ${change.syntax_type}
${this.renderLineNumbers(change.metadata.line_numbers.old)}
${this.escapeHtml(change.old)}
${this.renderLineNumbers(change.metadata.line_numbers.new)}
${this.escapeHtml(change.new)}
`).join(''); } renderInline(changes) { return Object.entries(changes).map(([field, change]) => `
${field} ${change.syntax_type}
${this.renderLineNumbers(change.metadata.line_numbers.new)}

                        ${this.renderInlineDiff(change.old, change.new)}
                    
`).join(''); } renderMetadata(metadata) { return `
${new Date(metadata.timestamp).toLocaleString()} ${metadata.user || 'Anonymous'} ${this.formatChangeType(metadata.change_type)} ${metadata.reason ? `${metadata.reason}` : ''}
`; } renderControls() { return `
`; } renderNavigation(navigation) { return `
${navigation.prev_id ? `` : ''} ${navigation.next_id ? `` : ''} Change ${navigation.current_position}
`; } renderLineNumbers(numbers) { return numbers.map(num => `${num}`).join(''); } renderInlineDiff(oldText, newText) { // Simple inline diff implementation - could be enhanced with more sophisticated diff algorithm const oldLines = oldText.split('\n'); const newLines = newText.split('\n'); const diffLines = []; for (let i = 0; i < Math.max(oldLines.length, newLines.length); i++) { if (oldLines[i] !== newLines[i]) { if (oldLines[i]) { diffLines.push(`${this.escapeHtml(oldLines[i])}`); } if (newLines[i]) { diffLines.push(`${this.escapeHtml(newLines[i])}`); } } else if (oldLines[i]) { diffLines.push(this.escapeHtml(oldLines[i])); } } return diffLines.join('\n'); } attachEventListeners() { // View mode switching this.container.querySelectorAll('.btn-view-mode').forEach(btn => { btn.addEventListener('click', () => { this.renderStrategy = btn.dataset.mode; this.render(this.currentDiffData); }); }); // Collapse/Expand functionality this.container.querySelectorAll('.diff-section').forEach(section => { section.querySelector('.diff-field-header').addEventListener('click', () => { section.classList.toggle('collapsed'); }); }); // Navigation this.container.querySelectorAll('.diff-navigation button').forEach(btn => { btn.addEventListener('click', () => { this.navigateToChange(btn.dataset.id); }); }); } async highlightSyntax() { const codeBlocks = this.container.querySelectorAll('code[class^="language-"]'); for (const block of codeBlocks) { const syntax = block.className.replace('language-', ''); const highlighter = this.syntaxHighlighters.get(syntax); if (highlighter) { await highlighter(block); } } } // Syntax highlighters async plainTextHighlighter(element) { // No highlighting needed for plain text return element; } async jsonHighlighter(element) { try { const content = element.textContent; const parsed = JSON.parse(content); element.textContent = JSON.stringify(parsed, null, 2); // Apply JSON syntax highlighting classes element.innerHTML = element.innerHTML.replace( /"([^"]+)":/g, '"$1":' ); } catch (e) { console.warn('JSON parsing failed:', e); } return element; } async pythonHighlighter(element) { // Basic Python syntax highlighting element.innerHTML = element.innerHTML .replace(/(def|class|import|from|return|if|else|try|except)\b/g, '$1') .replace(/(["'])(.*?)\1/g, '$1$2$1') .replace(/#.*/g, '$&'); return element; } updatePerformanceMetrics() { const renderTime = this.performance.endTime - this.performance.startTime; if (renderTime > 200) { // Performance budget: 200ms console.warn(`Diff render time (${renderTime}ms) exceeded performance budget`); } } escapeHtml(text) { const div = document.createElement('div'); div.textContent = text; return div.innerHTML; } formatChangeType(type) { const types = { 'C': 'Changed', 'D': 'Deleted', 'A': 'Added' }; return types[type] || type; } addCommentThread(anchor, thread) { this.commentThreads.push({ anchor, thread }); this.renderCommentThreads(); } renderCommentThreads() { this.commentThreads.forEach(({ anchor, thread }) => { const element = this.container.querySelector(`[data-anchor="${anchor}"]`); if (element) { const threadElement = document.createElement('div'); threadElement.className = 'comment-thread'; threadElement.innerHTML = thread.map(comment => `
${comment.author} ${new Date(comment.date).toLocaleString()}
${comment.content}
`).join(''); element.appendChild(threadElement); } }); } } export default DiffViewer;