MATH201/themes/zettels/assets/js/search.js

468 lines
12 KiB
JavaScript
Raw Normal View History

2023-12-29 20:05:27 -07:00
// On localhost the root url is /MATH201
// On sasserisop the root url is /sasserisop
const localhost = true;
const root = localhost ? "" : "/MATH201";
2023-09-27 10:58:42 -06:00
loadIndex()
// Highlight with jQuery
// from: https://stackoverflow.com/questions/41533785/how-to-highlight-search-text-in-html-with-the-help-of-js
// @todo: This is the only use of jQuery. Find a vanila JS way
2023-12-29 16:17:03 -07:00
jQuery.fn.highlight = function (pat) {
2023-09-27 10:58:42 -06:00
function innerHighlight(node, pat) {
var skip = 0;
if (node.nodeType == 3) {
var pos = node.data.toUpperCase().indexOf(pat);
if (pos >= 0) {
2023-12-29 16:17:03 -07:00
var spannode = document.createElement('span');
2023-09-27 10:58:42 -06:00
spannode.className = 'highlighted';
2023-12-29 16:17:03 -07:00
var middlebit = node.splitText(pos);
var endbit = middlebit.splitText(pat.length);
var middleclone = middlebit.cloneNode(true);
2023-09-27 10:58:42 -06:00
spannode.appendChild(middleclone);
middlebit.parentNode.replaceChild(spannode, middlebit);
2023-12-29 16:17:03 -07:00
skip = 1;
2023-09-27 10:58:42 -06:00
}
} else if (node.nodeType == 1 && node.childNodes && !/(script|style) /i.test(node.tagName)) {
for (var i = 0; i < node.childNodes.length; ++i) {
i += innerHighlight(node.childNodes[i], pat);
}
}
return skip;
}
2023-12-29 16:17:03 -07:00
return this.each(function () {
2023-09-27 10:58:42 -06:00
innerHighlight(this, pat.toUpperCase());
});
2023-12-29 16:17:03 -07:00
};
2023-09-27 10:58:42 -06:00
2023-12-29 16:17:03 -07:00
jQuery.fn.removeHighlight = function () {
2023-09-27 10:58:42 -06:00
function newNormalize(node) {
for (var i = 0, children = node.childNodes, nodeCount = children.length; i < nodeCount; i++) {
var child = children[i];
if (child.nodeType == 1) {
newNormalize(child);
continue;
}
if (child.nodeType != 3) { continue; }
var next = child.nextSibling;
if (next == null || next.nodeType != 3) { continue; }
var combined_text = child.nodeValue + next.nodeValue;
2023-12-29 16:17:03 -07:00
new_node = node.ownerDocument.createTextNode(combined_text);
2023-09-27 10:58:42 -06:00
node.insertBefore(new_node, child);
node.removeChild(child);
node.removeChild(next);
i--;
nodeCount--;
}
}
2023-12-29 16:17:03 -07:00
return this.find("span.highlighted").each(function () {
2023-09-27 10:58:42 -06:00
var thisParent = this.parentNode;
thisParent.replaceChild(this.firstChild, this);
newNormalize(thisParent);
}).end();
};
2023-12-29 16:17:03 -07:00
$(function () {
2023-09-27 10:58:42 -06:00
2023-12-29 16:17:03 -07:00
$('#search-input').bind('keyup change', function (ev) {
2023-09-27 10:58:42 -06:00
// pull in the new value
var searchTerm = $(this).val();
// remove any old highlighted terms
$('body').removeHighlight();
// disable highlighting if empty
2023-12-29 16:17:03 -07:00
if (searchTerm) {
2023-09-27 10:58:42 -06:00
// highlight the new term
2023-12-29 16:17:03 -07:00
$('body').highlight(searchTerm);
2023-09-27 10:58:42 -06:00
}
});
});
///
var scrollTop = 0
2023-12-29 16:17:03 -07:00
document.addEventListener("turbolinks:before-render", function () {
var search_index = document.getElementById("search-results");
var y = search_index.scrollTop;
scrollTop = y
2023-09-27 10:58:42 -06:00
})
2023-12-29 16:17:03 -07:00
document.addEventListener("turbolinks:render", function () {
2023-09-27 10:58:42 -06:00
var search_index = document.getElementById("search-results");
search_index.scrollTop = scrollTop
})
2023-12-29 16:17:03 -07:00
document.addEventListener("turbolinks:load", function () {
2023-09-27 10:58:42 -06:00
setNoteWrapperState()
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
const current = window.location.href
2023-12-29 16:17:03 -07:00
var els = document.getElementsByTagName("a");
for (var i = 0, l = els.length; i < l; i++) {
var el = els[i];
if (el.href === current) {
el.classList.add("selected")
} else {
el.classList.remove("selected")
}
}
2023-09-27 10:58:42 -06:00
})
// load the lecture note links in homepage
function load_lecture_links() {
// check if links have already been generated first
if ($("p.lecture-link").length == 0) {
fetchJSON(function (response) {
const notes = response;
// for homepage lecture links
const lecture_links = new Map();
notes.index.filter(n => !/^Drawing|^20/.test(n.title)).forEach(note => {
// Add lecture notes to a dictionary, and then sort them by lecture number.
// second last character of the title may be first digit of lecture number
const d1 = note.title[note.title.length - 2];
// find the lecture number, if there is one
if (/\d/.test(d1)) {
// it has a lecture number
// if the third last character is a digit, then the lecture number is 2 digits
const d2 = note.title[note.title.length - 3];
var lecture_number = /\d/.test(d2) ? Number(d2 + d1) : Number(d1);
// some lecture titles have the same last lecture number
if (lecture_links.has(lecture_number)) {
// prevents two lectures from having the same key
lecture_number += 0.5;
}
lecture_links.set(lecture_number, [note.title, note.permalink])
}
});
const sorted_links = new Map([...lecture_links.entries()].sort((a, b) => (a[0] - b[0])));
sorted_links.forEach(val => {
a_tag = document.createElement('a');
a_tag.innerText = val[0];
a_tag.href = val[1];
p_tag = document.createElement('p');
p_tag.append(a_tag);
p_tag.className = "lecture-link"
$("#note-wrapper br:nth-of-type(3)").before(p_tag);
});
}, root);
}
}
2023-09-27 10:58:42 -06:00
function loadIndex() {
2023-12-29 16:17:03 -07:00
fetchJSON(function (response) {
2023-09-27 10:58:42 -06:00
const notes = response;
const search_results = document.getElementById('search-results');
const tags = document.getElementById('tags');
const current_note = window.location.href;
2023-12-29 16:17:03 -07:00
// sorted notes by lecture number
const lecture_notes = new Map();
const non_lecture_notes = [];
// Fixed a bug where the search results had unwanted notes.
// The notes are now filtered first.
notes.index.filter(n => !/^Drawing|^20/.test(n.title)).forEach(note => {
const title = '<h4>' + note.title + '</h4>';
2023-09-27 10:58:42 -06:00
const summary = '<div>' + note.summary + '</div>';
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
const permalink = note.permalink
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
var thumbnail = ""
if (note.thumbnail === "") {
thumbnail = ""
} else {
// @todo:
// this loads the first image of the note
// for every note in the vault
// this isn't efficient at all
// 2 ideas:
// find a way of resizing with hugo
// use lazy loading.
thumbnail = '<img loading="lazy" src="' + note.thumbnail + '?nf_resize=fit&w=122&h=76"/><span style="display:none";>@attachments</span>'
}
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
const tags = '<span style="display:none">' + note.tags + '</span>'
var list_content;
if (current_note === permalink) {
2023-12-29 16:17:03 -07:00
list_content = '<a href="' + root + permalink + '" class="selected search-item" tabindex="0">' + title + summary + '</a>'
2023-09-27 10:58:42 -06:00
} else {
2023-12-29 16:17:03 -07:00
list_content = '<a href="' + root + permalink + '" class="search-item" tabindex="0">' + title + summary + thumbnail + tags + '</a>'
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
const child = document.createElement("li");
child.innerHTML = list_content;
2023-12-29 16:17:03 -07:00
// Add lecture notes to a dictionary, and then sort them by lecture number.
// second last character of the title may be first digit of lecture number
const d1 = note.title[note.title.length - 2];
// find the lecture number, if there is one
if (/\d/.test(d1)) {
// it has a lecture number
// if the third last character is a digit, then the lecture number is 2 digits
const d2 = note.title[note.title.length - 3];
var lecture_number = /\d/.test(d2) ? Number(d2 + d1) : Number(d1);
if (lecture_notes.has(lecture_number)) {
// prevent lectures from having the same key
lecture_number += 0.5;
}
2023-12-29 16:17:03 -07:00
lecture_notes.set(lecture_number, child);
} else {
non_lecture_notes.push(child);
}
});
// sort notes by lecture number
const sorted_notes = new Map([...lecture_notes.entries()].sort((a, b) => (a[0] - b[0])));
2023-12-29 16:17:03 -07:00
// Add sorted notes first
sorted_notes.forEach(val => {
search_results.append(val);
2023-09-27 10:58:42 -06:00
});
2023-12-29 16:17:03 -07:00
non_lecture_notes.forEach(child => {
search_results.append(child);
});
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
// @todo: wip
// notes.tags.forEach(tag => {
// const child = document.createElement("li");
// child.innerHTML = '<a onclick="focusTag(this)">' + tag + '</a>'
// tags.append(child)
// });
//
2023-12-29 16:17:03 -07:00
}, root);
}
function fetchJSON(callback, root) {
const requestURL = root + '/index.json';
2023-09-27 10:58:42 -06:00
const request = new XMLHttpRequest();
request.open('GET', requestURL, true);
request.responseType = 'json';
2023-12-29 16:17:03 -07:00
request.onreadystatechange = function () {
if (request.readyState === 4 && request.status === 200) {
callback(request.response);
}
2023-09-27 10:58:42 -06:00
};
request.send(null);
}
function focusTag(a) {
showNav()
performSearchWith(a.innerText)
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
}
function performSearchWith(query) {
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
// document.getElementById('search-input').value = query
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
var filter, ul, li, a, i, txtValue;
2023-12-29 16:17:03 -07:00
filter = query.toLowerCase();
filter = filter.replace('/[.*+?^${}()|[\]\\]/g', '\\$&');
var re = new RegExp(filter, 'g');
ul = document.getElementById("search-results");
li = ul.getElementsByTagName('li');
for (i = 0; i < li.length; i++) {
2023-09-27 10:58:42 -06:00
a = li[i].getElementsByTagName("a")[0];
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
txtValue = a.textContent || a.innerText;
if (txtValue.toLowerCase().indexOf(filter) > -1) {
2023-12-29 16:17:03 -07:00
li[i].style.display = "block";
2023-09-27 10:58:42 -06:00
} else {
2023-12-29 16:17:03 -07:00
li[i].style.display = "none";
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
}
2023-09-27 10:58:42 -06:00
}
function performSearch() {
2023-12-29 16:17:03 -07:00
var input, filter, ul, li, a, i, txtValue;
input = document.getElementById('search-input');
filter = input.value.toLowerCase();
filter = filter.replace('/[.*+?^${}()|[\]\\]/g', '\\$&');
var re = new RegExp(filter, 'g');
ul = document.getElementById("search-results");
li = ul.getElementsByTagName('li');
for (i = 0; i < li.length; i++) {
2023-09-27 10:58:42 -06:00
a = li[i].getElementsByTagName("a")[0];
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
txtValue = a.textContent || a.innerText;
if (txtValue.toLowerCase().indexOf(filter) > -1) {
2023-12-29 16:17:03 -07:00
li[i].style.display = "block";
2023-09-27 10:58:42 -06:00
} else {
2023-12-29 16:17:03 -07:00
li[i].style.display = "none";
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
}
2023-09-27 10:58:42 -06:00
}
// Keyboard shortcuts
2023-12-29 16:17:03 -07:00
document.addEventListener('keydown', function (evt) {
2023-09-27 10:58:42 -06:00
if (evt.metaKey && evt.which === 75 || evt.ctrlKey && evt.which === 75) {
document.getElementById("search-input").focus();
handleNavVisibility()
}
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
if (evt.key === "Escape" || evt.key === "Esc" | evt.keyCode === 27) {
hideNav()
}
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
});
var nav_is_visible = false;
function handleNavVisibility() {
2023-12-29 20:05:27 -07:00
$("#search").toggle("slide");
2023-09-27 10:58:42 -06:00
}
function showNav() {
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
document.getElementById("search").style.width = "300px";
document.getElementById("search").style.opacity = 1;
document.getElementById("search-header").style.opacity = 1;
document.getElementById("search-header").style.width = "299px";
pushNoteWrapper()
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
nav_is_visible = true;
}
function hideNav() {
document.getElementById("search").style.width = "0";
document.getElementById("search-header").style.width = "0";
document.getElementById("search").style.opacity = 0;
document.getElementById("search-header").style.opacity = 0;
2023-12-29 16:17:03 -07:00
document.getElementById("main").style.marginLeft = "0";
2023-09-27 10:58:42 -06:00
pullNoteWrapper()
2023-12-29 16:17:03 -07:00
2023-09-27 10:58:42 -06:00
nav_is_visible = false;
}
function setNoteWrapperState() {
if (nav_is_visible) {
pushNoteWrapper()
} else {
pullNoteWrapper()
}
}
function pushNoteWrapper() {
document.getElementById("main").style.marginLeft = "300px";
}
function pullNoteWrapper() {
document.getElementById("main").style.marginLeft = "0";
}
// Prefetching
// https://github.com/turbolinks/turbolinks/issues/313
const hoverTime = 300
const fetchers = {}
const doc = document.implementation.createHTMLDocument('prefetch')
2023-12-29 16:17:03 -07:00
function fetchPage(url, success) {
const xhr = new XMLHttpRequest()
xhr.open('GET', url)
xhr.setRequestHeader('VND.PREFETCH', 'true')
xhr.setRequestHeader('Accept', 'text/html')
xhr.onreadystatechange = () => {
if (xhr.readyState !== XMLHttpRequest.DONE) return
if (xhr.status !== 200) return
success(xhr.responseText)
}
xhr.send()
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
function prefetchTurbolink(url) {
fetchPage(url, responseText => {
doc.open()
doc.write(responseText)
doc.close()
const snapshot = Turbolinks.Snapshot.fromHTMLElement(doc.documentElement)
Turbolinks.controller.cache.put(url, snapshot)
})
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
function prefetch(url) {
if (prefetched(url)) return
prefetchTurbolink(url)
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
function prefetched(url) {
return location.href === url || Turbolinks.controller.cache.has(url)
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
function prefetching(url) {
return !!fetchers[url]
2023-09-27 10:58:42 -06:00
}
2023-12-29 16:17:03 -07:00
function cleanup(event) {
const element = event.target
clearTimeout(fetchers[element.href])
element.removeEventListener('mouseleave', cleanup)
2023-09-27 10:58:42 -06:00
}
document.addEventListener('mouseover', event => {
2023-12-29 16:17:03 -07:00
if (!event.target.dataset.prefetch) return
const url = event.target.href
if (prefetched(url)) return
if (prefetching(url)) return
cleanup(event)
event.target.addEventListener('mouseleave', cleanup)
fetchers[url] = setTimeout(() => prefetch(url), hoverTime)
2023-09-27 10:58:42 -06:00
})