From a4d6df730caf8cb67dbad86efe283dc55765afb0 Mon Sep 17 00:00:00 2001 From: Charlotte Wickham Date: Fri, 7 Aug 2026 14:07:48 -0700 Subject: [PATCH 1/2] Put the full checksum in the DOM, truncate it with CSS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The full SHA-256 lived only in a hover tooltip: keyboard users could not reach it, screen readers never heard it, nobody could copy it easily, and tippy put an invalid aria-expanded on the role-less div (axe aria-allowed-attr, WCAG 4.1.2). The truncated hash also used --bs-primary, which fails WCAG AA contrast for small text. Now the cell's text is the complete hash and only the rendering truncates (CSS ellipsis). The table looks the same as before, but screen readers announce the full value, double-click selects it for copying, and find-in-page matches it — no tooltip, no ARIA, default text color. The SHA-256 column header links to the release's checksums.txt so the full values are also visible at every viewport (WCAG 1.4.10 reflow). Also removes the tippy init for a.checksum, which matched nothing (checksums were divs). --- docs/download/_download.html | 25 ++++++++++--------------- styles.css | 9 +++++++-- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/download/_download.html b/docs/download/_download.html index 6c6ffd69f8..95dd79d7b2 100644 --- a/docs/download/_download.html +++ b/docs/download/_download.html @@ -74,11 +74,6 @@ } - window.tippy("a.checksum[data-tippy-content]", { - interactive: true, - placement: "top-end", - maxWidth: "none", - }); }) .catch((err) => { console.log(err); @@ -217,7 +212,15 @@ } const table = createTable(); - createHeadingRow(table, ["Platform", "Download", "Size", "SHA-256"]); + // Link the header to the release's checksums.txt: the full values stay + // reachable even where narrow screens truncate the hashes (WCAG 1.4.10). + const checksumsAsset = assets.find((asset) => + asset.name.endsWith("-checksums.txt") + ); + const shaHeading = checksumsAsset + ? `SHA-256` + : "SHA-256"; + createHeadingRow(table, ["Platform", "Download", "Size", shaHeading]); const tbody = table.createTBody(); assets.forEach((asset) => { @@ -228,11 +231,6 @@ } downloadTableContainer.append(table); - window.tippy(".checksum[data-tippy-content]", { - interactive: true, - placement: "top-end", - maxWidth: "none", - }); } function createTable() { @@ -287,12 +285,9 @@ const checksumCell = row.insertCell(i++); if (asset.checksum) { - const checkSumSlice = asset.checksum.slice(0, 7); - const checksumEl = window.document.createElement("div"); checksumEl.className = "checksum font-monospace"; - checksumEl.setAttribute("data-tippy-content", asset.checksum); - checksumEl.innerText = checkSumSlice; + checksumEl.innerText = asset.checksum; checksumCell.appendChild(checksumEl); } } diff --git a/styles.css b/styles.css index bd02daf0db..c946f5b0a4 100644 --- a/styles.css +++ b/styles.css @@ -91,11 +91,16 @@ body.quarto-dark #quarto-header { margin-bottom: 0; } +/* The cell holds the full hash; only the rendering truncates (ellipsis). + Screen readers, copying, and find-in-page all see the complete value, + and the header links to checksums.txt for the full values. */ .download-table .checksum { - color: var(--bs-primary); font-size: .775em; - cursor: pointer; padding-top: 4px; + max-width: 9ch; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .download-button { From 110d9f6013bae7a9c0661a7d4fc86b8d9deee629 Mon Sep 17 00:00:00 2001 From: Charlotte Wickham Date: Wed, 23 Sep 2026 15:39:43 -0700 Subject: [PATCH 2/2] Build the checksum header link with DOM APIs, not an HTML string createHeadingRow was assigning cell.outerHTML from a template literal containing an interpolated URL, the only place in this file that builds markup that way instead of createElement + .href/.innerText. Match the existing pattern so header cells are never built from unescaped strings. Also drops the WCAG 1.4.10 citation for the header link in the code comment - the mapping was shaky; the plain rationale stands on its own. --- docs/download/_download.html | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/download/_download.html b/docs/download/_download.html index 95dd79d7b2..f007da377d 100644 --- a/docs/download/_download.html +++ b/docs/download/_download.html @@ -212,14 +212,17 @@ } const table = createTable(); - // Link the header to the release's checksums.txt: the full values stay - // reachable even where narrow screens truncate the hashes (WCAG 1.4.10). + // Link the header to the release's checksums.txt so the full values + // stay reachable even where narrow screens truncate the hashes. const checksumsAsset = assets.find((asset) => asset.name.endsWith("-checksums.txt") ); - const shaHeading = checksumsAsset - ? `SHA-256` - : "SHA-256"; + let shaHeading = "SHA-256"; + if (checksumsAsset) { + shaHeading = window.document.createElement("a"); + shaHeading.href = checksumsAsset.download_url; + shaHeading.innerText = "SHA-256"; + } createHeadingRow(table, ["Platform", "Download", "Size", shaHeading]); const tbody = table.createTBody(); @@ -243,12 +246,14 @@ const header = table.createTHead(); var row = header.insertRow(0); row.className = "header"; - let cellCount = 0; headings.forEach((heading) => { - const cell = (row.insertCell( - cellCount - ).outerHTML = `${heading}`); - cellCount = cellCount + 1; + const cell = window.document.createElement("th"); + if (heading instanceof window.Node) { + cell.appendChild(heading); + } else { + cell.innerText = heading; + } + row.appendChild(cell); }); }