* add npm dependencies used in this theme * implement helper to configure JS and ESBuild * migrate jquery popper.js bootstrap fontawesome to js bundle * refactor main.js into smaller pieces, and moved navbar.js to assets * remove list.js. It adjusts post card height to be the same, but is actually not needed. * refactored notes.js, search.js, single.js into application.js * move ityped to js asset, implement experiences horizontal vertical line in css * align recent post height via css * migrated home.js and refactored into various sections * migrated darkMode feature to js bundle * moved mermaid feature to js bundle * migrate syntax highlight to js bundle * migrate katex ( js portion ) to js bundle * migrate pdf-js to js bundle by delegating to cdn * set explicit comparisions for feature envvars so js can properly optimize * removed goat-counter * more fixes for broken achievements and small bugs * more bug fixes * allow configuration of hightlight.js, fix video-player shortcode * remove jquery all together * add null handling and fix merge conflicts Co-authored-by: Aaron Qian <aaron@yeet.io>
133 lines
4.5 KiB
JavaScript
133 lines
4.5 KiB
JavaScript
import Fuse from 'fuse.js'
|
|
import Mark from 'mark.js'
|
|
|
|
window.addEventListener('DOMContentLoaded', () => {
|
|
const summaryInclude = 60
|
|
|
|
const fuseOptions = {
|
|
shouldSort: true,
|
|
includeMatches: true,
|
|
threshold: 0.0,
|
|
tokenize: true,
|
|
location: 0,
|
|
distance: 100,
|
|
maxPatternLength: 32,
|
|
minMatchCharLength: 1,
|
|
keys: [
|
|
{ name: 'title', weight: 0.8 },
|
|
{ name: 'hero', weight: 0.7 },
|
|
{ name: 'summary', weight: 0.6 },
|
|
{ name: 'date', weight: 0.5 },
|
|
{ name: 'contents', weight: 0.5 },
|
|
{ name: 'tags', weight: 0.3 },
|
|
{ name: 'categories', weight: 0.3 }
|
|
]
|
|
}
|
|
|
|
const searchQuery = param('keyword')
|
|
if (searchQuery) {
|
|
document.getElementById('search-query').value = searchQuery
|
|
executeSearch(searchQuery)
|
|
} else {
|
|
const node = document.createElement('p')
|
|
node.textContent = 'Please enter a word or phrase above'
|
|
document.getElementById('search-results')?.append(node)
|
|
}
|
|
|
|
function executeSearch (searchQuery) {
|
|
const url = window.location.href.split('/search/')[0] + '/index.json'
|
|
|
|
fetch(url).then(function (data) {
|
|
const pages = data
|
|
const fuse = new Fuse(pages, fuseOptions)
|
|
const results = fuse.search(searchQuery)
|
|
|
|
document.getElementById('search-box').value = searchQuery
|
|
if (results.length > 0) {
|
|
populateResults(results)
|
|
} else {
|
|
const node = document.createElement('p')
|
|
node.textContent = 'No matches found'
|
|
document.getElementById('search-results')?.append(node)
|
|
}
|
|
})
|
|
}
|
|
|
|
function populateResults (results) {
|
|
results.forEach(function (value, key) {
|
|
const contents = value.item.contents
|
|
let snippet = ''
|
|
const snippetHighlights = []
|
|
if (fuseOptions.tokenize) {
|
|
snippetHighlights.push(searchQuery)
|
|
} else {
|
|
value.matches.forEach(function (mvalue) {
|
|
if (mvalue.key === 'tags' || mvalue.key === 'categories') {
|
|
snippetHighlights.push(mvalue.value)
|
|
} else if (mvalue.key === 'contents') {
|
|
const start = mvalue.indices[0][0] - summaryInclude > 0 ? mvalue.indices[0][0] - summaryInclude : 0
|
|
const end = mvalue.indices[0][1] + summaryInclude < contents.length ? mvalue.indices[0][1] + summaryInclude : contents.length
|
|
snippet += contents.substring(start, end)
|
|
snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1))
|
|
}
|
|
})
|
|
}
|
|
|
|
if (snippet.length < 1) {
|
|
snippet += contents.substring(0, summaryInclude * 2)
|
|
}
|
|
// pull template from hugo template definition
|
|
const templateDefinition = document.getElementById('search-result-template').innerHTML
|
|
// replace values
|
|
const output = render(templateDefinition, {
|
|
key,
|
|
title: value.item.title,
|
|
hero: value.item.hero,
|
|
date: value.item.date,
|
|
summary: value.item.summary,
|
|
link: value.item.permalink,
|
|
tags: value.item.tags,
|
|
categories: value.item.categories,
|
|
snippet
|
|
})
|
|
|
|
const doc = new DOMParser().parseFromString(output, 'text/html')
|
|
document.getElementById('search-results').append(doc)
|
|
|
|
snippetHighlights.forEach(function (snipvalue) {
|
|
const context = document.getElementById('#summary-' + key)
|
|
const instance = new Mark(context)
|
|
instance.mark(snipvalue)
|
|
})
|
|
})
|
|
}
|
|
|
|
function param (name) {
|
|
return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ')
|
|
}
|
|
|
|
function render (templateString, data) {
|
|
let conditionalMatches, copy
|
|
const conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g
|
|
// since loop below depends on re.lastInxdex, we use a copy to capture any manipulations whilst inside the loop
|
|
copy = templateString
|
|
while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
|
|
if (data[conditionalMatches[1]]) {
|
|
// valid key, remove conditionals, leave contents.
|
|
copy = copy.replace(conditionalMatches[0], conditionalMatches[2])
|
|
} else {
|
|
// not valid, remove entire section
|
|
copy = copy.replace(conditionalMatches[0], '')
|
|
}
|
|
}
|
|
templateString = copy
|
|
// now any conditionals removed we can do simple substitution
|
|
let key, find, re
|
|
for (key in data) {
|
|
find = '\\$\\{\\s*' + key + '\\s*\\}'
|
|
re = new RegExp(find, 'g')
|
|
templateString = templateString.replace(re, data[key])
|
|
}
|
|
return templateString
|
|
}
|
|
})
|