Feat v0.6.5 renderer pipeline (#40)
All checks were successful
All checks were successful
Co-authored-by: Jeffrey Smith <jasafpro@gmail.com> Co-committed-by: Jeffrey Smith <jasafpro@gmail.com>
This commit was merged in pull request #40.
This commit is contained in:
@@ -4,106 +4,22 @@
|
||||
// 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.
|
||||
// ==========================================
|
||||
|
||||
Extensions.register({
|
||||
id: 'diff-viewer',
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
async init(ctx) {
|
||||
const self = this;
|
||||
|
||||
// ── Inject styles ──
|
||||
this._injectStyles();
|
||||
|
||||
ctx.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 = self._escapeHtml(line);
|
||||
|
||||
// File headers
|
||||
if (line.startsWith('--- ') || line.startsWith('+++ ')) {
|
||||
if (line.startsWith('+++ ')) {
|
||||
currentFile = line.slice(4).trim();
|
||||
}
|
||||
return `<div class="diff-line diff-file-header">${escaped}</div>`;
|
||||
}
|
||||
|
||||
// Hunk headers
|
||||
if (line.startsWith('@@')) {
|
||||
const hunkMatch = line.match(/^@@\s*-(\d+)(?:,\d+)?\s*\+(\d+)(?:,\d+)?\s*@@\s*(.*)/);
|
||||
const context = hunkMatch ? hunkMatch[3] : '';
|
||||
return `<div class="diff-line diff-hunk">${escaped}</div>`;
|
||||
}
|
||||
|
||||
// Additions
|
||||
if (line.startsWith('+')) {
|
||||
stats.added++;
|
||||
return `<div class="diff-line diff-add"><span class="diff-indicator">+</span>${self._escapeHtml(line.slice(1))}</div>`;
|
||||
}
|
||||
|
||||
// Deletions
|
||||
if (line.startsWith('-')) {
|
||||
stats.removed++;
|
||||
return `<div class="diff-line diff-del"><span class="diff-indicator">-</span>${self._escapeHtml(line.slice(1))}</div>`;
|
||||
}
|
||||
|
||||
// Context (lines starting with space or no prefix)
|
||||
if (line.startsWith(' ')) {
|
||||
return `<div class="diff-line diff-ctx"><span class="diff-indicator"> </span>${self._escapeHtml(line.slice(1))}</div>`;
|
||||
}
|
||||
|
||||
// Other (commit info, etc)
|
||||
return `<div class="diff-line diff-meta">${escaped}</div>`;
|
||||
}).join('');
|
||||
|
||||
const fileLabel = currentFile ? `<span class="diff-filename" title="${self._escapeHtml(currentFile)}">${self._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">📋 Copy</button>
|
||||
</div>
|
||||
<div class="diff-content">${rendered}</div>
|
||||
<details class="diff-source-toggle">
|
||||
<summary>📝 View raw</summary>
|
||||
<pre><code>${self._escapeHtml(code)}</code></pre>
|
||||
</details>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Copy handler
|
||||
const copyBtn = container.querySelector('.diff-copy-btn');
|
||||
if (copyBtn) {
|
||||
copyBtn.addEventListener('click', () => {
|
||||
navigator.clipboard.writeText(code).then(() => {
|
||||
copyBtn.textContent = '✓ Copied';
|
||||
setTimeout(() => { copyBtn.textContent = '📋 Copy'; }, 1500);
|
||||
}).catch(() => {});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_escapeHtml(str) {
|
||||
function _escapeHtml(str) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = str;
|
||||
return div.innerHTML;
|
||||
},
|
||||
}
|
||||
|
||||
_injectStyles() {
|
||||
// ── Styles ──────────────────────────────
|
||||
|
||||
function _injectStyles() {
|
||||
if (document.getElementById('ext-style-diff-viewer')) return;
|
||||
const style = document.createElement('style');
|
||||
style.id = 'ext-style-diff-viewer';
|
||||
@@ -158,9 +74,86 @@ Extensions.register({
|
||||
max-height: 200px; overflow: auto;
|
||||
}`;
|
||||
document.head.appendChild(style);
|
||||
},
|
||||
|
||||
destroy() {
|
||||
document.getElementById('ext-style-diff-viewer')?.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// ── 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 });
|
||||
}
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user