// 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 = `
${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]) => `
${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]) => `
${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 => `
`).join('');
element.appendChild(threadElement);
}
});
}
}
export default DiffViewer;