This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/packages/diff-viewer/js/script.js
Jeffrey Smith 81c28a50bf
All checks were successful
CI/CD / detect-changes (push) Successful in 4s
CI/CD / test-frontend (push) Successful in 6s
CI/CD / test-go-pg (push) Successful in 2m48s
CI/CD / test-sqlite (push) Successful in 2m52s
CI/CD / build-and-deploy (push) Successful in 1m20s
Feat v0.6.5 renderer pipeline (#40)
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com>
Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
2026-03-31 16:37:33 +00:00

160 lines
6.6 KiB
JavaScript

// ==========================================
// Diff Viewer — Browser Extension
// ==========================================
// Renders ```diff code blocks with syntax-highlighted
// additions, deletions, hunk headers, and context lines.
// No external dependencies.
//
// Registers with sw.renderers via the sw:ready event.
// ==========================================
(function () {
'use strict';
function _escapeHtml(str) {
const div = document.createElement('div');
div.textContent = str;
return div.innerHTML;
}
// ── Styles ──────────────────────────────
function _injectStyles() {
if (document.getElementById('ext-style-diff-viewer')) return;
const style = document.createElement('style');
style.id = 'ext-style-diff-viewer';
style.textContent = `
.diff-block {
background: var(--bg-2); border: 1px solid var(--border);
border-radius: 8px; overflow: hidden; margin: 12px 0;
}
.diff-toolbar {
display: flex; align-items: center; gap: 8px;
padding: 6px 12px; border-bottom: 1px solid var(--border);
font-size: 12px; color: var(--text-3);
}
.diff-filename {
flex: 1; font-family: var(--mono); font-weight: 600;
color: var(--text-2); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.diff-stats { display: flex; gap: 8px; font-family: var(--mono); font-weight: 600; }
.diff-stat-add { color: var(--success, #27ae60); }
.diff-stat-del { color: var(--error, #e74c3c); }
.diff-copy-btn {
background: none; border: 1px solid var(--border); border-radius: 4px;
padding: 2px 8px; font-size: 12px; cursor: pointer; color: var(--text-3);
}
.diff-copy-btn:hover { color: var(--text-2); border-color: var(--text-3); }
.diff-content {
font-family: var(--mono); font-size: 13px; line-height: 1.5;
overflow-x: auto;
}
.diff-line { padding: 1px 12px; white-space: pre; min-height: 1.5em; }
.diff-indicator {
display: inline-block; width: 1.2em; text-align: center;
opacity: 0.7; user-select: none;
}
.diff-add { background: rgba(39,174,96,0.12); color: var(--success, #27ae60); }
.diff-del { background: rgba(231,76,60,0.12); color: var(--error, #e74c3c); }
.diff-hunk {
background: rgba(52,152,219,0.08); color: var(--info, #3498db);
font-style: italic; border-top: 1px solid var(--border);
border-bottom: 1px solid var(--border); margin: 2px 0;
}
.diff-file-header { background: var(--bg-3); color: var(--text-2); font-weight: 600; }
.diff-ctx { color: var(--text-3); }
.diff-meta { color: var(--text-3); font-style: italic; }
.diff-source-toggle { border-top: 1px solid var(--border); font-size: 12px; }
.diff-source-toggle summary {
padding: 6px 12px; cursor: pointer; color: var(--text-3); user-select: none;
}
.diff-source-toggle summary:hover { color: var(--text-2); }
.diff-source-toggle pre {
margin: 0; border-radius: 0; border: none;
max-height: 200px; overflow: auto;
}`;
document.head.appendChild(style);
}
// ── Registration ────────────────────────
function register() {
if (!window.sw?.renderers) return;
_injectStyles();
sw.renderers.register('diff-block', {
type: 'block',
priority: 10,
pattern: 'diff',
render(lang, code, container) {
const lines = code.split('\n');
let stats = { added: 0, removed: 0 };
let currentFile = '';
const rendered = lines.map(line => {
const escaped = _escapeHtml(line);
if (line.startsWith('--- ') || line.startsWith('+++ ')) {
if (line.startsWith('+++ ')) currentFile = line.slice(4).trim();
return `<div class="diff-line diff-file-header">${escaped}</div>`;
}
if (line.startsWith('@@')) {
return `<div class="diff-line diff-hunk">${escaped}</div>`;
}
if (line.startsWith('+')) {
stats.added++;
return `<div class="diff-line diff-add"><span class="diff-indicator">+</span>${_escapeHtml(line.slice(1))}</div>`;
}
if (line.startsWith('-')) {
stats.removed++;
return `<div class="diff-line diff-del"><span class="diff-indicator">-</span>${_escapeHtml(line.slice(1))}</div>`;
}
if (line.startsWith(' ')) {
return `<div class="diff-line diff-ctx"><span class="diff-indicator"> </span>${_escapeHtml(line.slice(1))}</div>`;
}
return `<div class="diff-line diff-meta">${escaped}</div>`;
}).join('');
const fileLabel = currentFile
? `<span class="diff-filename" title="${_escapeHtml(currentFile)}">${_escapeHtml(currentFile)}</span>`
: '';
container.innerHTML = `
<div class="diff-block ext-rendered">
<div class="diff-toolbar">
${fileLabel}
<span class="diff-stats">
<span class="diff-stat-add">+${stats.added}</span>
<span class="diff-stat-del">-${stats.removed}</span>
</span>
<button class="diff-copy-btn" title="Copy diff">\ud83d\udccb Copy</button>
</div>
<div class="diff-content">${rendered}</div>
<details class="diff-source-toggle">
<summary>\ud83d\udcdd View raw</summary>
<pre><code>${_escapeHtml(code)}</code></pre>
</details>
</div>
`;
const copyBtn = container.querySelector('.diff-copy-btn');
if (copyBtn) {
copyBtn.addEventListener('click', () => {
navigator.clipboard.writeText(code).then(() => {
copyBtn.textContent = '\u2713 Copied';
setTimeout(() => { copyBtn.textContent = '\ud83d\udccb Copy'; }, 1500);
}).catch(() => {});
});
}
}
});
}
if (window.sw?._sdk) {
register();
} else {
document.addEventListener('sw:ready', register, { once: true });
}
})();