Add diff viewer line marking and submitting fixes by line

This commit is contained in:
Oracle 2026-04-17 13:09:06 +02:00
parent b4b7d989cb
commit 6596dc6d9b
Signed by: Oracle
SSH key fingerprint: SHA256:x4/RtnjUyuHkdvmwNDsWSfcfF1V5PNr3OpriZqOvCX8
15 changed files with 1610 additions and 82 deletions

View file

@ -5,68 +5,67 @@ interface Props {
}
const { code, hints } = Astro.props;
const id = `dv-${Math.random().toString(36).substring(2, 9)}`;
const lines = code.split('\n');
---
<div class="bg-[#0d1117] border border-[#30363d] rounded-md overflow-hidden">
<div class="bg-[#161b22] px-4 py-3 border-b border-[#30363d] flex items-center gap-2">
<div class="flex gap-1.5">
<div class="w-3 h-3 rounded-full bg-[#ff5f57]"></div>
<div class="w-3 h-3 rounded-full bg-[#febc2e]"></div>
<div class="w-3 h-3 rounded-full bg-[#28c840]"></div>
<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>
</div>
<span class="ml-2 text-sm text-[#8b949e]">{Astro.url.pathname.split('/').pop() || 'file.js'}</span>
</div>
<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
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">
<span class={`text-[#8b949e] ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}>
{lineNum}
</span>
</td>
<td class="px-4 py-0.5 whitespace-pre">
<span class={`text-[#e6edf3] ${hasHint ? 'bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
{line || ' '}
</span>
{hasHint && (
<span class="ml-2 text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity">
• {hintText?.replace(`Line ${lineNum}: `, '')}
<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">
<span class={`text-[#8b949e] line-number ${hasHint ? 'text-[#ff7b72] font-semibold' : ''}`}>
{lineNum}
</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
{hints.length > 0 && (
<div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]">
<h3 class="text-sm font-semibold text-[#c9d1d9] mb-2">Hints:</h3>
<ul class="space-y-1">
{hints.map((hint) => (
<li class="text-sm text-[#8b949e] flex items-start gap-2">
<span class="text-[#79c0ff] mt-0.5">•</span>
<span>{hint}</span>
</li>
))}
</ul>
</td>
<td class="px-4 py-0.5 whitespace-pre">
<span class={`text-[#e6edf3] ${hasHint ? 'bg-[#ff7b7233] px-2 -mx-2' : ''}`}>
{line || ' '}
</span>
{hasHint && (
<span class="ml-2 hint text-[#79c0ff] text-xs opacity-0 group-hover:opacity-100 transition-opacity">
• {hintText?.replace(`Line ${lineNum}: `, '')}
</span>
)}
</td>
</tr>
);
})}
</tbody>
</table>
</div>
)}
{hints.length > 0 && (
<div class="bg-[#161b22] px-4 py-3 border-t border-[#30363d]">
<h3 class="text-sm font-semibold text-[#c9d1d9] mb-2">Hints:</h3>
<ul class="space-y-1">
{hints.map((hint) => (
<li class="text-sm text-[#8b949e] flex items-start gap-2">
<span class="text-[#79c0ff] mt-0.5">•</span>
<span>{hint}</span>
</li>
))}
</ul>
</div>
)}
</div>
</div>
<style>
@ -79,4 +78,51 @@ const lines = code.split('\n');
td:first-child {
border-right: 1px solid #30363d;
}
tr.bugged td:first-child {
border-left: 3px solid #f85149;
}
tr.bugged .line-number {
color: #f85149;
}
tr.bugged {
background-color: #f8514918;
}
</style>
<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>