forked from laurens/fediversity_website
		
	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> | ||||
		Reference in a new issue