You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jena.apache.org by ki...@apache.org on 2023/04/07 11:09:34 UTC
[jena-site] 01/03: Add basic search with Fuse.js (search engine), Mark.js (word highlighter) and Hugo (search index)
This is an automated email from the ASF dual-hosted git repository.
kinow pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/jena-site.git
commit 7905e130de1df841d6700c472fb0b4502da26db9
Author: Bruno P. Kinoshita <ki...@users.noreply.github.com>
AuthorDate: Sun Feb 12 09:34:25 2023 +0100
Add basic search with Fuse.js (search engine), Mark.js (word highlighter) and Hugo (search index)
---
config.toml | 3 +
layouts/_default/baseof.html | 9 ++-
layouts/_default/index.json | 5 ++
layouts/_default/search.html | 164 +++++++++++++++++++++++++++++++++++++++++++
source/search/__index.md | 7 ++
static/js/fuse.min.js | 9 +++
static/js/mark.min.js | 7 ++
7 files changed, 203 insertions(+), 1 deletion(-)
diff --git a/config.toml b/config.toml
index ce2da183d..4ab62692b 100644
--- a/config.toml
+++ b/config.toml
@@ -44,3 +44,6 @@ repositorySourceBranch = "main"
siteRepositoryUrl = "https://github.com/apache/jena-site"
siteRepositorySourceBranch = "main"
+
+[outputs]
+home = ["HTML", "RSS", "JSON"]
diff --git a/layouts/_default/baseof.html b/layouts/_default/baseof.html
index 276a62ad6..497ee4b2c 100644
--- a/layouts/_default/baseof.html
+++ b/layouts/_default/baseof.html
@@ -101,7 +101,14 @@
</ul>
</li>
</ul>
-
+ <form class="d-flex" role="search" action="/search" method="GET">
+ <div class="input-group">
+ <input class="form-control border-end-0 border m-0" type="search" name="q" id="search-query" placeholder="Search...." aria-label="Search">
+ <button class="btn btn-outline-secondary border-start-0 border" type="submit">
+ <i class="bi-search"></i>
+ </button>
+ </div>
+ </form>
<ul class="navbar-nav ms-auto">
<li id="ask" class="nav-item"><a class="nav-link" href="/help_and_support/index.html"><span class="bi-patch-question"></span> Ask</a></li>
diff --git a/layouts/_default/index.json b/layouts/_default/index.json
new file mode 100644
index 000000000..ae08324d8
--- /dev/null
+++ b/layouts/_default/index.json
@@ -0,0 +1,5 @@
+{{- $.Scratch.Add "index" slice -}}
+{{- range .Site.RegularPages -}}
+{{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}}
+{{- end -}}
+{{- $.Scratch.Get "index" | jsonify -}}
diff --git a/layouts/_default/search.html b/layouts/_default/search.html
new file mode 100644
index 000000000..79af2183c
--- /dev/null
+++ b/layouts/_default/search.html
@@ -0,0 +1,164 @@
+{{ define "main" }}
+<!-- Source: https://makewithhugo.com/add-search-to-a-hugo-site/ -->
+<main>
+ <div id="search-results"></div>
+ <div class="search-loading">Loading...</div>
+
+ <script id="search-result-template" type="text/x-js-template">
+ <div id="summary-${key}">
+ <h3><a href="${link}">${title}</a></h3>
+ <p>${snippet}</p>
+ <p>
+ <small>
+ ${ isset tags }Tags: ${tags}<br>${ end }
+ </small>
+ </p>
+ </div>
+ </script>
+
+ <script src="/js/fuse.min.js" type="text/javascript" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+ <script src="/js/mark.min.js" type="text/javascript" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
+ <script type="text/javascript">
+ (function() {
+ const summaryInclude = 180;
+ const fuseOptions = {
+ shouldSort: true,
+ includeMatches: true,
+ includeScore: true,
+ tokenize: true,
+ location: 0,
+ distance: 100,
+ minMatchCharLength: 1,
+ keys: [
+ {name: "title", weight: 0.45},
+ {name: "contents", weight: 0.4},
+ {name: "tags", weight: 0.1},
+ {name: "categories", weight: 0.05}
+ ]
+ };
+
+ // =============================
+ // Search
+ // =============================
+
+ const inputBox = document.getElementById('search-query');
+ if (inputBox !== null) {
+ const searchQuery = param("q");
+ if (searchQuery) {
+ inputBox.value = searchQuery || "";
+ executeSearch(searchQuery, false);
+ } else {
+ document.getElementById('search-results').innerHTML = '<p class="search-results-empty">Please enter a word or phrase above, or see <a href="/tags/">all tags</a>.</p>';
+ }
+ }
+
+ function executeSearch(searchQuery) {
+
+ show(document.querySelector('.search-loading'));
+
+ fetch('/index.json').then(function (response) {
+ if (response.status !== 200) {
+ console.log('Looks like there was a problem. Status Code: ' + response.status);
+ return;
+ }
+ // Examine the text in the response
+ response.json().then(function (pages) {
+ const fuse = new Fuse(pages, fuseOptions);
+ const result = fuse.search(searchQuery);
+ if (result.length > 0) {
+ populateResults(result);
+ } else {
+ document.getElementById('search-results').innerHTML = '<p class=\"search-results-empty\">No matches found</p>';
+ }
+ hide(document.querySelector('.search-loading'));
+ })
+ .catch(function (err) {
+ console.log('Fetch Error :-S', err);
+ });
+ });
+ }
+
+ function populateResults(results) {
+
+ const searchQuery = document.getElementById("search-query").value;
+ const searchResults = document.getElementById("search-results");
+
+ // pull template from hugo template definition
+ const templateDefinition = document.getElementById("search-result-template").innerHTML;
+
+ results.forEach(function (value, key) {
+
+ const contents = value.item.contents;
+ let snippet = "";
+ const snippetHighlights = [];
+
+ snippetHighlights.push(searchQuery);
+ snippet = contents.substring(0, summaryInclude * 2) + '…';
+
+ //replace values
+ let tags = ""
+ if (value.item.tags) {
+ value.item.tags.forEach(function (element) {
+ tags = tags + "<a href='/tags/" + element + "'>" + "#" + element + "</a> "
+ });
+ }
+
+ const output = render(templateDefinition, {
+ key: key,
+ title: value.item.title,
+ link: value.item.permalink,
+ tags: tags,
+ categories: value.item.categories,
+ snippet: snippet
+ });
+ searchResults.innerHTML += output;
+
+ snippetHighlights.forEach(function (snipvalue, snipkey) {
+ const instance = new Mark(document.getElementById('summary-' + key));
+ instance.mark(snipvalue);
+ });
+
+ });
+ }
+
+ function render(templateString, data) {
+ let conditionalMatches, conditionalPattern, copy;
+ 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;
+ }
+
+ // Helper Functions
+ function show(elem) {
+ elem.style.display = 'block';
+ }
+ function hide(elem) {
+ elem.style.display = 'none';
+ }
+ function param(name) {
+ return decodeURIComponent((location.search.split(name + '=')[1] || '').split('&')[0]).replace(/\+/g, ' ');
+ }
+
+ })()
+ </script>
+</main>
+
+{{ end }}
diff --git a/source/search/__index.md b/source/search/__index.md
new file mode 100644
index 000000000..ec17b86dc
--- /dev/null
+++ b/source/search/__index.md
@@ -0,0 +1,7 @@
+---
+title: "Search Results"
+sitemap:
+priority : 0.1
+layout: "search"
+slug: index
+---
diff --git a/static/js/fuse.min.js b/static/js/fuse.min.js
new file mode 100644
index 000000000..d9da0b3c0
--- /dev/null
+++ b/static/js/fuse.min.js
@@ -0,0 +1,9 @@
+/**
+ * Fuse.js v6.6.2 - Lightweight fuzzy-search (http://fusejs.io)
+ *
+ * Copyright (c) 2022 Kiro Risk (http://kiro.me)
+ * All Rights Reserved. Apache Software License 2.0
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+var e,t;e=this,t=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,r)}return n}function t(t){for(var n=1;n<arguments.length;n++){var r=null!=arguments[n]?arguments[n]:{};n%2?e(Object(r),!0).forEach((function(e){c(t,e,r[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDe [...]
diff --git a/static/js/mark.min.js b/static/js/mark.min.js
new file mode 100644
index 000000000..1eea05338
--- /dev/null
+++ b/static/js/mark.min.js
@@ -0,0 +1,7 @@
+/*!***************************************************
+* mark.js v8.11.1
+* https://markjs.io/
+* Copyright (c) 2014–2018, Julian Kühnel
+* Released under the MIT license https://git.io/vwTVl
+*****************************************************/
+!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):e.Mark=t()}(this,function(){"use strict";var e="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},t=function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")},n=function(){ [...]