2026-04-16 19:44:23 +02:00
|
|
|
---
|
|
|
|
|
interface Props {
|
|
|
|
|
code: string;
|
|
|
|
|
hints: string[];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const { code, hints } = Astro.props;
|
2026-04-17 13:09:06 +02:00
|
|
|
const id = `dv-${Math.random().toString(36).substring(2, 9)}`;
|
2026-04-16 19:44:23 +02:00
|
|
|
|
|
|
|
|
const lines = code.split('\n');
|
|
|
|
|
---
|
|
|
|
|
|
2026-04-17 13:09:06 +02:00
|
|
|
<div id={id} class="diff-viewer-container" data-dv-id={id}>
|
|
|
|
|
<div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden">
|
|
|
|
|
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d]">
|
|
|
|
|
<span class="text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
|
2026-04-16 19:44:23 +02:00
|
|
|
</div>
|
|
|
|
|
|
2026-04-17 13:09:06 +02:00
|
|
|
<div class="overflow-x-auto">
|
|
|
|
|
<table class="w-full font-mono text-sm">
|
|
|
|
|
<tbody>
|
|
|
|
|
{lines.map((line, index) => {
|
|
|
|
|
const lineNum = index + 1;
|
|
|
|
|
const hasHint = hints.some(h => h.includes(`Line ${lineNum}`));
|
|
|
|
|
const hintText = hints.find(h => h.includes(`Line ${lineNum}`));
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<tr
|
|
|
|
|
data-line={lineNum}
|
|
|
|
|
class="group hover:bg-[#161b22] cursor-pointer transition-colors"
|
|
|
|
|
title={`Mark line ${lineNum} as buggy`}
|
|
|
|
|
>
|
|
|
|
|
<td class="w-16 text-right pr-4 select-none">
|
2026-04-28 15:59:47 +02:00
|
|
|
<span class={`text-[#8b949e] line-number ${hasHint ? 'has-hint-color' : ''}`}>
|
2026-04-17 13:09:06 +02:00
|
|
|
{lineNum}
|
2026-04-16 19:44:23 +02:00
|
|
|
</span>
|
2026-04-17 13:09:06 +02:00
|
|
|
</td>
|
|
|
|
|
<td class="px-4 py-0.5 whitespace-pre">
|
2026-04-28 15:59:47 +02:00
|
|
|
<span class={`text-[#e6edf3] ${hasHint ? 'has-hint-bg bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
|
2026-04-17 13:09:06 +02:00
|
|
|
{line || ' '}
|
|
|
|
|
</span>
|
|
|
|
|
{hasHint && (
|
2026-04-28 15:59:47 +02:00
|
|
|
<span class="ml-2 hint text-[#79c0ff] text-xs hidden">
|
2026-04-17 13:09:06 +02:00
|
|
|
• {hintText?.replace(`Line ${lineNum}: `, '')}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
2026-04-16 19:44:23 +02:00
|
|
|
</div>
|
2026-04-17 13:09:06 +02:00
|
|
|
|
2026-04-28 15:59:47 +02:00
|
|
|
|
2026-04-17 13:09:06 +02:00
|
|
|
</div>
|
2026-04-16 19:44:23 +02:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
tr {
|
|
|
|
|
line-height: 1.6;
|
|
|
|
|
}
|
|
|
|
|
tr:hover {
|
|
|
|
|
background-color: #161b22;
|
|
|
|
|
}
|
|
|
|
|
td:first-child {
|
|
|
|
|
border-right: 1px solid #30363d;
|
|
|
|
|
}
|
2026-04-17 13:09:06 +02:00
|
|
|
tr.bugged td:first-child {
|
|
|
|
|
border-left: 3px solid #f85149;
|
|
|
|
|
}
|
|
|
|
|
tr.bugged .line-number {
|
|
|
|
|
color: #f85149;
|
|
|
|
|
}
|
|
|
|
|
tr.bugged {
|
|
|
|
|
background-color: #f8514918;
|
|
|
|
|
}
|
2026-04-28 15:59:47 +02:00
|
|
|
.diff-viewer-container:not(.revealed) tr.bugged .line-number.has-hint-color {
|
|
|
|
|
color: #f85149 !important;
|
|
|
|
|
}
|
|
|
|
|
.diff-viewer-container:not(.revealed) tr:not(.bugged) span.line-number.has-hint-color {
|
|
|
|
|
color: #8b949e !important;
|
|
|
|
|
font-weight: normal !important;
|
|
|
|
|
}
|
|
|
|
|
.diff-viewer-container.revealed tr.bugged .hint {
|
|
|
|
|
display: inline;
|
|
|
|
|
}
|
|
|
|
|
.diff-viewer-container:not(.revealed) td > span.has-hint-bg {
|
|
|
|
|
background-color: transparent !important;
|
|
|
|
|
}
|
2026-04-16 19:44:23 +02:00
|
|
|
</style>
|
2026-04-17 13:09:06 +02:00
|
|
|
|
|
|
|
|
<script client:load>
|
|
|
|
|
(() => {
|
|
|
|
|
const el = document.querySelector('[data-dv-id]');
|
|
|
|
|
if (!el) return;
|
|
|
|
|
|
|
|
|
|
const buggedLines = new Set();
|
|
|
|
|
|
|
|
|
|
function updateBuggedState() {
|
|
|
|
|
const rows = el.querySelectorAll('tr[data-line]');
|
|
|
|
|
rows.forEach(row => {
|
|
|
|
|
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
|
|
|
|
row.classList.toggle('bugged', buggedLines.has(lineNum));
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
el.addEventListener('click', function(e) {
|
|
|
|
|
const target = e.target;
|
|
|
|
|
if (!(target instanceof HTMLElement)) return;
|
|
|
|
|
const row = target.closest('tr[data-line]');
|
|
|
|
|
if (!row) return;
|
|
|
|
|
|
|
|
|
|
const lineNum = parseInt(row.getAttribute('data-line') || '0', 10);
|
|
|
|
|
if (buggedLines.has(lineNum)) {
|
|
|
|
|
buggedLines.delete(lineNum);
|
|
|
|
|
} else {
|
|
|
|
|
buggedLines.add(lineNum);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateBuggedState();
|
|
|
|
|
|
|
|
|
|
el.dispatchEvent(new CustomEvent('bugged-line-toggle', {
|
|
|
|
|
bubbles: true,
|
|
|
|
|
detail: { line: lineNum, bugged: buggedLines.has(lineNum), allBugged: [...buggedLines] }
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
})();
|
|
|
|
|
</script>
|