Mélanger les filtres (ordre A-Z) avec la vue des documents ?

Je me demande si nous pouvons utiliser les filtres pour afficher une catégorie en utilisant la vue docs et A-Z (par ordre alphabétique).

Nous avons un glossaire qui devrait afficher la vue thématique ordonnée :slight_smile:

Actuellement, la vue des filtres est séparée des docs :

J’ai fini par utiliser un composant de thème personnalisé à cet effet :

// A-Z nécessite la catégorie entière : le cœur ne peut pas trier une liste de sujets par titre
// (TopicQuery::SORTABLE_MAPPING n'a pas de "title"), donc on épuise la liste paginée
// avant de trier. Limité — les catégories de documentation sont petites ; au-delà, on retombe sur
// le tri de ce qui est chargé, ce que le MutationObserver ci-dessous maintient à jour.
// ponytail : supprimez cette boucle entière si le cœur implémente un jour le tri par titre côté serveur.
const MAX_DOC_PAGES = 12; // ~360 sujets à 30 par page
let docLoadRun = 0;

async function loadAllDocTopics(api) {
  const run = ++docLoadRun;

  // `controller:discovery/topics` est obsolète dans la version actuelle de Discourse
  // (identifiant d'obsolescence : discourse.discovery-topics-controller). Discovery utilise désormais un
  // contrôleur dédié par filtre ; les catégories de documentation sont toujours la liste des sujets
// les plus récents, donc on récupère directement ce contrôleur. Un échec ici signifie simplement qu'on trie
  // la première page, donc chaque étape est chaînée avec l'opérateur optionnel.
  const list = api.container.lookup("controller:discovery/latest")?.model;
  if (!list?.loadMore) {
    return;
  }

  for (let page = 0; page < MAX_DOC_PAGES; page++) {
    if (!list.more_topics_url) {
      return;
    }

    const before = list.topics?.length ?? 0;
    await list.loadMore();

    // Navigation vers une autre page en cours de chargement, ou la liste a cessé de grandir — dans les deux cas, on s'arrête.
    if (run !== docLoadRun || (list.topics?.length ?? 0) === before) {
      return;
    }
  }
}

// Trie la liste des sujets de la documentation de A à Z, en collationnant avec la langue de la page <html lang>.
let docSortObservers = new Map();

function docSortLocale() {
  const lang = document.documentElement.lang;
  if (!lang) {
    return undefined;
  }

  try {
    Intl.getCanonicalLocales(lang); // lève une RangeError si l'étiquette est mal formée
    return lang;
  } catch {
    return undefined; // retombe sur le locale par défaut du navigateur
  }
}

function sortDocTopicList(body, locale) {
  const rows = [...body.querySelectorAll(":scope > .topic-list-item")];
  if (rows.length < 2) {
    return;
  }

  const sorted = [...rows].sort((a, b) => {
    const titleA = a.querySelector(".title")?.textContent.trim() ?? "";
    const titleB = b.querySelector(".title")?.textContent.trim() ?? "";
    return titleA.localeCompare(titleB, locale);
  });

  if (sorted.every((row, i) => rows[i] === row)) {
    return; // déjà trié — empêche l'observateur de se réinjecter
  }

  // Insère avant ce qui suit les lignes, en gardant le sentinelle de chargement plus en bas.
  const anchor = rows[rows.length - 1].nextSibling;

  // Épingle la première ligne visible pour que les insertions au-dessus ne fassent pas sauter le défilement.
  const pinned = rows.find((row) => row.getBoundingClientRect().bottom > 0);
  const pinnedTop = pinned?.getBoundingClientRect().top;

  // Observateur en pause pour que nos propres déplacements ne réentrent pas dans cette fonction.
  const observer = docSortObservers.get(body);
  observer?.disconnect();
  sorted.forEach((row) => body.insertBefore(row, anchor));
  observer?.observe(body, { childList: true });

  if (pinned) {
    window.scrollBy(0, pinned.getBoundingClientRect().top - pinnedTop);
  }
}

function sortDocCategoryTopicLists() {
  docSortObservers.forEach((observer) => observer.disconnect());
  docSortObservers = new Map();

  const locale = docSortLocale();

  // Ce n'est pas un conteneur flex/grid, donc le réordonnancement doit déplacer les nœuds DOM — l'ordre CSS est ignoré.
  document.querySelectorAll(".topic-list.doc-simple-mode .topic-list-body").forEach((body) => {
    // try/finally pour que la liste soit toujours révélée même si le tri lève une exception.
    try {
      sortDocTopicList(body, locale);
    } finally {
      body.classList.add("docs-sorted");
    }

    // Trie uniquement une fois le chargement terminé — le réordonnancement en cours de chargement bloquait le chargeur.
    let settleTimer = null;
    const observer = new MutationObserver(() => {
      window.clearTimeout(settleTimer);
      settleTimer = window.setTimeout(() => {
        sortDocTopicList(body, locale);
      }, 400);
    });
    observer.observe(body, { childList: true });
    docSortObservers.set(body, observer);
  });
}

Je pense que le code peut très probablement être amélioré, j’accepte volontiers des suggestions et/ou des PR.

Hmm - malheureusement, je reçois une erreur 404 lorsque je clique sur le lien dans votre message : https://github.com/somos-criptonautas/discourse-horizon-mod-nautas/blob/main/javascripts/api-initializers/init.gjs

Oh, désolé pour ça. J’ai mis à jour l’URL, il s’agit d’un code interne et je suis toujours en train de travailler dessus, alors merci de le considérer comme expérimental.

Je reste attentif à tout retour, toute suggestion ou toute erreur que vous pourriez trouver en examinant le code ou en l’essayant ! Je sais que ce n’est pas la solution la plus élégante, mais elle fonctionne pour notre cas d’usage.