2f18a9c643ecf990e9525610fdda0a31620ca530
hiram
  Fri Aug 30 22:40:54 2024 -0700
first pass at highlighting the match strings refs #32596

diff --git src/hg/js/assemblySearch.js src/hg/js/assemblySearch.js
index f6f8332..9258d16 100644
--- src/hg/js/assemblySearch.js
+++ src/hg/js/assemblySearch.js
@@ -229,59 +229,131 @@
 // call with visible true to make visible, false to hide
 function advancedSearchVisible(visible) {
   var advancedSearchButton = document.getElementById("advancedSearchButton");
   var searchOptions = document.getElementById("advancedSearchOptions");
   if (visible) {
     searchOptions.style.display = "flex";
     advancedSearchButton.textContent = "hide advanced search options";
     stateObject.advancedSearchVisible = true;
   } else {
     searchOptions.style.display = "none";
     advancedSearchButton.textContent = "show advanced search options";
     stateObject.advancedSearchVisible = false;
   }
 }
 
+// Function to highlight words in the result that match the words
+//  in the queryString.  Work through each item of the rowData object,
+//  for each string, find out if it matches any of the words in the
+//  queryString.  This is tricky, there are extra characters besides
+//  just [a-zA-Z] that are getting in the way of these matches, where
+//  the MySQL could match, for example: checking:
+//      'HG02257' =? '(HG02257.pat'
+//      'HG02257' =? 'HG02257.alt.pat.f1_v2'
+//  doesn't match here, but MySQL match did
+
+function highlightMatch(queryString, rowData) {
+    // fixup the queryString words to get rid of the special characters
+    var words = queryString.split(/\s+/);
+    var wholeWord = [];	// going to be words that match completely
+    var prefix = [];	// going to be words that match prefix
+    for (let word of words) {
+       var noPrefix = word.replace(/^[-+]/, '');	// remove + - beginning
+       if (noPrefix.endsWith("*")) {
+         prefix.push(noPrefix.replace(/\*$/, ''));
+       } else {
+         wholeWord.push(noPrefix);
+       }
+    }
+    if (wholeWord.length > 0) {
+      for (let word of wholeWord) {
+        for (let key in rowData) {
+           if (rowData.hasOwnProperty(key)) {
+              if (typeof rowData[key] === 'string') {
+                 let value = rowData[key];
+                 let subWords = value.split(/\s+/);
+                 let newString = ""
+                 for (let subWord of subWords) {
+                   if ( word.toLowerCase() === subWord.toLowerCase() ) {
+                      newString += " <span class='highlight'>" + subWord + "</span>";
+                   } else {
+                      newString += " " + subWord;
+                   }
+                 }
+                 newString = newString.trim();
+                 if (newString !== rowData[key])
+                    rowData[key] = newString;
+              }
+           }
+        }
+      }
+    }
+    if (prefix.length > 0) {
+      for (let word of prefix) {
+        for (let key in rowData) {
+           if (rowData.hasOwnProperty(key)) {
+              if (typeof rowData[key] === 'string') {
+                 let value = rowData[key];
+                 let subWords = value.split(/\s+/);
+                 let newString = ""
+                 for (let subWord of subWords) {
+                   if ( subWord.toLowerCase().startsWith(word.toLowerCase())) {
+                      newString += " <span class='highlight'>" + subWord + "</span>";
+                   } else {
+                      newString += " " + subWord;
+                   }
+                 }
+                 newString = newString.trim();
+                 if (newString !== rowData[key])
+                    rowData[key] = newString;
+              }
+           }
+        }
+      }
+    }
+}
+
 // Function to generate the table and extra information
 function populateTableAndInfo(jsonData) {
     var tableHeader = document.getElementById('tableHeader');
     var tableBody = document.getElementById('tableBody');
     var metaData = document.getElementById('metaData');
     document.getElementById('resultCounts').innerHTML = "";
     document.getElementById('elapsedTime').innerHTML = "0";
 
     // Clear existing table content
     tableHeader.innerHTML = '';
     tableBody.innerHTML = '';
     metaData.innerHTML = '';
 
     // Extract the genomic entries and the extra info
     var genomicEntries = {};
     var extraInfo = {};
 
     for (var key in jsonData) {
         if (jsonData[key].scientificName) {
             genomicEntries[key] = jsonData[key];
         } else {
             extraInfo[key] = jsonData[key];
         }
     }
 
     headerRefresh(tableHeader);
 
     var count = 0;
     for (var id in genomicEntries) {
+        highlightMatch(extraInfo.q, genomicEntries[id]);
         var dataRow = '<tr>';
         var browserUrl = id;
         var asmInfoUrl = id;
         if (genomicEntries[id].browserExists) {
           if (id.startsWith("GC")) {
             browserUrl = "<a href='/h/" + id + "?position=lastDbPos' target=_blank>view</a>";
             asmInfoUrl = "<a href='https://www.ncbi.nlm.nih.gov/assembly/" + id + "' target=_blank>" + id + "</a>";
           } else {
             browserUrl = "<a href='/cgi-bin/hgTracks?db=" + id + "' target=_blank>view</a>";
             asmInfoUrl = "<a href='https://hgdownload.soe.ucsc.edu/goldenPath/" + id + "/bigZips/' target=_blank>" + id + "</a>";
           }
           dataRow += "<th>" + browserUrl + "</th>";
         } else {
           dataRow += "<th><button type=button' onclick='asmOpenModal(this)' name=" + id + "'>request</button></th>";
         }