You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
278 lines
9.5 KiB
JavaScript
278 lines
9.5 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) ? 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;
|
|
}
|
|
}
|
|
});
|
|
});
|