From f3a40eadbbd1fd3d612baebaac8fbb31039b18ba Mon Sep 17 00:00:00 2001 From: Kosztyk <36381705+Kosztyk@users.noreply.github.com> Date: Mon, 12 Jan 2026 01:03:19 +0200 Subject: [PATCH] Refactor button and link attributes for accessibility Refactor attributes for buttons and links to use aria-disabled and remove busy attributes. Update comments for clarity and maintainability. --- src/pages/results.tsx | 45 +++++++++++++++---------------------------- 1 file changed, 15 insertions(+), 30 deletions(-) diff --git a/src/pages/results.tsx b/src/pages/results.tsx index 3f63d13..4faec61 100644 --- a/src/pages/results.tsx +++ b/src/pages/results.tsx @@ -25,7 +25,6 @@ function ResultsArticle({ files: Filename[]; outputPath: string; }) { - // If the DB schema includes jobs.num_files use it; otherwise fall back to files.length const maxFiles = typeof job.num_files === "number" && Number.isFinite(job.num_files) ? Number(job.num_files) @@ -38,7 +37,6 @@ function ResultsArticle({ const isDone = maxFiles > 0 ? doneFiles >= maxFiles : true; const disabledLinkClass = "pointer-events-none opacity-50"; - const busyAttrs = { disabled: true, "aria-busy": "true" } as const; return (
@@ -51,7 +49,7 @@ function ResultsArticle({ !isDone ? disabledLinkClass : "" }`} href={`${WEBROOT}/delete/${job.id}`} - {...(!isDone ? busyAttrs : {})} + aria-disabled={!isDone ? "true" : undefined} >

Delete

@@ -62,7 +60,7 @@ function ResultsArticle({ }`} href={`${WEBROOT}/archive/${job.id}`} download - {...(!isDone ? busyAttrs : {})} + aria-disabled={!isDone ? "true" : undefined} >

Tar

@@ -71,7 +69,8 @@ function ResultsArticle({ id="cxDownloadAll" type="button" class="flex btn-primary flex-row gap-2 text-contrast" - {...(!isDone ? busyAttrs : {})} + disabled={!isDone ? "true" : undefined} + aria-busy={!isDone ? "true" : undefined} >

All

@@ -215,7 +214,7 @@ function ResultsArticle({ @@ -253,11 +252,6 @@ function ResultsArticle({ ); } -/** - * IMPORTANT FIX: - * Results HTML is re-rendered via /progress/:jobId and replaces the article + modal. - * So we MUST NOT keep stale DOM references. We re-query elements on each action. - */ const shareJs = ` (function () { const WEBROOT = ${JSON.stringify(WEBROOT)}; @@ -316,13 +310,11 @@ const shareJs = ` currentFileName = null; } - // Delegated: always works even after /progress replaces the article document.addEventListener("click", (e) => { const t = e.target; const btn = t && (t.closest ? t.closest('[data-share="true"]') : null); if (!btn) return; - // kill old handlers (e.g. alert() from older results.js) e.preventDefault(); e.stopPropagation(); if (e.stopImmediatePropagation) e.stopImmediatePropagation(); @@ -332,7 +324,6 @@ const shareJs = ` openModal(jobId, fileName); }, true); - // Delegated close (because modal DOM is replaced during progress polling) document.addEventListener("click", (e) => { const id = e.target && e.target.id; if (id === "cxShareClose" || id === "cxShareCancel") { @@ -340,7 +331,6 @@ const shareJs = ` closeModal(); } if (id === "cxShareModal") { - // click outside dialog closes closeModal(); } }, true); @@ -350,7 +340,6 @@ const shareJs = ` if (e.key === "Escape" && r.modal && !r.modal.classList.contains("hidden")) closeModal(); }); - // Delegated copy document.addEventListener("click", async (e) => { const t = e.target; if (!t || t.id !== "cxShareCopy") return; @@ -369,7 +358,6 @@ const shareJs = ` } }, true); - // Delegated submit document.addEventListener("click", async (e) => { const t = e.target; if (!t || t.id !== "cxShareSubmit") return; @@ -413,13 +401,13 @@ const shareJs = ` } const url = - json?.share_url || - json?.share_link || - json?.data?.url || - json?.data?.share?.url || + (json && typeof json === "object" && json.share_url) || + (json && typeof json === "object" && json.share_link) || + (json && typeof json === "object" && json.data && json.data.url) || + (json && typeof json === "object" && json.data && json.data.share && json.data.share.url) || null; - if (url) { + if (url && typeof url === "string") { r.linkEl.value = url; r.linkBlock.classList.remove("hidden"); } @@ -436,7 +424,6 @@ const shareJs = ` } }, true); - // Download All: delegated, because button is replaced during progress polling document.addEventListener("click", (e) => { const t = e.target; const btn = t && (t.closest ? t.closest("#cxDownloadAll") : null); @@ -458,7 +445,6 @@ const shareJs = ` export const results = new Elysia() .use(userService) - .get( "/results-share.js", () => @@ -470,7 +456,6 @@ export const results = new Elysia() }), { auth: true }, ) - .get( "/results/:jobId", async ({ params, set, cookie: { job_id }, user }) => { @@ -506,10 +491,10 @@ export const results = new Elysia() - {/* keep existing file */} + {/* existing file */}