| | |
| |
|
| | function badgeClass(source) { |
| | return 'badge-' + source.toLowerCase().replace(/[^a-z0-9]/g, ''); |
| | } |
| |
|
| | async function loadProblem() { |
| | try { |
| | const response = await fetch(`/api/${datasetSlug}/problem/${problemIdx}`); |
| | const problem = await response.json(); |
| |
|
| | if (problem.error) { |
| | document.getElementById('problem-content').innerHTML = |
| | '<div class="card"><p style="color: red;">Error: ' + problem.error + '</p></div>'; |
| | return; |
| | } |
| |
|
| | renderProblem(problem); |
| | } catch (error) { |
| | document.getElementById('problem-content').innerHTML = |
| | '<div class="card"><p style="color: red;">Error loading problem: ' + error.message + '</p></div>'; |
| | } |
| | } |
| |
|
| | function renderProblem(problem) { |
| | const container = document.getElementById('problem-content'); |
| |
|
| | |
| | let html = ` |
| | <div class="card"> |
| | <div class="problem-header"> |
| | <h2>${escapeHtml(problem.entry_point)}</h2> |
| | <span class="badge ${badgeClass(problem.source)}">${escapeHtml(problem.source)}</span> |
| | </div> |
| | <div class="problem-meta"> |
| | <div class="meta-item"> |
| | <span class="meta-label">Task ID:</span> |
| | <span class="meta-value">${escapeHtml(problem.task_id)}</span> |
| | </div> |
| | <div class="meta-item"> |
| | <span class="meta-label">Index:</span> |
| | <span class="meta-value">${problem.idx}</span> |
| | </div> |
| | <div class="meta-item"> |
| | <span class="meta-label">Dataset:</span> |
| | <span class="meta-value">${escapeHtml(datasetName)}</span> |
| | </div> |
| | ${problem.inputs.length > 0 ? ` |
| | <div class="meta-item"> |
| | <span class="meta-label">Test Inputs:</span> |
| | <span class="meta-value">${problem.inputs.length}</span> |
| | </div>` : ''} |
| | </div> |
| | </div> |
| | `; |
| |
|
| | |
| | if (problem.solutions && problem.solutions.length > 0) { |
| | |
| | if (problem.description) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Problem Statement</h2> |
| | <pre class="code-block" style="white-space: pre-wrap;">${escapeHtml(problem.description)}</pre> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | problem.solutions.forEach((sol, i) => { |
| | html += ` |
| | <div class="card"> |
| | <h2>Solution ${i + 1} <span style="font-size:0.8rem;color:#7f8c8d;font-weight:400;">${escapeHtml(sol.solution_id)}</span></h2> |
| | <div class="complexity-badges" style="margin-bottom: 15px;"> |
| | `; |
| | if (sol.language) { |
| | html += ` |
| | <div class="complexity-item"> |
| | <span class="complexity-label">Language</span> |
| | <span class="badge badge-info">${escapeHtml(sol.language)}</span> |
| | </div>`; |
| | } |
| | if (sol.time_complexity) { |
| | html += ` |
| | <div class="complexity-item"> |
| | <span class="complexity-label">Time</span> |
| | <span class="complexity-value">${escapeHtml(sol.time_complexity)}</span> |
| | </div>`; |
| | } |
| | if (sol.space_complexity) { |
| | html += ` |
| | <div class="complexity-item"> |
| | <span class="complexity-label">Space</span> |
| | <span class="complexity-value">${escapeHtml(sol.space_complexity)}</span> |
| | </div>`; |
| | } |
| | html += ` |
| | </div> |
| | <div class="code-with-tasks"> |
| | ${sol.highlighted_code} |
| | </div> |
| | </div> |
| | `; |
| | }); |
| |
|
| | |
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | return; |
| | } |
| |
|
| | |
| | if (problem.buggy_code !== undefined && problem.fixed_code !== undefined) { |
| | |
| | if (problem.description) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Problem Statement</h2> |
| | <pre class="code-block" style="white-space: pre-wrap;">${escapeHtml(problem.description)}</pre> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Bug Information</h2> |
| | <div class="bug-info"> |
| | <span class="bug-category">${escapeHtml(problem.bug_category || '')}</span> |
| | <span class="bug-subtype">${escapeHtml(problem.bug_subtype || '')}</span> |
| | </div> |
| | <p style="margin-top: 10px;">${escapeHtml(problem.bug_explanation || '')}</p> |
| | `; |
| | if (problem.difficulty) { |
| | html += `<p style="margin-top: 8px; color: #7f8c8d;">Difficulty: <strong>${escapeHtml(problem.difficulty)}</strong></p>`; |
| | } |
| | html += `</div>`; |
| |
|
| | |
| | const unifiedDiff = computeUnifiedDiff(problem.buggy_code, problem.fixed_code); |
| | html += ` |
| | <div class="card"> |
| | <h2>Changes</h2> |
| | <div class="diff-view">${renderComputedDiff(unifiedDiff)}</div> |
| | </div> |
| | `; |
| |
|
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Full Code Comparison</h2> |
| | <div class="diff-container"> |
| | <div class="diff-panel"> |
| | <h3><span class="diff-label-buggy">Before</span></h3> |
| | <div class="code-with-tasks">${problem.buggy_highlighted_code}</div> |
| | </div> |
| | <div class="diff-panel"> |
| | <h3><span class="diff-label-fixed">After</span></h3> |
| | <div class="code-with-tasks">${problem.fixed_highlighted_code}</div> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| |
|
| | |
| | if (problem.examples && problem.examples.length > 0) { |
| | html += `<div class="card"><h2>Examples</h2>`; |
| | problem.examples.forEach((ex, i) => { |
| | html += `<pre class="code-block" style="margin-bottom: 10px; white-space: pre-wrap;">${escapeHtml(ex)}</pre>`; |
| | }); |
| | html += `</div>`; |
| | } |
| |
|
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | return; |
| | } |
| |
|
| | |
| | if (problem.lang_solutions && problem.lang_solutions.length > 0) { |
| | |
| | const hasBuggy = problem.lang_solutions.some(sol => sol.buggy_code); |
| |
|
| | |
| | if (hasBuggy && (problem.bug_type || problem.failure_symptoms)) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Bug Information</h2> |
| | <div class="bug-info"> |
| | ${problem.bug_type ? `<span class="bug-category">${escapeHtml(problem.bug_type)}</span>` : ''} |
| | ${problem.failure_symptoms ? `<span class="bug-subtype">${escapeHtml(problem.failure_symptoms)}</span>` : ''} |
| | </div> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Source Code</h2> |
| | `; |
| |
|
| | |
| | if (hasBuggy) { |
| | html += ` |
| | <div class="lang-tabs" id="code-mode-tabs" style="margin-bottom: 10px;"> |
| | <button class="lang-tab active" onclick="toggleCodeMode('canonical')">Canonical</button> |
| | <button class="lang-tab" onclick="toggleCodeMode('buggy')">Buggy</button> |
| | </div> |
| | `; |
| | } |
| |
|
| | html += `<div class="lang-tabs" id="lang-tabs">`; |
| | problem.lang_solutions.forEach((sol, i) => { |
| | const label = sol.language_label || sol.language; |
| | html += `<button class="lang-tab ${i === 0 ? 'active' : ''}" onclick="showLangTab(${i})">${escapeHtml(label)}</button>`; |
| | }); |
| | html += `</div>`; |
| |
|
| | problem.lang_solutions.forEach((sol, i) => { |
| | html += ` |
| | <div class="lang-code-panel ${i === 0 ? 'active' : ''}" id="lang-panel-${i}"> |
| | <div class="code-with-tasks" id="lang-code-canonical-${i}">${sol.highlighted_code}</div> |
| | ${sol.buggy_code ? `<div class="code-with-tasks" id="lang-code-buggy-${i}" style="display:none;">${sol.buggy_highlighted_code}</div>` : ''} |
| | </div> |
| | `; |
| | }); |
| | html += `</div>`; |
| |
|
| | |
| | html += `<div class="card" id="lang-test-container">`; |
| | if (problem.lang_solutions[0].test) { |
| | html += `<h2>Test Suite</h2><pre class="code-block">${escapeHtml(problem.lang_solutions[0].test)}</pre>`; |
| | } |
| | html += `</div>`; |
| |
|
| | |
| | if (problem.description) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Problem Description</h2> |
| | <div style="padding: 10px; line-height: 1.6; white-space: pre-wrap;">${escapeHtml(problem.description)}</div> |
| | </div> |
| | `; |
| | } |
| |
|
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | window._currentCodeMode = 'canonical'; |
| | return; |
| | } |
| |
|
| | |
| | if (problem.patch !== undefined) { |
| | |
| | const ghLinks = []; |
| | if (problem.repo_url) ghLinks.push(`<a href="${escapeHtml(problem.repo_url)}" target="_blank" class="gh-link">Repository</a>`); |
| | if (problem.issue_url) ghLinks.push(`<a href="${escapeHtml(problem.issue_url)}" target="_blank" class="gh-link">Issue</a>`); |
| | if (problem.commit_url) ghLinks.push(`<a href="${escapeHtml(problem.commit_url)}" target="_blank" class="gh-link">Base Commit</a>`); |
| | if (ghLinks.length > 0) { |
| | html += `<div class="card gh-links-bar">${ghLinks.join('')}</div>`; |
| | } |
| |
|
| | |
| | const metaBadges = []; |
| | if (problem.version) metaBadges.push(`<span class="badge badge-info">v${escapeHtml(problem.version)}</span>`); |
| | if (problem.created_at) metaBadges.push(`<span class="badge badge-info">${escapeHtml(problem.created_at.split('T')[0])}</span>`); |
| | if (problem.commit_hash) metaBadges.push(`<span class="badge badge-info">${escapeHtml(problem.commit_hash.substring(0, 12))}</span>`); |
| | if (problem.diff_languages) metaBadges.push(`<span class="badge badge-info">${escapeHtml(problem.diff_languages)}</span>`); |
| | if (metaBadges.length > 0) { |
| | html += `<div style="margin-bottom: 15px;">${metaBadges.join(' ')}</div>`; |
| | } |
| |
|
| | |
| | if (problem.description) { |
| | html += ` |
| | <div class="card"> |
| | <h2>${problem.issue_url ? 'Issue Description' : 'Description'}</h2> |
| | <div class="issue-statement">${escapeHtml(problem.description)}</div> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | html += renderDiffFiles(problem.patch, 'Solution Patch'); |
| |
|
| | |
| | if (problem.test_patch) { |
| | html += renderDiffFiles(problem.test_patch, 'Test Patch'); |
| | } |
| |
|
| | |
| | if (problem.fail_to_pass && problem.fail_to_pass.length > 0) { |
| | html += `<div class="card"><h2>Tests: Fail → Pass</h2><ul class="test-id-list">`; |
| | problem.fail_to_pass.forEach(t => { |
| | html += `<li>${escapeHtml(t)}</li>`; |
| | }); |
| | html += `</ul></div>`; |
| | } |
| |
|
| | |
| | if (problem.hints) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Hints</h2> |
| | <div class="issue-statement">${escapeHtml(problem.hints)}</div> |
| | </div> |
| | `; |
| | } |
| |
|
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | return; |
| | } |
| |
|
| | |
| | if (problem.fim_ground_truth !== undefined) { |
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Fill-in-the-Middle</h2> |
| | <div class="lang-tabs" id="fim-tabs"> |
| | <button class="lang-tab" onclick="showFimTab(0)">With Gap</button> |
| | <button class="lang-tab active" onclick="showFimTab(1)">Completed</button> |
| | <button class="lang-tab" onclick="showFimTab(2)">Completion Only</button> |
| | </div> |
| | <div class="lang-code-panel" id="fim-panel-0"> |
| | <div class="code-with-tasks">${problem.highlighted_code}</div> |
| | </div> |
| | <div class="lang-code-panel active" id="fim-panel-1"> |
| | <div class="fim-merged-legend"> |
| | <span class="coverage-swatch" style="background:#ffffcc; border:1px solid #ccc;"></span> |
| | Inserted completion (lines ${problem.fim_gt_start_line}–${problem.fim_gt_end_line}) |
| | </div> |
| | <div class="code-with-tasks">${problem.fim_merged_highlighted}</div> |
| | </div> |
| | <div class="lang-code-panel" id="fim-panel-2"> |
| | <div class="fim-answer">${problem.fim_ground_truth_highlighted || escapeHtml(problem.fim_ground_truth)}</div> |
| | </div> |
| | </div> |
| | `; |
| |
|
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | return; |
| | } |
| |
|
| | |
| | if (problem.vulnerable_code !== undefined || problem.is_vulnerable !== undefined) { |
| | |
| | const isVuln = problem.is_vulnerable; |
| | html += ` |
| | <div class="card"> |
| | <h2>Vulnerability Information</h2> |
| | <div style="margin-bottom: 10px;"> |
| | <span class="vuln-status ${isVuln ? 'vuln-status-vulnerable' : 'vuln-status-patched'}"> |
| | ${isVuln ? 'Vulnerable' : 'Patched'} |
| | </span> |
| | ${problem.cwe_id ? `<span class="cwe-badge">${escapeHtml(problem.cwe_id)}</span>` : ''} |
| | ${problem.cve_id ? `<span class="badge badge-info">${escapeHtml(problem.cve_id)}</span>` : ''} |
| | ${problem.project ? `<span class="badge badge-info">${escapeHtml(problem.project)}</span>` : ''} |
| | </div> |
| | ${problem.description ? `<p style="margin-top: 10px; color: #555;">${escapeHtml(problem.description).substring(0, 500)}</p>` : ''} |
| | </div> |
| | `; |
| |
|
| | |
| | if (problem.vulnerable_code && problem.patched_code) { |
| | const vulnDiff = computeUnifiedDiff(problem.vulnerable_code, problem.patched_code); |
| | html += ` |
| | <div class="card"> |
| | <h2>Changes</h2> |
| | <div class="diff-view">${renderComputedDiff(vulnDiff)}</div> |
| | </div> |
| | `; |
| | html += ` |
| | <div class="card"> |
| | <h2>Full Code Comparison</h2> |
| | <div class="diff-container"> |
| | <div class="diff-panel"> |
| | <h3><span class="diff-label-buggy">Vulnerable</span></h3> |
| | <div class="code-with-tasks">${problem.vulnerable_highlighted_code}</div> |
| | </div> |
| | <div class="diff-panel"> |
| | <h3><span class="diff-label-fixed">Patched</span></h3> |
| | <div class="code-with-tasks">${problem.patched_highlighted_code}</div> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| | } else { |
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Source Code</h2> |
| | <div class="code-with-tasks">${problem.highlighted_code}</div> |
| | </div> |
| | `; |
| | } |
| |
|
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | return; |
| | } |
| |
|
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Source Code</h2> |
| | <div class="code-with-tasks" id="code-container"> |
| | ${problem.highlighted_code} |
| | </div> |
| | </div> |
| | `; |
| |
|
| | |
| | if (!hasTasks) { |
| | |
| | const simpleLinks = []; |
| | if (problem.repo_url) simpleLinks.push(`<a href="${escapeHtml(problem.repo_url)}" target="_blank" class="gh-link">Repository</a>`); |
| | if (problem.issue_url) simpleLinks.push(`<a href="${escapeHtml(problem.issue_url)}" target="_blank" class="gh-link">Issue</a>`); |
| | if (problem.commit_url) simpleLinks.push(`<a href="${escapeHtml(problem.commit_url)}" target="_blank" class="gh-link">Commit</a>`); |
| | if (simpleLinks.length > 0) { |
| | html += `<div class="card gh-links-bar">${simpleLinks.join('')}</div>`; |
| | } |
| |
|
| | |
| | if (problem.description) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Problem Description</h2> |
| | <div style="padding: 10px; line-height: 1.6; white-space: pre-wrap;">${escapeHtml(problem.description)}</div> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | if (problem.difficulty || problem.contest_date || problem.tags || problem.cf_rating) { |
| | let metaHtml = ''; |
| | if (problem.difficulty) { |
| | metaHtml += `<span class="badge badge-info">Difficulty: ${escapeHtml(problem.difficulty)}</span>`; |
| | } |
| | if (problem.cf_rating) { |
| | metaHtml += `<span class="badge badge-info">Rating: ${problem.cf_rating}</span>`; |
| | } |
| | if (problem.contest_date) { |
| | metaHtml += `<span class="badge badge-info">Date: ${escapeHtml(problem.contest_date.split('T')[0])}</span>`; |
| | } |
| | if (problem.tags && problem.tags.length > 0) { |
| | problem.tags.forEach(tag => { |
| | metaHtml += `<span class="badge badge-info">${escapeHtml(tag)}</span>`; |
| | }); |
| | } |
| | html += `<div style="margin-bottom: 15px;">${metaHtml}</div>`; |
| | } |
| |
|
| | |
| | if (problem.inputs && problem.inputs.length > 0) { |
| | html += `<div class="card"><h2>Inputs & Outputs</h2>`; |
| | problem.inputs.forEach((inp, i) => { |
| | const out = (problem.outputs && problem.outputs[i]) || ''; |
| | html += ` |
| | <div class="io-section" style="margin-bottom: 15px;"> |
| | <div class="task-section"> |
| | <h3>Input ${i + 1}</h3> |
| | <pre class="code-block">${escapeHtml(inp)}</pre> |
| | </div> |
| | <div class="task-section"> |
| | <h3>Output</h3> |
| | <pre class="code-block">${escapeHtml(out)}</pre> |
| | </div> |
| | </div> |
| | `; |
| | }); |
| | html += `</div>`; |
| | } |
| |
|
| | |
| | if (problem.test) { |
| | html += ` |
| | <div class="card"> |
| | <h2>Test Suite</h2> |
| | <pre class="code-block">${escapeHtml(problem.test)}</pre> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | return; |
| | } |
| |
|
| | |
| | if (problem.tasks.length > 0 && problem.tasks[0].given !== undefined) { |
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Tasks</h2> |
| | <div class="task-selector" id="task-selector"> |
| | `; |
| | problem.tasks.forEach((task, idx) => { |
| | html += ` |
| | <button class="task-btn ${idx === 0 ? 'active' : ''}" |
| | onclick="showCruxTask(${idx})"> |
| | ${escapeHtml(task.name)} |
| | </button> |
| | `; |
| | }); |
| | html += ` |
| | </div> |
| | <div id="task-content"></div> |
| | </div> |
| | `; |
| |
|
| | |
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| | window.currentProblem = problem; |
| | showCruxTask(0); |
| | return; |
| | } |
| |
|
| | |
| | |
| | html = ` |
| | <div class="card"> |
| | <div class="problem-header"> |
| | <h2>${escapeHtml(problem.entry_point)}</h2> |
| | <span class="badge ${badgeClass(problem.source)}">${escapeHtml(problem.source)}</span> |
| | </div> |
| | <div class="problem-meta"> |
| | <div class="meta-item"> |
| | <span class="meta-label">Task ID:</span> |
| | <span class="meta-value">${escapeHtml(problem.task_id)}</span> |
| | </div> |
| | <div class="meta-item"> |
| | <span class="meta-label">Index:</span> |
| | <span class="meta-value">${problem.idx}</span> |
| | </div> |
| | <div class="meta-item"> |
| | <span class="meta-label">Dataset:</span> |
| | <span class="meta-value">${escapeHtml(datasetName)}</span> |
| | </div> |
| | <div class="meta-item"> |
| | <span class="meta-label">Test Inputs:</span> |
| | <span class="meta-value">${problem.inputs.length}</span> |
| | </div> |
| | </div> |
| | </div> |
| | |
| | <div class="card"> |
| | <h2>Source Code</h2> |
| | <div class="coverage-legend" id="coverage-legend"> |
| | <strong>Coverage:</strong> |
| | <span class="coverage-legend-item"> |
| | <span class="coverage-swatch" style="background:#d4edda; border:1px solid #28a745;"></span> |
| | Executed |
| | </span> |
| | <span class="coverage-legend-item"> |
| | <span class="coverage-swatch" style="background:#f8d7da; border:1px solid #dc3545;"></span> |
| | Not executed |
| | </span> |
| | </div> |
| | <div class="code-with-tasks" id="code-container"> |
| | ${problem.highlighted_code} |
| | <svg id="arrow-overlay" xmlns="http://www.w3.org/2000/svg"> |
| | <defs> |
| | <marker id="arrowhead" markerWidth="8" markerHeight="6" |
| | refX="8" refY="3" orient="auto"> |
| | <polygon points="0 0, 8 3, 0 6" class="exec-arrow-head"/> |
| | </marker> |
| | </defs> |
| | </svg> |
| | </div> |
| | </div> |
| | `; |
| |
|
| | |
| | html += ` |
| | <div class="card"> |
| | <h2>Test Cases & Tasks</h2> |
| | <p>Select a test input to view associated reasoning tasks:</p> |
| | <div class="task-selector" id="task-selector"> |
| | `; |
| |
|
| | problem.tasks.forEach((task, idx) => { |
| | html += ` |
| | <button class="task-btn ${idx === 0 ? 'active' : ''}" |
| | onclick="showTask(${idx})"> |
| | Input ${task.input_idx + 1} |
| | </button> |
| | `; |
| | }); |
| |
|
| | html += ` |
| | </div> |
| | <div id="task-content"></div> |
| | </div> |
| | `; |
| |
|
| | |
| | html += ` |
| | <div class="navigation-hint"> |
| | <strong>Tip:</strong> Use the Previous/Next buttons at the top to browse through problems, |
| | or return to the list view to filter by dataset source or search by name. |
| | </div> |
| | `; |
| |
|
| | container.innerHTML = html; |
| |
|
| | |
| | window.currentProblem = problem; |
| |
|
| | |
| | showTask(0); |
| | } |
| |
|
| | function injectTaskMarkers(taskItems) { |
| | const codePre = document.querySelector('.source .code pre'); |
| |
|
| | |
| | if (codePre && !window._codePreOriginalHtml) { |
| | window._codePreOriginalHtml = codePre.innerHTML; |
| | } |
| |
|
| | |
| | window._linenoSpanCache = null; |
| |
|
| | |
| | window._currentTaskItems = taskItems || []; |
| |
|
| | |
| | if (codePre && window._codePreOriginalHtml) { |
| | codePre.innerHTML = window._codePreOriginalHtml; |
| | } |
| |
|
| | if (!taskItems || taskItems.length === 0) { |
| | return; |
| | } |
| |
|
| | |
| | const tasksByLine = {}; |
| | taskItems.forEach(item => { |
| | if (!tasksByLine[item.lineno]) tasksByLine[item.lineno] = []; |
| | tasksByLine[item.lineno].push(item.var); |
| | }); |
| |
|
| | |
| | if (!codePre) return; |
| | const codeLines = codePre.innerHTML.split('\n'); |
| | codePre.innerHTML = codeLines.map((line, idx) => { |
| | const lineNum = idx + 1; |
| | if (tasksByLine[lineNum] && line.trim() !== '') { |
| | const vars = tasksByLine[lineNum]; |
| | return line + `<span class="task-marker" data-lineno="${lineNum}" data-vars="${escapeHtml(vars.join(', '))}">${escapeHtml(vars.join(', '))}</span>`; |
| | } |
| | return line; |
| | }).join('\n'); |
| |
|
| | } |
| |
|
| | function applyCoverage(coverageSet, totalLines) { |
| | |
| | document.querySelectorAll('td.linenos .normal').forEach(el => { |
| | el.classList.remove('line-executed', 'line-not-executed'); |
| | }); |
| |
|
| | if (!coverageSet) { |
| | const legend = document.getElementById('coverage-legend'); |
| | if (legend) legend.style.display = 'none'; |
| | return; |
| | } |
| |
|
| | const legend = document.getElementById('coverage-legend'); |
| | if (legend) legend.style.display = 'block'; |
| |
|
| | |
| | document.querySelectorAll('td.linenos .normal').forEach(span => { |
| | const lineNum = parseInt(span.textContent.trim()); |
| | if (!isNaN(lineNum) && lineNum <= totalLines) { |
| | span.classList.add(coverageSet.has(lineNum) ? 'line-executed' : 'line-not-executed'); |
| | } |
| | }); |
| | } |
| |
|
| | |
| | window._nextLinesMap = {}; |
| |
|
| | async function loadAndApplyGroundTruth(problemIdx, inputIdx, taskItems) { |
| | |
| | taskItems.forEach(item => { |
| | const el = document.getElementById(`gt-${item.lineno}-${item.var}`); |
| | if (el) { el.textContent = '…'; el.className = 'gt-answer loading'; } |
| | }); |
| |
|
| | |
| | window._nextLinesMap = {}; |
| |
|
| | try { |
| | const resp = await fetch(`/api/${datasetSlug}/problem/${problemIdx}/ground_truth/${inputIdx}`); |
| | const gt = await resp.json(); |
| |
|
| | if (gt.status !== 'ok') { |
| | taskItems.forEach(item => { |
| | const el = document.getElementById(`gt-${item.lineno}-${item.var}`); |
| | if (el) { el.textContent = gt.status === 'error' ? '(exec error)' : '(unavailable)'; el.className = 'gt-answer'; } |
| | }); |
| | applyCoverage(null, 0); |
| | return; |
| | } |
| |
|
| | |
| | const coverageSet = new Set(gt.coverage); |
| | applyCoverage(coverageSet, gt.total_lines); |
| |
|
| | |
| | const answerMap = {}; |
| | gt.variable_answers.forEach(a => { |
| | answerMap[`${a.lineno}-${a.var}`] = a.answer_str; |
| | }); |
| | taskItems.forEach(item => { |
| | const el = document.getElementById(`gt-${item.lineno}-${item.var}`); |
| | if (el) { |
| | const answer = answerMap[`${item.lineno}-${item.var}`] || '(not available)'; |
| | el.textContent = answer; |
| | el.className = 'gt-answer'; |
| | } |
| | }); |
| |
|
| | |
| | if (gt.next_lines_answers) { |
| | gt.next_lines_answers.forEach(a => { |
| | window._nextLinesMap[a.lineno] = a.next_lines; |
| | }); |
| | } |
| |
|
| | |
| | attachArrowHoverHandlers(); |
| |
|
| | } catch (e) { |
| | taskItems.forEach(item => { |
| | const el = document.getElementById(`gt-${item.lineno}-${item.var}`); |
| | if (el) { el.textContent = '(error)'; el.className = 'gt-answer'; } |
| | }); |
| | } |
| | } |
| |
|
| | |
| | window._linenoSpanCache = null; |
| |
|
| | function buildLinenoSpanCache(container) { |
| | const cache = {}; |
| | container.querySelectorAll('td.linenos .normal').forEach(span => { |
| | const n = parseInt(span.textContent.trim()); |
| | if (!isNaN(n)) cache[n] = span; |
| | }); |
| | window._linenoSpanCache = cache; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | function getLinenoSpanRect(lineNum, container) { |
| | if (!window._linenoSpanCache) buildLinenoSpanCache(container); |
| | const span = window._linenoSpanCache[lineNum]; |
| | if (!span) return null; |
| | const spanRect = span.getBoundingClientRect(); |
| | const containerRect = container.getBoundingClientRect(); |
| | return { |
| | top: spanRect.top - containerRect.top + container.scrollTop, |
| | bottom: spanRect.bottom - containerRect.top + container.scrollTop, |
| | left: spanRect.left - containerRect.left, |
| | right: spanRect.right - containerRect.left, |
| | width: spanRect.width, |
| | height: spanRect.height, |
| | midY: (spanRect.top + spanRect.bottom) / 2 - containerRect.top + container.scrollTop, |
| | }; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | function drawArrows(sourceLineNum, targetLineNums) { |
| | const container = document.getElementById('code-container'); |
| | const svg = document.getElementById('arrow-overlay'); |
| | if (!container || !svg) return; |
| |
|
| | |
| | svg.querySelectorAll('.arrow-path').forEach(el => el.remove()); |
| |
|
| | const srcRect = getLinenoSpanRect(sourceLineNum, container); |
| | if (!srcRect) return; |
| |
|
| | |
| | svg.setAttribute('height', container.scrollHeight); |
| |
|
| | targetLineNums.forEach(targetLineNum => { |
| | if (targetLineNum === -1) return; |
| |
|
| | const dstRect = getLinenoSpanRect(targetLineNum, container); |
| | if (!dstRect) return; |
| |
|
| | |
| | const x1 = srcRect.right + 2; |
| | const y1 = srcRect.midY; |
| |
|
| | |
| | const x2 = dstRect.right + 2; |
| | const y2 = dstRect.midY; |
| |
|
| | |
| | const curveOffset = Math.max(30, Math.abs(y2 - y1) * 0.4); |
| |
|
| | |
| | const cx1 = x1 + curveOffset; |
| | const cy1 = y1; |
| | const cx2 = x2 + curveOffset; |
| | const cy2 = y2; |
| |
|
| | const path = document.createElementNS('http://www.w3.org/2000/svg', 'path'); |
| | path.setAttribute('d', `M ${x1} ${y1} C ${cx1} ${cy1}, ${cx2} ${cy2}, ${x2} ${y2}`); |
| | path.setAttribute('class', 'exec-arrow arrow-path'); |
| | path.setAttribute('marker-end', 'url(#arrowhead)'); |
| | svg.appendChild(path); |
| | }); |
| | } |
| |
|
| | |
| | |
| | |
| | function clearArrows() { |
| | const svg = document.getElementById('arrow-overlay'); |
| | if (svg) { |
| | svg.querySelectorAll('.arrow-path').forEach(el => el.remove()); |
| | } |
| | } |
| |
|
| | |
| | let _markerListenersAbort = null; |
| |
|
| | |
| | |
| | |
| | |
| | function attachArrowHoverHandlers() { |
| | |
| | if (_markerListenersAbort) _markerListenersAbort.abort(); |
| | _markerListenersAbort = new AbortController(); |
| | const { signal } = _markerListenersAbort; |
| |
|
| | document.querySelectorAll('.task-marker').forEach(marker => { |
| | marker.addEventListener('mouseenter', () => { |
| | const lineNum = parseInt(marker.dataset.lineno); |
| | if (!lineNum) return; |
| | const nextLines = window._nextLinesMap[lineNum]; |
| | if (nextLines && nextLines.length > 0) { |
| | drawArrows(lineNum, nextLines); |
| | } |
| | }, { signal }); |
| |
|
| | marker.addEventListener('mouseleave', () => { |
| | clearArrows(); |
| | }, { signal }); |
| | }); |
| | } |
| |
|
| | function showCruxTask(taskIdx) { |
| | const problem = window.currentProblem; |
| | const task = problem.tasks[taskIdx]; |
| |
|
| | |
| | document.querySelectorAll('.task-btn').forEach((btn, idx) => { |
| | btn.classList.toggle('active', idx === taskIdx); |
| | }); |
| |
|
| | const givenLabel = task.given === 'input' ? 'Input (given)' : 'Output (given)'; |
| | const predictLabel = task.predict === 'output' ? 'Output (predict)' : 'Input (predict)'; |
| | const givenValue = task.given === 'input' ? task.input : task.output; |
| | const predictValue = task.predict === 'output' ? task.output : task.input; |
| |
|
| | const html = ` |
| | <div class="task-details"> |
| | <div class="task-section"> |
| | <p style="margin-bottom: 12px; color: #7f8c8d;">${escapeHtml(task.description)}</p> |
| | </div> |
| | <div class="io-section"> |
| | <div class="task-section"> |
| | <h3>${escapeHtml(givenLabel)}</h3> |
| | <pre class="code-block">${escapeHtml(givenValue)}</pre> |
| | </div> |
| | <div class="task-section"> |
| | <h3>${escapeHtml(predictLabel)}</h3> |
| | <pre class="code-block crux-answer">${escapeHtml(predictValue)}</pre> |
| | </div> |
| | </div> |
| | </div> |
| | `; |
| |
|
| | document.getElementById('task-content').innerHTML = html; |
| | } |
| |
|
| | function showTask(taskIdx) { |
| | const problem = window.currentProblem; |
| | const task = problem.tasks[taskIdx]; |
| |
|
| | |
| | const buttons = document.querySelectorAll('.task-btn'); |
| | buttons.forEach((btn, idx) => { |
| | if (idx === taskIdx) { |
| | btn.classList.add('active'); |
| | } else { |
| | btn.classList.remove('active'); |
| | } |
| | }); |
| |
|
| | |
| | injectTaskMarkers(task.task_items); |
| |
|
| | |
| | applyCoverage(null, 0); |
| |
|
| | |
| | const ioSection = task.test_class_code |
| | ? `<div class="io-section"> |
| | <div class="task-section"> |
| | <h3>Input</h3> |
| | <pre class="code-block">${escapeHtml(task.input)}</pre> |
| | </div> |
| | </div> |
| | <div class="task-section"> |
| | <h3>Test Class — <code>${escapeHtml(task.test_class_name)}</code></h3> |
| | <pre class="code-block">${escapeHtml(task.test_class_code)}</pre> |
| | </div>` |
| | : `<div class="io-section"> |
| | <div class="task-section"> |
| | <h3>Input</h3> |
| | <pre class="code-block">${escapeHtml(task.input)}</pre> |
| | </div> |
| | <div class="task-section"> |
| | <h3>Expected Output</h3> |
| | <pre class="code-block">${escapeHtml(task.output)}</pre> |
| | </div> |
| | </div>`; |
| |
|
| | let html = ` |
| | <div class="task-details"> |
| | ${ioSection} |
| | `; |
| |
|
| | |
| | if (task.task_items && task.task_items.length > 0) { |
| | html += ` |
| | <div class="task-section"> |
| | <h3>Reasoning Tasks</h3> |
| | <p style="margin-bottom: 10px; color: #7f8c8d;"> |
| | Variable state at each execution point (correct answer shown in |
| | <span style="background:#17a2b8;color:white;padding:1px 6px;border-radius:3px;font-size:0.82rem;">teal</span>): |
| | </p> |
| | <ul class="task-items-list"> |
| | `; |
| |
|
| | task.task_items.forEach(item => { |
| | html += ` |
| | <li> |
| | <span class="line-ref">Line ${item.lineno}</span> |
| | <span class="var-name">${escapeHtml(item.var)}</span> |
| | <span class="gt-answer loading" id="gt-${item.lineno}-${item.var}">…</span> |
| | </li> |
| | `; |
| | }); |
| |
|
| | html += ` |
| | </ul> |
| | </div> |
| | `; |
| | } |
| |
|
| | |
| | if (task.output_pred) { |
| | html += ` |
| | <div class="task-section"> |
| | <h3>Output Completion Task</h3> |
| | <p style="margin-bottom: 10px; color: #7f8c8d;"> |
| | The model needs to complete this test assertion: |
| | </p> |
| | <pre class="code-block">${escapeHtml(task.output_pred)}</pre> |
| | </div> |
| | `; |
| | } |
| |
|
| | html += `</div>`; |
| |
|
| | document.getElementById('task-content').innerHTML = html; |
| |
|
| | |
| | if (hasGroundTruth && task.task_items) { |
| | loadAndApplyGroundTruth(problem.idx, task.input_idx, task.task_items); |
| | } |
| | } |
| |
|
| | function showLangTab(idx) { |
| | |
| | const langTabsContainer = document.getElementById('lang-tabs'); |
| | if (langTabsContainer) { |
| | langTabsContainer.querySelectorAll('.lang-tab').forEach((tab, i) => { |
| | tab.classList.toggle('active', i === idx); |
| | }); |
| | } |
| | document.querySelectorAll('.lang-code-panel').forEach((panel, i) => { |
| | panel.classList.toggle('active', i === idx); |
| | }); |
| | |
| | if (window._currentCodeMode) { |
| | const problem = window.currentProblem; |
| | if (problem && problem.lang_solutions) { |
| | const sol = problem.lang_solutions[idx]; |
| | if (sol) { |
| | const canonical = document.getElementById('lang-code-canonical-' + idx); |
| | const buggy = document.getElementById('lang-code-buggy-' + idx); |
| | if (canonical) canonical.style.display = window._currentCodeMode === 'canonical' ? '' : 'none'; |
| | if (buggy) buggy.style.display = window._currentCodeMode === 'buggy' ? '' : 'none'; |
| | |
| | if (window._currentCodeMode === 'buggy' && !buggy && canonical) { |
| | canonical.style.display = ''; |
| | } |
| | } |
| | } |
| | } |
| | |
| | const problem = window.currentProblem; |
| | if (problem && problem.lang_solutions) { |
| | const sol = problem.lang_solutions[idx]; |
| | const testContainer = document.getElementById('lang-test-container'); |
| | if (testContainer && sol && sol.test) { |
| | testContainer.innerHTML = `<h2>Test Suite</h2><pre class="code-block">${escapeHtml(sol.test)}</pre>`; |
| | } else if (testContainer) { |
| | testContainer.innerHTML = ''; |
| | } |
| | } |
| | } |
| |
|
| | function toggleCodeMode(mode) { |
| | window._currentCodeMode = mode; |
| | const problem = window.currentProblem; |
| | if (!problem || !problem.lang_solutions) return; |
| |
|
| | |
| | const modeTabs = document.querySelectorAll('#code-mode-tabs .lang-tab'); |
| | modeTabs.forEach(tab => { |
| | tab.classList.toggle('active', tab.textContent.trim().toLowerCase() === mode); |
| | }); |
| |
|
| | |
| | problem.lang_solutions.forEach((sol, i) => { |
| | const canonical = document.getElementById('lang-code-canonical-' + i); |
| | const buggy = document.getElementById('lang-code-buggy-' + i); |
| | if (canonical) canonical.style.display = mode === 'canonical' ? '' : 'none'; |
| | if (buggy) buggy.style.display = mode === 'buggy' ? '' : 'none'; |
| | }); |
| | } |
| |
|
| | function showFimTab(idx) { |
| | const tabs = document.querySelectorAll('#fim-tabs .lang-tab'); |
| | tabs.forEach((tab, i) => tab.classList.toggle('active', i === idx)); |
| | for (let i = 0; i < 3; i++) { |
| | const panel = document.getElementById('fim-panel-' + i); |
| | if (panel) panel.classList.toggle('active', i === idx); |
| | } |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | function renderDiffFiles(diffText, title) { |
| | if (!diffText) return ''; |
| | |
| | const files = []; |
| | let current = null; |
| | diffText.split('\n').forEach(line => { |
| | if (line.startsWith('diff --git')) { |
| | if (current) files.push(current); |
| | |
| | const m = line.match(/^diff --git a\/(.+?) b\/(.+)/); |
| | const filePath = m ? m[2] : line; |
| | current = { path: filePath, lines: [line] }; |
| | } else if (current) { |
| | current.lines.push(line); |
| | } else { |
| | |
| | if (!current) current = { path: '', lines: [] }; |
| | current.lines.push(line); |
| | } |
| | }); |
| | if (current) files.push(current); |
| |
|
| | if (files.length === 0) return ''; |
| |
|
| | let html = ''; |
| | if (files.length === 1 && !files[0].path) { |
| | |
| | html += `<div class="card"><h2>${escapeHtml(title)}</h2><div class="diff-view">${renderDiff(diffText)}</div></div>`; |
| | } else { |
| | html += `<div class="card"><h2>${escapeHtml(title)}</h2>`; |
| | files.forEach(file => { |
| | const diffChunk = file.lines.join('\n'); |
| | |
| | let adds = 0, dels = 0; |
| | file.lines.forEach(l => { |
| | if (l.startsWith('+') && !l.startsWith('+++')) adds++; |
| | if (l.startsWith('-') && !l.startsWith('---')) dels++; |
| | }); |
| | const statsHtml = `<span class="diff-file-stats"><span class="diff-stat-add">+${adds}</span> <span class="diff-stat-del">-${dels}</span></span>`; |
| | html += ` |
| | <div class="diff-file-section"> |
| | <div class="diff-file-header"> |
| | <span class="diff-file-path">${escapeHtml(file.path)}</span> |
| | ${statsHtml} |
| | </div> |
| | <div class="diff-view">${renderDiff(diffChunk)}</div> |
| | </div> |
| | `; |
| | }); |
| | html += `</div>`; |
| | } |
| | return html; |
| | } |
| |
|
| | |
| | |
| | |
| | function renderDiff(diffText) { |
| | if (!diffText) return ''; |
| | const lines = diffText.split('\n'); |
| | let oldLine = 0, newLine = 0; |
| | const rows = []; |
| |
|
| | lines.forEach(line => { |
| | if (line.startsWith('diff ')) { |
| | rows.push(`<tr class="diff-tr-header"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-header">${escapeHtml(line)}</td></tr>`); |
| | return; |
| | } |
| | if (line.startsWith('---') || line.startsWith('+++')) { |
| | rows.push(`<tr class="diff-tr-header"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-header">${escapeHtml(line)}</td></tr>`); |
| | return; |
| | } |
| | if (line.startsWith('@@')) { |
| | |
| | const m = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/); |
| | if (m) { |
| | oldLine = parseInt(m[1]); |
| | newLine = parseInt(m[2]); |
| | } |
| | rows.push(`<tr class="diff-tr-hunk"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-hunk">${escapeHtml(line)}</td></tr>`); |
| | return; |
| | } |
| | if (line.startsWith('+')) { |
| | rows.push(`<tr class="diff-tr-add"><td class="diff-ln"></td><td class="diff-ln">${newLine}</td><td class="diff-td-add">${escapeHtml(line.substring(1))}</td></tr>`); |
| | newLine++; |
| | } else if (line.startsWith('-')) { |
| | rows.push(`<tr class="diff-tr-del"><td class="diff-ln">${oldLine}</td><td class="diff-ln"></td><td class="diff-td-del">${escapeHtml(line.substring(1))}</td></tr>`); |
| | oldLine++; |
| | } else if (line.startsWith(' ')) { |
| | rows.push(`<tr class="diff-tr-ctx"><td class="diff-ln">${oldLine}</td><td class="diff-ln">${newLine}</td><td class="diff-td-ctx">${escapeHtml(line.substring(1))}</td></tr>`); |
| | oldLine++; |
| | newLine++; |
| | } else if (line.trim() === '') { |
| | |
| | } else { |
| | rows.push(`<tr class="diff-tr-ctx"><td class="diff-ln">${oldLine}</td><td class="diff-ln">${newLine}</td><td class="diff-td-ctx">${escapeHtml(line)}</td></tr>`); |
| | oldLine++; |
| | newLine++; |
| | } |
| | }); |
| |
|
| | return `<table class="diff-table">${rows.join('')}</table>`; |
| | } |
| |
|
| | |
| | |
| | |
| | |
| | function computeUnifiedDiff(oldText, newText) { |
| | const oldLines = (oldText || '').split('\n'); |
| | const newLines = (newText || '').split('\n'); |
| |
|
| | |
| | const m = oldLines.length, n = newLines.length; |
| | |
| | if (m * n > 500000) { |
| | const result = []; |
| | oldLines.forEach(l => result.push({type: 'del', line: l})); |
| | newLines.forEach(l => result.push({type: 'add', line: l})); |
| | return result; |
| | } |
| |
|
| | const dp = Array.from({length: m + 1}, () => new Uint16Array(n + 1)); |
| | for (let i = 1; i <= m; i++) { |
| | for (let j = 1; j <= n; j++) { |
| | if (oldLines[i - 1] === newLines[j - 1]) { |
| | dp[i][j] = dp[i - 1][j - 1] + 1; |
| | } else { |
| | dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); |
| | } |
| | } |
| | } |
| |
|
| | |
| | const result = []; |
| | let i = m, j = n; |
| | while (i > 0 || j > 0) { |
| | if (i > 0 && j > 0 && oldLines[i - 1] === newLines[j - 1]) { |
| | result.push({type: 'context', line: oldLines[i - 1]}); |
| | i--; j--; |
| | } else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) { |
| | result.push({type: 'add', line: newLines[j - 1]}); |
| | j--; |
| | } else { |
| | result.push({type: 'del', line: oldLines[i - 1]}); |
| | i--; |
| | } |
| | } |
| | result.reverse(); |
| |
|
| | |
| | const contextSize = 3; |
| | const hasChange = result.map(r => r.type !== 'context'); |
| | const show = new Uint8Array(result.length); |
| | for (let k = 0; k < result.length; k++) { |
| | if (hasChange[k]) { |
| | for (let c = Math.max(0, k - contextSize); c <= Math.min(result.length - 1, k + contextSize); c++) { |
| | show[c] = 1; |
| | } |
| | } |
| | } |
| |
|
| | const compacted = []; |
| | let lastShown = -1; |
| | for (let k = 0; k < result.length; k++) { |
| | if (show[k]) { |
| | if (lastShown >= 0 && k - lastShown > 1) { |
| | compacted.push({type: 'separator', line: '...'}); |
| | } |
| | compacted.push(result[k]); |
| | lastShown = k; |
| | } |
| | } |
| |
|
| | return compacted.length > 0 ? compacted : result; |
| | } |
| |
|
| | |
| | |
| | |
| | function renderComputedDiff(diffEntries) { |
| | let oldLine = 1, newLine = 1; |
| | const rows = diffEntries.map(entry => { |
| | if (entry.type === 'separator') { |
| | return `<tr class="diff-tr-hunk"><td class="diff-ln"></td><td class="diff-ln"></td><td class="diff-td-hunk">${escapeHtml(entry.line)}</td></tr>`; |
| | } |
| | if (entry.type === 'del') { |
| | const row = `<tr class="diff-tr-del"><td class="diff-ln">${oldLine}</td><td class="diff-ln"></td><td class="diff-td-del">${escapeHtml(entry.line)}</td></tr>`; |
| | oldLine++; |
| | return row; |
| | } |
| | if (entry.type === 'add') { |
| | const row = `<tr class="diff-tr-add"><td class="diff-ln"></td><td class="diff-ln">${newLine}</td><td class="diff-td-add">${escapeHtml(entry.line)}</td></tr>`; |
| | newLine++; |
| | return row; |
| | } |
| | |
| | const row = `<tr class="diff-tr-ctx"><td class="diff-ln">${oldLine}</td><td class="diff-ln">${newLine}</td><td class="diff-td-ctx">${escapeHtml(entry.line)}</td></tr>`; |
| | oldLine++; |
| | newLine++; |
| | return row; |
| | }); |
| | return `<table class="diff-table">${rows.join('')}</table>`; |
| | } |
| |
|
| | function escapeHtml(text) { |
| | if (text === null || text === undefined) return ''; |
| | const div = document.createElement('div'); |
| | div.textContent = text; |
| | return div.innerHTML; |
| | } |
| |
|
| | loadProblem(); |
| |
|