b761b0b0fc3cd9e830db0f11a536e0da29d7c9e5
max
  Wed Sep 9 08:58:30 2026 -0700
A copy-to-clipboard button goes back to its own label after three seconds

Saying "Copied" and then keeping that as the label left no sign that the button
could be used again.  It now says "Copied" for three seconds and then puts back
its own label and icon.  A second copy while the message is up restarts the
three seconds rather than adopting "Copied" as the label to go back to.

The assembly search page carries its own borrowed copy of copyToClipboard and
does not load utils.js, so it gets the same treatment there.

refs #38294

diff --git src/hg/js/assemblySearch.js src/hg/js/assemblySearch.js
index 5f24e802dc9..1f767e4dc78 100644
--- src/hg/js/assemblySearch.js
+++ src/hg/js/assemblySearch.js
@@ -583,34 +583,52 @@
         targetId = ev.target.parentNode.getAttribute("data-target");
     var textEl = document.getElementById(targetId);
     var text = textEl.getAttribute("data-copy");
     if (text===null)
         text = textEl.innerText;
 
     var textArea = document.createElement("textarea");
     textArea.value = text;
     // Avoid scrolling to bottom
     textArea.style.top = "0";
     textArea.style.left = "0";
     textArea.style.position = "fixed";
     document.body.appendChild(textArea);
     textArea.focus();
     textArea.select();
-    document.execCommand('copy');
+    var ok = false;
+    try {
+        ok = document.execCommand('copy');
+    } catch (e) {
+        ok = false;
+    }
     document.body.removeChild(textArea);
+    if (ok) {
+        /* Say "Copied", then put the button's own label back after three seconds so it is clear
+         * that the button can be used again.  This page does not load utils.js, so it cannot call
+         * copyButtonSaysCopied() there; keep the two in step. */
+        if (buttonEl.copyButtonLabel === undefined)
+            buttonEl.copyButtonLabel = buttonEl.innerHTML;
+        if (buttonEl.copyButtonTimer)
+            clearTimeout(buttonEl.copyButtonTimer);
         buttonEl.innerHTML = 'Copied';
-    ev.preventDefault();
+        buttonEl.copyButtonTimer = setTimeout(function() {
+            buttonEl.innerHTML = buttonEl.copyButtonLabel;
+            buttonEl.copyButtonTimer = null;
+        }, 3000);
+    }
+    return ok;
 }
 
 // do not allow both checkboxes to go off
 function atLeastOneCheckBoxOn(e) {
   var mustExist = document.getElementById('mustExist').checked;
   var notExist = document.getElementById('notExist').checked;
   if (! mustExist && ! notExist ) {  // turn on the other one when both off
      if (e.name === "mustExist") {
        document.getElementById('notExist').checked = true;
      } else {
        document.getElementById('mustExist').checked = true;
      }
   }
 }