// Diff Viewer using diff2html const DiffViewer = { init(containerId) { this.container = document.getElementById(containerId); if (!this.container) { console.error('Diff container not found:', containerId); return; } }, // Render unified diff string renderDiff(diffString, options = {}) { if (!this.container) return; const config = { drawFileList: options.drawFileList !== false, fileListToggle: options.fileListToggle !== false, fileListStartVisible: options.fileListStartVisible !== false, fileContentToggle: options.fileContentToggle !== false, matching: options.matching || 'lines', outputFormat: options.outputFormat || 'side-by-side', synchronisedScroll: true, highlight: true, renderNothingWhenEmpty: false, }; // Use diff2html to render const diffHtml = Diff2Html.html(diffString, config); this.container.innerHTML = diffHtml; // Add syntax highlighting this.container.querySelectorAll('pre code').forEach(block => { if (window.hljs) { hljs.highlightElement(block); } }); }, // Render per-file diffs renderFiles(files, options = {}) { if (!this.container) return; if (!files || files.length === 0) { this.container.innerHTML = '
No changes
'; return; } // Combine all patches const combinedDiff = files.map(f => f.patch).join('\n'); this.renderDiff(combinedDiff, options); }, // Load and render diff from API async loadDiff(repoId, base, head, options = {}) { if (!this.container) return; this.container.innerHTML = '

Loading diff...

'; try { const url = `/api/repos/${repoId}/diff?base=${encodeURIComponent(base)}&head=${encodeURIComponent(head)}`; const resp = await fetch(url); if (!resp.ok) throw new Error('Failed to load diff'); const data = await resp.json(); if (data.diff) { this.renderDiff(data.diff, options); } else if (Array.isArray(data)) { this.renderFiles(data, options); } } catch (err) { this.container.innerHTML = `
Error loading diff: ${err.message}
`; } }, // Load per-file diffs async loadFiles(repoId, base, head) { if (!this.container) return; this.container.innerHTML = '

Loading files...

'; try { const url = `/api/repos/${repoId}/diff?base=${encodeURIComponent(base)}&head=${encodeURIComponent(head)}&per_file=true`; const resp = await fetch(url); if (!resp.ok) throw new Error('Failed to load diff'); const files = await resp.json(); this.renderFiles(files); } catch (err) { this.container.innerHTML = `
Error: ${err.message}
`; } }, // Switch view mode (side-by-side or line-by-line) setViewMode(mode) { if (!this.container) return; const config = { drawFileList: true, outputFormat: mode === 'unified' ? 'line-by-line' : 'side-by-side', }; // Re-render with existing diff content const existingDiff = this.container.querySelector('.d2h-diff-table'); if (existingDiff) { // Get the raw diff from the container's data const rawDiff = this.container.dataset.rawDiff; if (rawDiff) { this.renderDiff(rawDiff, config); } } }, // Toggle file list visibility toggleFileList() { const fileList = this.container.querySelector('.d2h-file-list'); if (fileList) { fileList.style.display = fileList.style.display === 'none' ? 'block' : 'none'; } }, // Expand/collapse all files toggleAllFiles(expand) { const fileContents = this.container.querySelectorAll('.d2h-file-wrapper'); fileContents.forEach(file => { const content = file.querySelector('.d2h-file-diff'); if (content) { content.style.display = expand ? 'block' : 'none'; } }); } }; // Export for use window.DiffViewer = DiffViewer;