Skip to main content
Settings
Color Mode
Theme Skin
Background

Appearance preferences are saved in this browser only.

Environment
Current Environment Production

Built with JEKYLL_ENV=production. Changes require deployment.

Quick Links
Theme & Build
Jekyll v3.10.0
Last Build Aug 05, 04:35
Page Location
Page Info
Layout default
Collection none
Path fr/docs/features/site-search.md
URL /fr/docs/features/site-search/
This page was translated automatically from the English original. View the original

Recherche sur le site

Fonctionnalité de recherche côté client avec interface modale, index JSON et activation par raccourci clavier.

Recherche sur le site

Fonctionnalité de recherche côté client avec une interface modale Bootstrap et une activation par raccourci clavier.

La fenêtre modale de recherche du site ouverte avec « docker » saisi, affichant des résultats en direct — chacun avec un titre et un extrait où le terme correspondant est mis en évidence

Ouvrez-la depuis l’icône de recherche dans la barre de navigation (ou appuyez sur /), saisissez une requête, et les correspondances apparaissent instantanément — l’index est construit au moment du build et la recherche s’effectue entièrement dans le navigateur, ce qui fonctionne sur GitHub Pages sans aucun backend.

Aperçu

  • Côté client : Aucun serveur requis
  • Index JSON : Index de recherche pré-construit
  • Interface modale : Interface modale Bootstrap
  • Raccourci clavier : Appuyez sur / pour rechercher

Fonctionnement

graph LR
    A[User Types] --> B[Search JSON Index]
    B --> C[Filter Results]
    C --> D[Display Matches]
    D --> E[Navigate to Page]
  1. Jekyll construit search.json avec tout le contenu des pages
  2. L’utilisateur ouvre la fenêtre de recherche (clic ou touche /)
  3. JavaScript filtre l’index au fur et à mesure de la saisie
  4. Les résultats renvoient vers les pages correspondantes

Index de recherche

Fichier généré

Jekyll génère search.json :

[
  {
    "title": "Getting Started",
    "url": "/docs/getting-started/",
    "content": "Welcome to the documentation...",
    "categories": ["docs"],
    "tags": ["setup", "installation"]
  }
]

Modèle d’index

---
layout: null
---
[
  {% for page in site.pages %}
    {% if page.title %}
    {
      "title": {{ page.title | jsonify }},
      "url": {{ page.url | jsonify }},
      "content": {{ page.content | strip_html | truncate: 500 | jsonify }},
      "categories": {{ page.categories | jsonify }},
      "tags": {{ page.tags | jsonify }}
    }{% unless forloop.last %},{% endunless %}
    {% endif %}
  {% endfor %}
]

Interface modale

Structure HTML

<div class="modal fade" id="siteSearchModal" tabindex="-1">
  <div class="modal-dialog modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <input type="search" 
               class="form-control" 
               data-search-input
               placeholder="Search documentation..."
               autofocus>
        <button type="button" class="btn-close" data-bs-dismiss="modal"></button>
      </div>
      <div class="modal-body">
        <div data-search-results></div>
        <div data-search-empty class="text-center text-muted py-4">
          Type to search...
        </div>
      </div>
    </div>
  </div>
</div>

Bouton d’activation de la recherche

<button class="btn btn-outline-secondary" data-search-toggle>
  <i class="bi bi-search"></i>
  <span class="d-none d-md-inline ms-1">Search</span>
  <kbd class="ms-2">/</kbd>
</button>

JavaScript

Contrôleur de recherche

function initSearchModal() {
  const modalEl = document.getElementById('siteSearchModal');
  const searchInput = modalEl.querySelector('[data-search-input]');
  const resultsContainer = modalEl.querySelector('[data-search-results]');
  let searchIndex = null;

  // Load search index
  async function loadIndex() {
    const response = await fetch('/search.json');
    searchIndex = await response.json();
  }

  // Perform search
  function search(query) {
    if (!searchIndex || !query) return [];
    
    const terms = query.toLowerCase().split(' ');
    return searchIndex.filter(item => {
      const content = `${item.title} ${item.content}`.toLowerCase();
      return terms.every(term => content.includes(term));
    });
  }

  // Render results
  function renderResults(results) {
    if (results.length === 0) {
      resultsContainer.innerHTML = '<p class="text-muted">No results found.</p>';
      return;
    }
    
    resultsContainer.innerHTML = results.map(item => `
      <a href="${item.url}" class="search-result d-block p-2 rounded">
        <strong>${item.title}</strong>
        <small class="d-block text-muted">${item.url}</small>
      </a>
    `).join('');
  }

  // Event listeners
  searchInput.addEventListener('input', () => {
    renderResults(search(searchInput.value));
  });

  // Load index when modal opens
  modalEl.addEventListener('show.bs.modal', loadIndex);
}

Raccourci clavier

document.addEventListener('keydown', (e) => {
  // Skip if typing in input
  if (e.target.matches('input, textarea')) return;
  
  if (e.key === '/') {
    e.preventDefault();
    const modal = bootstrap.Modal.getOrCreateInstance(
      document.getElementById('siteSearchModal')
    );
    modal.show();
  }
});

Configuration

Exclure des pages

# In page front matter
search: false
{% unless page.search == false %}
  // Include in index
{% endunless %}

Champs de l’index

Contrôlez ce qui est indexé :

"content": {{ page.content | strip_html | truncate: 1000 | jsonify }}

Mise en forme

.search-result {
  text-decoration: none;
  color: inherit;
  transition: background-color 0.2s;
}

.search-result:hover {
  background-color: var(--bs-light);
}

.search-result.active {
  background-color: var(--bs-primary);
  color: white;
}
// Arrow key navigation in results
searchInput.addEventListener('keydown', (e) => {
  const results = resultsContainer.querySelectorAll('.search-result');
  const active = resultsContainer.querySelector('.search-result.active');
  
  if (e.key === 'ArrowDown') {
    e.preventDefault();
    const next = active ? active.nextElementSibling : results[0];
    if (next) {
      active?.classList.remove('active');
      next.classList.add('active');
    }
  }
  
  if (e.key === 'Enter' && active) {
    window.location.href = active.href;
  }
});

Performance

Chargement différé

Charger l’index uniquement lorsque nécessaire :

let indexPromise = null;

function getIndex() {
  if (!indexPromise) {
    indexPromise = fetch('/search.json').then(r => r.json());
  }
  return indexPromise;
}

Anti-rebond

let timeout;
searchInput.addEventListener('input', () => {
  clearTimeout(timeout);
  timeout = setTimeout(() => {
    renderResults(search(searchInput.value));
  }, 150);
});

Dépannage

L’index ne se construit pas

  1. Vérifiez que le modèle search.json existe
  2. Vérifiez que le build Jekyll l’inclut
  3. Recherchez d’éventuelles erreurs Liquid

La recherche ne fonctionne pas

  1. Vérifiez que search.json est accessible
  2. Consultez la console du navigateur pour repérer les erreurs
  3. Testez la validité du JSON

Conflits de raccourcis clavier

  1. Recherchez d’autres gestionnaires /
  2. Vérifiez que l’ID de la fenêtre modale correspond
  3. Testez dans différents navigateurs

Voir aussi

Voir aussi

  • [[Features]]
  • [[Keyboard Navigation]]