5a249cd50f592a3b7598110eec792cfc942fab3f max Wed Sep 9 08:52:06 2026 -0700 Address the v504 code review hgSession: an anonymous share name that arrives with the request is saved only when it is not already in the table. Every anonymous link sits under the one reserved user "l", so a name already there stays as it is and the caller is told so. The top-right Share dialog is unaffected, since it passes a name it has just reserved and such a name does not exist yet. Snapshot names are now left out of both My Sessions listings, which is what their "__" prefix has claimed all along. Share dialog: "Create link & copy" reports the copy instead of promising it. copyToClipboard says whether the text reached the clipboard, the dialog passes that on when a browser refuses, and it tries the asynchronous clipboard API before giving up. The preview is built with the same encoding the server uses, so a name holding a hyphen or a slash previews as the link that really gets made. Cancelling out of the name editor no longer copies a second time, and a reply with no link in it says so rather than showing "undefined". hgBlat: a second click on the share button while the first request is still out no longer mints a second snapshot session, and a box dismissed during the wait stays closed. Also: a snapshot moves a cart value into durable storage only when it is a trash path, the way sessionData's own callers check; sqlAddressMatch keeps to its own documented precondition when handed an empty address; alphaGenomeToWig compares its output with its input rather than with itself, rejects a position that is not all digits and skips an empty score; and hgc's default iframe width reaches the browser as one percent sign. refs #38294 diff --git src/hg/js/hgBlat.js src/hg/js/hgBlat.js index 178b986220d..dd80fbc5c6f 100644 --- src/hg/js/hgBlat.js +++ src/hg/js/hgBlat.js @@ -268,30 +268,40 @@ function blatApplyTooltips() { $('#blatTable thead th').each(function() { var tip = BLAT_HEADER_TIPS[$(this).text().trim()]; if (tip) { $(this).attr('title', tip); } }); if (typeof convertTitleTagsToMouseovers === 'function') { convertTitleTagsToMouseovers(); } } // ---- share a link -------------------------------------------------------- // The snapshot link we created for this page view, cached so re-opening the box doesn't make another. var blatShareCachedUrl = null; +// True while the create request is on its way. The cached URL only exists once the reply is in, so +// without this a second click during the wait mints a second snapshot session for the same results. +var blatShareBusy = false; + +// Is the share box on screen? A reply that arrives after the user has clicked the button again to +// dismiss the box must not reopen it. +function blatShareBoxOpen(box) { + return box.style.display === 'flex'; +} + // Render the share box. url set -> show the link + Copy; url null -> "Creating link…"; msg (url null) // -> show an error. function blatShowShareBox(box, url, msg) { box.style.display = 'flex'; if (msg) { box.innerHTML = '<span class="gbShareMsg gbShareFull" style="color:#a00">' + htmlEncode(msg) + '</span>'; return; } if (!url) { box.innerHTML = '<span class="gbShareMsg gbShareFull">Creating link…</span>'; return; } box.innerHTML = '<span class="gbShareMsg gbShareFull">Anyone with this <b>shareable link</b> can open ' + @@ -315,48 +325,58 @@ function blatShareLink() { // Create (or reveal) a durable share link. It is backed by a lightweight "snapshot" session that // stores only db + the results bigPsl - not the whole cart - under a server-generated unique name // (see lib/snapshotSession.c). hgBlat's ?u=&s= reopen (doShareReopen) rebuilds the results table // from that bigPsl. The token generation, uniqueness and cleanup are shared with hgc and the // top-right "Share a link". var box = document.getElementById('gbShareBox'); if (!box) { return; } if (box.style.display === 'flex') { box.style.display = 'none'; return; } // toggle off // Already viewing a shared session link: the current URL is itself the shareable link. if (/[?&]s=/.test(window.location.search)) { blatShowShareBox(box, window.location.href); return; } // Already created one this page view: reuse it rather than creating another session. if (blatShareCachedUrl) { blatShowShareBox(box, blatShareCachedUrl); return; } + // One is already being created: show it working again instead of asking for a second one. + if (blatShareBusy) { blatShowShareBox(box, null); return; } var cfg = hgBlatData.config; + blatShareBusy = true; blatShowShareBox(box, null); // "Creating link…" var body = 'hgsid=' + encodeURIComponent(cfg.hgsid || '') + '&hgS_doSaveSessionJson=1&hgS_shareAnon=1&hgS_snapshotType=blat'; fetch('../cgi-bin/hgSession', {method: 'POST', credentials: 'same-origin', headers: {'Content-Type': 'application/x-www-form-urlencoded'}, body: body}) .then(function(r) { return r.json(); }) .then(function(data) { + blatShareBusy = false; if (!data || !data.name) { + if (blatShareBoxOpen(box)) blatShowShareBox(box, null, (data && data.error) || 'Could not create the link.'); return; } blatShareCachedUrl = window.location.origin + '/cgi-bin/hgBlat?u=l&s=' + encodeURIComponent(data.name); + // Keep a box the user dismissed while waiting closed; the link is cached for the + // next click on the button. + if (blatShareBoxOpen(box)) blatShowShareBox(box, blatShareCachedUrl); }) .catch(function() { + blatShareBusy = false; + if (blatShareBoxOpen(box)) blatShowShareBox(box, null, 'Could not reach the server. Please try again.'); }); } // ---- Rename BLAT track (modal) ------------------------------------------- // The results custom track is built (and renamed) by hgBlat.c's inline code, which exposes a small // window.blatRenameCt(name, description) helper (it POSTs to hgc's buildBigPsl and rebuilds the // track). We reuse that helper (no new endpoint), just swapping its old inline toggle-form UI for a // proper modal dialog. The current name/description come from cfg (hgBlat.c), not a global, so this // does not depend on any generic page-global. function blatRenameModalHtml(cfg) { // hgSession link is relative (same /cgi-bin/), carrying db + hgsid so the session page opens in // this assembly and cart. var sessionUrl = `hgSession?db=${encodeURIComponent(cfg.db)}&hgsid=${encodeURIComponent(cfg.hgsid)}`;