loadJQuery(); addPolyfills(); addExtensions(); // --- Get infos about this script file --- var scriptElement = document.querySelector('script[src$="static/content.js"]'); var scriptDir = scriptElement.src.substr(0, scriptElement.src.lastIndexOf('/')); // --- User data --- var user = { fontSize: 1.0, clickTab: 0, displaySidebar: true, colorScheme: null, collapseQuickRef: false } // --- Cached data --- // To have the data remain while navigating through the docs, it'll be stored into // sessionStorage. Fallbacks to window.name if sessionStorage is not supported. // Note: CHM doesn't support window.localStorage/sessionStorage or cookies. var cache = { colorScheme: user.colorScheme, scriptDir: scriptDir, fontSize: user.fontSize, forceNoFrame: false, forceNoScript: false, clickTab: user.clickTab, displaySidebar: user.displaySidebar, sidebarWidth: '18em', collapseQuickRef: user.collapseQuickRef, RightIsFocused: true, toc_clickItem: null, toc_clickItemTemp: null, toc_scrollPos: 0, index_filter:-1, index_input: "", index_clickItem: 0, index_scrollPos: 0, search_data: {}, search_highlightWords: false, search_input: "", search_clickItem: 0, search_scrollPos: 0, load: function() { if (window.sessionStorage) { var data = JSON.parse(window.sessionStorage.getItem('data')); if (!data) return false; } else try { var data = JSON.parse(window.name); } catch(e) { return false; } if (data.scriptDir != scriptDir) return false; $.extend(this, data); return true; }, save: function() { if (window.sessionStorage) window.sessionStorage.setItem('data', JSON.stringify(this)); else window.name = JSON.stringify(this); }, set: function(prop, value) { this[prop] = value; try { postMessageToFrame('updateCache', [prop, value]) } catch(e) {} return value; } }; // --- Main Execute Area --- // Set global variables: var forceNoScript = forceNoScript || false; var isCacheLoaded = cache.load(); var workingDir = getWorkingDir(); var relPath = getRelativePath(location.href, workingDir); var equivPath = $('meta[name|="ahk:equiv"]').prop('content'); var isInsideCHM = (location.href.search(/::/) > 0) ? 1 : 0; var supportsHistory = (history.replaceState) && !isInsideCHM; var isFrameCapable = !cache.forceNoFrame && (isInsideCHM || supportsHistory); var isInsideFrame = (window.self !== window.top); var isSearchBot = navigator.userAgent.match(/googlebot|bingbot|slurp/i); var isTouch = !!("ontouchstart" in window) || !!(navigator.msMaxTouchPoints); // http://stackoverflow.com/a/9851769 var isOpera = (!!window.opr && !!opr.addons) || !!window.opera || navigator.userAgent.indexOf(' OPR/') >= 0; // Opera 8.0+ var isFirefox = typeof InstallTrigger !== 'undefined'; // Firefox 1.0+ var isSafari = Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0; // At least Safari 3+: "[object HTMLElementConstructor]" var isIE = /*@cc_on!@*/false || !!document.documentMode; // Internet Explorer 6-11 var isIE8 = !-[1,]; // Internet Explorer 8 or below var isIE9 = navigator.userAgent.match(/MSIE 9/); // Internet Explorer 9 var isEdge = !isIE && !!window.StyleMedia; // Edge 20+ var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.csi); // Chrome 1+ var isBlink = (isChrome || isOpera) && !!window.CSS; // Blink engine detection var structure = new ctor_structure; var toc = new ctor_toc; var index = new ctor_index; var search = new ctor_search; var features = new ctor_features; var translate = {dataPath: scriptDir + '/source/data_translate.js'}; var deprecate = {dataPath: scriptDir + '/source/data_deprecate.js'}; scriptElement.insertAdjacentHTML('afterend', structure.metaViewport); var isPhone = (document.documentElement.clientWidth <= 600); (function() { // Exit the script if the user is a search bot. This is done because we want // to prevent the search bot from parsing the elements added via javascript, // otherwise the search results would be distorted: if (isSearchBot) return; // Get user data: if (!isCacheLoaded) { if (isInsideCHM) { var m = scriptDir.match(/mk:@MSITStore:(.*?)\\[^\\]+\.chm/i); if (m[1]) loadScript(decodeURI(m[1]) + '\\chm_config.js', function () { try { $.extend(cache, overwriteProps(user, config)); setInitialSettings(); } catch (e) {} }); } else if (window.localStorage) { config = JSON.parse(window.localStorage.getItem('config')); $.extend(cache, overwriteProps(user, config)); setInitialSettings(); } else if (navigator.cookieEnabled) { config = document.cookie.match(/config=([^;]+)/); config && (config = JSON.parse(config[1])); $.extend(cache, overwriteProps(user, config)); setInitialSettings(); } } else setInitialSettings(); function setInitialSettings() { // font size if (!isFrameCapable && cache.fontSize != 1) $('head').append(''); // color scheme structure.setScheme(cache.colorScheme); } // Exit the script on sites which doesn't need the sidebar: if (forceNoScript || cache.forceNoScript) return; // Special treatments for pages inside a frame: if (isFrameCapable) { if (isInsideFrame) { if (cache.fontSize != 1) $('head').append(''); normalizeParentURL = function() { var url = getUrlParameter('url'); postMessageToParent('normalizeURL', [url ? {"href": url} : $.extend({}, window.location), document.title, supportsHistory ? history.state : null, equivPath]); if (cache.toc_clickItemTemp) if (supportsHistory) history.replaceState($.extend(history.state, {toc_clickItemTemp: cache.toc_clickItemTemp}), null, null); cache.set('toc_clickItemTemp', null); } normalizeParentURL(); $(window).on('hashchange', normalizeParentURL); structure.setScheme(cache.colorScheme); structure.addShortcuts(); structure.addAnchorFlash(); structure.saveCacheBeforeLeaving(); if (!isIE) { structure.hideFrameBeforeLeaving(); postMessageToParent('unhideFrame', []); } $(document).ready(function() { $('html').attr({ id: 'right'}); features.add(); }); $(window).on('message onmessage', function(event) { var data = parseJSON(event.originalEvent.data); if (Array.isArray(data) === false) return; switch(data[0]) { case 'updateCache': if(typeof data[1] === 'object') $.extend(cache, data[1]); else cache[data[1]] = data[2]; break; case 'highlightWords': search.highlightWords(data[1]); break; case 'scrollToMatch': search.scrollToMatch(data[1]); break; case 'setScheme': structure.setScheme(data[1]); break; } }); return; } else { $(window).on('message onmessage', function(event) { var data = parseJSON(event.originalEvent.data); if (Array.isArray(data) === false) return; switch(data[0]) { case 'normalizeURL': var relPath = getRelativePath(data[1].href, workingDir); try { if (history.replaceState) history.replaceState(null, null, data[1].href); } catch(e) { if (history.replaceState) history.replaceState(null, null, "?frame=" + encodeURI(relPath).replace(/#/g, '%23')); } document.title = data[2]; if (structure.modifyTools) structure.modifyTools(relPath, data[4]); if ($('#left > div.toc li > span.selected a').attr('href') == data[1].href) break; else if (data[3] && data[3].toc_clickItemTemp) { toc.deselect($('#left > div.toc')); $('#left > div.toc li > span').eq(data[3].toc_clickItemTemp).trigger('select'); } else toc.preSelect($('#left > div.toc'), data[1]); break; case 'pressKey': structure.pressKey(data[1]); break; case 'updateQuickRef': structure.updateQuickRef(data[1], data[2]); break; case 'hideFrame': $('#right .load').hide().show(0); // reload animation document.getElementById('frame').className = 'hidden'; break; case 'unhideFrame': document.getElementById('frame').className = 'visible'; break; } }); $(window).on('hashchange', function() { structure.openSite(location.href); }); } } // Add elements for sidebar: structure.build(); // Load current URL into frame: if (isFrameCapable) $(document).ready(function() { if (window.sessionStorage) window.sessionStorage.setItem('data', JSON.stringify(cache)); else document.getElementById('frame').contentWindow.name = JSON.stringify(cache); structure.openSite(scriptDir + '/../' + (getUrlParameter('frame') || relPath)); }); // Modify the site: document.getElementsByTagName('body')[0].className += ' body'; structure.modify(); if (!isFrameCapable) $(document).ready(features.add); toc.modify(); index.modify(); search.modify(); })(); // --- Constructor: Table of content --- function ctor_toc() { var self = this; self.dataPath = scriptDir + '/source/data_toc.js'; self.create = function(input) { // Create and add TOC items. var ul = document.createElement("ul"); for(var i = 0; i < input.length; i++) { var text = input[i][0]; var path = input[i][1]; var subitems = input[i][2]; if (path != '') { var el = document.createElement("a"); el.href = workingDir + path; if (cache.deprecate_data[path]) el.className = "deprecated"; } else var el = document.createElement("button"); if (isIE8) el.innerHTML = text; else { el.setAttribute("data-content", text); el.setAttribute("aria-label", text); } var span = document.createElement("span"); span.innerHTML = el.outerHTML; var li = document.createElement("li"); li.title = text; if (cache.deprecate_data[path]) li.title += "\n\n" + T("Deprecated. New scripts should use {0} instead.").format(cache.deprecate_data[path]); if (subitems != undefined && subitems.length > 0) { li.className = "closed"; li.innerHTML = span.outerHTML; li.innerHTML += self.create(subitems).outerHTML; } else li.innerHTML = span.outerHTML; ul.innerHTML += li.outerHTML; } return ul; }; // --- Modify the elements of the TOC tab --- self.modify = function() { if (!retrieveData(self.dataPath, "toc_data", "tocData", self.modify)) return; if (!retrieveData(deprecate.dataPath, "deprecate_data", "deprecateData", self.modify)) return; if (!retrieveData(translate.dataPath, "translate_data", "translateData", self.modify)) return; $toc = $('#left div.toc').html(self.create(cache.toc_data)); $tocList = $toc.find('li > span'); // --- Fold items with subitems --- $toc.find('li > ul').hide(); // --- Hook up events --- // Select the item on click: registerEvent($toc, 'click', 'li > span', function() { $this = $(this); cache.set('toc_clickItem', $tocList.index(this)); cache.set('toc_scrollPos', $toc.scrollTop()); // Fold/unfold item with subitems: if ($this.parent().has("ul").length) { $this.siblings("ul").slideToggle(100); $this.closest("li").toggleClass("closed opened"); } // Higlight and open item with link: if ($this.has("a").length) { self.deselect($toc); $this.trigger('select'); structure.openSite($this.children('a').attr('href')); structure.focusContent(); return false; } }); // Highlight the item and parents on select: registerEvent($toc, 'select', 'li > span', function() { $this = $(this); // Highlight the item: $this.addClass("selected"); // Highlight its parents: $this.parent("li").has('ul').addClass('highlighted'); $this.parent().parents('li').addClass('highlighted'); // Unfold parent items: $this.parents("ul").show(); $this.parents("ul").closest("li").removeClass('closed').addClass('opened'); }); // --- Show scrollbar on mouseover --- if (!isTouch) // if not touch device. { $toc.css("overflow", "hidden").hover(function() { $(this).css("overflow", "auto"); }, function() { $(this).css("overflow", "hidden"); }); } self.preSelect($toc, location); if (!isFrameCapable || cache.search_input) $(document).ready(function() { setTimeout( function() { self.preSelect($toc, location); }, 0); }); }; self.preSelect = function($toc, url) { // Apply stored settings. var tocList = $toc.find('li > span'); var clicked = tocList.eq(cache.toc_clickItem); var found = null; var foundList = []; var foundNoHashList = []; var url_href = (url.href.slice(-1) == '/') ? url.href + 'index.htm' : url.href; for (var i = 0; i < tocList.length; i++) { var href = tocList[i].firstChild.href; if (!href) continue; // Search for items matching the address: if (href == url_href) foundList.push($(tocList[i])); // Search for items matching the address without anchor: else if (href == url_href.substring(0, url_href.length - url.hash.length)) foundNoHashList.push($(tocList[i])); } if (foundList.length) found = $(foundList).map($.fn.toArray); else if (foundNoHashList.length) found = $(foundNoHashList).map($.fn.toArray); var el = found; // If the last clicked item can be found in the matches, use it instead: if (clicked.is(found)) el = clicked; else cache.set('toc_scrollPos', ""); // Force calculated scrolling. // If items are found: if (el) { // Highlight items and parents: self.deselect($toc); el.trigger('select'); // Scroll to the last match: if (cache.toc_scrollPos != "" || cache.toc_scrollPos != 0) $toc.scrollTop(cache.toc_scrollPos); if (!isScrolledIntoView(el, $toc)) { el[el.length-1].scrollIntoView(false); $toc.scrollTop($toc.scrollTop()+100); } } } self.deselect = function($toc) { // Deselect all items. $toc.find("span.selected").removeClass("selected"); $toc.find(".highlighted").removeClass("highlighted"); } } // --- Constructor: Keyword search --- function ctor_index() { var self = this; self.dataPath = scriptDir + '/source/data_index.js'; self.create = function(input, filter) { // Create and add the index links. var output = '', label, path, type; var type_name = {2: T("function"), 4: T("operator"), 6: T("class")}; var lang = T('en'); var collator = window.Intl ? new Intl.Collator(lang) : null; if (collator) input.sort(function(a, b) { return collator.compare(a[0], b[0]); }); else input.sort(function(a, b) { return a[0].localeCompare(b[0], lang); }); for (var i = 0, len = input.length; i < len; i++) { label = input[i][0]; path = input[i][1]; type = input[i][2]; if (filter != -1 && type != filter) continue; // Append type name for ambiguities: if (filter == -1 && type && type_name[type]) if (input[i-1] && input[i-1][0] == label || input[i+1] && input[i+1][0] == label) label += ' (' + type_name[type] + ')'; var a = document.createElement("a"); a.href = workingDir + path; a.setAttribute("tabindex", "-1"); if (isIE8) a.innerHTML = label; else { a.setAttribute("data-content", label); a.setAttribute("aria-label", label); } output += a.outerHTML; } return output; }; self.modify = function() { // Modify the elements of the index tab. if (!retrieveData(self.dataPath, "index_data", "indexData", self.modify)) return; if (!retrieveData(translate.dataPath, "translate_data", "translateData", self.modify)) return; var $index = $('#left div.index'); var $indexSelect = $index.find('.select select'); var $indexInput = $index.find('.input input'); var $indexList = $index.find('div.list'); // --- Hook up events --- // Filter list on change: $indexSelect.on('change', function(e) { cache.set('index_filter', this.value); if(this.value == -1) $(this).addClass('empty'); else $(this).removeClass('empty'); $indexList.html(self.create(cache.index_data, this.value)); structure.addEventsForListBoxItems($indexList); }); // Select closest index entry and show color indicator on input: $indexInput.on('keyup input', function(e, noskip) { var $this = $(this); var prevInput = cache.index_input; // defaults to undefined var input = cache.set('index_input', $this.val().toLowerCase()); // if no input, remove color indicator and return: if (!input) { $this.removeAttr('class'); return; } // Skip subsequent index-matching if we have the same query as the last search, to prevent double execution: if (!noskip && input == prevInput) return; // Otherwise find the first item which matches the input value: var indexListChildren = $indexList.children(); var match = self.findMatch(indexListChildren, input); // Select the found item, scroll to it and add color indicator: if (match.length) { match.click(); // Scroll to 5th item below the match to improve readability: scrollTarget = match.next().next().next().next().next(); if (!scrollTarget.length) { scrollTarget = indexListChildren.last(); }; scrollTarget[0].scrollIntoView(false); $this.attr('class', 'match'); // 'items found' } else $this.attr('class', 'mismatch'); // 'items not found' }); $indexSelect.val(cache.index_filter).trigger('change'); self.preSelect($indexList, $indexInput); if (!isFrameCapable || cache.index_input) $(document).ready(function() { setTimeout( function() { self.preSelect($indexList, $indexInput); }, 0); }); }; self.findMatch = function(indexListChildren, input) { var match = {}; if (!input) return match; for (var i = 0; i < indexListChildren.length; i++) { var text = isIE8 ? indexListChildren[i].innerText : indexListChildren[i].getAttribute('data-content'); var listitem = text.substr(0, input.length).toLowerCase(); if (listitem == input) { match = indexListChildren.eq(i); break; } } return match; }; self.preSelect = function($indexList, $indexInput) { // Apply stored settings. var clicked = $indexList.children().eq(cache.index_clickItem); $indexInput.val(cache.index_input); if (cache.index_scrollPos == null) $indexInput.trigger('keyup', true); else { $indexList.scrollTop(cache.index_scrollPos); clicked.click(); } }; } // --- Constructor: Full text search --- function ctor_search() { var self = this; self.dataPath = scriptDir + '/source/data_search.js'; self.modify = function() { // Modify the elements of the search tab. if (!retrieveData(self.dataPath, "search_index", "SearchIndex", self.modify)) return; if (!retrieveData(self.dataPath, "search_files", "SearchFiles", self.modify)) return; if (!retrieveData(self.dataPath, "search_titles", "SearchTitles", self.modify)) return; var $search = $('#left div.search'); var $searchList = $search.find('div.list'); var $searchInput = $search.find('.input input'); var $searchCheckBox = $search.find('.checkbox input'); // --- Hook up events --- // Refresh the search list and show color indicator on input: $searchInput.on('keyup input', function(e, noskip) { var $this = $(this); var prevInput = cache.search_input; // defaults to undefined var input = cache.set('search_input', $this.val()); // if no input, empty the search list, remove color indicator and return: if (!input) { $searchList.empty(); $this.removeAttr('class'); return; } // Skip subsequent search if we have the same query as the last search, to prevent double execution: if (!noskip && input == prevInput) return; // Otherwise fill the search list: cache.set('search_data', self.create(input)); $searchList.html(cache.search_data); structure.addEventsForListBoxItems($searchList); // Select the first item and add color indicator: var searchListChildren = $searchList.children(); if (searchListChildren.length) { searchListChildren.first().click(); cache.set('search_clickItem', 0); $this.attr('class', 'match'); // 'items found' } else $this.attr('class', 'mismatch'); // 'items not found' }); self.preSelect($searchList, $searchInput, $searchCheckBox); if (!isFrameCapable) setTimeout( function() { self.preSelect($searchList, $searchInput, $searchCheckBox); }, 0); }; self.preSelect = function($searchList, $searchInput, $searchCheckBox) { // Apply stored settings. $searchInput.val(cache.search_input); if (cache.search_scrollPos == null) $searchInput.trigger('keyup', true); else { $searchList.html(cache.search_data); structure.addEventsForListBoxItems($searchList); $searchList.scrollTop(cache.search_scrollPos); $searchList.children().eq(cache.search_clickItem).click(); } $searchCheckBox.prop('checked', cache.search_highlightWords); }; self.convertToArray = function(SearchText) { // Convert text to array. // Normalize whitespace: SearchText = SearchText.toLowerCase().replace(/^ +| +$| +(?= )|\+/, ''); if (SearchText == '') return ''; else // split and remove undefined or empty strings return $(SearchText.split(' ')).filter(function(){return (!!this)}); } self.highlightWords = function(words) { var content = $(isInsideFrame ? 'body' : '#right .area'); if(words) { var qry = self.convertToArray(words); for (var i = 0; i < qry.length; i++) { content.highlight(qry[i]); } self.scrollToMatch(); // Scroll to first match. } else content.removeHighlight(); } self.scrollToMatch = function(direction) { var matches = $(isInsideFrame ? 'body' : '#right .area').find('span.search_highlight'); if (!matches.length) return; var currMatch = matches.filter('.current'); if (currMatch.length) { index = matches.index(currMatch) + (direction == 'next' ? 1 : -1); if (index > matches.length - 1) index = 0; else if (index < 0) index = matches.length - 1; currMatch.removeClass('current'); } else index = 0; matches.eq(index).addClass('current'); matches.eq(index)[0].scrollIntoView(isIE8 ? true : {block: 'center'}); } self.create = function(qry) { // Create search list. var PartialIndex = {}; var RESULT_LIMIT = 50; qry = self.convertToArray(qry); if (qry == '') return function file_has_all_words(file_index, words, start) { for ( ; start < words.length; ++start) { var iw = index_partial(words[start]) if (!iw || iw.indexOf(file_index) == -1) return false } return true } // Get each word from index and clone for modification below: var all_results = [] for (var i = 0; i < qry.length; ++i) { var t = qry[i].replace(/(\(|\(\))$/,'') // special case for page names ending with () var w = index_partial(t) w = w ? w.slice() : [] all_results[i] = get_results(w) } var ranked = rank_results(all_results,qry) return append_results(ranked); // Get normal results for each term: function get_results(w) { var c = 0 var ret = [] for (var i = 0; i < w.length && c < RESULT_LIMIT; ++i) { if (!file_has_all_words(w[i], qry, 1)) continue // Skip files which don't have all the words. c++ var f = cache.search_files[w[i]] // data.files excludes '.htm' to save space, so add it back: if (f.indexOf('#') != -1) f = f.replace('#', '.htm#') else f = f + '.htm' // var ret_i = { u: location.href.replace(/\/docs\/.*/, "/docs/" + f), var ret_i = { u: f, t: (cache.search_titles[w[i]] || f) } ret.push(ret_i) } return ret } function rank_results(aro,terms) { // Organize the info: var aro_k = [] var aro_ua = [] var aro_ka = [] for (var i = 0; i < aro.length; ++i) { aro_k[i] = [] if (aro[i] === undefined) { continue; } for (var j = 0; j < aro[i].length; ++j) { aro_k[i].push(aro[i][j].t) aro_ka.push(aro[i][j].t) aro_ua[aro[i][j].t] = aro[i][j].u } } // Assemble list of unique results: var ukeys = [] for (var i = 0; i < aro_ka.length; ++i) if (ukeys.indexOf(aro_ka[i]) == -1) ukeys.push(aro_ka[i]) // The lower the rank the better // normal ranking (based on page contents): var uranks = [] for (var i = 0; i < ukeys.length; ++i) { uranks[ukeys[i]] = [] for (var j = 0; j < aro_k.length; ++j) { uranks[ukeys[i]][j] = ukeys[i].indexOf(aro_k[j]) } } // Added ranking (based on page names) // and calculate the ranks: for (var i = 0; i < ukeys.length; ++i){ var name = ukeys[i] var tmp = uranks[name] // If the name contains any of the search terms: if ( anyContains(name.toLowerCase(),terms) ) { tmp.push(0) // Give it a better rank. } else { tmp.push(1,8) // Give it a worse rank, Tweakable! } uranks[name] = array_avg(tmp) } // Sort results by rank average and finalize: var ret = [] for (var i = 0; i < ukeys.length; ++i){ var name = ukeys[i] var url = aro_ua[name] var avg = uranks[name] ret.push({n:name,u:url,a:avg}) } ret.sort(function(a,b){return (a.a - b.a)}) ret.slice(0,RESULT_LIMIT) return ret } function array_avg(arr) { var sum = 0 var total = arr.length for (var j = 0; j < arr.length; ++j){ if ( (arr[j] < 0) || (arr[j-1] == arr[j]) ) total -= 1 // Give a worse rank, duplicate ranks are ignored. else sum += arr[j] } return (sum/total) } function anyContains(str, arr) { // http://stackoverflow.com/a/5582640/883015 for (var i = 0, len = arr.length; i < len; ++i) { if (str.indexOf(arr[i]) != -1) return 1 } return 0 } function append_results(ro) { var output = ''; for (var t = 0; t < ro.length && t < RESULT_LIMIT; ++t) { var a = document.createElement("a"); a.href = workingDir + ro[t].u; a.setAttribute("tabindex", "-1"); if (isIE8) a.innerHTML = ro[t].n; else { a.setAttribute("data-content", ro[t].n); a.setAttribute("aria-label", ro[t].n); } output += a.outerHTML; } return output; } function decode_numbers(a) { // Decode a string of [a-zA-Z] based 'numbers' var n = [] for (i = 0; i < a.length; i += 2) n.push(decode_number(a.substr(i, 2))) return n } function decode_number(a) { // Decode a number encoded by encode_number() in build_search.ahk: var n = 0, c for (var i = 0; i < a.length; ++i) { c = a.charCodeAt(i) n = n*52 + c - ((c >= 97 && c <= 122) ? 97 : 39) } return n } function index_whole(word) { // Return a word from the index, decoding the list of files // if it hasn't been decoded already: var files = cache.search_index[word] if (typeof(files) == "string") { files = decode_numbers(files) cache.search_index[word] = files } return files } function index_partial(word) { if (word[0] == '+') // + prefix disables partial matching. return index_whole(word.substr(1)) // Check if we've already indexed this partial word. var files = PartialIndex[word] if (files !== undefined) return files // Find all words in search.index which *contain* this word // and cache the result. files = [] var files_low = [] for (iw in cache.search_index) { var p = iw.indexOf(word) if (p != -1) files.push.apply(p == 0 ? files : files_low, index_whole(iw)) } files = files.concat(files_low) var unique = [] for (var i = 0; i < files.length; ++i) if (unique.indexOf(files[i]) == -1) unique.push(files[i]) PartialIndex[word] = unique return unique } }; } // --- Constructor: Navigation structure --- function ctor_structure() { var self = this; self.metaViewport = ''; self.template = '
  • Δ
  • ¬