Files
openlayers/config/jsdoc/api/template/static/scripts/main.js
Maximilian Krög 2dec296aae Use css to show / hide items; track matched items
... to improve search speed.

Instead of marking all items as hidden, and then marking the matched
ones again, this keeps track of all matched items and only
removes those that no longer match and adds those that previously did
not match.
2020-02-21 22:57:53 +01:00

306 lines
10 KiB
JavaScript

$(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) ? 10000 : 0;
}
// If everything else is equal, prefer shorter names, and prefer classes over modules
let weight = matchedItem.dataset.longname.length - name.length * 100;
if (name.match(re.begin)) {
weight += 100000;
if (re.baseName.test(name)) {
weight += 10000000;
if (re.fullName.test(name)) {
weight += 100000000;
if (re.completeName.test(name)) {
weight += 1000000000;
}
}
}
}
return weight;
}
}
const search = (function () {
const $nav = $('.navigation');
const $navList = $nav.find('.list');
let $classItems;
let $members;
let stateClass = (function () {
$nav.removeClass('search-started searching');
$nav.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 = $navList.find('.item[data-longname="' + longname + '"]:eq(0)');
$currentItem.prependTo($navList);
$currentItem.addClass('item-current');
return {
$nav: $nav,
$navList: $navList,
$currentItem: $currentItem,
lastSearchTerm: undefined,
lastState: {},
getClassList: function () {
return $classItems || ($classItems = $navList.find('li.item'));
},
getMembers: function () {
return $members || ($members = $navList.find('.item li'));
},
changeStateClass: function (newClass) {
if (newClass !== stateClass) {
const navNode = $nav.get(0);
navNode.classList.remove(stateClass);
navNode.classList.add(newClass);
stateClass = newClass;
}
},
manualToggle: function ($node, show) {
$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-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.manualToggle(search.$currentItem, true);
}
} else {
search.changeStateClass('searching');
searchTerm = searchTerm.toLowerCase();
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.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,
// Do the weight thing
weight: getSearchWeight(classEntry, beginOnly) * 10000,
subItems: {}
};
classes.push(cls);
classEntry.classList.add('match');
}
cls.weight += getSearchWeight(li, true) + 1;
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');
}
});
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) * 10000,
subItems: {}
};
classes.push(cls);
classEntry.classList.add('match');
}
});
clearOldMatches(search.lastState, searchState);
search.lastState = searchState;
classes.sort(function (a, b) {
return a.weight - b.weight;
});
for (let i = classes.length - 1; i >= 0; --i) {
navList.appendChild(classes[i].item);
}
}
}
const searchInput = $('#search').get(0);
// Skip searches when typing fast.
let key;
let start;
const brandNode = document.querySelector('.brand');
function queueSearch() {
if (!key) {
start = Date.now();
key = setTimeout(function () {
key = undefined;
const searchTerm = searchInput.value;
doSearch(searchTerm);
const time = Date.now() - start
brandNode.innerHTML = time + ' ms';
const msg = [searchTerm + ':', time, 'ms'];
if (searchTerm.length >= minInputForSearch) {
msg.push(search.lastState);
}
console.log.apply(console, msg);
start = undefined;
}, 0);
}
}
// Search Items
searchInput.addEventListener('input', queueSearch);
if (searchInput.value) {
doSearch(searchInput.value);
}
// Toggle when click an item element
search.$nav.on('click', '.toggle', function (e) {
const clsItem = $(this).closest('.item');
let shown;
clsItem.find('.member-list').each(function (i, v) {
shown = $(v).is(':visible');
return !shown;
});
search.manualToggle(clsItem, !shown);
});
// Auto resizing on navigation
var _onResize = function () {
var height = $(window).height();
var $el = $('.navigation');
$el.height(height).find('.list').height(height - 133);
};
$(window).on('resize', _onResize);
_onResize();
var currentVersion = document.getElementById('package-version').innerHTML;
// warn about outdated version
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;
}
}
});
// create source code links to github
var srcLinks = $('div.tag-source');
srcLinks.each(function(i, el) {
var textParts = el.innerHTML.trim().split(', ');
var link = 'https://github.com/openlayers/openlayers/blob/v' + currentVersion + '/src/ol/' +
textParts[0];
el.innerHTML = '<a href="' + link + '">' + textParts[0] + '</a>, ' +
'<a href="' + link + textParts[1].replace('line ', '#L') + '">' +
textParts[1] + '</a>';
});
});