Implementeren zoekfunctionaliteit
This commit is contained in:
		
							parent
							
								
									55c242d4b6
								
							
						
					
					
						commit
						372746317e
					
				
					 16 changed files with 247 additions and 13 deletions
				
			
		
							
								
								
									
										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>
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue