diff --git a/.eslintrc b/.eslintrc
index 5c11324..1bcf8e0 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -7,6 +7,6 @@
"globals": {
"mermaid": true,
"$": true,
- "process": true
+ "URLSearchParams": true
}
}
diff --git a/content/search.md b/content/search.md
new file mode 100644
index 0000000..f290ee3
--- /dev/null
+++ b/content/search.md
@@ -0,0 +1,7 @@
+---
+title: Search
+layout: search
+outputs: [json, html]
+image: unsplash:mdS647i6-ZM
+hidden_from_navbar: true
+---
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
index fcdedcf..20c9506 100644
--- a/layouts/_default/baseof.html
+++ b/layouts/_default/baseof.html
@@ -41,6 +41,8 @@
{{ partial "script_async.html" "js/app.js" }}
+ {{ block "scripts_extra" . }}{{ end }}
+
{{ if not .Site.IsServer }}
{{ end }}
diff --git a/layouts/_default/search.html b/layouts/_default/search.html
new file mode 100644
index 0000000..cb9d021
--- /dev/null
+++ b/layouts/_default/search.html
@@ -0,0 +1,29 @@
+{{ define "main" }}
+ {{ $pages := where .Site.RegularPages "Section" "!=" ""}}
+
+
+
+ {{ partial "content.html" . }}
+
+
+
+
+
+ {{ range $pages }}
+ {{ partial "list_item.html" . }}
+ {{ end }}
+
+
+
+
+
+{{ end }}
+
+{{ define "scripts_extra" }}
+ {{ partial "script_async.html" "js/search.js" }}
+{{ end }}
diff --git a/layouts/_default/search.json b/layouts/_default/search.json
new file mode 100644
index 0000000..53a1ef1
--- /dev/null
+++ b/layouts/_default/search.json
@@ -0,0 +1,8 @@
+{{- $result := slice -}}
+
+{{- range (where .Site.RegularPages "Section" "!=" "") -}}
+ {{- $data := dict "title" .Title "content" .Plain "id" (.File.UniqueID) -}}
+ {{- $result = $result | append $data -}}
+{{- end -}}
+
+{{ jsonify $result }}
diff --git a/layouts/index.html b/layouts/index.html
index 388d2f2..6598a7a 100644
--- a/layouts/index.html
+++ b/layouts/index.html
@@ -5,6 +5,12 @@
+
{{ if .Params.image }}
diff --git a/package-lock.json b/package-lock.json
index 2e3fb35..9716bad 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -13,7 +13,8 @@
"elevator.js": "1.0.1",
"jquery": "3.6.0",
"lg-thumbnail": "1.2.1",
- "lightgallery": "1.10.0"
+ "lightgallery": "1.10.0",
+ "lunr": "2.3.9"
},
"devDependencies": {
"broken-link-checker-local": "0.2.1",
@@ -4564,6 +4565,11 @@
"yallist": "^2.1.2"
}
},
+ "node_modules/lunr": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
+ },
"node_modules/map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
@@ -11480,6 +11486,11 @@
"yallist": "^2.1.2"
}
},
+ "lunr": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
+ "integrity": "sha512-zTU3DaZaF3Rt9rhN3uBMGQD3dD2/vFQqnvZCDv4dl5iOzq2IZQqTxu90r4E5J+nP70J3ilqVCrbho2eWaeW8Ow=="
+ },
"map-age-cleaner": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
diff --git a/package.json b/package.json
index d3633b5..c333d26 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
"elevator.js": "1.0.1",
"jquery": "3.6.0",
"lg-thumbnail": "1.2.1",
- "lightgallery": "1.10.0"
+ "lightgallery": "1.10.0",
+ "lunr": "2.3.9"
}
}
diff --git a/scripts/build.sh b/scripts/build.sh
index efa8603..2881f92 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -8,7 +8,7 @@ rm -rf $OUTPUT_DIR
rm -rf $STATIC_BUILD
rm -rf $BASEDIR/resources
-mkdir -p $STATIC_BUILD/js $STATIC_BUILD/css $STATIC_BUILD/audio
+mkdir -p $STATIC_BUILD/css $STATIC_BUILD/audio
cp -r $BASEDIR/node_modules/lightgallery/dist/fonts $STATIC_BUILD
cp -r $STATIC_SRC/img $STATIC_BUILD/img
@@ -17,7 +17,7 @@ cp -r $BASEDIR/node_modules/@fortawesome/fontawesome-free/css/all.min.css $STATI
cp $BASEDIR/node_modules/lightgallery/dist/css/lightgallery.min.css $STATIC_BUILD/css/lightgallery.css
cp -r $BASEDIR/node_modules/@fortawesome/fontawesome-free/webfonts $STATIC_BUILD
-cp $STATIC_SRC/js/app.js $STATIC_BUILD/js/app.js
+cp -r $STATIC_SRC/js/ $STATIC_BUILD/js/
cp $BASEDIR/node_modules/jquery/dist/jquery.min.js $STATIC_BUILD/js/jquery.min.js
cp $BASEDIR/node_modules/lightgallery/dist/js/lightgallery-all.min.js $STATIC_BUILD/js/lightgallery.min.js
cp $BASEDIR/node_modules/bootstrap/dist/js/bootstrap.bundle.min.js $STATIC_BUILD/js/bootstrap.min.js
diff --git a/static/src/js/search.js b/static/src/js/search.js
new file mode 100644
index 0000000..bac97ac
--- /dev/null
+++ b/static/src/js/search.js
@@ -0,0 +1,110 @@
+const lunr = require('lunr');
+
+const MIN_SEARCH_LENGTH = 3;
+
+const searchInput = $('#search-input');
+const searchResultsMessage = $('#search-results-message');
+const searchResultsCount = $('#search-results-count');
+const searchResultsList = $('.search-results');
+const searchResults = searchResultsList.children();
+
+let searchIndex;
+
+function showMessage(message) {
+ if (message) {
+ searchResults.hide();
+ searchResultsCount.hide();
+ searchResultsMessage.show();
+ searchResultsMessage.text(message);
+ } else {
+ searchResultsMessage.hide();
+ }
+}
+
+function getInitialSearchTerm() {
+ const urlParams = new URLSearchParams(window.location.search);
+ return urlParams.get('q') || '';
+}
+
+function onSearch(term) {
+ if (term.length < MIN_SEARCH_LENGTH) {
+ searchResultsCount.hide();
+ showMessage(
+ `Enter a search term (of at least ${MIN_SEARCH_LENGTH} characters) to search`
+ );
+ return;
+ }
+ showMessage('');
+
+ // do search
+ const results = searchIndex.search(term).map(match => match.ref);
+
+ if (!results.length) {
+ showMessage('No results found');
+ return;
+ }
+
+ // Show the results
+ searchResults.each(function() {
+ const el = $(this);
+ if (results.includes(this.dataset.id)) {
+ el.css('display', 'flex');
+ } else {
+ el.css('display', 'none');
+ }
+ });
+
+ // Sort results by accuracy
+ searchResults
+ .sort(function(a, b) {
+ const aOrder = results.indexOf(a.dataset.id);
+ const bOrder = results.indexOf(b.dataset.id);
+ if (aOrder < bOrder) {
+ return -1;
+ }
+ if (aOrder > bOrder) {
+ return 1;
+ }
+ return 0;
+ })
+ .appendTo(searchResultsList);
+
+ // Show number of results
+ searchResultsCount.text(`Found ${results.length} results`);
+ searchResultsCount.show();
+}
+
+$(document).ready(function() {
+ searchInput.prop('disabled', true);
+ const initialSearchTerm = getInitialSearchTerm();
+ searchInput.val(initialSearchTerm);
+
+ $.getJSON('./index.json', function(data) {
+ // populate the index
+ searchIndex = lunr(function() {
+ this.ref('id');
+ this.field('title');
+ this.field('content');
+
+ data.forEach(function(page) {
+ this.add(page);
+ }, this);
+ });
+
+ // add event handler
+ searchInput.on('input', function(event) {
+ onSearch(event.target.value);
+ });
+
+ // re-enable the input
+ searchInput.prop('disabled', false);
+
+ // Do an initial search
+ onSearch(searchInput.val());
+
+ // Scroll to the title if we landed on the page with a search term
+ if (initialSearchTerm) {
+ document.getElementsByTagName('h1')[0].scrollIntoView();
+ }
+ });
+});
diff --git a/static/src/scss/style.scss b/static/src/scss/style.scss
index 8777d2f..4861344 100644
--- a/static/src/scss/style.scss
+++ b/static/src/scss/style.scss
@@ -110,6 +110,17 @@ table td {
#index-header {
width: 100%;
height: calc(100vh - #{$footer-height} - #{$navbar-height});
+
+ #search-input {
+ border: 3px solid $black;
+ background-color: opacify($black, 0.8);
+ text-align: center;
+ color: $body-color;
+
+ &::placeholder {
+ color: opacify($body-color, 0.8);
+ }
+ }
}
footer {
@@ -335,3 +346,23 @@ a.no-color-change {
width: 100%;
}
}
+
+#main.search-page {
+ .search-results {
+ .list-page-item {
+ display: none;
+ }
+ }
+
+ #search-input {
+ border: 0;
+ background-color: opacify($black, 0.6);
+ color: $body-color;
+ }
+
+ .input-group-text {
+ border: 0;
+ background-color: $black;
+ color: $body-color;
+ }
+}