From c32566979f4517ae6676b65ed5663829996ffe32 Mon Sep 17 00:00:00 2001 From: fen Date: Thu, 17 Sep 2026 17:18:15 -0500 Subject: [PATCH] #260: .swapbtn grid for copy feedback (fix attempt 2) Label and checkmark stack in one grid cell (inline-grid, grid-area 1/1, checkmark visibility-hidden until .ok), so the button width is static by construction across the click. Replaces the min-width pin from fix attempt 1, which enlarged buttons by up to 0.64px (ceil of fractional width) and leaked the pin after restore. Applies to paste-view Copy/Link (paste.html + paste.js copyFeedback) and the /new result-box copy button (new.js). Verified with CDP bounding-box probes: Copy width/x identical before/during/after at 1400x900 and 375x812 (including the previously failing 52.36px mobile case), Link button static, checkmark visible during feedback, no minWidth pin set. --- internal/web/static/app.css | 8 ++++++++ internal/web/static/new.js | 8 ++------ internal/web/static/paste.js | 12 +++--------- internal/web/templates/paste.html | 4 ++-- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/internal/web/static/app.css b/internal/web/static/app.css index a18cc9e..ccd69c6 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -580,6 +580,14 @@ a.admin-link:hover { color: var(--fg); text-decoration: underline; } outline-offset: 1px; } +/* swapbtn (#260): the label and the checkmark occupy the same grid cell, so + toggling feedback never changes the button's width — no pinning needed */ +.swapbtn { display: inline-grid; } +.swapbtn > * { grid-area: 1 / 1; } +.swapbtn .btn-check { visibility: hidden; } +.swapbtn.ok .btn-check { visibility: visible; } +.swapbtn.ok .btn-label { visibility: hidden; } + /* in-place copy success feedback (#53) */ .iconbtn.ok, .btn.ok { color: var(--ok); diff --git a/internal/web/static/new.js b/internal/web/static/new.js index df2c191..c36af83 100644 --- a/internal/web/static/new.js +++ b/internal/web/static/new.js @@ -192,18 +192,14 @@ async function create() { // button, password auto-unlock, then redirect to the paste. function finishCreate(data) { const url = location.origin + '/' + (data.custom_slug || data.id); - showResult('' + url + ' ', 'ok'); + showResult('' + url + ' ', 'ok'); $('result').dataset.token = data.deletion_token || ''; const copyBtn = document.getElementById('result-copy'); copyBtn.addEventListener('click', () => { try { navigator.clipboard.writeText(url); copyBtn.classList.add('ok'); // in-place success feedback (#53) - // #260: pin the pre-swap width so the wider/narrower glyph never - // shifts neighbouring elements; release it when the label restores. - copyBtn.style.minWidth = Math.ceil(copyBtn.getBoundingClientRect().width) + 'px'; - copyBtn.textContent = '✓'; - setTimeout(() => { copyBtn.classList.remove('ok'); copyBtn.textContent = '⧉'; copyBtn.style.minWidth = ''; }, 2000); + setTimeout(() => copyBtn.classList.remove('ok'), 2000); } catch(e) { toast('Copy failed', 'error'); } }); // token carried via sessionStorage, never in the URL (#143) diff --git a/internal/web/static/paste.js b/internal/web/static/paste.js index 365f986..a6d1e73 100644 --- a/internal/web/static/paste.js +++ b/internal/web/static/paste.js @@ -18,17 +18,11 @@ function toggleStats() { } function copyFeedback(btn) { if (!btn) return; - if (!btn.dataset.label) btn.dataset.label = btn.textContent; // remember the original label (Copy/Link) - // #260: keep the button width static during the feedback so surrounding - // elements never jump. Pin the pre-swap width, release it on restore. - if (!btn.dataset.pinned) { - btn.style.minWidth = Math.ceil(btn.getBoundingClientRect().width) + 'px'; - btn.dataset.pinned = '1'; - } + // #260: the .swapbtn grid stacks label + checkmark in one cell, so the + // width is static by construction — feedback is a pure class toggle. btn.classList.add('ok'); - btn.textContent = '✓'; clearTimeout(btn._okh); - btn._okh = setTimeout(() => { btn.classList.remove('ok'); btn.textContent = btn.dataset.label; }, 2000); + btn._okh = setTimeout(() => btn.classList.remove('ok'), 2000); } function copyContent(btn) { navigator.clipboard.writeText(document.getElementById('raw-content').value) diff --git a/internal/web/templates/paste.html b/internal/web/templates/paste.html index f035074..a437fec 100644 --- a/internal/web/templates/paste.html +++ b/internal/web/templates/paste.html @@ -8,8 +8,8 @@
Raw - Link - Copy + Link + Copy {{if .DeletionToken}}Delete{{end}} -- 2.54.0