Fix Preview button in jobs sidebar not opening modal

Root cause: inline onclick with JSON.stringify(title) broke when title
contained quotes, special chars, or was empty. The HTML attribute parser
got confused by mismatched quotes, so click handler never fired.

Fix:
- Replaced inline onclick handlers with data-action attributes
- Added single delegated click listener at document level
- Title stored in element dataset (no HTML quoting issues)
- Added escapeHtml() helper for safe rendering of titles/errors

Now clicking Preview in the right sidebar opens the fullscreen modal
correctly, regardless of filename characters.
This commit is contained in:
Sebastjan Artič 2026-04-29 13:39:04 +00:00
parent 90cdad516b
commit 671b512917

View File

@ -769,19 +769,19 @@
const actions = [];
if (job.status === "done") {
actions.push(`<button class="small" onclick="window.open('/api/download/${job.id}')">⬇ Download</button>`);
actions.push(`<button class="small ghost" onclick="previewJob('${job.id}', ${JSON.stringify(title)})">▶ Preview</button>`);
actions.push(`<button class="small" data-action="download" data-id="${job.id}">⬇ Download</button>`);
actions.push(`<button class="small ghost" data-action="preview" data-id="${job.id}">▶ Preview</button>`);
}
actions.push(`<button class="small ghost" onclick="deleteJob('${job.id}')"></button>`);
actions.push(`<button class="small ghost" data-action="delete" data-id="${job.id}"></button>`);
el.innerHTML = `
<div class="job-head">
<div class="job-title" title="${title}">${title}</div>
<div class="job-title" title="${escapeHtml(title)}">${escapeHtml(title)}</div>
<span class="badge ${job.status}">${statusLabel}</span>
</div>
${job.current_step ? `<div class="step">${job.current_step}</div>` : ""}
${job.current_step ? `<div class="step">${escapeHtml(job.current_step)}</div>` : ""}
${showBar}
${job.error ? `<div class="error-text">⚠ ${job.error}</div>` : ""}
${job.error ? `<div class="error-text">⚠ ${escapeHtml(job.error)}</div>` : ""}
<div class="meta">
<span>${job.source_type === "youtube" ? "YouTube" : "Upload"}</span>
${sizeStr ? `<span>${sizeStr}</span>` : ""}
@ -790,9 +790,40 @@
</div>
<div class="actions">${actions.join("")}</div>
`;
// Shrani naslov za preview modal
el.dataset.title = title;
return el;
}
function escapeHtml(s) {
if (s == null) return "";
return String(s)
.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll('"', "&quot;")
.replaceAll("'", "&#39;");
}
// Globalno delegirano poslušanje za action gumbe (Download / Preview / Delete)
document.addEventListener("click", (e) => {
const btn = e.target.closest("button[data-action]");
if (!btn) return;
const action = btn.dataset.action;
const id = btn.dataset.id;
if (!id) return;
const card = btn.closest(".job");
const title = card?.dataset.title || "";
if (action === "download") {
window.open(`/api/download/${id}`);
} else if (action === "preview") {
previewJob(id, title);
} else if (action === "delete") {
deleteJob(id);
}
});
async function deleteJob(id) {
if (!confirm("Izbrišem ta job?")) return;
await fetch(`/api/jobs/${id}`, { method: "DELETE" });