diff --git a/assets/js/router.js b/assets/js/router.js index 787cd765..3bdc8102 100644 --- a/assets/js/router.js +++ b/assets/js/router.js @@ -1,16 +1,26 @@ -import { router, navigate, reload, prefetch } from "https://unpkg.com/million@1.9.6/dist/router.mjs" +import { + apply, + navigate, + prefetch, + router, +} from "https://unpkg.com/million@1.9.8-0/dist/router.mjs" -export const attachSPARouting = (draw) => { +export const attachSPARouting = (init, rerender) => { // Attach SPA functions to the global Million namespace window.Million = { - router, + apply, navigate, - reload, prefetch, - }; - router(".singlePage") - // We need on initial load, then subsequent redirs - // requestAnimationFrame() delays graph draw until SPA routing is finished - reload(draw) - window.addEventListener("DOMContentLoaded", () => requestAnimationFrame(draw)) + router, + } + + const render = () => requestAnimationFrame(rerender) + + window.addEventListener("DOMContentLoaded", () => { + apply((doc) => init(doc)) + init() + router(".singlePage") + render() + }) + window.addEventListener("million:navigate", render) } diff --git a/assets/js/search.js b/assets/js/search.js index ee006471..5896061b 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -7,47 +7,44 @@ const removeMarkdown = ( gfm: true, useImgAltText: false, preserveLinks: false, - } + }, ) => { - let output = markdown || '' - output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, '') + let output = markdown || "" + output = output.replace(/^(-\s*?|\*\s*?|_\s*?){3,}\s*$/gm, "") try { if (options.stripListLeaders) { if (options.listUnicodeChar) - output = output.replace( - /^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, - options.listUnicodeChar + ' $1' - ) - else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, '$1') + output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, options.listUnicodeChar + " $1") + else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$1") } if (options.gfm) { output = output - .replace(/\n={2,}/g, '\n') - .replace(/~{3}.*\n/g, '') - .replace(/~~/g, '') - .replace(/`{3}.*\n/g, '') + .replace(/\n={2,}/g, "\n") + .replace(/~{3}.*\n/g, "") + .replace(/~~/g, "") + .replace(/`{3}.*\n/g, "") } if (options.preserveLinks) { - output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, '$1 ($2)') + output = output.replace(/\[(.*?)\][\[\(](.*?)[\]\)]/g, "$1 ($2)") } output = output - .replace(/<[^>]*>/g, '') - .replace(/^[=\-]{2,}\s*$/g, '') - .replace(/\[\^.+?\](\: .*?$)?/g, '') - .replace(/(#{1,6})\s+(.+)\1?/g, '$2') - .replace(/\s{0,2}\[.*?\]: .*?$/g, '') - .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? '$1' : '') - .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, '$1') - .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, '$1') - .replace(/^\s{0,3}>\s?/g, '') - .replace(/(^|\n)\s{0,3}>\s?/g, '\n\n') - .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, '') - .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, '$2') - .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, '$2') - .replace(/(`{3,})(.*?)\1/gm, '$2') - .replace(/`(.+?)`/g, '$1') - .replace(/\n{2,}/g, '\n\n') + .replace(/<[^>]*>/g, "") + .replace(/^[=\-]{2,}\s*$/g, "") + .replace(/\[\^.+?\](\: .*?$)?/g, "") + .replace(/(#{1,6})\s+(.+)\1?/g, "$2") + .replace(/\s{0,2}\[.*?\]: .*?$/g, "") + .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? "$1" : "") + .replace(/\[(.*?)\][\[\(].*?[\]\)]/g, "$1") + .replace(/!?\[\[\S[^\[\]\|]*(?:\|([^\[\]]*))?\S\]\]/g, "$1") + .replace(/^\s{0,3}>\s?/g, "") + .replace(/(^|\n)\s{0,3}>\s?/g, "\n\n") + .replace(/^\s{1,2}\[(.*?)\]: (\S+)( ".*?")?\s*$/g, "") + .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2") + .replace(/([\*_]{1,3})(\S.*?\S{0,1})\1/g, "$2") + .replace(/(`{3,})(.*?)\1/gm, "$2") + .replace(/`(.+?)`/g, "$1") + .replace(/\n{2,}/g, "\n\n") } catch (e) { console.error(e) return markdown @@ -64,27 +61,28 @@ const highlight = (content, term) => { if (directMatchIdx !== -1) { const h = highlightWindow / 2 const before = content.substring(0, directMatchIdx).split(" ").slice(-h) - const after = content.substring(directMatchIdx + term.length, content.length - 1).split(" ").slice(0, h) - return (before.length == h ? `...${before.join(" ")}` : before.join(" ")) + `${term}` + after.join(" ") + const after = content + .substring(directMatchIdx + term.length, content.length - 1) + .split(" ") + .slice(0, h) + return ( + (before.length == h ? `...${before.join(" ")}` : before.join(" ")) + + `${term}` + + after.join(" ") + ) } - const tokenizedTerm = term.split(/\s+/).filter((t) => t !== '') - const splitText = content.split(/\s+/).filter((t) => t !== '') + const tokenizedTerm = term.split(/\s+/).filter((t) => t !== "") + const splitText = content.split(/\s+/).filter((t) => t !== "") const includesCheck = (token) => - tokenizedTerm.some((term) => - token.toLowerCase().startsWith(term.toLowerCase()) - ) + tokenizedTerm.some((term) => token.toLowerCase().startsWith(term.toLowerCase())) const occurrencesIndices = splitText.map(includesCheck) // calculate best index let bestSum = 0 let bestIndex = 0 - for ( - let i = 0; - i < Math.max(occurrencesIndices.length - highlightWindow, 0); - i++ - ) { + for (let i = 0; i < Math.max(occurrencesIndices.length - highlightWindow, 0); i++) { const window = occurrencesIndices.slice(i, i + highlightWindow) const windowSum = window.reduce((total, cur) => total + cur, 0) if (windowSum >= bestSum) { @@ -94,10 +92,7 @@ const highlight = (content, term) => { } const startIndex = Math.max(bestIndex - highlightWindow, 0) - const endIndex = Math.min( - startIndex + 2 * highlightWindow, - splitText.length - ) + const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length) const mappedText = splitText .slice(startIndex, endIndex) .map((token) => { @@ -106,27 +101,28 @@ const highlight = (content, term) => { } return token }) - .join(' ') - .replaceAll(' ', ' ') - return `${startIndex === 0 ? '' : '...'}${mappedText}${endIndex === splitText.length ? '' : '...' - }` -}; + .join(" ") + .replaceAll(' ', " ") + return `${startIndex === 0 ? "" : "..."}${mappedText}${ + endIndex === splitText.length ? "" : "..." + }` +} -(async function() { +;(async function () { const encoder = (str) => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/) const contentIndex = new FlexSearch.Document({ cache: true, - charset: 'latin:extra', + charset: "latin:extra", optimize: true, index: [ { - field: 'content', - tokenize: 'reverse', + field: "content", + tokenize: "reverse", encode: encoder, }, { - field: 'title', - tokenize: 'forward', + field: "title", + tokenize: "forward", encode: encoder, }, ], @@ -154,10 +150,8 @@ const highlight = (content, term) => { const redir = (id, term) => { // SPA navigation window.Million.navigate( - new URL( - `${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/` - ), - '.singlePage' + new URL(`${BASE_URL.replace(/\/$/g, "")}${id}#:~:text=${encodeURIComponent(term)}/`), + ".singlePage", ) closeSearch() } @@ -169,24 +163,24 @@ const highlight = (content, term) => { content: content[id].content, }) - const source = document.getElementById('search-bar') - const results = document.getElementById('results-container') + const source = document.getElementById("search-bar") + const results = document.getElementById("results-container") let term - source.addEventListener('keyup', (e) => { - if (e.key === 'Enter') { - const anchor = document.getElementsByClassName('result-card')[0] + source.addEventListener("keyup", (e) => { + if (e.key === "Enter") { + const anchor = document.getElementsByClassName("result-card")[0] redir(anchor.id, term) } }) - source.addEventListener('input', (e) => { + source.addEventListener("input", (e) => { term = e.target.value const searchResults = contentIndex.search(term, [ { - field: 'content', + field: "content", limit: 10, }, { - field: 'title', + field: "title", limit: 5, }, ]) @@ -198,7 +192,7 @@ const highlight = (content, term) => { return [...results[0].result] } } - const allIds = new Set([...getByField('title'), ...getByField('content')]) + const allIds = new Set([...getByField("title"), ...getByField("content")]) const finalResults = [...allIds].map(formatForDisplay) // display @@ -213,58 +207,55 @@ const highlight = (content, term) => { resultToHTML({ ...result, term, - }) + }), ) - .join('\n') - const anchors = [...document.getElementsByClassName('result-card')] + .join("\n") + const anchors = [...document.getElementsByClassName("result-card")] anchors.forEach((anchor) => { anchor.onclick = () => redir(anchor.id, term) }) } }) - const searchContainer = document.getElementById('search-container') + const searchContainer = document.getElementById("search-container") function openSearch() { - if ( - searchContainer.style.display === 'none' || - searchContainer.style.display === '' - ) { - source.value = '' - results.innerHTML = '' - searchContainer.style.display = 'block' + if (searchContainer.style.display === "none" || searchContainer.style.display === "") { + source.value = "" + results.innerHTML = "" + searchContainer.style.display = "block" source.focus() } else { - searchContainer.style.display = 'none' + searchContainer.style.display = "none" } } function closeSearch() { - searchContainer.style.display = 'none' + searchContainer.style.display = "none" } - document.addEventListener('keydown', (event) => { - if (event.key === 'k' && (event.ctrlKey || event.metaKey)) { + document.addEventListener("keydown", (event) => { + if (event.key === "k" && (event.ctrlKey || event.metaKey)) { event.preventDefault() openSearch() } - if (event.key === 'Escape') { + if (event.key === "Escape") { event.preventDefault() closeSearch() } }) - const searchButton = document.getElementById('search-icon') - searchButton.addEventListener('click', (evt) => { + const searchButton = document.getElementById("search-icon") + searchButton.addEventListener("click", (evt) => { openSearch() }) - searchButton.addEventListener('keydown', (evt) => { + searchButton.addEventListener("keydown", (evt) => { openSearch() }) - searchContainer.addEventListener('click', (evt) => { + searchContainer.addEventListener("click", (evt) => { closeSearch() }) - document.getElementById('search-space').addEventListener('click', (evt) => { + document.getElementById("search-space").addEventListener("click", (evt) => { evt.stopPropagation() }) })() diff --git a/layouts/partials/head.html b/layouts/partials/head.html index ba02260e..6eb8eaf3 100644 --- a/layouts/partials/head.html +++ b/layouts/partials/head.html @@ -10,11 +10,7 @@ end }} - + { + const render = () => { + // NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page, adds event listeners, etc. If you are only dealing with basic DOM replacement, use the init function const siteBaseURL = new URL({{$.Site.BaseURL}}); const pathBase = siteBaseURL.pathname; const pathWindow = window.location.pathname; const isHome = pathBase == pathWindow; - // NOTE: everything within this callback will be executed for every page navigation. This is a good place to put JavaScript that loads or modifies data on the page. {{if $.Site.Data.config.enableFooter}} const container = document.getElementById("graph-container") // retry if the graph is not ready - if (!container) return requestAnimationFrame(draw) + if (!container) return requestAnimationFrame(render) // clear the graph in case there is anything within it container.textContent = "" @@ -93,6 +89,7 @@ } {{end}} + {{if $.Site.Data.config.enableLinkPreview}} initPopover( {{strings.TrimRight "/" .Site.BaseURL }}, @@ -100,8 +97,12 @@ {{$.Site.Data.config.enableLatex}} ) {{end}} + } + + const init = (doc = document) => { + // NOTE: everything within this callback will be executed for initial page navigation. This is a good place to put JavaScript that only replaces DOM nodes. {{if $.Site.Data.config.enableLatex}} - renderMathInElement(document.body, { + renderMathInElement(doc.body, { delimiters: [ {left: '$$', right: '$$', display: true}, {left: '$', right: '$', display: false}, @@ -116,7 +117,7 @@ resources.Minify }} {{else}} {{end}}