c5a326f3cc42beca0b59c284a2819763548c166a
max
  Wed Jul 8 05:31:13 2026 -0700
Add top-right "Share a link" and "Login" links to the menu bar, refs #10138

New js/topLinks.js drives two links at the top-right of the menu bar on all pages:

- Login: links to the login page when logged out; when logged in it shows the
username and opens an account dialog (My Sessions / My Custom Tracks /
My Track Hubs, change password, sign out).
- Share a link: on hgTracks, one click saves the current view as a session and
shows a copyable short link, with an optional "Specify name" rename step; works
logged-in or anonymously (reserved user "l"). On hgTrackUi and the hgc/hgGene
item-details popup it instead shares the page URL with the hgsid stripped and
the db argument kept.

hgSession gains two JSON endpoints (hgS_doSaveSessionJson, hgS_doRenameSessionJson)
that reuse saveCartAsSession() and addSessionLink(). The menu bar is patched in
lib/web.c (CGI pages) and hgMenubar.c (static pages) via comment placeholders in
globalNavBar.inc. On narrow/phone screens the links collapse into a menu icon with
a dropdown so they no longer overlap the menu.

diff --git src/hg/lib/web.c src/hg/lib/web.c
index 76d43b490e4..f2cff7eb275 100644
--- src/hg/lib/web.c
+++ src/hg/lib/web.c
@@ -1607,30 +1607,31 @@
 char *navBarFile = "inc/globalNavBar.inc";
 struct stat statBuf;
 char *scriptName = cgiScriptName();
 if (cart)
     safef(uiVars, sizeof(uiVars), "%s=%s", cartSessionVarName(), cartSessionId(cart));
 else
     uiVars[0] = 0;
 
 if(docRoot == NULL)
     // tolerate missing docRoot (i.e. don't bother with menu when running from command line)
     return NULL;
 
 jsIncludeFile("jquery.js", NULL);
 jsIncludeFile("jquery.plugins.js", NULL);
 jsIncludeFile("utils.js", NULL);
+jsIncludeFile("topLinks.js", NULL);
 webIncludeResourceFile("nice_menu.css");
 
 webIncludeLocalJs();
 
 // Read in menu bar html
 safef(buf, sizeof(buf), "%s/%s", docRoot, navBarFile);
 fd = mustOpen(buf, "r");
 fstat(fileno(fd), &statBuf);
 int len = statBuf.st_size;
 menuStr = needMem(len + 1);
 mustRead(fd, menuStr, statBuf.st_size);
 menuStr[len] = 0;
 carefulClose(&fd);
 
 if (cart)
@@ -1654,32 +1655,87 @@
         {
         struct trackDb *tdb = hTrackDbForTrack(db, track);
         if (tdb)
 	    {
 	    struct trackDb *topLevel = trackDbTopLevelSelfOrParent(tdb); 
 	    char *undupedTrack = dupTrackSkipToSourceName(topLevel->track);
 	    safef(hgTablesOptions, sizeof  hgTablesOptions, 
 		    "../cgi-bin/hgTables?hgta_doMainPage=1&hgta_group=%s&hgta_track=%s&hgta_table=%s&", 
 		    topLevel->grp, undupedTrack, tdb->table);
 	    menuStr = replaceChars(menuStr, "../cgi-bin/hgTables?", hgTablesOptions);
 	    trackDbFree(&tdb);
 	    }
         }
     }
 
-if(!loginSystemEnabled())
-    stripRegEx(menuStr, "<\\!-- LOGIN_START -->.*<\\!-- LOGIN_END -->", REG_ICASE);
+// Fill in the top-right Login link (placeholder <!-- LOGIN_LINK --> in globalNavBar.inc, inside
+// the #topRightLinks container).  Logged out: a "Login" link to hgSession.  Logged in: the
+// username, which opens an account dialog (handled in topLinks.js using the data-* attributes
+// below).  No login system: removed.
+    {
+    char *loginLi = "";
+    if (loginSystemEnabled() || wikiLinkEnabled())
+        {
+        char *userName = wikiLinkUserName();
+        struct dyString *dy = dyStringNew(512);
+        if (userName == NULL)
+            {
+            // Link straight to the login page (same target hgSession's own Login link uses),
+            // rather than bouncing the user through hgSession first.
+            char *loginUrl = wikiLinkUserLoginUrl(cart ? cartSessionId(cart) : "");
+            dyStringPrintf(dy, "<a class='topRightLink' href='%s' id='loginLink' "
+                "title='Log in to save and share sessions'>Login</a>", loginUrl);
+            }
+        else
+            {
+            char *hgsid = cart ? cartSessionId(cart) : NULL;
+            char *logoutUrl = wikiLinkUserLogoutUrl(hgsid);
+            char *changePwUrl = wikiLinkChangePasswordUrl(hgsid);
+            dyStringPrintf(dy, "<a class='topRightLink' href='#' id='loginLink' "
+                "title='Account info and sign out' "
+                "data-username=\"%s\" data-logouturl=\"%s\" data-changepwurl=\"%s\">%s</a>",
+                userName, logoutUrl, changePwUrl ? changePwUrl : "", userName);
+            }
+        loginLi = dyStringCannibalize(&dy);
+        }
+    menuStr = replaceChars(menuStr, "<!-- LOGIN_LINK -->", loginLi);
+    }
+
+// Fill in the top-right "Share a link" link (placeholder <!-- SHARE_LINK -->).
+// data-sharemode tells topLinks.js what to do:
+//   "session" (hgTracks): save the current view as a session and show a short link; branches on
+//             data-loggedin to either name a session (logged in) or make an anonymous link.
+//   "url" (hgTrackUi): just show the current page URL with the hgsid stripped, for sharing.
+    {
+    char *shareLi = "";
+    boolean isHgTracks = (scriptName && endsWith(scriptName, "hgTracks"));
+    boolean isHgTrackUi = (scriptName && endsWith(scriptName, "hgTrackUi"));
+    if (isHgTracks || isHgTrackUi)
+        {
+        char *userName = (loginSystemEnabled() || wikiLinkEnabled()) ? wikiLinkUserName() : NULL;
+        char *shareMode = isHgTrackUi ? "url" : "session";
+        struct dyString *dy = dyStringNew(256);
+        dyStringPrintf(dy, "<a class='topRightLink' href='#' id='shareLink' "
+            "title='Get a link to this view to share with others' "
+            "data-sharemode='%s' data-loggedin='%d'", shareMode, (userName != NULL));
+        if (userName != NULL)
+            dyStringPrintf(dy, " data-username=\"%s\"", userName);
+        dyStringAppend(dy, ">Share a link</a>");
+        shareLi = dyStringCannibalize(&dy);
+        }
+    menuStr = replaceChars(menuStr, "<!-- SHARE_LINK -->", shareLi);
+    }
 
 if(scriptName)
     {  // Provide optional official mirror servers menu items
     char *geoMenu = geoMirrorMenu();
     char *pattern = "<!-- OPTIONAL_MIRROR_MENU -->";
     char *newMenuStr = replaceChars(menuStr, pattern, geoMenu);
     freez(&menuStr);
     menuStr = newMenuStr;
     }
 
 
 if(scriptName)
     {
     // Provide view menu for some CGIs.
     struct dyString *viewItems = dyStringCreate("%s","");