Skip to main content
Seb's blog
logo PassionGNU/Linux

Vie du blog: la recherche est en place.

La recherche est en place, de deux façons, j’ai cherché sur le net, un moyen simple sans rajouts externes ou d’appels à un quelconque service. Ma grande fainéantise voulait que je fasse un script vite fait pour apporter la recherche Google dans mon blog et puis j’ai pensé à l’éthique et tout ce que je prône
avec le libre, le slogan même de ce blog “PassionGNU/Linux, la passion du libre…”, puis je me suis dis que j’étais avec 11ty qui permet facilement de faire tout ce qu’on souhaite en deux coups alors pourquoi pas tenter de voir ce qui est fait ailleurs.

Dans un premier temps, je suis tomber sur le blog de Duncan McDougall, tomber est un bien grand mot puisque c’est déjà par là-bas que j’avais vu le billet sur NPM, Quick Tip: npm outdated and npm update que je me suis permis de traduire dans Pense-bête: npm obsolète et mise à jour npm.. Le truc avec sa méthode c’est qu’il utilise Elasticlunr celui la même qu’utilise Zola:

Zola can build a search index from the sections and pages content to be used by a JavaScript library such as elasticlunr.

On verra par la suite si c’est vraiment utile pour mon cas, ne l’ayant pas trouvé super sur Zola. J’ai encore regardé avec le même blog sous Zola et la recherche avec celui-ci, la recherche avec Elasticlunr via Zola me trouve bien moins de billets avec les mots que je donne et passe au silence certain ayant pourtant bien les mots clefs. De plus, il me fait un fichier pour sa base de donnée assez conséquent de 22Mo, je peux bien sur descendre en terme de poids en jouant avec la configuration et le champs truncate_content_length = 300, ce qui fera un fichier de 4mo, mais qui pour le coup diminuera les possibles résultats.

Je cherche donc autre chose, j’avais vu un starter d’Eleventy avec une recherche qui prenait directement le fichier des flux atom, RSS ou json. Le starter en question est minimal-developer qu’on peut voir en action par ici. Méthode que j’utilisais dans ma première version de mon blog avec 11ty, qui ne marche pas sans une petite correction, dans le script search.js il faut modifier comme ceci:

on remplace feed.JSON:

fetch("/feed/feed.JSON")

par feed.json:

fetch("/feed/feed.json")

Cette méthode a le mérite d’utiliser comme base de données, une qui est déjà en place, celle des flux.

Pour la mettre en place il suffit d’aller chercher les deux fichiers qui nous intéresse ou de les faire, une pour faire la page de recherche et le script qui se chargera de scruter dans le fichier des flux.

La page de recherche que j’appel search.njk (ou tout autre nom):

---
layout: layouts/home.njk
permalink: /search/
eleventyNavigation:
  key: Search
  order: 8
---

<h1>Search</h1>
<input type="hidden" id="feed" value="{{ '/feed/feed.json' | url }}" />
<input type="text" autocomplete="on" id="search" name="search" placeholder="" value="{{ tag }}" />
<div id="results"></div>
<script defer type="text/javascript" src="{{ '/js/search.js' | url }}"></script>

Le script .JS de recherche search.js (on doit lui donner le nom de la dernier ligne du fichier précédent <script defer type="text/javascript" src="{{ '/js/search.js' | url }}"></script>):

document.addEventListener('DOMContentLoaded', function (event) {
    const search = document.getElementById('search');
    const results = document.getElementById('results');
    let posts = [];
    let search_term = '';
    fetch("/feed/feed.json")
        .then(res => res.json())
        .then(data => {
            posts = data?.items;
            console.log("data", posts);
        });

    search.addEventListener('input', (event) => {
        console.log("Search");
        search_term = event.target.value.toLowerCase();
        showList();
      });

      const showList = () => {
        results.innerHTML = '';
        if (search_term.length <= 0) return;
        let result = posts?.filter((name) => name.title.toLowerCase().indexOf(search_term.toLowerCase()) >= 0);
        if (result.length == 0) {
          const li = document.createElement('li');
          li.innerHTML = `No results found`;
          results.appendChild(li);
        }
        result.forEach((e) => {
          const li = document.createElement('li');
          li.innerHTML = `<time datetime="${e.date_published?.split("T")[0]}">${e.date_published?.split("T")[0]}</time><span>&nbsp; - &nbsp;</span><a href="${e.url}">${e.title}</a>`;
          results.appendChild(li);
        });
      };

  });

Deux petites secondes pour prendre le temps de comprendre, tout les noms de fichiers sont modifiables à condition de modifier aussi en amont, ma search.njk s’attend a trouver un script du nom search.js dans un dossier js à la racine du site et ce script fait sa recherche depuis /feed/feed.json. Rien de plus.

Mais admettons maintenant qu’on a un site 11ty du plus simple appareil, ou même en partant avec un starter sans flux, en trois petits fichiers on peut facilement obtenir un truc performant. Je pars sur le tuto de joshinator sobrement appelé 11ty search que j’adapte que légèrement par endroit.

Une page de recherche, on peut tout aussi bien reprendre et adapter la première qu’on a vu plus haut, je vais changer et la nommer research.njk:

---
layout: layouts/home.njk
permalink: /research/
eleventyNavigation:
  key: Recherche
  order: 9
---

<h1>Search test</h1>
<input type="text" id="search" autocomplete="on" />
<div id="results"></div>
<script src="/js/research.js" async defer></script>

Un script que je nommerais comme sur la dernière ligne du fichier précédente, research.js:

(async () => {
  document.getElementById('search').addEventListener('keyup', (event) => {
    const searchString = event.target.value.toLowerCase()
    const results = []
    posts.forEach((post) => {
      if (
        post.title.toLowerCase().includes(searchString) ||
        post.excerpt.toLowerCase().includes(searchString) ||
        post.content_html.toLowerCase().includes(searchString) ||
        post.keywords.toLowerCase().includes(searchString) ||
        post.tags.toLowerCase().includes(searchString)
      ) {
        results.push(`<a href="${post.url}">
          <h2>
            ${post.title}
          </h2>
          <p>
            ${post.excerpt}
          </p>
        </a>`)
      }
    })

    document.getElementById('results').innerHTML = results.join('')
  })

  const posts = await fetch('/research.json').then(res => res.json())
})()

Et enfin le fichier qui va créer cette base de données (peu importe son nom), ce fichier doit simplement se trouver avec les autres pages de notre site, je l’ai appelé research-index.njk:

---
permalink: "/research.json"
---
[
  {%- for post in collections.posts | reverse %}
    {%- set url %}{{ post.url | url }}{% endset -%}
    {
      "url": "{{ url }}",
      "title": "{{ post.data.title }}",
      "excerpt": "{{ post.data.excerpt }}",
      "content_html": {% if post.templateContent %}{{ post.templateContent | transformWithHtmlBase(absolutePostUrl, post.url) | dump | safe }}{% else %}""{% endif %},
      "keywords": "{{ post.data.keywords }}",
      "tags": "{{ post.data.tags }}"
    }
    {%- if not loop.last -%}
    ,
    {%- endif -%}
  {%- endfor %}
]

Et voila, c’est fait, vous avez deux jolies pages de recherche. Je vous laisse tester en allant sur search et recherche.

Commencer la discussion: Venez écrire un commentaire dans le forum.