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"
|
||||
baseName = "calendar"
|
||||
|
||||
[SearchIndex]
|
||||
mediatype = "application/json"
|
||||
basename = "searchindex"
|
||||
|
||||
#[outputFormats.XMLEvent]
|
||||
# mediaType = "application/xml"
|
||||
# baseName = "schedule"
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
# 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
|
||||
home = ["HTML", "RSS", "JSON"]
|
||||
home = ["HTML", "RSS", "JSON", "SearchIndex"]
|
||||
section = ["HTML"]
|
||||
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">
|
||||
{{ partialCached "header.html" . }}
|
||||
{{ partial "breadcrumb.html" . }}
|
||||
<section>
|
||||
<!-- <h2 class="post">{{ .Title }}</h2> -->
|
||||
{{- block "main" . }}
|
||||
{{ .Content }}
|
||||
|
@ -19,7 +18,7 @@
|
|||
{{ if isset .Params "show_child_pages" }}
|
||||
{{ if eq .Params.show_child_pages true }}
|
||||
<section>
|
||||
<p>Relevante pagina's:</p>
|
||||
<h3>Gerelateerde pagina's</h3>
|
||||
<ul>
|
||||
{{ range .Pages }}
|
||||
<li>
|
||||
|
@ -32,19 +31,23 @@
|
|||
{{ end }}
|
||||
|
||||
{{ if eq .Section "posts" }}
|
||||
<div class="post-date">
|
||||
<span class="g time">{{.Date.Format "January 2, 2006"}} </span> ∙
|
||||
{{ $taxonomy := "tags" }} {{ with .Param $taxonomy }}
|
||||
{{ range $index, $tag := . }} {{ with $.Site.GetPage (printf "/%s/%s"
|
||||
$taxonomy $tag) -}}
|
||||
<a href="{{ .Permalink }}">{{ $tag | urlize }}</a>
|
||||
{{- end -}} {{- end -}}
|
||||
{{ end }}
|
||||
</div>
|
||||
{{ end }}
|
||||
<section>
|
||||
<h3>Tags</h3>
|
||||
<div class="post-date">
|
||||
<span class="g time">{{.Date.Format "January 2, 2006"}} </span> ∙
|
||||
{{ $taxonomy := "tags" }} {{ with .Param $taxonomy }}
|
||||
{{ range $index, $tag := . }} {{ with $.Site.GetPage (printf "/%s/%s" $taxonomy $tag) -}}
|
||||
<a href="{{ .Permalink }}">{{ $tag | urlize }}</a>
|
||||
{{- end -}} {{- end -}}
|
||||
{{ end }}
|
||||
</div>
|
||||
</section>
|
||||
{{ end }}
|
||||
|
||||
</div>
|
||||
</main>
|
||||
{{ partial "footer.html" . }}
|
||||
</body>
|
||||
{{ partialCached "scripts_loadlast.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 }}
|
||||
</ul>
|
||||
</nav>
|
||||
<a href="/zoeken/">{{ partial "show-svg-icon.html" (dict "context" . "icon" "magnifying-glass") }}</a>
|
||||
</div>
|
||||
</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