Befreiung vom (digitalen) Überfluss
Volltextsuche für HUGO
Eine einfache javascriptbasierte Suchfunktion für statisch gehostete Websites.
Wer in einer statisch gehosteten Website eine Suchfunktion anbieten will, greift evtl. auf eine externe Suchmaschinen wie »Google Programmable Search Engine« zurück oder verlinkt extern auf eine Suchmaschine mit dem Searchparameter “site:deinedomain.com”. Das ist unter Datenschutz- und Usability-Gesichtspunkten natürlich nicht optimal.
Die hier von Tanja Becker (Javascript) und mir (Integration in HUGO) vorgestellte Suche basiert auf einem JSON-Array, das alle Inhalte der Website enthält und eine Javascript-Suchfunktion, die das Array auswertet. Das JSON-Array wird automatisch vom Static Site Generator HUGO generiert. Der Einbau der Suchfunktion in eine bestehende HUGO-Website ist sehr einfach und besteht aus diesen Schritten:
- Template index.json anlegen
- hugo.toml ergänzen
- Javascript search.js in Website einfügen
- Shortcode search.html anlegen und ggf. anpassen
- Such-Seite search.md anlegen
JSON-Array
Lege ein Template index.json im Verzeichnis themes/<meinTheme>/layouts/
an:
index.json:
[ {{- $i := 0 -}}
{{- range where .Site.RegularPages "Section" "ne" "" -}}
{{- if not .Params.noSearch -}}
{{- if gt $i 0 }},{{ end -}}
{"date":"{{ .Date.Unix }}", "url":"{{ .Permalink }}", "title":{{ .Title | jsonify }}, "summary":{{ with .Description}}{{ . | plainify | jsonify }}{{ else }}{{ .Summary | plainify | jsonify }}{{ end }}, "content":{{ .Content | plainify | jsonify }},"tags":[ {{- $t := 0 }}{{- range .Param "tags" -}}{{ if gt $t 0 }},{{ end }}{{ . | jsonify }}{{ $t = add $t 1 }}{{ end -}} ], "section": {{ .Section | jsonify -}} }
{{- $i = add $i 1 -}}
{{- end -}}
{{- end -}} ]
Hiermit wird ein JSON-Array erzeugt, das automatisch den Content aller Seiten aus allen Sections enthält. Seiten mit noSearch: true
im Frontmatter werden ignoriert. Description überschreibt Summary.
Ergänze in hugo.toml (vormals config.toml) den folgenden Eintrag:
[outputs]
home = [ "HTML", "JSON" ]
Das sorgt dafür, dass im Document-Root automatisch eine Datei index.json (basierend auf dem Template index.json) angelegt wird, die den gesamten Content der Website enthält.
Suchfunktion
- Speichere das Javascript search.js in
static/js/search.js
Download search.js - Lege einen Shortcode search.html im Verzeichnis
themes/<meinTheme>/layouts/shortcodes/
mit folgendem Inhalt an - und binde ihn mit {{< search >}} in Deiner Suchseite search.md ein.
search.md:
---
title: "Volltextsuche"
summary: "Seite nach Stichworten durchsuchen"
date: 2020-10-29
---
{{< search >}}
search.html:
<form id="custom-search" name="custom-search" method="post" action="" onsubmit="customSearchResults(); return false;">
<p>
<input id="custom-search-field" type="text" name="search" value="" title="Search String" placeholder="SearchString">
<!-- <input type="submit" value="Suchen"> -->
</p>
<!-- <p><em>durchsuchen:</em><br>
<input type="checkbox"name="section[]" value="site" checked="checked"> alles<br>
<input type="checkbox" name="section[]" value="post"> Blog<br>
<input type="checkbox" name="section[]" value="other-section"> Other Section
</p> -->
<p>
<input type="radio" name="option" value="AND" checked="checked"> UND-Suche<br>
<input type="radio" name="option" value="OR"> ODER-Suche
</p>
</form>
<div id="custom-search-results"></div>
<script>
// CUSTOM AREA
let params = {
json_src : '/index.json', // for multiple sources: comma separated list of JSONarrays
minlength : 3,
defaultsearch : 'AND',
sort_date : 'DESC',
autocomplete : 1, // 0: form needs a submit button
section_search : 0, // 1: needs checkboxes with name="section[]"
badwords : 'und,oder,aber,wenn,also,der,die,das,den,dem,des,ein,eines,einer', //ignore this words
json_wait : '<p><em>Einen Moment bitte, Suche wird geladen...</em></p>',
json_ready : '<p><em>Bitte geben Sie einen Suchbegriff ein</em></p>',
extern_icon : ' (externer Link)', // marker for external links (optional)
err_badstring : '<p>Der Suchbegriff ist zu kurz!</p>',
err_noresult : '<p>Leider kein Suchergebnis. Bitte versuchen Sie es noch einmal.</p>',
err_norequest : '<p style="text-align: center; color:red;">Die Volltextsuche steht zur Zeit nicht zurVerfügung.</p>',
err_filefailed : '<p style="text-align: center;color: red;">Eine Datei konnte nicht abgerufen werden.</p>',
res_one_item : '<p><em>[CNT] SUCHERGEBNIS</em></p>',
res_more_items : '<p><em>[CNT] SUCHERGEBNISSE</em></p>',
res_out_top : '<ul>',
res_out_bottom : '</ul>',
res_item_tpl : '<li><a href="[URL]">[TITLE]</a><br>[DATE]:[SUMMARY]<br><em>[SECTION][TAGS]</em></li>',
add_searchlink : '<p><a href="https://duckduckgo.com/?q=site:yourdomain.com [QUERY]" target="_blank"><i>Nicht zufrieden mit den Suchergebnissen? Externe Suche via DuckDuckGo ...</i></a></p>'
};
// Translation of section name (optional)
let section_trans = {
"post" : "Blog",
// "other-section" : "Other Section"
};
let searchfield_weight = {
"title" : 5,
"tags" : 5,
"summary" : 2,
"content" : 1
};
// CUSTOM AREA END
</script>
<script type="text/javascript" src="/js/search.js"></script>
Download Quelltext search.html
Hinweise
Dieses Beispiel liefert eine einfache Suchfunktion, die in jeder HUGO-Website funktioniert (ggf. muss der HTML-Code in search.html an das bestehende Theme angepasst werden):
hier testen…
- Die Suche beginnt, so bald mindestens 3 Zeichen getippt wurden (
minlength
). badwords
enthält eine Liste von Worten, die ignoriert werden sollen.- Wer die Suche lieber per Submit-Button auslösen möchte, muss
autocomplete
auf “0” setzen. - Man kann die Such-Seite auch per ?query=Suchbegriff aufrufen.
- Die Suchergebnisse werden nach der Anzahl der Treffer des Suchbegriffs sortiert, wobei mit
searchfield_weight
eine Gewichtung vorgenommen werden kann. “5” bedeutet, dass der Treffer im entsprechenden Feld mit dem Faktor 5 gewichtet wird. - Die Suchfunktion durchsucht alle Sections. Wenn man in den Suchergebnissen anzeigen will, aus welcher Section das Ergebnis stammt, kann mit
section_trans
eine Übersetzung des internen Section-Keys vorgenommen werden. - Es ist mit
add_searchlink
möglich, zusätzlich auf eine externe Suchmaschine zu verlinken (im Beispiel DuckDuckGo), falls die interne Suche im Einzelfall kein befriedigendes Ergebnis liefert.
weitere Optionen
- Die Suchfunktion kann verschiedene Sections getrennt durchsuchen. Dazu muss
section_search
auf “1” gesetzt werden und im Suchformular entsprechende Checkboxes ergänzt werden (im Beispiel auskommentiert). - Es ist möglich mehrere JSON-Arrays als Quelle für die Suche anzugeben. Das ist vor allem dann sinnvoll, wenn eine einzige Suchfunktion mehrere Websites durchsuchen soll. Damit in den Ergebnissen gekennzeichnet werden kann, welche Treffer lokal und welche von einer externen Website stammen, kann jedes Item im Array der externen Quelle noch den Wert
"extern":"1"
enthalten. Das führt dazu, dass im Suchergebnis hinter[TITLE]
der String ausextern_icon
angefügt wird. Beispiel: bienenkiste.de/suchen/?query=Königin - Bei sehr großen JSON-Dateien kann es einen Moment dauern, bis die Datei geladen ist. In dieser Zeit wird
json_wait
angezeigt. Hier kann - falls im Theme verfügbar - ein Spinner/Loader-Icon eingefügt werden. So bald die Datei geladen ist, wirdjson_ready
angezeigt.
» Diskussion bei discourse.gohugo.io...
Bild: eigenes Foto aus der Ausstellung »Laß leuchten, Peter Rühmkorf zum Neunzigsten«.