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/topLinks.js src/hg/js/topLinks.js
index 312058fa024..bb00e0e1438 100644
--- src/hg/js/topLinks.js
+++ src/hg/js/topLinks.js
@@ -1,31 +1,31 @@
 // topLinks.js - behavior for the two top-right menu-bar links, "Login" and "Share a link".
 //
 //   Login: when the user is logged in, the menu item shows their username and opens a small
 //   account dialog (change password, sign out).  When logged out it is a plain link to hgSession,
 //   so this script does nothing for it.
 //
 //   Share a link (browser pages only): saves the current view as a session and shows a copyable
 //   short link.  Logged-in users get to name the session and choose whether others may open it;
 //   logged-out users get an anonymous link created on the fly.
 //
 // The C code that builds the menu bar (lib/web.c menuBar() for CGI pages, hgMenubar.c for static
 // pages) fills in the data-* attributes that this script reads.  The dialog is a self-contained
 // overlay so it works on static pages too (no jQuery UI dependency).
 
 /* jshint esversion: 8 */
-/* global $, document, window, URL, getHgsid, copyToClipboard */
+/* global $, document, window, URL, navigator, getHgsid, copyToClipboard, copyButtonSaysCopied */
 
 var topLinks = (function() {
     "use strict";
 
     // Small helper to build an element with properties and inline styles set from the DOM
     // (assigning to element.style.* is CSP-safe, unlike a style="" attribute in markup).
     function el(tag, props, styles) {
         var e = document.createElement(tag);
         var k;
         if (props)
             for (k in props)
                 if (props.hasOwnProperty(k)) e[k] = props[k];
         if (styles)
             for (k in styles)
                 if (styles.hasOwnProperty(k)) e.style[k] = styles[k];
@@ -270,31 +270,32 @@
         // browser may refuse it on those grounds, so say which of the two happened instead of
         // promising the clipboard either way.  autoCopy is spent here: coming back to this view,
         // e.g. by cancelling out of the name editor, must not copy a second time.
         if (opts.autoCopy) {
             opts.autoCopy = false;
             copyBtn.click();
             if (!copied) {
                 var note = el("p", {textContent: "Your browser did not allow the copy. Use the " +
                     "button above to copy the link."}, {marginTop: "8px", color: "#a00"});
                 body.appendChild(note);
                 // The asynchronous clipboard API does not need a click of the user's, so it can
                 // still get there in a browser that grants the permission.  Only then is the
                 // warning wrong, so take it back.
                 if (navigator.clipboard && navigator.clipboard.writeText) {
                     navigator.clipboard.writeText(url).then(function() {
-                        copyBtn.innerHTML = "Copied";
+                        if (typeof copyButtonSaysCopied === "function")
+                            copyButtonSaysCopied(copyBtn);
                         note.remove();
                     }, function() { });
                 }
             }
         }
 
         if (opts.session)
             appendManageNote(body, opts.loggedIn);
         else if (opts.pageNote)
             body.appendChild(el("p", {textContent: "This link shows the page only. It does not " +
                 "restore the tracks you currently have displayed."}, {marginTop: "14px"}));
     }
 
     // The "Specify name" editor: rename the session, then show the updated link.
     function showRename(body, currentUrl, opts) {