]> Git Repo - pico-vscode.git/blob - web/docs/search/search.js
Merge branch 'main' into main
[pico-vscode.git] / web / docs / search / search.js
1 /*
2  @licstart  The following is the entire license notice for the JavaScript code in this file.
3
4  The MIT License (MIT)
5
6  Copyright (C) 1997-2020 by Dimitri van Heesch
7
8  Permission is hereby granted, free of charge, to any person obtaining a copy of this software
9  and associated documentation files (the "Software"), to deal in the Software without restriction,
10  including without limitation the rights to use, copy, modify, merge, publish, distribute,
11  sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is
12  furnished to do so, subject to the following conditions:
13
14  The above copyright notice and this permission notice shall be included in all copies or
15  substantial portions of the Software.
16
17  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
18  BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
20  DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
23  @licend  The above is the entire license notice for the JavaScript code in this file
24  */
25 function convertToId(search)
26 {
27   var result = '';
28   for (i=0;i<search.length;i++)
29   {
30     var c = search.charAt(i);
31     var cn = c.charCodeAt(0);
32     if (c.match(/[a-z0-9\u0080-\uFFFF]/))
33     {
34       result+=c;
35     }
36     else if (cn<16)
37     {
38       result+="_0"+cn.toString(16);
39     }
40     else
41     {
42       result+="_"+cn.toString(16);
43     }
44   }
45   return result;
46 }
47
48 function getXPos(item)
49 {
50   var x = 0;
51   if (item.offsetWidth)
52   {
53     while (item && item!=document.body)
54     {
55       x   += item.offsetLeft;
56       item = item.offsetParent;
57     }
58   }
59   return x;
60 }
61
62 function getYPos(item)
63 {
64   var y = 0;
65   if (item.offsetWidth)
66   {
67      while (item && item!=document.body)
68      {
69        y   += item.offsetTop;
70        item = item.offsetParent;
71      }
72   }
73   return y;
74 }
75
76 /* A class handling everything associated with the search panel.
77
78    Parameters:
79    name - The name of the global variable that will be
80           storing this instance.  Is needed to be able to set timeouts.
81    resultPath - path to use for external files
82 */
83 function SearchBox(name, resultsPath, label, extension)
84 {
85   if (!name || !resultsPath) {  alert("Missing parameters to SearchBox."); }
86   if (!extension || extension == "") { extension = ".html"; }
87
88   // ---------- Instance variables
89   this.name                  = name;
90   this.resultsPath           = resultsPath;
91   this.keyTimeout            = 0;
92   this.keyTimeoutLength      = 500;
93   this.closeSelectionTimeout = 300;
94   this.lastSearchValue       = "";
95   this.lastResultsPage       = "";
96   this.hideTimeout           = 0;
97   this.searchIndex           = 0;
98   this.searchActive          = false;
99   this.searchLabel           = label;
100   this.extension             = extension;
101
102   // ----------- DOM Elements
103
104   this.DOMSearchField = function()
105   {  return document.getElementById("MSearchField");  }
106
107   this.DOMSearchSelect = function()
108   {  return document.getElementById("MSearchSelect");  }
109
110   this.DOMSearchSelectWindow = function()
111   {  return document.getElementById("MSearchSelectWindow");  }
112
113   this.DOMPopupSearchResults = function()
114   {  return document.getElementById("MSearchResults");  }
115
116   this.DOMPopupSearchResultsWindow = function()
117   {  return document.getElementById("MSearchResultsWindow");  }
118
119   this.DOMSearchClose = function()
120   {  return document.getElementById("MSearchClose"); }
121
122   this.DOMSearchBox = function()
123   {  return document.getElementById("MSearchBox");  }
124
125   // ------------ Event Handlers
126
127   // Called when focus is added or removed from the search field.
128   this.OnSearchFieldFocus = function(isActive)
129   {
130     this.Activate(isActive);
131   }
132
133   this.OnSearchSelectShow = function()
134   {
135     var searchSelectWindow = this.DOMSearchSelectWindow();
136     var searchField        = this.DOMSearchSelect();
137
138     var left = getXPos(searchField);
139     var top  = getYPos(searchField);
140     top += searchField.offsetHeight;
141
142     // show search selection popup
143     searchSelectWindow.style.display='block';
144     searchSelectWindow.style.left =  left + 'px';
145     searchSelectWindow.style.top  =  top  + 'px';
146
147     // stop selection hide timer
148     if (this.hideTimeout)
149     {
150       clearTimeout(this.hideTimeout);
151       this.hideTimeout=0;
152     }
153     return false; // to avoid "image drag" default event
154   }
155
156   this.OnSearchSelectHide = function()
157   {
158     this.hideTimeout = setTimeout(this.name +".CloseSelectionWindow()",
159                                   this.closeSelectionTimeout);
160   }
161
162   // Called when the content of the search field is changed.
163   this.OnSearchFieldChange = function(evt)
164   {
165     if (this.keyTimeout) // kill running timer
166     {
167       clearTimeout(this.keyTimeout);
168       this.keyTimeout = 0;
169     }
170
171     var e  = (evt) ? evt : window.event; // for IE
172     if (e.keyCode==40 || e.keyCode==13)
173     {
174       if (e.shiftKey==1)
175       {
176         this.OnSearchSelectShow();
177         var win=this.DOMSearchSelectWindow();
178         for (i=0;i<win.childNodes.length;i++)
179         {
180           var child = win.childNodes[i]; // get span within a
181           if (child.className=='SelectItem')
182           {
183             child.focus();
184             return;
185           }
186         }
187         return;
188       }
189       else
190       {
191         window.frames.MSearchResults.postMessage("take_focus", "*");
192       }
193     }
194     else if (e.keyCode==27) // Escape out of the search field
195     {
196       this.DOMSearchField().blur();
197       this.DOMPopupSearchResultsWindow().style.display = 'none';
198       this.DOMSearchClose().style.display = 'none';
199       this.lastSearchValue = '';
200       this.Activate(false);
201       return;
202     }
203
204     // strip whitespaces
205     var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
206
207     if (searchValue != this.lastSearchValue) // search value has changed
208     {
209       if (searchValue != "") // non-empty search
210       {
211         // set timer for search update
212         this.keyTimeout = setTimeout(this.name + '.Search()',
213                                      this.keyTimeoutLength);
214       }
215       else // empty search field
216       {
217         this.DOMPopupSearchResultsWindow().style.display = 'none';
218         this.DOMSearchClose().style.display = 'none';
219         this.lastSearchValue = '';
220       }
221     }
222   }
223
224   this.SelectItemCount = function(id)
225   {
226     var count=0;
227     var win=this.DOMSearchSelectWindow();
228     for (i=0;i<win.childNodes.length;i++)
229     {
230       var child = win.childNodes[i]; // get span within a
231       if (child.className=='SelectItem')
232       {
233         count++;
234       }
235     }
236     return count;
237   }
238
239   this.SelectItemSet = function(id)
240   {
241     var i,j=0;
242     var win=this.DOMSearchSelectWindow();
243     for (i=0;i<win.childNodes.length;i++)
244     {
245       var child = win.childNodes[i]; // get span within a
246       if (child.className=='SelectItem')
247       {
248         var node = child.firstChild;
249         if (j==id)
250         {
251           node.innerHTML='&#8226;';
252         }
253         else
254         {
255           node.innerHTML='&#160;';
256         }
257         j++;
258       }
259     }
260   }
261
262   // Called when an search filter selection is made.
263   // set item with index id as the active item
264   this.OnSelectItem = function(id)
265   {
266     this.searchIndex = id;
267     this.SelectItemSet(id);
268     var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
269     if (searchValue!="" && this.searchActive) // something was found -> do a search
270     {
271       this.Search();
272     }
273   }
274
275   this.OnSearchSelectKey = function(evt)
276   {
277     var e = (evt) ? evt : window.event; // for IE
278     if (e.keyCode==40 && this.searchIndex<this.SelectItemCount()) // Down
279     {
280       this.searchIndex++;
281       this.OnSelectItem(this.searchIndex);
282     }
283     else if (e.keyCode==38 && this.searchIndex>0) // Up
284     {
285       this.searchIndex--;
286       this.OnSelectItem(this.searchIndex);
287     }
288     else if (e.keyCode==13 || e.keyCode==27)
289     {
290       this.OnSelectItem(this.searchIndex);
291       this.CloseSelectionWindow();
292       this.DOMSearchField().focus();
293     }
294     return false;
295   }
296
297   // --------- Actions
298
299   // Closes the results window.
300   this.CloseResultsWindow = function()
301   {
302     this.DOMPopupSearchResultsWindow().style.display = 'none';
303     this.DOMSearchClose().style.display = 'none';
304     this.Activate(false);
305   }
306
307   this.CloseSelectionWindow = function()
308   {
309     this.DOMSearchSelectWindow().style.display = 'none';
310   }
311
312   // Performs a search.
313   this.Search = function()
314   {
315     this.keyTimeout = 0;
316
317     // strip leading whitespace
318     var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
319
320     var code = searchValue.toLowerCase().charCodeAt(0);
321     var idxChar = searchValue.substr(0, 1).toLowerCase();
322     if ( 0xD800 <= code && code <= 0xDBFF && searchValue > 1) // surrogate pair
323     {
324       idxChar = searchValue.substr(0, 2);
325     }
326
327     var resultsPage;
328     var resultsPageWithSearch;
329     var hasResultsPage;
330
331     var idx = indexSectionsWithContent[this.searchIndex].indexOf(idxChar);
332     if (idx!=-1)
333     {
334        var hexCode=idx.toString(16);
335        resultsPage = this.resultsPath + '/' + indexSectionNames[this.searchIndex] + '_' + hexCode + this.extension;
336        resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
337        hasResultsPage = true;
338     }
339     else // nothing available for this search term
340     {
341        resultsPage = this.resultsPath + '/nomatches' + this.extension;
342        resultsPageWithSearch = resultsPage;
343        hasResultsPage = false;
344     }
345
346     window.frames.MSearchResults.location = resultsPageWithSearch;
347     var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
348
349     if (domPopupSearchResultsWindow.style.display!='block')
350     {
351        var domSearchBox = this.DOMSearchBox();
352        this.DOMSearchClose().style.display = 'inline-block';
353        var domPopupSearchResults = this.DOMPopupSearchResults();
354        var left = getXPos(domSearchBox) + 150; // domSearchBox.offsetWidth;
355        var top  = getYPos(domSearchBox) + 20;  // domSearchBox.offsetHeight + 1;
356        domPopupSearchResultsWindow.style.display = 'block';
357        left -= domPopupSearchResults.offsetWidth;
358        var maxWidth = document.body.clientWidth;
359        var width = 400;
360        if (left<10) left=10;
361        if (width+left+8>maxWidth) width=maxWidth-left-8;
362        domPopupSearchResultsWindow.style.top     = top  + 'px';
363        domPopupSearchResultsWindow.style.left    = left + 'px';
364        domPopupSearchResultsWindow.style.width   = width + 'px';
365     }
366
367     this.lastSearchValue = searchValue;
368     this.lastResultsPage = resultsPage;
369   }
370
371   // -------- Activation Functions
372
373   // Activates or deactivates the search panel, resetting things to
374   // their default values if necessary.
375   this.Activate = function(isActive)
376   {
377     if (isActive || // open it
378         this.DOMPopupSearchResultsWindow().style.display == 'block'
379        )
380     {
381       this.DOMSearchBox().className = 'MSearchBoxActive';
382
383       var searchField = this.DOMSearchField();
384
385       if (searchField.value == this.searchLabel) // clear "Search" term upon entry
386       {
387         searchField.value = '';
388         this.searchActive = true;
389       }
390     }
391     else if (!isActive) // directly remove the panel
392     {
393       this.DOMSearchBox().className = 'MSearchBoxInactive';
394       this.DOMSearchField().value   = this.searchLabel;
395       this.searchActive             = false;
396       this.lastSearchValue          = ''
397       this.lastResultsPage          = '';
398     }
399   }
400 }
401
402 // -----------------------------------------------------------------------
403
404 // The class that handles everything on the search results page.
405 function SearchResults(name)
406 {
407     // The number of matches from the last run of <Search()>.
408     this.lastMatchCount = 0;
409     this.lastKey = 0;
410     this.repeatOn = false;
411
412     // Toggles the visibility of the passed element ID.
413     this.FindChildElement = function(id)
414     {
415       var parentElement = document.getElementById(id);
416       var element = parentElement.firstChild;
417
418       while (element && element!=parentElement)
419       {
420         if (element.nodeName.toLowerCase() == 'div' && element.className == 'SRChildren')
421         {
422           return element;
423         }
424
425         if (element.nodeName.toLowerCase() == 'div' && element.hasChildNodes())
426         {
427            element = element.firstChild;
428         }
429         else if (element.nextSibling)
430         {
431            element = element.nextSibling;
432         }
433         else
434         {
435           do
436           {
437             element = element.parentNode;
438           }
439           while (element && element!=parentElement && !element.nextSibling);
440
441           if (element && element!=parentElement)
442           {
443             element = element.nextSibling;
444           }
445         }
446       }
447     }
448
449     this.Toggle = function(id)
450     {
451       var element = this.FindChildElement(id);
452       if (element)
453       {
454         if (element.style.display == 'block')
455         {
456           element.style.display = 'none';
457         }
458         else
459         {
460           element.style.display = 'block';
461         }
462       }
463     }
464
465     // Searches for the passed string.  If there is no parameter,
466     // it takes it from the URL query.
467     //
468     // Always returns true, since other documents may try to call it
469     // and that may or may not be possible.
470     this.Search = function(search)
471     {
472       if (!search) // get search word from URL
473       {
474         search = window.location.search;
475         search = search.substring(1);  // Remove the leading '?'
476         search = unescape(search);
477       }
478
479       search = search.replace(/^ +/, ""); // strip leading spaces
480       search = search.replace(/ +$/, ""); // strip trailing spaces
481       search = search.toLowerCase();
482       search = convertToId(search);
483
484       var resultRows = document.getElementsByTagName("div");
485       var matches = 0;
486
487       var i = 0;
488       while (i < resultRows.length)
489       {
490         var row = resultRows.item(i);
491         if (row.className == "SRResult")
492         {
493           var rowMatchName = row.id.toLowerCase();
494           rowMatchName = rowMatchName.replace(/^sr\d*_/, ''); // strip 'sr123_'
495
496           if (search.length<=rowMatchName.length &&
497              rowMatchName.substr(0, search.length)==search)
498           {
499             row.style.display = 'block';
500             matches++;
501           }
502           else
503           {
504             row.style.display = 'none';
505           }
506         }
507         i++;
508       }
509       document.getElementById("Searching").style.display='none';
510       if (matches == 0) // no results
511       {
512         document.getElementById("NoMatches").style.display='block';
513       }
514       else // at least one result
515       {
516         document.getElementById("NoMatches").style.display='none';
517       }
518       this.lastMatchCount = matches;
519       return true;
520     }
521
522     // return the first item with index index or higher that is visible
523     this.NavNext = function(index)
524     {
525       var focusItem;
526       while (1)
527       {
528         var focusName = 'Item'+index;
529         focusItem = document.getElementById(focusName);
530         if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
531         {
532           break;
533         }
534         else if (!focusItem) // last element
535         {
536           break;
537         }
538         focusItem=null;
539         index++;
540       }
541       return focusItem;
542     }
543
544     this.NavPrev = function(index)
545     {
546       var focusItem;
547       while (1)
548       {
549         var focusName = 'Item'+index;
550         focusItem = document.getElementById(focusName);
551         if (focusItem && focusItem.parentNode.parentNode.style.display=='block')
552         {
553           break;
554         }
555         else if (!focusItem) // last element
556         {
557           break;
558         }
559         focusItem=null;
560         index--;
561       }
562       return focusItem;
563     }
564
565     this.ProcessKeys = function(e)
566     {
567       if (e.type == "keydown")
568       {
569         this.repeatOn = false;
570         this.lastKey = e.keyCode;
571       }
572       else if (e.type == "keypress")
573       {
574         if (!this.repeatOn)
575         {
576           if (this.lastKey) this.repeatOn = true;
577           return false; // ignore first keypress after keydown
578         }
579       }
580       else if (e.type == "keyup")
581       {
582         this.lastKey = 0;
583         this.repeatOn = false;
584       }
585       return this.lastKey!=0;
586     }
587
588     this.Nav = function(evt,itemIndex)
589     {
590       var e  = (evt) ? evt : window.event; // for IE
591       if (e.keyCode==13) return true;
592       if (!this.ProcessKeys(e)) return false;
593
594       if (this.lastKey==38) // Up
595       {
596         var newIndex = itemIndex-1;
597         var focusItem = this.NavPrev(newIndex);
598         if (focusItem)
599         {
600           var child = this.FindChildElement(focusItem.parentNode.parentNode.id);
601           if (child && child.style.display == 'block') // children visible
602           {
603             var n=0;
604             var tmpElem;
605             while (1) // search for last child
606             {
607               tmpElem = document.getElementById('Item'+newIndex+'_c'+n);
608               if (tmpElem)
609               {
610                 focusItem = tmpElem;
611               }
612               else // found it!
613               {
614                 break;
615               }
616               n++;
617             }
618           }
619         }
620         if (focusItem)
621         {
622           focusItem.focus();
623         }
624         else // return focus to search field
625         {
626            parent.document.getElementById("MSearchField").focus();
627         }
628       }
629       else if (this.lastKey==40) // Down
630       {
631         var newIndex = itemIndex+1;
632         var focusItem;
633         var item = document.getElementById('Item'+itemIndex);
634         var elem = this.FindChildElement(item.parentNode.parentNode.id);
635         if (elem && elem.style.display == 'block') // children visible
636         {
637           focusItem = document.getElementById('Item'+itemIndex+'_c0');
638         }
639         if (!focusItem) focusItem = this.NavNext(newIndex);
640         if (focusItem)  focusItem.focus();
641       }
642       else if (this.lastKey==39) // Right
643       {
644         var item = document.getElementById('Item'+itemIndex);
645         var elem = this.FindChildElement(item.parentNode.parentNode.id);
646         if (elem) elem.style.display = 'block';
647       }
648       else if (this.lastKey==37) // Left
649       {
650         var item = document.getElementById('Item'+itemIndex);
651         var elem = this.FindChildElement(item.parentNode.parentNode.id);
652         if (elem) elem.style.display = 'none';
653       }
654       else if (this.lastKey==27) // Escape
655       {
656         parent.searchBox.CloseResultsWindow();
657         parent.document.getElementById("MSearchField").focus();
658       }
659       else if (this.lastKey==13) // Enter
660       {
661         return true;
662       }
663       return false;
664     }
665
666     this.NavChild = function(evt,itemIndex,childIndex)
667     {
668       var e  = (evt) ? evt : window.event; // for IE
669       if (e.keyCode==13) return true;
670       if (!this.ProcessKeys(e)) return false;
671
672       if (this.lastKey==38) // Up
673       {
674         if (childIndex>0)
675         {
676           var newIndex = childIndex-1;
677           document.getElementById('Item'+itemIndex+'_c'+newIndex).focus();
678         }
679         else // already at first child, jump to parent
680         {
681           document.getElementById('Item'+itemIndex).focus();
682         }
683       }
684       else if (this.lastKey==40) // Down
685       {
686         var newIndex = childIndex+1;
687         var elem = document.getElementById('Item'+itemIndex+'_c'+newIndex);
688         if (!elem) // last child, jump to parent next parent
689         {
690           elem = this.NavNext(itemIndex+1);
691         }
692         if (elem)
693         {
694           elem.focus();
695         }
696       }
697       else if (this.lastKey==27) // Escape
698       {
699         parent.searchBox.CloseResultsWindow();
700         parent.document.getElementById("MSearchField").focus();
701       }
702       else if (this.lastKey==13) // Enter
703       {
704         return true;
705       }
706       return false;
707     }
708 }
709
710 function setKeyActions(elem,action)
711 {
712   elem.setAttribute('onkeydown',action);
713   elem.setAttribute('onkeypress',action);
714   elem.setAttribute('onkeyup',action);
715 }
716
717 function setClassAttr(elem,attr)
718 {
719   elem.setAttribute('class',attr);
720   elem.setAttribute('className',attr);
721 }
722
723 function createResults()
724 {
725   var results = document.getElementById("SRResults");
726   for (var e=0; e<searchData.length; e++)
727   {
728     var id = searchData[e][0];
729     var srResult = document.createElement('div');
730     srResult.setAttribute('id','SR_'+id);
731     setClassAttr(srResult,'SRResult');
732     var srEntry = document.createElement('div');
733     setClassAttr(srEntry,'SREntry');
734     var srLink = document.createElement('a');
735     srLink.setAttribute('id','Item'+e);
736     setKeyActions(srLink,'return searchResults.Nav(event,'+e+')');
737     setClassAttr(srLink,'SRSymbol');
738     srLink.innerHTML = searchData[e][1][0];
739     srEntry.appendChild(srLink);
740     if (searchData[e][1].length==2) // single result
741     {
742       srLink.setAttribute('href',searchData[e][1][1][0]);
743       srLink.setAttribute('onclick','parent.searchBox.CloseResultsWindow()');
744       if (searchData[e][1][1][1])
745       {
746        srLink.setAttribute('target','_parent');
747       }
748       else
749       {
750        srLink.setAttribute('target','_blank');
751       }
752       var srScope = document.createElement('span');
753       setClassAttr(srScope,'SRScope');
754       srScope.innerHTML = searchData[e][1][1][2];
755       srEntry.appendChild(srScope);
756     }
757     else // multiple results
758     {
759       srLink.setAttribute('href','javascript:searchResults.Toggle("SR_'+id+'")');
760       var srChildren = document.createElement('div');
761       setClassAttr(srChildren,'SRChildren');
762       for (var c=0; c<searchData[e][1].length-1; c++)
763       {
764         var srChild = document.createElement('a');
765         srChild.setAttribute('id','Item'+e+'_c'+c);
766         setKeyActions(srChild,'return searchResults.NavChild(event,'+e+','+c+')');
767         setClassAttr(srChild,'SRScope');
768         srChild.setAttribute('href',searchData[e][1][c+1][0]);
769         srChild.setAttribute('onclick','parent.searchBox.CloseResultsWindow()');
770         if (searchData[e][1][c+1][1])
771         {
772          srChild.setAttribute('target','_parent');
773         }
774         else
775         {
776          srChild.setAttribute('target','_blank');
777         }
778         srChild.innerHTML = searchData[e][1][c+1][2];
779         srChildren.appendChild(srChild);
780       }
781       srEntry.appendChild(srChildren);
782     }
783     srResult.appendChild(srEntry);
784     results.appendChild(srResult);
785   }
786 }
787
788 function init_search()
789 {
790   var results = document.getElementById("MSearchSelectWindow");
791   for (var key in indexSectionLabels)
792   {
793     var link = document.createElement('a');
794     link.setAttribute('class','SelectItem');
795     link.setAttribute('onclick','searchBox.OnSelectItem('+key+')');
796     link.href='javascript:void(0)';
797     link.innerHTML='<span class="SelectionMark">&#160;</span>'+indexSectionLabels[key];
798     results.appendChild(link);
799   }
800   searchBox.OnSelectItem(0);
801 }
802 /* @license-end */
This page took 0.067996 seconds and 4 git commands to generate.