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:

  1. Template index.json anlegen
  2. config.toml ergänzen
  3. Javascript search.js in Website einfügen
  4. Shortcode search.html anlegen und ggf. anpassen
  5. Such-Seite search.md anlegen

Beispiel…


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 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 aus extern_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, wird json_ready angezeigt.

» Diskussion bei discourse.gohugo.io...


Bild: eigenes Foto aus der Ausstellung »Laß leuchten, Peter Rühmkorf zum Neunzigsten«.


« Matomo cookiefrei | Die digitale Religion »