Mischfilter (Reihenfolge A-Z) mit Dokumentenansicht?

Ich frage mich, ob wir die Filter verwenden können, um eine Kategorie mit der Dokumentenansicht und A-Z (alphabetisch) anzuzeigen.\n\nWir haben ein Glossar, das die Themenansicht geordnet anzeigen soll :slight_smile: \n\nDerzeit ist die Filteransicht getrennt von den Dokumenten:\n\n

Ich habe am Ende eine eigene Theme-Komponente dafür verwendet:

// A-Z benötigt die gesamte Kategorie: core kann eine Themenliste nicht nach Titel sortieren
// (TopicQuery::SORTABLE_MAPPING hat kein "title"), daher durchlaufen wir die paginierte Liste
// vollständig, bevor wir sortieren. Begrenzt — Dokumentationskategorien sind klein; bei größeren
// wird auf die Sortierung der geladenen Einträge zurückgefallen, was der MutationObserver unten aktuell hält.
// ponytail: diesen ganzen Loop entfernen, falls core jemals serverseitige Titel-Sortierung bereitstellt.
const MAX_DOC_PAGES = 12; // ~360 Themen bei 30 pro Seite
let docLoadRun = 0;

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

  // `controller:discovery/topics` ist im aktuellen Discourse veraltet
  // (Deprecation-ID: discourse.discovery-topics-controller). Discovery verwendet nun einen
  // dedizierten Controller pro Filter; Dokumentationskategorien sind immer die neueste Themenliste,
  // daher suchen wir diesen Controller direkt auf. Ein Fehler hier bedeutet nur, dass wir die
  // erste Seite sortieren, daher ist jeder Schritt optional-chained.
  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();

    // Mittendrin navigiert oder die Liste wächst nicht mehr — in beiden Fällen: aufhören.
    if (run !== docLoadRun || (list.topics?.length ?? 0) === before) {
      return;
    }
  }
}

// Sortiert die Dokumentations-Themenliste A-Z, kollationiert mit dem <html lang> der Seite.
let docSortObservers = new Map();

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

  try {
    Intl.getCanonicalLocales(lang); // wirft RangeError bei fehlerhaftem Tag
    return lang;
  } catch {
    return undefined; // fällt auf die Standardsprache des Browsers zurück
  }
}

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; // bereits sortiert — verhindert, dass der Observer sich selbst füttert
  }

  // Vor dem Element einfügen, das nach den Zeilen folgt, damit das load-more-Sentinel zuletzt bleibt.
  const anchor = rows[rows.length - 1].nextSibling;

  // Die erste sichtbare Zeile fixieren, damit Einfügungen darüber den Scroll nicht springen lassen.
  const pinned = rows.find((row) => row.getBoundingClientRect().bottom > 0);
  const pinnedTop = pinned?.getBoundingClientRect().top;

  // Observer pausiert, damit unsere eigenen Bewegungen diese Funktion nicht erneut aufrufen.
  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();

  // Kein flex/grid-Container, daher muss das Reordern DOM-Knoten bewegen — CSS `order` wird ignoriert.
  document.querySelectorAll(".topic-list.doc-simple-mode .topic-list-body").forEach((body) => {
    // try/finally, damit die Liste immer angezeigt wird, auch wenn beim Sortieren ein Fehler auftritt.
    try {
      sortDocTopicList(body, locale);
    } finally {
      body.classList.add("docs-sorted");
    }

    // Nur sortieren, wenn das Laden abgeschlossen ist — Reordern während des Ladens hat den Loader blockiert.
    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);
  });
}

Ich denke, der Code lässt sich sehr wahrscheinlich verbessern. Ich nehme Vorschläge und/oder PRs gerne an.