From 2127249b6c64e91b0e4ea182344fcf8c2540e07f Mon Sep 17 00:00:00 2001 From: Aiden Bai Date: Thu, 28 Apr 2022 23:24:31 -0700 Subject: [PATCH] Add Cmd-K support --- .pnpm-debug.log | 14 +++ assets/js/search.js | 281 ++++++++++++++++++++++++-------------------- 2 files changed, 165 insertions(+), 130 deletions(-) create mode 100644 .pnpm-debug.log diff --git a/.pnpm-debug.log b/.pnpm-debug.log new file mode 100644 index 000000000..bd65a3352 --- /dev/null +++ b/.pnpm-debug.log @@ -0,0 +1,14 @@ +{ + "0 debug pnpm:scope": { + "selected": 1 + }, + "1 error pnpm": { + "code": "ERR_PNPM_NO_SCRIPT", + "err": { + "name": "pnpm", + "message": "Missing script: dev", + "code": "ERR_PNPM_NO_SCRIPT", + "stack": "pnpm: Missing script: dev\n at Object.handler (/opt/homebrew/Cellar/node/17.3.0/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.9/node_modules/pnpm/dist/pnpm.cjs:178363:15)\n at async /opt/homebrew/Cellar/node/17.3.0/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.9/node_modules/pnpm/dist/pnpm.cjs:182614:21\n at async run (/opt/homebrew/Cellar/node/17.3.0/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.9/node_modules/pnpm/dist/pnpm.cjs:182588:34)\n at async runPnpm (/opt/homebrew/Cellar/node/17.3.0/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.9/node_modules/pnpm/dist/pnpm.cjs:182807:5)\n at async /opt/homebrew/Cellar/node/17.3.0/pnpm-global/5/node_modules/.pnpm/pnpm@6.32.9/node_modules/pnpm/dist/pnpm.cjs:182799:7" + } + } +} \ No newline at end of file diff --git a/assets/js/search.js b/assets/js/search.js index 0aacb5f44..cfbe368f4 100644 --- a/assets/js/search.js +++ b/assets/js/search.js @@ -9,47 +9,47 @@ const removeMarkdown = ( 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" + options.listUnicodeChar + ' $1' ); - else output = output.replace(/^([\s\t]*)([\*\-\+]|\d+\.)\s+/gm, "$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(/\s{0,2}\[.*?\]: .*?$/g, "") - .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? "$1" : "") - .replace(/\[(.*?)\][\[\(].*?[\]\)]/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(/<[^>]*>/g, '') + .replace(/^[=\-]{2,}\s*$/g, '') + .replace(/\[\^.+?\](\: .*?$)?/g, '') + .replace(/\s{0,2}\[.*?\]: .*?$/g, '') + .replace(/\!\[(.*?)\][\[\(].*?[\]\)]/g, options.useImgAltText ? '$1' : '') + .replace(/\[(.*?)\][\[\(].*?[\]\)]/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( /^(\n)?\s{0,}#{1,6}\s+| {0,}(\n)?\s{0,}#{0,} {0,}(\n)?\s{0,}$/gm, - "$1$2$3" + '$1$2$3' ) - .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(/([\*_]{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; @@ -58,182 +58,203 @@ const removeMarkdown = ( }; // ----- -(async function() { - const encoder = str => str.toLowerCase().split(/([^a-z]|[^\x00-\x7F])+/) +(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", - encode: encoder, - }, { - field: "title", - tokenize: "forward", - encode: encoder, - }] - }) + index: [ + { + field: 'content', + tokenize: 'reverse', + encode: encoder, + }, + { + field: 'title', + tokenize: 'forward', + encode: encoder, + }, + ], + }); - const { content } = await fetchData + const { content } = await fetchData; for (const [key, value] of Object.entries(content)) { contentIndex.add({ id: key, title: value.title, content: removeMarkdown(value.content), - }) + }); } const highlight = (content, term) => { - const highlightWindow = 20 - 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())) + const highlightWindow = 20; + 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()) + ); - const occurrencesIndices = splitText - .map(includesCheck) + 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++) { - const window = occurrencesIndices.slice(i, i + highlightWindow) - const windowSum = window.reduce((total, cur) => total + cur, 0) + let bestSum = 0; + let bestIndex = 0; + 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) { - bestSum = windowSum - bestIndex = i + bestSum = windowSum; + bestIndex = i; } } - const startIndex = Math.max(bestIndex - highlightWindow, 0) - const endIndex = Math.min(startIndex + 2 * highlightWindow, splitText.length) + const startIndex = Math.max(bestIndex - highlightWindow, 0); + const endIndex = Math.min( + startIndex + 2 * highlightWindow, + splitText.length + ); const mappedText = splitText .slice(startIndex, endIndex) - .map(token => { + .map((token) => { if (includesCheck(token)) { - return `${token}` + return `${token}`; } - return token + return token; }) - .join(" ") - .replaceAll(' ', " ") - return `${startIndex === 0 ? "" : "..."}${mappedText}${endIndex === splitText.length ? "" : "..."}` - } + .join(' ') + .replaceAll(' ', ' '); + return `${startIndex === 0 ? '' : '...'}${mappedText}${ + endIndex === splitText.length ? '' : '...' + }`; + }; const resultToHTML = ({ url, title, content, term }) => { - const text = removeMarkdown(content) - const resultTitle = highlight(title, term) - const resultText = highlight(text, term) + const text = removeMarkdown(content); + const resultTitle = highlight(title, term); + const resultText = highlight(text, term); return `` - } + `; + }; const redir = (id, term) => { - window.location.href = `${BASE_URL}${id}#:~:text=${encodeURIComponent(term)}/` - } + navigate( + new URL( + `${BASE_URL.slice(0, -1)}${id}#:~:text=${encodeURIComponent(term)}/` + ) + ); + }; - const formatForDisplay = id => ({ + const formatForDisplay = (id) => ({ id, url: id, title: content[id].title, - content: content[id].content - }) + content: content[id].content, + }); - 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] - redir(anchor.id, term) + 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]; + redir(anchor.id, term); } - }) + }); source.addEventListener('input', (e) => { - term = e.target.value + term = e.target.value; const searchResults = contentIndex.search(term, [ { - field: "content", + field: 'content', limit: 10, }, { - field: "title", + field: 'title', limit: 5, - } - ]) - const getByField = field => { - const results = searchResults.filter(x => x.field === field) + }, + ]); + const getByField = (field) => { + const results = searchResults.filter((x) => x.field === field); if (results.length === 0) { - return [] + return []; } else { - return [...results[0].result] + return [...results[0].result]; } - } - const allIds = new Set([...getByField('title'), ...getByField('content')]) - const finalResults = [...allIds].map(formatForDisplay) + }; + const allIds = new Set([...getByField('title'), ...getByField('content')]); + const finalResults = [...allIds].map(formatForDisplay); // display if (finalResults.length === 0) { results.innerHTML = `` + `; } else { results.innerHTML = finalResults - .map(result => resultToHTML({ - ...result, - term, - })) - .join("\n") - const anchors = document.getElementsByClassName("result-card"); - [...anchors].forEach(anchor => { - anchor.onclick = () => redir(anchor.id, term) - }) + .map((result) => + resultToHTML({ + ...result, + term, + }) + ) + .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" - source.focus() + 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)) { - event.preventDefault() - openSearch() + if (event.key === 'k' && (event.ctrlKey || event.metaKey)) { + event.preventDefault(); + openSearch(); } - if (event.key === "Escape") { - event.preventDefault() - closeSearch() + if (event.key === 'Escape') { + event.preventDefault(); + closeSearch(); } - }) + }); - const searchButton = document.getElementById("search-icon") + const searchButton = document.getElementById('search-icon'); searchButton.addEventListener('click', (evt) => { - openSearch() - }) + openSearch(); + }); searchButton.addEventListener('keydown', (evt) => { - openSearch() - }) + openSearch(); + }); searchContainer.addEventListener('click', (evt) => { - closeSearch() - }) - document.getElementById("search-space").addEventListener('click', (evt) => { - evt.stopPropagation() - }) -})() - + closeSearch(); + }); + document.getElementById('search-space').addEventListener('click', (evt) => { + evt.stopPropagation(); + }); +})();