dcat/doc/api/static-assets/script.js

501 lines
15 KiB
JavaScript

/*
*
* Update script.js versions in all lib/templates when modifying this file!
*
*/
function initSideNav() {
const leftNavToggle = document.getElementById('sidenav-left-toggle');
const leftDrawer = document.querySelector('.sidebar-offcanvas-left');
const overlay = document.getElementById('overlay-under-drawer');
function toggleBoth() {
if (leftDrawer) {
leftDrawer.classList.toggle('active');
}
if (overlay) {
overlay.classList.toggle('active');
}
}
if (overlay) {
overlay.addEventListener('click', toggleBoth);
}
if (leftNavToggle) {
leftNavToggle.addEventListener('click', toggleBoth);
}
}
function saveLeftScroll() {
const leftSidebar = document.getElementById('dartdoc-sidebar-left');
sessionStorage.setItem('dartdoc-sidebar-left-scrollt' + window.location.pathname, leftSidebar.scrollTop.toString());
sessionStorage.setItem('dartdoc-sidebar-left-scrolll' + window.location.pathname, leftSidebar.scrollLeft.toString());
}
function saveMainContentScroll() {
const mainContent = document.getElementById('dartdoc-main-content');
sessionStorage.setItem('dartdoc-main-content-scrollt' + window.location.pathname, mainContent.scrollTop.toString());
sessionStorage.setItem('dartdoc-main-content-scrolll' + window.location.pathname, mainContent.scrollLeft.toString());
}
function saveRightScroll() {
const rightSidebar = document.getElementById('dartdoc-sidebar-right');
sessionStorage.setItem('dartdoc-sidebar-right-scrollt' + window.location.pathname, rightSidebar.scrollTop.toString());
sessionStorage.setItem('dartdoc-sidebar-right-scrolll' + window.location.pathname, rightSidebar.scrollLeft.toString());
}
function restoreScrolls() {
const leftSidebar = document.getElementById('dartdoc-sidebar-left');
const mainContent = document.getElementById('dartdoc-main-content');
const rightSidebar = document.getElementById('dartdoc-sidebar-right');
try {
const leftSidebarX = sessionStorage.getItem('dartdoc-sidebar-left-scrolll' + window.location.pathname);
const leftSidebarY = sessionStorage.getItem('dartdoc-sidebar-left-scrollt' + window.location.pathname);
const mainContentX = sessionStorage.getItem('dartdoc-main-content-scrolll' + window.location.pathname);
const mainContentY = sessionStorage.getItem('dartdoc-main-content-scrollt' + window.location.pathname);
const rightSidebarX = sessionStorage.getItem('dartdoc-sidebar-right-scrolll' + window.location.pathname);
const rightSidebarY = sessionStorage.getItem('dartdoc-sidebar-right-scrollt' + window.location.pathname);
leftSidebar.scrollTo(parseFloat(leftSidebarX), parseFloat(leftSidebarY));
mainContent.scrollTo(parseFloat(mainContentX), parseFloat(mainContentY));
rightSidebar.scrollTo(parseFloat(rightSidebarX), parseFloat(rightSidebarY));
} finally {
// Set visibility to visible after scroll to prevent the brief appearance of the
// panel in the wrong position.
leftSidebar.style.visibility = 'visible';
mainContent.style.visibility = 'visible';
rightSidebar.style.visibility = 'visible';
}
}
function initScrollSave() {
const leftSidebar = document.getElementById('dartdoc-sidebar-left');
const mainContent = document.getElementById('dartdoc-main-content');
const rightSidebar = document.getElementById('dartdoc-sidebar-right');
leftSidebar.addEventListener("scroll", saveLeftScroll, true);
mainContent.addEventListener("scroll", saveMainContentScroll, true);
rightSidebar.addEventListener("scroll", saveRightScroll, true);
}
const weights = {
'library' : 2,
'class' : 2,
'mixin' : 3,
'extension' : 3,
'typedef' : 3,
'method' : 4,
'accessor' : 4,
'operator' : 4,
'constant' : 4,
'property' : 4,
'constructor' : 4
};
function findMatches(index, query) {
if (query === '') {
return [];
}
const allMatches = [];
index.forEach(element => {
function score(value) {
value -= element.overriddenDepth * 10;
const weightFactor = weights[element.type] || 4;
allMatches.push({element: element, score: (value / weightFactor) >> 0});
}
const name = element.name;
const qualifiedName = element.qualifiedName;
const lowerName = name.toLowerCase();
const lowerQualifiedName = qualifiedName.toLowerCase();
const lowerQuery = query.toLowerCase();
if (name === query || qualifiedName === query || name === `dart:${query}`) {
score(2000);
} else if (lowerName === `dart:${lowerQuery}`) {
score(1800);
} else if (lowerName === lowerQuery || lowerQualifiedName === lowerQuery) {
score(1700);
} else if (query.length > 1) {
if (name.startsWith(query) || qualifiedName.startsWith(query)) {
score(750);
} else if (lowerName.startsWith(lowerQuery) || lowerQualifiedName.startsWith(lowerQuery)) {
score(650);
} else if (name.includes(query) || qualifiedName.includes(query)) {
score(500);
} else if (lowerName.includes(lowerQuery) || lowerQualifiedName.includes(query)) {
score(400);
}
}
});
allMatches.sort((a, b) => {
const x = b.score - a.score;
if (x === 0) {
return a.element.name.length - b.element.name.length;
}
return x;
});
const justElements = [];
for (let i = 0; i < allMatches.length; i++) {
justElements.push(allMatches[i].element);
}
return justElements;
}
let baseHref = '';
const minLength = 1;
const suggestionLimit = 10;
function initializeSearch(input, index) {
input.disabled = false;
input.setAttribute('placeholder', 'Search API Docs');
// Handle grabbing focus when the users types / outside of the input
document.addEventListener('keypress', (event) => {
if (event.code === 'Slash' && !(document.activeElement instanceof HTMLInputElement)) {
event.preventDefault();
input.focus();
}
});
// Prepare elements
const parentForm = input.parentNode;
const wrapper = document.createElement('div');
wrapper.classList.add('tt-wrapper');
parentForm.replaceChild(wrapper, input);
const inputHint = document.createElement('input');
inputHint.setAttribute('type', 'text');
inputHint.setAttribute('autocomplete', 'off');
inputHint.setAttribute('readonly', 'true');
inputHint.setAttribute('spellcheck', 'false');
inputHint.setAttribute('tabindex', '-1');
inputHint.classList.add('typeahead', 'tt-hint');
wrapper.appendChild(inputHint);
input.setAttribute('autocomplete', 'off');
input.setAttribute('spellcheck', 'false');
input.classList.add('tt-input');
wrapper.appendChild(input);
const listBox = document.createElement('div');
listBox.setAttribute('role', 'listbox');
listBox.setAttribute('aria-expanded', 'false');
listBox.style.display = 'none';
listBox.classList.add('tt-menu');
const presentation = document.createElement('div');
presentation.classList.add('tt-elements');
listBox.appendChild(presentation);
wrapper.appendChild(listBox);
// Set up various search functionality
function highlight(text, query) {
query = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
return text.replace(new RegExp(query, 'gi'), (matched) => {
return `<strong class='tt-highlight'>${matched}</strong>`;
});
}
function createSuggestion(query, match) {
const suggestion = document.createElement('div');
suggestion.setAttribute('data-href', match.href);
suggestion.classList.add('tt-suggestion');
const suggestionTitle = document.createElement('span');
suggestionTitle.classList.add('tt-suggestion-title');
suggestionTitle.innerHTML = highlight(`${match.name} ${match.type.toLowerCase()}`, query);
suggestion.appendChild(suggestionTitle);
if (match.enclosedBy) {
const fromLib = document.createElement('div');
fromLib.classList.add('search-from-lib');
fromLib.innerHTML = `from ${highlight(match.enclosedBy.name, query)}`;
suggestion.appendChild(fromLib);
}
suggestion.addEventListener('mousedown', event => {
event.preventDefault();
});
suggestion.addEventListener('click', event => {
if (match.href) {
window.location = baseHref + match.href;
event.preventDefault();
}
});
return suggestion;
}
let storedValue = null;
let actualValue = '';
let hint = null;
let suggestionElements = [];
let suggestionsInfo = [];
let selectedElement = null;
function setHint(value) {
hint = value;
inputHint.value = value || '';
}
function updateSuggestions(query, suggestions) {
suggestionsInfo = [];
suggestionElements = [];
presentation.textContent = '';
if (suggestions.length < minLength) {
setHint(null)
hideSuggestions();
return;
}
for (let i = 0; i < suggestions.length; i++) {
const element = createSuggestion(query, suggestions[i]);
suggestionElements.push(element);
presentation.appendChild(element);
}
suggestionsInfo = suggestions;
setHint(query + suggestions[0].name.slice(query.length));
selectedElement = null;
showSuggestions();
}
function handle(newValue, forceUpdate) {
if (actualValue === newValue && !forceUpdate) {
return;
}
if (newValue === null || newValue.length === 0) {
updateSuggestions('', []);
return;
}
const suggestions = findMatches(index, newValue).slice(0, suggestionLimit);
actualValue = newValue;
updateSuggestions(newValue, suggestions);
}
function showSuggestions() {
if (presentation.hasChildNodes()) {
listBox.style.display = 'block';
listBox.setAttribute('aria-expanded', 'true');
}
}
function hideSuggestions() {
listBox.style.display = 'none';
listBox.setAttribute('aria-expanded', 'false');
}
// Hook up events
input.addEventListener('focus', () => {
handle(input.value, true);
});
input.addEventListener('blur', () => {
selectedElement = null;
if (storedValue !== null) {
input.value = storedValue;
storedValue = null;
}
hideSuggestions();
setHint(null);
});
input.addEventListener('input', event => {
handle(event.target.value);
});
input.addEventListener('keydown', event => {
if (suggestionElements.length === 0) {
return;
}
if (event.code === 'Enter') {
const selectingElement = selectedElement || 0;
const href = suggestionElements[selectingElement].dataset.href;
if (href) {
window.location = baseHref + href;
}
return;
}
if (event.code === 'Tab') {
if (selectedElement === null) {
// The user wants to fill the field with the hint
if (hint !== null) {
input.value = hint;
handle(hint);
event.preventDefault();
}
} else {
// The user wants to fill the input field with their currently selected suggestion
handle(suggestionsInfo[selectedElement].name);
storedValue = null;
selectedElement = null;
event.preventDefault();
}
return;
}
const lastIndex = suggestionElements.length - 1;
const previousSelectedElement = selectedElement;
if (event.code === 'ArrowUp') {
if (selectedElement === null) {
selectedElement = lastIndex;
} else if (selectedElement === 0) {
selectedElement = null;
} else {
selectedElement--;
}
} else if (event.code === 'ArrowDown') {
if (selectedElement === null) {
selectedElement = 0;
} else if (selectedElement === lastIndex) {
selectedElement = null;
} else {
selectedElement++;
}
} else {
if (storedValue !== null) {
storedValue = null;
handle(input.value);
}
return;
}
if (previousSelectedElement !== null) {
suggestionElements[previousSelectedElement].classList.remove('tt-cursor');
}
if (selectedElement !== null) {
const selected = suggestionElements[selectedElement];
selected.classList.add('tt-cursor');
// Guarantee the selected element is visible
if (selectedElement === 0) {
listBox.scrollTop = 0;
} else if (selectedElement === lastIndex) {
listBox.scrollTop = listBox.scrollHeight;
} else {
const offsetTop = selected.offsetTop;
const parentOffsetHeight = listBox.offsetHeight;
if (offsetTop < parentOffsetHeight || parentOffsetHeight < (offsetTop + selected.offsetHeight)) {
selected.scrollIntoView({behavior: 'auto', block: 'nearest'});
}
}
if (storedValue === null) {
// Store the actual input value to display their currently selected item
storedValue = input.value;
}
input.value = suggestionsInfo[selectedElement].name;
setHint('');
} else if (storedValue !== null && previousSelectedElement !== null) {
// They are moving back to the input field, so return the stored value
input.value = storedValue;
setHint(storedValue + suggestionsInfo[0].name.slice(storedValue.length));
storedValue = null;
}
event.preventDefault();
});
}
document.addEventListener('DOMContentLoaded', () => {
// Place this first so that unexpected exceptions in other JavaScript do not block page visibility.
restoreScrolls();
hljs.highlightAll();
initSideNav();
initScrollSave();
const searchBox = document.getElementById('search-box');
const searchBody = document.getElementById('search-body');
const searchSidebar = document.getElementById('search-sidebar');
if (document.body.getAttribute('data-using-base-href') === 'false') {
// If dartdoc did not add a base-href tag, we will need to add the relative
// path ourselves.
baseHref = document.body.getAttribute('data-base-href');
}
function disableSearch() {
console.log('Could not activate search functionality.');
if (searchBox) {
searchBox.placeholder = 'Failed to initialize search';
}
if (searchBody) {
searchBody.placeholder = 'Failed to initialize search';
}
if (searchSidebar) {
searchSidebar.placeholder = 'Failed to initialize search';
}
}
if ('fetch' in window) {
fetch(baseHref + 'index.json', {method: 'GET'})
.then(response => response.json())
.then(index => {
// Handle if the user specified a `search` parameter in the URL
if ('URLSearchParams' in window) {
const search = new URLSearchParams(window.location.search).get('search');
if (search) {
const matches = findMatches(search);
if (matches.length !== 0) {
window.location = baseHref + matches[0].href;
return;
}
}
}
// Initialize all three search fields
if (searchBox) {
initializeSearch(searchBox, index);
}
if (searchBody) {
initializeSearch(searchBody, index);
}
if (searchSidebar) {
initializeSearch(searchSidebar, index);
}
})
.catch(() => {
disableSearch();
});
} else {
disableSearch();
}
});