Schrek.fr

Mon super blog :)

  1. home
  2. posts
  3. 2023
  4. 06
  5. hugo-recherche

Recherche dans Hugo

Cet article fait partie de la série: Hugo


J’ai desactivé la recherche sur le site. Mais ça marche.

Il me manquait la recherche sur Hugo, j’en est chié une histoire de Jquery.

config.toml

On va demander a Hugo de générer un fichier JSON, pour récupérer les données du site.

[outputs]
    home = [ "HTML", "JSON", "RSS"]

Quand on lance Hugo, on a droit a une erreur.

Change of config file detected, rebuilding site.
2023-05-02 11:39:09.348 +0200
WARN 2023/05/02 11:39:09 found no layout file for "JSON" for kind "home": You should create a template file which matches Hugo Layouts Lookup Rules for this combination.
Rebuilt in 205 ms

Index.json

il faut créer le fichier index.json dans notre theme themes/schrek/layouts

{{- $.Scratch.Add "index" slice -}}
{{- range .Site.RegularPages -}}
    {{- $.Scratch.Add "index" (dict "title" .Title "tags" .Params.tags "categories" .Params.categories "contents" .Plain "permalink" .Permalink) -}}
{{- end -}}
{{- $.Scratch.Get "index" | jsonify -}}

Fichiers JS

  • jquery.min.js
  • fuse.min.js
  • mark.min.js
  • jquery.mark.min
  • search.js

On peux choisir de charger le footer:

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.4/jquery.min.js" integrity="sha512-pumBsjNRGGqkPzKHndZMaAG+bir374sORyzM3uulLV14lN5LyykqNk8eEeUlUkB3U0M4FApyaHraT65ihJhDpQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fuse.js/6.6.2/fuse.min.js" integrity="sha512-Nqw1tH3mpavka9cQCc5zWWEZNfIPdOYyQFjlV1NvflEtQ0/XI6ZQ+H/D3YgJdqSUJlMLAPRj/oXlaHCFbFCjoQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/mark.min.js" integrity="sha512-5CYOlHXGh6QpOFA/TeTylKLWfB3ftPsde7AnmhuitiTX4K5SqCLBeKro6sPS8ilsz1Q4NRx3v8Ko2IBiszzdww==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mark.js/8.11.1/jquery.mark.js" integrity="sha512-19TrqSGVSwaC2IDGHrD+tAkX59/w5cXy0BHDVwn7OJQXxavORhFSFM7DOO9soXKuo8O7gGNHiG9R2vFrXRBcTQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script defer src="/js/search.js"></script>

Sinon on les télécharge dans le dossier du thème themes/schrek/assets/js

{{ $js := resources.Get "js/jquery.min.js" | minify }}

ca sert a réduire la taille du fichier pour un meilleur chargement.

  <script defer src="{{ $js.RelPermalink }}"></script>
{{ $js := resources.Get "js/jquery.min.js" | minify }}
    <script defer src="{{ $js.RelPermalink }}"></script>
{{ $js := resources.Get "js/fuse.min.js" | minify }}
    <script defer src="{{ $js.RelPermalink }}"></script>
{{ $js := resources.Get "js/mark.min.js" | minify }}
    <script defer src="{{ $js.RelPermalink }}"></script>
{{ $js := resources.Get "js/jquery.mark.min.js" | minify }}
    <script defer src="{{ $js.RelPermalink }}"></script>
{{ $js := resources.Get "js/search.js" | minify }}
    <script defer src="{{ $js.RelPermalink }}"></script>

Search.js

On va creer le fichier search.js dans /static/js ou bien /asset/js selon ce que vous avez choisi.

summaryInclude = 60;
var fuseOptions = {
    shouldSort: true,
    includeMatches: true,
    threshold: 0,
    tokenize: true,
    location: 0,
    distance: 100,
    maxPatternLength: 32,
    minMatchCharLength: 1,
    keys: [
        { name: "title", weight: 0.8 },
        { name: "contents", weight: 0.5 },
        { name: "tags", weight: 0.3 },
        { name: "categories", weight: 0.3 },
    ],
};
var searchQuery = param("s");
if (searchQuery) {
    $("#search-query").val(searchQuery);
    executeSearch(searchQuery);
} else {
    $("#search-results").append("<p>Un mot ou une phrase</p>");
}
function executeSearch(searchQuery) {
    $.getJSON("/index.json", function (data) {
        var pages = data;
        var fuse = new Fuse(pages, fuseOptions);
        var result = fuse.search(searchQuery);
        console.log({ matches: result });
        if (result.length > 0) {
            populateResults(result);
        } else {
            $("#search-results").append("<h1>Rien Trouvé</h1>");
        }
    });
}
function populateResults(result) {
    $.each(result, function (key, value) {
        var contents = value.item.contents;
        var snippet = "";
        var snippetHighlights = [];
        var tags = [];
        if (fuseOptions.tokenize) {
            snippetHighlights.push(searchQuery);
        } else {
            $.each(value.matches, function (matchKey, mvalue) {
                if (mvalue.key == "tags" || mvalue.key == "categories") {
                    snippetHighlights.push(mvalue.value);
                } else if (mvalue.key == "contents") {
                    start = mvalue.indices[0][0] - summaryInclude > 0 ? mvalue.indices[0][0] - summaryInclude : 0;
                    end = mvalue.indices[0][1] + summaryInclude < contents.length ? mvalue.indices[0][1] + summaryInclude : contents.length;
                    snippet += contents.substring(start, end);
                    snippetHighlights.push(mvalue.value.substring(mvalue.indices[0][0], mvalue.indices[0][1] - mvalue.indices[0][0] + 1));
                }
            });
        }
        if (snippet.length < 1) {
            snippet += contents.substring(0, summaryInclude * 2);
        }
        var templateDefinition = $("#search-result-template").html();
        var output = render(templateDefinition, { key: key, title: value.item.title, link: value.item.permalink, tags: value.item.tags, categories: value.item.categories, snippet: snippet });
        $("#search-results").append(output);
        $.each(snippetHighlights, function (snipkey, snipvalue) {
            $("#summary-" + key).mark(snipvalue);
        });
    });
}
function param(name) {
    return decodeURIComponent((location.search.split(name + "=")[1] || "").split("&")[0]).replace(/\+/g, " ");
}
function render(templateString, data) {
    var conditionalMatches, conditionalPattern, copy;
    conditionalPattern = /\$\{\s*isset ([a-zA-Z]*) \s*\}(.*)\$\{\s*end\s*}/g;
    copy = templateString;
    while ((conditionalMatches = conditionalPattern.exec(templateString)) !== null) {
        if (data[conditionalMatches[1]]) {
            copy = copy.replace(conditionalMatches[0], conditionalMatches[2]);
        } else {
            copy = copy.replace(conditionalMatches[0], "");
        }
    }
    templateString = copy;
    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;
}

Search.html

Toujours dans notre thème dans le dossier /layouts/_default/ , ca sera la page de recherche, j’utilise uikit comme framework.

{{ define "main" }}
<div class="uk-container uk-background-muted  uk-box-shadow-large uk-padding">
<div class="uk-margin">
  <form class="uk-search uk-search-default" action="{{ "search" | absURL }}">
      <span uk-search-icon></span>
      <input class="uk-search-input" type="search" placeholder="Search" aria-label="Search" id="search-query" name="s">
  </form>
  <div id="search-results">
    <h3>Résultats</h3>
   </div>
</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">
  <article class="uk-article">
    <h3 class="uk-article-title"><a  href="${link}">${title}</a></a></h3>
    <p class="uk-article-meta">${ isset categories }<p>Categories: ${categories}</p>${ end }
    ${ isset tags }<p>Tags: ${tags}</p>${ end }</p>
    <p class="uk-text-lead">${snippet}</p>
  </article>
</script>
</div>
{{ end }}

Search.md

On revient a la racine du site dans le dossier content/, il faut créer un fichier search.md

---
title: "Search Results"
sitemap:
  priority : 0.1
layout: "search"
---


This file exists solely to respond to /search URL with the related `search` layout template.

No content shown here is rendered, all content is based in the template layouts/page/search.html

Setting a very low sitemap priority will tell search engines this is not important content.

This implementation uses Fusejs, jquery and mark.js


## Initial setup

Search  depends on additional output content type of JSON in config.toml
\```
[outputs]
  home = ["HTML", "JSON"]
\```

## Searching additional fileds

To search additional fields defined in front matter, you must add it in 2 places.

### Edit layouts/_default/index.JSON
This exposes the values in /index.json
i.e. add `category`
\```
...
  "contents":{{ .Content | plainify | jsonify }}
  {{ if .Params.tags }},
  "tags":{{ .Params.tags | jsonify }}{{end}},
  "categories" : {{ .Params.categories | jsonify }},
...
\```

### Edit fuse.js options to Search
`static/js/search.js`
\```
keys: [
  "title",
  "contents",
  "tags",
  "categories"
]
\```

Presque fini

On lance hugo

$ hugo serve -D -F

On a deja ca:

http://localhost:1313/search/

Barre de recherche

Pour la barre de recherche dans une menu .

    <div class="uk-margin">
      <form class="uk-search uk-search-default" action='{{ with .GetPage "/search" }}{{.Permalink}}{{end}}' method="get" >
          <span uk-search-icon></span>
          <input class="uk-search-input" type="search" placeholder="Search" aria-label="Search" id="search-query" name="s">
      </form>
    </div>

Liens

https://javifm.com/en/blog/search-in-hugo-with-lunr/

comments powered by Disqus