1
// This file is part of Natural Docs, which is Copyright � 2003-2010 Greg Valure
2
// Natural Docs is licensed under version 3 of the GNU Affero General Public License (AGPL)
3
// Refer to License.txt for the complete details
5
// This file may be distributed with documentation files generated by Natural Docs.
6
// Such documentation is not covered by Natural Docs' copyright and licensing,
7
// and may have its own copyright and distribution terms as decided by its author.
12
// ____________________________________________________________________________
14
var agt=navigator.userAgent.toLowerCase();
18
if (agt.indexOf("opera") != -1)
20
browserType = "Opera";
22
if (agt.indexOf("opera 7") != -1 || agt.indexOf("opera/7") != -1)
23
{ browserVer = "Opera7"; }
24
else if (agt.indexOf("opera 8") != -1 || agt.indexOf("opera/8") != -1)
25
{ browserVer = "Opera8"; }
26
else if (agt.indexOf("opera 9") != -1 || agt.indexOf("opera/9") != -1)
27
{ browserVer = "Opera9"; }
30
else if (agt.indexOf("applewebkit") != -1)
32
browserType = "Safari";
34
if (agt.indexOf("version/3") != -1)
35
{ browserVer = "Safari3"; }
36
else if (agt.indexOf("safari/4") != -1)
37
{ browserVer = "Safari2"; }
40
else if (agt.indexOf("khtml") != -1)
42
browserType = "Konqueror";
45
else if (agt.indexOf("msie") != -1)
49
if (agt.indexOf("msie 6") != -1)
50
{ browserVer = "IE6"; }
51
else if (agt.indexOf("msie 7") != -1)
52
{ browserVer = "IE7"; }
55
else if (agt.indexOf("gecko") != -1)
57
browserType = "Firefox";
59
if (agt.indexOf("rv:1.7") != -1)
60
{ browserVer = "Firefox1"; }
61
else if (agt.indexOf("rv:1.8)") != -1 || agt.indexOf("rv:1.8.0") != -1)
62
{ browserVer = "Firefox15"; }
63
else if (agt.indexOf("rv:1.8.1") != -1)
64
{ browserVer = "Firefox2"; }
70
// ____________________________________________________________________________
73
function GetXPosition(item)
77
if (item.offsetWidth != null)
79
while (item != document.body && item != null)
81
position += item.offsetLeft;
82
item = item.offsetParent;
90
function GetYPosition(item)
94
if (item.offsetWidth != null)
96
while (item != document.body && item != null)
98
position += item.offsetTop;
99
item = item.offsetParent;
107
function MoveToPosition(item, x, y)
109
// Opera 5 chokes on the px extension, so it can use the Microsoft one instead.
111
if (item.style.left != null)
113
item.style.left = x + "px";
114
item.style.top = y + "px";
116
else if (item.style.pixelLeft != null)
118
item.style.pixelLeft = x;
119
item.style.pixelTop = y;
126
// ____________________________________________________________________________
129
function ToggleMenu(id)
131
if (!window.document.getElementById)
134
var display = window.document.getElementById(id).style.display;
136
if (display == "none")
137
{ display = "block"; }
139
{ display = "none"; }
141
window.document.getElementById(id).style.display = display;
144
function HideAllBut(ids, max)
146
if (document.getElementById)
148
ids.sort( function(a,b) { return a - b; } );
153
if (ids.length > 0 && number == ids[0])
157
document.getElementById("MGroupContent" + number).style.display = "none";
168
// ____________________________________________________________________________
171
var tooltipTimer = 0;
173
function ShowTip(event, tooltipID, linkID)
176
{ clearTimeout(tooltipTimer); };
178
var docX = event.clientX + window.pageXOffset;
179
var docY = event.clientY + window.pageYOffset;
181
var showCommand = "ReallyShowTip('" + tooltipID + "', '" + linkID + "', " + docX + ", " + docY + ")";
183
tooltipTimer = setTimeout(showCommand, 1000);
186
function ReallyShowTip(tooltipID, linkID, docX, docY)
193
if (document.getElementById)
195
tooltip = document.getElementById(tooltipID);
196
link = document.getElementById(linkID);
198
/* else if (document.all)
200
tooltip = eval("document.all['" + tooltipID + "']");
201
link = eval("document.all['" + linkID + "']");
206
var left = GetXPosition(link);
207
var top = GetYPosition(link);
208
top += link.offsetHeight;
211
// The fallback method is to use the mouse X and Y relative to the document. We use a separate if and test if its a number
212
// in case some browser snuck through the above if statement but didn't support everything.
214
if (!isFinite(top) || top == 0)
220
// Some spacing to get it out from under the cursor.
224
// Make sure the tooltip doesnt get smushed by being too close to the edge, or in some browsers, go off the edge of the
225
// page. We do it here because Konqueror does get offsetWidth right even if it doesnt get the positioning right.
227
if (tooltip.offsetWidth != null)
229
var width = tooltip.offsetWidth;
230
var docWidth = document.body.clientWidth;
232
if (left + width > docWidth)
233
{ left = docWidth - width - 1; }
235
// If there's a horizontal scroll bar we could go past zero because it's using the page width, not the window width.
240
MoveToPosition(tooltip, left, top);
241
tooltip.style.visibility = "visible";
245
function HideTip(tooltipID)
249
clearTimeout(tooltipTimer);
255
if (document.getElementById)
256
{ tooltip = document.getElementById(tooltipID); }
257
else if (document.all)
258
{ tooltip = eval("document.all['" + tooltipID + "']"); }
261
{ tooltip.style.visibility = "hidden"; }
266
// Blockquote fix for IE
267
// ____________________________________________________________________________
272
if (browserVer == "IE6")
274
var scrollboxes = document.getElementsByTagName('blockquote');
276
if (scrollboxes.item(0))
279
window.onresize=NDOnResize;
287
function NDOnResize()
289
if (resizeTimer != 0)
290
{ clearTimeout(resizeTimer); };
292
resizeTimer = setTimeout(NDDoResize, 250);
296
function NDDoResize()
298
var scrollboxes = document.getElementsByTagName('blockquote');
304
while (item = scrollboxes.item(i))
306
item.style.width = 100;
311
while (item = scrollboxes.item(i))
313
item.style.width = item.parentNode.offsetWidth;
317
clearTimeout(resizeTimer);
323
/* ________________________________________________________________________________________________________
326
________________________________________________________________________________________________________
328
A class handling everything associated with the search panel.
332
name - The name of the global variable that will be storing this instance. Is needed to be able to set timeouts.
333
mode - The mode the search is going to work in. Pass <NaturalDocs::Builder::Base->CommandLineOption()>, so the
334
value will be something like "HTML" or "FramedHTML".
336
________________________________________________________________________________________________________
340
function SearchPanel(name, mode, resultsPath)
342
if (!name || !mode || !resultsPath)
343
{ alert("Incorrect parameters to SearchPanel."); };
347
// ________________________________________________________________________
351
The name of the global variable that will be storing this instance of the class.
357
The mode the search is going to work in, such as "HTML" or "FramedHTML".
363
The relative path from the current HTML page to the results page directory.
365
this.resultsPath = resultsPath;
369
The timeout used between a keystroke and when a search is performed.
374
var: keyTimeoutLength
375
The length of <keyTimeout> in thousandths of a second.
377
this.keyTimeoutLength = 500;
381
The last search string executed, or an empty string if none.
383
this.lastSearchValue = "";
387
The last results page. The value is only relevant if <lastSearchValue> is set.
389
this.lastResultsPage = "";
392
var: deactivateTimeout
394
The timeout used between when a control is deactivated and when the entire panel is deactivated. Is necessary
395
because a control may be deactivated in favor of another control in the same panel, in which case it should stay
398
this.deactivateTimout = 0;
401
var: deactivateTimeoutLength
402
The length of <deactivateTimeout> in thousandths of a second.
404
this.deactivateTimeoutLength = 200;
409
// Group: DOM Elements
410
// ________________________________________________________________________
413
// Function: DOMSearchField
414
this.DOMSearchField = function()
415
{ return document.getElementById("MSearchField"); };
417
// Function: DOMSearchType
418
this.DOMSearchType = function()
419
{ return document.getElementById("MSearchType"); };
421
// Function: DOMPopupSearchResults
422
this.DOMPopupSearchResults = function()
423
{ return document.getElementById("MSearchResults"); };
425
// Function: DOMPopupSearchResultsWindow
426
this.DOMPopupSearchResultsWindow = function()
427
{ return document.getElementById("MSearchResultsWindow"); };
429
// Function: DOMSearchPanel
430
this.DOMSearchPanel = function()
431
{ return document.getElementById("MSearchPanel"); };
436
// Group: Event Handlers
437
// ________________________________________________________________________
441
Function: OnSearchFieldFocus
442
Called when focus is added or removed from the search field.
444
this.OnSearchFieldFocus = function(isActive)
446
this.Activate(isActive);
451
Function: OnSearchFieldChange
452
Called when the content of the search field is changed.
454
this.OnSearchFieldChange = function()
458
clearTimeout(this.keyTimeout);
462
var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
464
if (searchValue != this.lastSearchValue)
466
if (searchValue != "")
468
this.keyTimeout = setTimeout(this.name + ".Search()", this.keyTimeoutLength);
472
if (this.mode == "HTML")
473
{ this.DOMPopupSearchResultsWindow().style.display = "none"; };
474
this.lastSearchValue = "";
481
Function: OnSearchTypeFocus
482
Called when focus is added or removed from the search type.
484
this.OnSearchTypeFocus = function(isActive)
486
this.Activate(isActive);
491
Function: OnSearchTypeChange
492
Called when the search type is changed.
494
this.OnSearchTypeChange = function()
496
var searchValue = this.DOMSearchField().value.replace(/ +/g, "");
498
if (searchValue != "")
506
// Group: Action Functions
507
// ________________________________________________________________________
511
Function: CloseResultsWindow
512
Closes the results window.
514
this.CloseResultsWindow = function()
516
this.DOMPopupSearchResultsWindow().style.display = "none";
517
this.Activate(false, true);
525
this.Search = function()
529
var searchValue = this.DOMSearchField().value.replace(/^ +/, "");
530
var searchTopic = this.DOMSearchType().value;
532
var pageExtension = searchValue.substr(0,1);
534
if (pageExtension.match(/^[a-z]/i))
535
{ pageExtension = pageExtension.toUpperCase(); }
536
else if (pageExtension.match(/^[0-9]/))
537
{ pageExtension = 'Numbers'; }
539
{ pageExtension = "Symbols"; };
542
var resultsPageWithSearch;
545
// indexSectionsWithContent is defined in searchdata.js
546
if (indexSectionsWithContent[searchTopic][pageExtension] == true)
548
resultsPage = this.resultsPath + '/' + searchTopic + pageExtension + '.html';
549
resultsPageWithSearch = resultsPage+'?'+escape(searchValue);
550
hasResultsPage = true;
554
resultsPage = this.resultsPath + '/NoResults.html';
555
resultsPageWithSearch = resultsPage;
556
hasResultsPage = false;
560
if (this.mode == "HTML")
561
{ resultsFrame = window.frames.MSearchResults; }
562
else if (this.mode == "FramedHTML")
563
{ resultsFrame = window.top.frames['Content']; };
566
if (resultsPage != this.lastResultsPage ||
568
// Bug in IE. If everything becomes hidden in a run, none of them will be able to be reshown in the next for some
569
// reason. It counts the right number of results, and you can even read the display as "block" after setting it, but it
570
// just doesn't work in IE 6 or IE 7. So if we're on the right page but the previous search had no results, reload the
571
// page anyway to get around the bug.
572
(browserType == "IE" && hasResultsPage &&
573
(!resultsFrame.searchResults || resultsFrame.searchResults.lastMatchCount == 0)) )
576
resultsFrame.location.href = resultsPageWithSearch;
579
// So if the results page is right and there's no IE bug, reperform the search on the existing page. We have to check if there
580
// are results because NoResults.html doesn't have any JavaScript, and it would be useless to do anything on that page even
582
else if (hasResultsPage)
584
// We need to check if this exists in case the frame is present but didn't finish loading.
585
if (resultsFrame.searchResults)
586
{ resultsFrame.searchResults.Search(searchValue); }
588
// Otherwise just reload instead of waiting.
590
{ resultsFrame.location.href = resultsPageWithSearch; };
594
var domPopupSearchResultsWindow = this.DOMPopupSearchResultsWindow();
596
if (this.mode == "HTML" && domPopupSearchResultsWindow.style.display != "block")
598
var domSearchType = this.DOMSearchType();
600
var left = GetXPosition(domSearchType);
601
var top = GetYPosition(domSearchType) + domSearchType.offsetHeight;
603
MoveToPosition(domPopupSearchResultsWindow, left, top);
604
domPopupSearchResultsWindow.style.display = 'block';
608
this.lastSearchValue = searchValue;
609
this.lastResultsPage = resultsPage;
614
// Group: Activation Functions
615
// Functions that handle whether the entire panel is active or not.
616
// ________________________________________________________________________
622
Activates or deactivates the search panel, resetting things to their default values if necessary. You can call this on every
623
control's OnBlur() and it will handle not deactivating the entire panel when focus is just switching between them transparently.
627
isActive - Whether you're activating or deactivating the panel.
628
ignoreDeactivateDelay - Set if you're positive the action will deactivate the panel and thus want to skip the delay.
630
this.Activate = function(isActive, ignoreDeactivateDelay)
632
// We want to ignore isActive being false while the results window is open.
633
if (isActive || (this.mode == "HTML" && this.DOMPopupSearchResultsWindow().style.display == "block"))
635
if (this.inactivateTimeout)
637
clearTimeout(this.inactivateTimeout);
638
this.inactivateTimeout = 0;
641
this.DOMSearchPanel().className = 'MSearchPanelActive';
643
var searchField = this.DOMSearchField();
645
if (searchField.value == 'Search')
646
{ searchField.value = ""; }
648
else if (!ignoreDeactivateDelay)
650
this.inactivateTimeout = setTimeout(this.name + ".InactivateAfterTimeout()", this.inactivateTimeoutLength);
654
this.InactivateAfterTimeout();
660
Function: InactivateAfterTimeout
662
Called by <inactivateTimeout>, which is set by <Activate()>. Inactivation occurs on a timeout because a control may
663
receive OnBlur() when focus is really transferring to another control in the search panel. In this case we don't want to
664
actually deactivate the panel because not only would that cause a visible flicker but it could also reset the search value.
665
So by doing it on a timeout instead, there's a short period where the second control's OnFocus() can cancel the deactivation.
667
this.InactivateAfterTimeout = function()
669
this.inactivateTimeout = 0;
671
this.DOMSearchPanel().className = 'MSearchPanelInactive';
672
this.DOMSearchField().value = "Search";
674
this.lastSearchValue = "";
675
this.lastResultsPage = "";
682
/* ________________________________________________________________________________________________________
685
_________________________________________________________________________________________________________
687
The class that handles everything on the search results page.
688
_________________________________________________________________________________________________________
692
function SearchResults(name, mode)
696
The mode the search is going to work in, such as "HTML" or "FramedHTML".
702
The number of matches from the last run of <Search()>.
704
this.lastMatchCount = 0;
709
Toggles the visibility of the passed element ID.
711
this.Toggle = function(id)
713
if (this.mode == "FramedHTML")
716
var parentElement = document.getElementById(id);
718
var element = parentElement.firstChild;
720
while (element && element != parentElement)
722
if (element.nodeName == 'DIV' && element.className == 'ISubIndex')
724
if (element.style.display == 'block')
725
{ element.style.display = "none"; }
727
{ element.style.display = 'block'; }
730
if (element.nodeName == 'DIV' && element.hasChildNodes())
731
{ element = element.firstChild; }
732
else if (element.nextSibling)
733
{ element = element.nextSibling; }
738
element = element.parentNode;
740
while (element && element != parentElement && !element.nextSibling);
742
if (element && element != parentElement)
743
{ element = element.nextSibling; };
752
Searches for the passed string. If there is no parameter, it takes it from the URL query.
754
Always returns true, since other documents may try to call it and that may or may not be possible.
756
this.Search = function(search)
760
search = window.location.search;
761
search = search.substring(1); // Remove the leading ?
762
search = unescape(search);
765
search = search.replace(/^ +/, "");
766
search = search.replace(/ +$/, "");
767
search = search.toLowerCase();
769
if (search.match(/[^a-z0-9]/)) // Just a little speedup so it doesn't have to go through the below unnecessarily.
771
search = search.replace(/\_/g, "_und");
772
search = search.replace(/\ +/gi, "_spc");
773
search = search.replace(/\~/g, "_til");
774
search = search.replace(/\!/g, "_exc");
775
search = search.replace(/\@/g, "_att");
776
search = search.replace(/\#/g, "_num");
777
search = search.replace(/\$/g, "_dol");
778
search = search.replace(/\%/g, "_pct");
779
search = search.replace(/\^/g, "_car");
780
search = search.replace(/\&/g, "_amp");
781
search = search.replace(/\*/g, "_ast");
782
search = search.replace(/\(/g, "_lpa");
783
search = search.replace(/\)/g, "_rpa");
784
search = search.replace(/\-/g, "_min");
785
search = search.replace(/\+/g, "_plu");
786
search = search.replace(/\=/g, "_equ");
787
search = search.replace(/\{/g, "_lbc");
788
search = search.replace(/\}/g, "_rbc");
789
search = search.replace(/\[/g, "_lbk");
790
search = search.replace(/\]/g, "_rbk");
791
search = search.replace(/\:/g, "_col");
792
search = search.replace(/\;/g, "_sco");
793
search = search.replace(/\"/g, "_quo");
794
search = search.replace(/\'/g, "_apo");
795
search = search.replace(/\</g, "_lan");
796
search = search.replace(/\>/g, "_ran");
797
search = search.replace(/\,/g, "_com");
798
search = search.replace(/\./g, "_per");
799
search = search.replace(/\?/g, "_que");
800
search = search.replace(/\//g, "_sla");
801
search = search.replace(/[^a-z0-9\_]i/gi, "_zzz");
804
var resultRows = document.getElementsByTagName("div");
808
while (i < resultRows.length)
810
var row = resultRows.item(i);
812
if (row.className == "SRResult")
814
var rowMatchName = row.id.toLowerCase();
815
rowMatchName = rowMatchName.replace(/^sr\d*_/, '');
817
if (search.length <= rowMatchName.length && rowMatchName.substr(0, search.length) == search)
819
row.style.display = "block";
823
{ row.style.display = "none"; };
829
document.getElementById("Searching").style.display="none";
832
{ document.getElementById("NoMatches").style.display="block"; }
834
{ document.getElementById("NoMatches").style.display="none"; }
836
this.lastMatchCount = matches;