From 57e2d2be666f4daca0b19e4f7230d9130713a198 Mon Sep 17 00:00:00 2001 From: Yuriy Andamasov Date: Wed, 6 May 2026 23:46:53 +0300 Subject: fix(codecopier): guard UI updates against out-of-order async completions Rapid re-clicks queue multiple `navigator.clipboard.writeText` promises; without a request token, an older promise resolving later can overwrite the newer click's UI (or have its 2-second timeout fire on the new click's state). Add a per-button `copyRequestId` that each click increments and captures locally; the success/failure UI flip and the revert-timeout body all bail out early if the captured token no longer matches the current one. Addresses CodeRabbit review feedback on PR #1890. \xf0\x9f\xa4\x96 Generated by [robots](https://vyos.io) --- docs/_static/js/codecopier.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'docs/_static/js/codecopier.js') diff --git a/docs/_static/js/codecopier.js b/docs/_static/js/codecopier.js index 02143b92..4b0941c9 100644 --- a/docs/_static/js/codecopier.js +++ b/docs/_static/js/codecopier.js @@ -54,6 +54,16 @@ $(document).ready(async function () { }) => { const id = currentTarget.dataset.identifier const divWithNeededId = $(`div[data-identifier='${id}']`) + + // Per-click request token. Rapid re-clicks queue multiple writeText + // promises; without this guard, an older promise resolving later + // can overwrite the newer click's UI (or have its timeout fire on + // the new state). Each click increments the token, captures the + // local copy, and bails out of any UI/timeout work if the captured + // token no longer matches the current one. + const requestId = (divWithNeededId.data('copyRequestId') || 0) + 1 + divWithNeededId.data('copyRequestId', requestId) + // Read from the .copyDiv's parentElement (the inner .highlight div it was // injected into via insertAdjacentHTML('beforeend', ...)) rather than // currentTarget.offsetParent — offsetParent depends on CSS positioning @@ -75,17 +85,20 @@ $(document).ready(async function () { try { await navigator.clipboard.writeText(textToCopy) + if (divWithNeededId.data('copyRequestId') !== requestId) return // Only flip the button into the success state when the write actually // resolves — earlier versions showed "Copied!" even on failure. divWithNeededId.addClass('copiedNotifier') divWithNeededId.html('Copied!') } catch (error) { + if (divWithNeededId.data('copyRequestId') !== requestId) return console.error('Copying text failed, please try again', error) divWithNeededId.addClass('copyFailedNotifier') divWithNeededId.html('Failed') } const revertTimeout = setTimeout(() => { + if (divWithNeededId.data('copyRequestId') !== requestId) return divWithNeededId.html(innersOfCopyDiv) divWithNeededId.removeClass('copiedNotifier copyFailedNotifier') divWithNeededId.removeData('revertTimeout') -- cgit v1.2.3