just-the-docs/assets/js/just-the-docs.js
Peter Mosses 70b34f01f7
Combination (#578)
This PR combines (and resolves conflicts between) #448, #463, #466, #494, #495, #496, #498, and #572. 

The main aim is to facilitate use of several of the implemented features _together_, when using the fork as a remote theme. It should also simplify merging the included PRs into a future release.

The branch [combination-rec-nav](https://github.com/pdmosses/just-the-docs/tree/combination-rec-nav) adds [multi-level navigation](https://github.com/pmarsceill/just-the-docs/pull/462) and (NEW:) [sibling links](https://github.com/pmarsceill/just-the-docs/pull/394) to the branch used for this PR. It includes updated [documentation for the navigation structure](https://pdmosses.github.io/just-the-docs/docs/navigation-structure/), and reorganised and extended [navigation tests](https://pdmosses.github.io/just-the-docs/tests/navigation/). The documentation and the tests can be browsed at the (temporary) [website published from the combination-rec-nav branch](https://pdmosses.github.io/just-the-docs/).

_Caveat:_ The changes to v0.3.3 in this PR and #462 have not yet been reviewed or approved, and may need updating before merging into a release of the theme. If you use a branch from a PR as a remote theme, there is a risk of such updates affecting your website. Moreover, these branches are likely to be deleted after they have been merged. To avoid  such problems, you could copy the branch that you want to use to your own fork of the theme.

Co-authored-by: Matt Wang <matt@matthewwang.me>
2022-07-04 12:15:10 -07:00

485 lines
16 KiB
JavaScript

---
---
(function (jtd, undefined) {
// Event handling
jtd.addEvent = function(el, type, handler) {
if (el.attachEvent) el.attachEvent('on'+type, handler); else el.addEventListener(type, handler);
}
jtd.removeEvent = function(el, type, handler) {
if (el.detachEvent) el.detachEvent('on'+type, handler); else el.removeEventListener(type, handler);
}
jtd.onReady = function(ready) {
// in case the document is already rendered
if (document.readyState!='loading') ready();
// modern browsers
else if (document.addEventListener) document.addEventListener('DOMContentLoaded', ready);
// IE <= 8
else document.attachEvent('onreadystatechange', function(){
if (document.readyState=='complete') ready();
});
}
// Show/hide mobile menu
function initNav() {
jtd.addEvent(document, 'click', function(e){
var target = e.target;
while (target && !(target.classList && target.classList.contains('nav-list-expander'))) {
target = target.parentNode;
}
if (target) {
e.preventDefault();
target.parentNode.classList.toggle('active');
}
});
const siteNav = document.getElementById('site-nav');
const mainHeader = document.getElementById('main-header');
const menuButton = document.getElementById('menu-button');
jtd.addEvent(menuButton, 'click', function(e){
e.preventDefault();
if (menuButton.classList.toggle('nav-open')) {
siteNav.classList.add('nav-open');
mainHeader.classList.add('nav-open');
} else {
siteNav.classList.remove('nav-open');
mainHeader.classList.remove('nav-open');
}
});
{%- if site.search_enabled != false and site.search.button %}
const searchInput = document.getElementById('search-input');
const searchButton = document.getElementById('search-button');
jtd.addEvent(searchButton, 'click', function(e){
e.preventDefault();
mainHeader.classList.add('nav-open');
searchInput.focus();
});
{%- endif %}
}
{%- if site.search_enabled != false %}
// Site search
function initSearch() {
var request = new XMLHttpRequest();
request.open('GET', '{{ "assets/js/search-data.json" | relative_url }}', true);
request.onload = function(){
if (request.status >= 200 && request.status < 400) {
var docs = JSON.parse(request.responseText);
lunr.tokenizer.separator = {{ site.search.tokenizer_separator | default: site.search_tokenizer_separator | default: "/[\s\-/]+/" }}
var index = lunr(function(){
this.ref('id');
this.field('title', { boost: 200 });
this.field('content', { boost: 2 });
{%- if site.search.rel_url != false %}
this.field('relUrl');
{%- endif %}
this.metadataWhitelist = ['position']
for (var i in docs) {
this.add({
id: i,
title: docs[i].title,
content: docs[i].content,
{%- if site.search.rel_url != false %}
relUrl: docs[i].relUrl
{%- endif %}
});
}
});
searchLoaded(index, docs);
} else {
console.log('Error loading ajax request. Request status:' + request.status);
}
};
request.onerror = function(){
console.log('There was a connection error');
};
request.send();
}
function searchLoaded(index, docs) {
var index = index;
var docs = docs;
var searchInput = document.getElementById('search-input');
var searchResults = document.getElementById('search-results');
var mainHeader = document.getElementById('main-header');
var currentInput;
var currentSearchIndex = 0;
function showSearch() {
document.documentElement.classList.add('search-active');
}
function hideSearch() {
document.documentElement.classList.remove('search-active');
}
function update() {
currentSearchIndex++;
var input = searchInput.value;
if (input === '') {
hideSearch();
} else {
showSearch();
// scroll search input into view, workaround for iOS Safari
window.scroll(0, -1);
setTimeout(function(){ window.scroll(0, 0); }, 0);
}
if (input === currentInput) {
return;
}
currentInput = input;
searchResults.innerHTML = '';
if (input === '') {
return;
}
var results = index.query(function (query) {
var tokens = lunr.tokenizer(input)
query.term(tokens, {
boost: 10
});
query.term(tokens, {
wildcard: lunr.Query.wildcard.TRAILING
});
});
if ((results.length == 0) && (input.length > 2)) {
var tokens = lunr.tokenizer(input).filter(function(token, i) {
return token.str.length < 20;
})
if (tokens.length > 0) {
results = index.query(function (query) {
query.term(tokens, {
editDistance: Math.round(Math.sqrt(input.length / 2 - 1))
});
});
}
}
if (results.length == 0) {
var noResultsDiv = document.createElement('div');
noResultsDiv.classList.add('search-no-result');
noResultsDiv.innerText = 'No results found';
searchResults.appendChild(noResultsDiv);
} else {
var resultsList = document.createElement('ul');
resultsList.classList.add('search-results-list');
searchResults.appendChild(resultsList);
addResults(resultsList, results, 0, 10, 100, currentSearchIndex);
}
function addResults(resultsList, results, start, batchSize, batchMillis, searchIndex) {
if (searchIndex != currentSearchIndex) {
return;
}
for (var i = start; i < (start + batchSize); i++) {
if (i == results.length) {
return;
}
addResult(resultsList, results[i]);
}
setTimeout(function() {
addResults(resultsList, results, start + batchSize, batchSize, batchMillis, searchIndex);
}, batchMillis);
}
function addResult(resultsList, result) {
var doc = docs[result.ref];
var resultsListItem = document.createElement('li');
resultsListItem.classList.add('search-results-list-item');
resultsList.appendChild(resultsListItem);
var resultLink = document.createElement('a');
resultLink.classList.add('search-result');
resultLink.setAttribute('href', doc.url);
resultsListItem.appendChild(resultLink);
var resultTitle = document.createElement('div');
resultTitle.classList.add('search-result-title');
resultLink.appendChild(resultTitle);
var resultDoc = document.createElement('div');
resultDoc.classList.add('search-result-doc');
resultDoc.innerHTML = '<svg viewBox="0 0 24 24" class="search-result-icon"><use xlink:href="#svg-doc"></use></svg>';
resultTitle.appendChild(resultDoc);
var resultDocTitle = document.createElement('div');
resultDocTitle.classList.add('search-result-doc-title');
resultDocTitle.innerHTML = doc.doc;
resultDoc.appendChild(resultDocTitle);
var resultDocOrSection = resultDocTitle;
if (doc.doc != doc.title) {
resultDoc.classList.add('search-result-doc-parent');
var resultSection = document.createElement('div');
resultSection.classList.add('search-result-section');
resultSection.innerHTML = doc.title;
resultTitle.appendChild(resultSection);
resultDocOrSection = resultSection;
}
var metadata = result.matchData.metadata;
var titlePositions = [];
var contentPositions = [];
for (var j in metadata) {
var meta = metadata[j];
if (meta.title) {
var positions = meta.title.position;
for (var k in positions) {
titlePositions.push(positions[k]);
}
}
if (meta.content) {
var positions = meta.content.position;
for (var k in positions) {
var position = positions[k];
var previewStart = position[0];
var previewEnd = position[0] + position[1];
var ellipsesBefore = true;
var ellipsesAfter = true;
for (var k = 0; k < {{ site.search.preview_words_before | default: 5 }}; k++) {
var nextSpace = doc.content.lastIndexOf(' ', previewStart - 2);
var nextDot = doc.content.lastIndexOf('. ', previewStart - 2);
if ((nextDot >= 0) && (nextDot > nextSpace)) {
previewStart = nextDot + 1;
ellipsesBefore = false;
break;
}
if (nextSpace < 0) {
previewStart = 0;
ellipsesBefore = false;
break;
}
previewStart = nextSpace + 1;
}
for (var k = 0; k < {{ site.search.preview_words_after | default: 10 }}; k++) {
var nextSpace = doc.content.indexOf(' ', previewEnd + 1);
var nextDot = doc.content.indexOf('. ', previewEnd + 1);
if ((nextDot >= 0) && (nextDot < nextSpace)) {
previewEnd = nextDot;
ellipsesAfter = false;
break;
}
if (nextSpace < 0) {
previewEnd = doc.content.length;
ellipsesAfter = false;
break;
}
previewEnd = nextSpace;
}
contentPositions.push({
highlight: position,
previewStart: previewStart, previewEnd: previewEnd,
ellipsesBefore: ellipsesBefore, ellipsesAfter: ellipsesAfter
});
}
}
}
if (titlePositions.length > 0) {
titlePositions.sort(function(p1, p2){ return p1[0] - p2[0] });
resultDocOrSection.innerHTML = '';
addHighlightedText(resultDocOrSection, doc.title, 0, doc.title.length, titlePositions);
}
if (contentPositions.length > 0) {
contentPositions.sort(function(p1, p2){ return p1.highlight[0] - p2.highlight[0] });
var contentPosition = contentPositions[0];
var previewPosition = {
highlight: [contentPosition.highlight],
previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd,
ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter
};
var previewPositions = [previewPosition];
for (var j = 1; j < contentPositions.length; j++) {
contentPosition = contentPositions[j];
if (previewPosition.previewEnd < contentPosition.previewStart) {
previewPosition = {
highlight: [contentPosition.highlight],
previewStart: contentPosition.previewStart, previewEnd: contentPosition.previewEnd,
ellipsesBefore: contentPosition.ellipsesBefore, ellipsesAfter: contentPosition.ellipsesAfter
}
previewPositions.push(previewPosition);
} else {
previewPosition.highlight.push(contentPosition.highlight);
previewPosition.previewEnd = contentPosition.previewEnd;
previewPosition.ellipsesAfter = contentPosition.ellipsesAfter;
}
}
var resultPreviews = document.createElement('div');
resultPreviews.classList.add('search-result-previews');
resultLink.appendChild(resultPreviews);
var content = doc.content;
for (var j = 0; j < Math.min(previewPositions.length, {{ site.search.previews | default: 3 }}); j++) {
var position = previewPositions[j];
var resultPreview = document.createElement('div');
resultPreview.classList.add('search-result-preview');
resultPreviews.appendChild(resultPreview);
if (position.ellipsesBefore) {
resultPreview.appendChild(document.createTextNode('... '));
}
addHighlightedText(resultPreview, content, position.previewStart, position.previewEnd, position.highlight);
if (position.ellipsesAfter) {
resultPreview.appendChild(document.createTextNode(' ...'));
}
}
}
{%- if site.search.rel_url != false %}
var resultRelUrl = document.createElement('span');
resultRelUrl.classList.add('search-result-rel-url');
resultRelUrl.innerText = doc.relUrl;
resultTitle.appendChild(resultRelUrl);
{%- endif %}
}
function addHighlightedText(parent, text, start, end, positions) {
var index = start;
for (var i in positions) {
var position = positions[i];
var span = document.createElement('span');
span.innerHTML = text.substring(index, position[0]);
parent.appendChild(span);
index = position[0] + position[1];
var highlight = document.createElement('span');
highlight.classList.add('search-result-highlight');
highlight.innerHTML = text.substring(position[0], index);
parent.appendChild(highlight);
}
var span = document.createElement('span');
span.innerHTML = text.substring(index, end);
parent.appendChild(span);
}
}
jtd.addEvent(searchInput, 'focus', function(){
setTimeout(update, 0);
});
jtd.addEvent(searchInput, 'keyup', function(e){
switch (e.keyCode) {
case 27: // When esc key is pressed, hide the results and clear the field
searchInput.value = '';
break;
case 38: // arrow up
case 40: // arrow down
case 13: // enter
e.preventDefault();
return;
}
update();
});
jtd.addEvent(searchInput, 'keydown', function(e){
switch (e.keyCode) {
case 38: // arrow up
e.preventDefault();
var active = document.querySelector('.search-result.active');
if (active) {
active.classList.remove('active');
if (active.parentElement.previousSibling) {
var previous = active.parentElement.previousSibling.querySelector('.search-result');
previous.classList.add('active');
}
}
return;
case 40: // arrow down
e.preventDefault();
var active = document.querySelector('.search-result.active');
if (active) {
if (active.parentElement.nextSibling) {
var next = active.parentElement.nextSibling.querySelector('.search-result');
active.classList.remove('active');
next.classList.add('active');
}
} else {
var next = document.querySelector('.search-result');
if (next) {
next.classList.add('active');
}
}
return;
case 13: // enter
e.preventDefault();
var active = document.querySelector('.search-result.active');
if (active) {
active.click();
} else {
var first = document.querySelector('.search-result');
if (first) {
first.click();
}
}
return;
}
});
jtd.addEvent(document, 'click', function(e){
if (e.target != searchInput) {
hideSearch();
}
});
}
{%- endif %}
// Switch theme
jtd.getTheme = function() {
var cssFileHref = document.querySelector('[rel="stylesheet"]').getAttribute('href');
return cssFileHref.substring(cssFileHref.lastIndexOf('-') + 1, cssFileHref.length - 4);
}
jtd.setTheme = function(theme) {
var cssFile = document.querySelector('[rel="stylesheet"]');
cssFile.setAttribute('href', '{{ "assets/css/just-the-docs-" | relative_url }}' + theme + '.css');
}
// Scroll site-nav to ensure the link to the current page is visible
function scrollNav() {
const href = document.location.href.split('#')[0].replace(/(.+?)\/+$/, "$1");
const siteNav = document.getElementById('site-nav');
const targetLink = siteNav.querySelector('a[href="' + href + '"], a[href="' + href + '/"]');
if(targetLink){
const rect = targetLink.getBoundingClientRect();
siteNav.scrollBy(0, rect.top - 3*rect.height);
}
}
// Document ready
jtd.onReady(function(){
initNav();
{%- if site.search_enabled != false %}
initSearch();
{%- endif %}
scrollNav();
});
})(window.jtd = window.jtd || {});
{% include js/custom.js %}