summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSerghei Iakovlev <egrep@protonmail.ch>2022-07-10 15:08:59 +0200
committerSerghei Iakovlev <egrep@protonmail.ch>2022-07-10 15:08:59 +0200
commit05a643db54d7349c8c44f819737e48ff5555f9ff (patch)
treed7ed6d8cdc0d9dc91368fffd3eb432197f087d10
parente77bc7115a6376e46d83a30d5104e1a7806f8773 (diff)
downloadgohugo-theme-ed-05a643db54d7349c8c44f819737e48ff5555f9ff.tar.gz
Escape user input and handle 404 for index.json
-rw-r--r--assets/js/search.js50
-rw-r--r--layouts/partials/data.html3
2 files changed, 40 insertions, 13 deletions
diff --git a/assets/js/search.js b/assets/js/search.js
index bd9d791..4a2a815 100644
--- a/assets/js/search.js
+++ b/assets/js/search.js
@@ -17,11 +17,23 @@ const SENTENCE_BOUNDARY_REGEX = /\b\.\s/gm;
// in the blurb as it is being built.
const WORD_REGEX = /\b(\w*)[\W|\s|\b]?/gm;
+function buildPath(...args) {
+ return args.map((part, i) => {
+ if (i === 0) {
+ return part.trim().replace(/[/]*$/g, '');
+ }
+ return part.trim().replace(/(^[/]*|[/]*$)/g, '');
+ }).filter(x=>x.length).join('/');
+}
+
function initConfig() {
const defaults = {
strings: {
searchEnterTerm: 'Please enter a search term.',
searchNoResults: 'No results found.'
+ },
+ site: {
+ baseUrl: '/'
}
};
@@ -35,7 +47,11 @@ function initConfig() {
async function initSearchIndex() {
try {
- const response = await fetch('/index.json');
+ const url = buildPath(config.site.baseUrl, 'index.json');
+ const response = await fetch(url);
+
+ if (response.status !== 200) return;
+
pagesIndex = await response.json();
// Create the lunr index for the search
@@ -91,6 +107,8 @@ function searchSite(query) {
}
function getSearchResults(query) {
+ if (typeof searchIndex === 'undefined') return [];
+
return searchIndex.search(query).flatMap((hit) => {
if (hit.ref === 'undefined') return [];
let pageMatch = pagesIndex.filter((page) => page.href === hit.ref)[0];
@@ -178,15 +196,21 @@ function updateSearchResults(query, results) {
}
function createSearchResultBlurb(query, pageContent) {
+ // g: Global search
+ // m: Multi-line search
+ // i: Case-insensitive search
const searchQueryRegex = new RegExp(createQueryStringRegex(query), 'gmi');
+
const searchQueryHits = Array.from(
pageContent.matchAll(searchQueryRegex),
(m) => m.index
);
+
const sentenceBoundaries = Array.from(
pageContent.matchAll(SENTENCE_BOUNDARY_REGEX),
(m) => m.index
);
+
let parsedSentence = '';
let searchResultText = '';
let lastEndOfSentence = 0;
@@ -217,16 +241,8 @@ function createSearchResultBlurb(query, pageContent) {
}
function createQueryStringRegex(query) {
- const searchTerms = query.split(' ');
- if (searchTerms.length === 1) {
- return query;
- }
- query = '';
- for (const term of searchTerms) {
- query += `${term}|`;
- }
- query = query.slice(0, -1);
- return `(${query})`;
+ let escaped = RegExp.escape(query);
+ return escaped.split(' ').length === 1 ? `(${escaped})` : `(${escaped.split(' ').join('|')})`;
}
function tokenize(input) {
@@ -291,6 +307,15 @@ function ellipsize(input, maxLength) {
return input.slice(0, words[maxLength].end) + '...';
}
+// RegExp.escape() polyfill
+if (!RegExp.escape) {
+ RegExp.escape = function(str) {
+ // $& means the whole matched string
+ return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ };
+}
+
+// 'string'.matchAll(str, regex) polyfill
if (!String.prototype.matchAll) {
String.prototype.matchAll = function (regex) {
function ensureFlag(flags, flag) {
@@ -312,12 +337,11 @@ document.addEventListener('DOMContentLoaded', function () {
const searchForm = document.getElementById('search-form');
const searchInput = document.getElementById('search');
- config = initConfig();
-
if (searchForm === null || searchInput === null) {
return;
}
+ config = initConfig();
initSearchIndex();
searchForm.addEventListener('submit', (e) => {
diff --git a/layouts/partials/data.html b/layouts/partials/data.html
index cdd445c..fa12238 100644
--- a/layouts/partials/data.html
+++ b/layouts/partials/data.html
@@ -6,6 +6,9 @@
"strings": {
"searchEnterTerm": {{ i18n "search_enter_term" }},
"searchNoResults": {{ i18n "search_no_results" }}
+ },
+ "site": {
+ "baseUrl": {{ site.BaseURL }}
}
}
</script>