Implementeren zoekfunctionaliteit
This commit is contained in:
parent
55c242d4b6
commit
372746317e
9
assets/js/fuse.js
Normal file
9
assets/js/fuse.js
Normal file
File diff suppressed because one or more lines are too long
7
assets/js/mark.min.js
vendored
Normal file
7
assets/js/mark.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
assets/js/scripts.json
Normal file
6
assets/js/scripts.json
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"scripts": [
|
||||||
|
"js/fuse.js",
|
||||||
|
"js/mark.min.js"
|
||||||
|
]
|
||||||
|
}
|
|
@ -15,6 +15,10 @@
|
||||||
mediaType = "text/calendar"
|
mediaType = "text/calendar"
|
||||||
baseName = "calendar"
|
baseName = "calendar"
|
||||||
|
|
||||||
|
[SearchIndex]
|
||||||
|
mediatype = "application/json"
|
||||||
|
basename = "searchindex"
|
||||||
|
|
||||||
#[outputFormats.XMLEvent]
|
#[outputFormats.XMLEvent]
|
||||||
# mediaType = "application/xml"
|
# mediaType = "application/xml"
|
||||||
# baseName = "schedule"
|
# baseName = "schedule"
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
# Voor de home-page maken we een HTML, RSS en JSON Feed
|
# Voor de home-page maken we een HTML, RSS en JSON Feed
|
||||||
# Secties alleen in HTML en voor pagina's in zowel HTML als CalendarEvent (iCAL) waar het van toepassing is
|
# Secties alleen in HTML en voor pagina's in zowel HTML als CalendarEvent (iCAL) waar het van toepassing is
|
||||||
home = ["HTML", "RSS", "JSON"]
|
home = ["HTML", "RSS", "JSON", "SearchIndex"]
|
||||||
section = ["HTML"]
|
section = ["HTML"]
|
||||||
page = ["HTML", "CalendarEvent"]
|
page = ["HTML", "CalendarEvent"]
|
||||||
|
|
||||||
|
|
9
content/zoeken/index.md
Normal file
9
content/zoeken/index.md
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
---
|
||||||
|
title: "Zoekresultaten"
|
||||||
|
sitemap:
|
||||||
|
priority : 0.1
|
||||||
|
layout: "search"
|
||||||
|
---
|
||||||
|
|
||||||
|
<!-- De content van deze pagina zal niet zichtbaar zijn. Deze pagina is aanwezig zodat die reageert op requests voor /zoeken/ -->
|
||||||
|
|
1
static/afbeeldingen/iconen/magnifying-glass.svg
Normal file
1
static/afbeeldingen/iconen/magnifying-glass.svg
Normal file
|
@ -0,0 +1 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--! Font Awesome Pro 6.4.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license (Commercial License) Copyright 2023 Fonticons, Inc. --><path d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
|
After Width: | Height: | Size: 481 B |
141
static/js/search.js
Normal file
141
static/js/search.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
// MB: created at 2023-04-24
|
||||||
|
|
||||||
|
|
||||||
|
summaryInclude = 50;
|
||||||
|
var fuseOptions = {
|
||||||
|
shouldSort: true,
|
||||||
|
includeMatches: true,
|
||||||
|
includeScore: true,
|
||||||
|
tokenize: true,
|
||||||
|
location: 0,
|
||||||
|
distance: 100,
|
||||||
|
minMatchCharLength: 1,
|
||||||
|
keys: [
|
||||||
|
{name: "title", weight: 0.45},
|
||||||
|
{name: "content", weight: 0.4},
|
||||||
|
{name: "tags", weight: 0.1},
|
||||||
|
{name: "categories", weight: 0.05}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// =============================
|
||||||
|
// Search
|
||||||
|
// =============================
|
||||||
|
|
||||||
|
var inputBox = document.getElementById('search-query');
|
||||||
|
if (inputBox !== null) {
|
||||||
|
var searchQuery = param("q");
|
||||||
|
if (searchQuery) {
|
||||||
|
inputBox.value = searchQuery || "";
|
||||||
|
executeSearch(searchQuery, false);
|
||||||
|
} else {
|
||||||
|
document.getElementById('search-results').innerHTML = '<p class="search-results-empty">Typ een woord om te zoeken of bekijk alle <a href="/tags/">tags</a>.</p>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function executeSearch(searchQuery) {
|
||||||
|
|
||||||
|
show(document.querySelector('.search-loading'));
|
||||||
|
|
||||||
|
fetch('/searchindex.json').then(function (response) {
|
||||||
|
if (response.status !== 200) {
|
||||||
|
console.log('Looks like there was a problem. Status Code: ' + response.status);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
console.log('/searchindex.json loaded');
|
||||||
|
}
|
||||||
|
// Examine the text in the response
|
||||||
|
response.json().then(function (pages) {
|
||||||
|
var fuse = new Fuse(pages, fuseOptions);
|
||||||
|
var 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) {
|
||||||
|
|
||||||
|
var searchQuery = document.getElementById("search-query").value;
|
||||||
|
var searchResults = document.getElementById("search-results");
|
||||||
|
|
||||||
|
// pull template from hugo template definition
|
||||||
|
var templateDefinition = document.getElementById("search-result-template").innerHTML;
|
||||||
|
|
||||||
|
results.forEach(function (value, key) {
|
||||||
|
|
||||||
|
var content = value.item.content;
|
||||||
|
var snippet = "";
|
||||||
|
var snippetHighlights = [];
|
||||||
|
|
||||||
|
snippetHighlights.push(searchQuery);
|
||||||
|
snippet = content.substring(0, summaryInclude * 2) + '…';
|
||||||
|
|
||||||
|
//replace values
|
||||||
|
var tags = ""
|
||||||
|
if (value.item.tags) {
|
||||||
|
value.item.tags.forEach(function (element) {
|
||||||
|
tags = tags + "<a href='/tags/" + element + "'>" + "#" + element + "</a> "
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var output = render(templateDefinition, {
|
||||||
|
key: key,
|
||||||
|
title: value.item.title,
|
||||||
|
link: value.item.url,
|
||||||
|
tags: tags,
|
||||||
|
categories: value.item.categories,
|
||||||
|
snippet: snippet
|
||||||
|
});
|
||||||
|
searchResults.innerHTML += output;
|
||||||
|
|
||||||
|
snippetHighlights.forEach(function (snipvalue, snipkey) {
|
||||||
|
var instance = new Mark(document.getElementById('summary-' + key));
|
||||||
|
instance.mark(snipvalue);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(templateString, data) {
|
||||||
|
var 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 content.
|
||||||
|
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
|
||||||
|
var 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, ' ');
|
||||||
|
}
|
|
@ -8,7 +8,6 @@
|
||||||
<div class="content">
|
<div class="content">
|
||||||
{{ partialCached "header.html" . }}
|
{{ partialCached "header.html" . }}
|
||||||
{{ partial "breadcrumb.html" . }}
|
{{ partial "breadcrumb.html" . }}
|
||||||
<section>
|
|
||||||
<!-- <h2 class="post">{{ .Title }}</h2> -->
|
<!-- <h2 class="post">{{ .Title }}</h2> -->
|
||||||
{{- block "main" . }}
|
{{- block "main" . }}
|
||||||
{{ .Content }}
|
{{ .Content }}
|
||||||
|
@ -19,7 +18,7 @@
|
||||||
{{ if isset .Params "show_child_pages" }}
|
{{ if isset .Params "show_child_pages" }}
|
||||||
{{ if eq .Params.show_child_pages true }}
|
{{ if eq .Params.show_child_pages true }}
|
||||||
<section>
|
<section>
|
||||||
<p>Relevante pagina's:</p>
|
<h3>Gerelateerde pagina's</h3>
|
||||||
<ul>
|
<ul>
|
||||||
{{ range .Pages }}
|
{{ range .Pages }}
|
||||||
<li>
|
<li>
|
||||||
|
@ -32,19 +31,23 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
||||||
{{ if eq .Section "posts" }}
|
{{ if eq .Section "posts" }}
|
||||||
|
<section>
|
||||||
|
<h3>Tags</h3>
|
||||||
<div class="post-date">
|
<div class="post-date">
|
||||||
<span class="g time">{{.Date.Format "January 2, 2006"}} </span> ∙
|
<span class="g time">{{.Date.Format "January 2, 2006"}} </span> ∙
|
||||||
{{ $taxonomy := "tags" }} {{ with .Param $taxonomy }}
|
{{ $taxonomy := "tags" }} {{ with .Param $taxonomy }}
|
||||||
{{ range $index, $tag := . }} {{ with $.Site.GetPage (printf "/%s/%s"
|
{{ range $index, $tag := . }} {{ with $.Site.GetPage (printf "/%s/%s" $taxonomy $tag) -}}
|
||||||
$taxonomy $tag) -}}
|
|
||||||
<a href="{{ .Permalink }}">{{ $tag | urlize }}</a>
|
<a href="{{ .Permalink }}">{{ $tag | urlize }}</a>
|
||||||
{{- end -}} {{- end -}}
|
{{- end -}} {{- end -}}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
|
||||||
</section>
|
</section>
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
{{ partial "footer.html" . }}
|
{{ partial "footer.html" . }}
|
||||||
</body>
|
</body>
|
||||||
|
{{ partialCached "scripts_loadlast.html" . }}
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
3
themes/nluug/layouts/_default/readme.txt
Normal file
3
themes/nluug/layouts/_default/readme.txt
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
index.json
|
||||||
|
|
||||||
|
Genereert een index (/index.json) ten behoeve van zoekfunctionaliteit
|
26
themes/nluug/layouts/_default/search.html
Normal file
26
themes/nluug/layouts/_default/search.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
{{ define "main" }}
|
||||||
|
<h2>Zoeken</h2>
|
||||||
|
{{ partial "search-form.html" . }}
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
|
||||||
|
<div id="search-results"></div>
|
||||||
|
<div class="search-loading">Wachten op zoekopdracht of resulten... (JavaScript is voor deze pagina vereist)</div>
|
||||||
|
|
||||||
|
<!-- this template is sucked in by search.js and appended to the search-results div above. So editing here will adjust style -->
|
||||||
|
<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>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{{ end }}
|
5
themes/nluug/layouts/list.searchindex.json
Normal file
5
themes/nluug/layouts/list.searchindex.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{{ $index := slice }}
|
||||||
|
{{ range .Site.RegularPages }}
|
||||||
|
{{ $index = $index | append (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "content" .Plain "url" .Permalink) }}
|
||||||
|
{{ end }}
|
||||||
|
{{ $index | jsonify }}
|
|
@ -30,5 +30,6 @@
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
<a href="/zoeken/">{{ partial "show-svg-icon.html" (dict "context" . "icon" "magnifying-glass") }}</a>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
15
themes/nluug/layouts/partials/scripts_loadlast.html
Normal file
15
themes/nluug/layouts/partials/scripts_loadlast.html
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
<script src="{{ "js/search.js" | absURL }}"></script>
|
||||||
|
|
||||||
|
{{ $assetBusting := not .Site.Params.disableAssetsBusting }}
|
||||||
|
{{ $scripts := getJSON "assets/js/scripts.json" }}
|
||||||
|
|
||||||
|
{{ $.Scratch.Set "jslibs" slice }}
|
||||||
|
{{ range $scripts.scripts }}
|
||||||
|
{{ $.Scratch.Add "jslibs" (resources.Get . ) }}
|
||||||
|
{{ end }}
|
||||||
|
|
||||||
|
{{ $js := .Scratch.Get "jslibs" | resources.Concat "js/combined-scripts.js" | resources.Minify | fingerprint }}
|
||||||
|
<script
|
||||||
|
src="{{ $js.RelPermalink }}{{ if $assetBusting }}?{{ now.Unix }}{{ end }}"
|
||||||
|
integrity="{{ $js.Data.Integrity }}"
|
||||||
|
></script>
|
4
themes/nluug/layouts/partials/search-form.html
Normal file
4
themes/nluug/layouts/partials/search-form.html
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<form action="/zoeken/" method="GET">
|
||||||
|
🔍 <input type="search" name="q" id="search-query" placeholder="Zoekterm..">
|
||||||
|
<button type="submit">Zoek</button>
|
||||||
|
</form>
|
Reference in a new issue