$(function () { 'use strict'; // Allow user configuration? const allowRegex = true; const minInputForSearch = 1; const minInputForFullText = 2; const expandAllOnInputWithoutSearch = true; function constructRegex(searchTerm, makeRe, allowRegex) { try { if (allowRegex) { return makeRe(searchTerm); } } catch (e) { } // In case of invalid regexp fall back to non-regexp, but still allow . to match / return makeRe(searchTerm.replace(/[.*+?^${}()|[\]\\]/g, '\\$&').replace(/\\\./g, '[./]')); } function getWeightFunction(searchTerm, allowRegex) { function makeRe(searchTerm) { return { begin: new RegExp('\\b' + searchTerm), // Begin matches word boundary baseName: new RegExp('\\b' + searchTerm + '[^/]*$'), // Begin matches word boundary of class / module name fullName: new RegExp('\\b' + searchTerm + '(?:[~.]|$)'), // Complete word(s) of class / module matches completeName: new RegExp('^' + searchTerm + '$') // Match from start to finish } } const re = constructRegex(searchTerm, makeRe, allowRegex); return function (matchedItem, beginOnly) { // We could get smarter on the weight here const name = matchedItem.dataset.name; if (beginOnly) { return re.baseName.test(name) ? 100 : 1; } // If everything else is equal, prefer shorter names, and prefer classes over modules let weight = 10000 + matchedItem.dataset.longname.length - name.length * 100; if (re.begin.test(name)) { weight += 10000; if (re.baseName.test(name)) { weight += 10000; if (re.fullName.test(name)) { weight += 10000; if (re.completeName.test(name)) { weight += 10000; } } } } return weight; } } const search = (function () { const $navList = $('.navigation-list'); const navListNode = $navList.get(0); let $classItems; let $members; let stateClass = (function () { $navList.removeClass('search-started searching'); $navList.addClass('search-empty'); return 'search-empty'; })(); let manualToggles = {}; // Show an item related a current documentation automatically const longname = $('.page-title').data('filename') .replace(/\.[a-z]+$/, '') .replace('module-', 'module:') .replace(/_/g, '/') .replace(/-/g, '~'); const currentItem = navListNode.querySelector('.item[data-longname="' + longname + '"]'); if (currentItem) { $navList.prepend(currentItem); } return { $navList: $navList, $currentItem: currentItem ? $(currentItem) : undefined, lastSearchTerm: undefined, lastState: {}, lastClasses: undefined, getClassList: function () { return $classItems || ($classItems = $navList.find('li.item')); }, getMembers: function () { return $members || ($members = $navList.find('.item li')); }, changeStateClass: function (newClass) { if (newClass !== stateClass) { navListNode.classList.remove(stateClass); navListNode.classList.add(newClass); stateClass = newClass; } }, manualToggle: function ($node, show) { $node.addClass('toggle-manual'); $node.toggleClass('toggle-manual-hide', !show); $node.toggleClass('toggle-manual-show', show); manualToggles[$node.data('longname')] = $node; }, clearManualToggles: function() { for (let clsName in manualToggles) { manualToggles[clsName].removeClass('toggle-manual toggle-manual-show toggle-manual-hide'); } manualToggles = {}; }, }; })(); const dummy = {subItems: {}}; function clearOldMatches(lastState, searchState) { for (let itemName in lastState) { const lastItem = lastState[itemName]; const item = searchState[itemName]; if (!item) { lastItem.item.classList.remove('match'); } if (lastItem.subItems) { clearOldMatches(lastItem.subItems, (item || dummy).subItems); } } } function doSearch(searchTerm) { searchTerm = searchTerm.toLowerCase(); const lastSearchTerm = search.lastSearchTerm; if (searchTerm === lastSearchTerm) { return; } // Avoid layout reflow by scrolling to top first. search.$navList.scrollTop(0); search.lastSearchTerm = searchTerm; search.clearManualToggles(); if (searchTerm.length < minInputForSearch) { const state = searchTerm.length && expandAllOnInputWithoutSearch ? 'search-started' : 'search-empty'; search.changeStateClass(state); if (lastSearchTerm !== undefined && lastSearchTerm.length >= minInputForSearch) { // Restore the original, sorted order search.$navList.append(search.getClassList()); } if (state === 'search-empty' && search.$currentItem) { search.manualToggle(search.$currentItem, true); } search.lastClasses = undefined; } else { search.changeStateClass('searching'); const beginOnly = searchTerm.length < minInputForFullText; const getSearchWeight = getWeightFunction(searchTerm, allowRegex); const re = constructRegex(searchTerm, function (searchTerm) { return new RegExp((beginOnly ? '\\b' : '') + searchTerm); }, allowRegex); const navList = search.$navList.get(0); const classes = []; const searchState = {}; search.getClassList().each(function (i, classEntry) { const className = classEntry.dataset.longname; if (!(className in searchState) && re.test(classEntry.dataset.name)) { const cls = searchState[className] = { item: classEntry, // Do the weight thing weight: getSearchWeight(classEntry, beginOnly) * 100000, subItems: {} }; classes.push(cls); classEntry.classList.add('match'); } }); search.getMembers().each(function (i, li) { const name = li.dataset.name; if (re.test(name)) { const itemMember = li.parentElement.parentElement; const classEntry = itemMember.parentElement; const className = classEntry.dataset.longname; let cls = searchState[className]; if (!cls) { cls = searchState[className] = { item: classEntry, weight: 0, subItems: {} }; classes.push(cls); classEntry.classList.add('match'); } cls.weight += getSearchWeight(li, true); const memberType = itemMember.dataset.type; let members = cls.subItems[memberType]; if (!members) { members = cls.subItems[memberType] = { item: itemMember, subItems: {} }; itemMember.classList.add('match'); } members.subItems[name] = { item: li }; li.classList.add('match'); } }); classes.sort(function (a, b) { return b.weight - a.weight; }); clearOldMatches(search.lastState, searchState); search.lastState = searchState; search.lastClasses = classes; for (let i = 0, ii = classes.length; i < ii; ++i) { navList.appendChild(classes[i].item); } } } const searchInput = $('#search').get(0); // Skip searches when typing fast. let key; function queueSearch() { if (!key) { key = setTimeout(function () { key = undefined; doSearch(searchInput.value); }, 0); } } // Search Items searchInput.addEventListener('input', queueSearch); searchInput.addEventListener('keydown', function(e) { if (e.key === 'Enter') { doSearch(searchInput.value); const first = search.lastClasses ? search.lastClasses[0].item : null; if (first) { window.location.href = first.querySelector('.title a').href; } } }); doSearch(searchInput.value); searchInput.focus(); // Toggle when click an item element search.$navList.on('click', '.toggle', function (e) { if (event.target.tagName.toLowerCase() === 'a') { return; } const clsItem = $(this).closest('.item'); const show = !clsItem.hasClass('toggle-manual-show'); search.manualToggle(clsItem, show); }); // warn about outdated version var currentVersion = document.getElementById('package-version').innerHTML; var packageUrl = 'https://raw.githubusercontent.com/openlayers/openlayers.github.io/build/package.json'; fetch(packageUrl).then(function(response) { return response.json(); }).then(function(json) { var latestVersion = json.version; document.getElementById('latest-version').innerHTML = latestVersion; var url = window.location.href; var branchSearch = url.match(/\/([^\/]*)\/apidoc\//); var cookieText = 'dismissed=-' + latestVersion + '-'; var dismissed = document.cookie.indexOf(cookieText) != -1; if (branchSearch && !dismissed && /^v[0-9\.]*$/.test(branchSearch[1]) && currentVersion != latestVersion) { var link = url.replace(branchSearch[0], '/latest/apidoc/'); fetch(link, {method: 'head'}).then(function(response) { var a = document.getElementById('latest-link'); a.href = response.status == 200 ? link : '../../latest/apidoc/'; }); var latestCheck = document.getElementById('latest-check'); latestCheck.style.display = ''; document.getElementById('latest-dismiss').onclick = function() { latestCheck.style.display = 'none'; document.cookie = cookieText; } } }); });