¿Mezclar filtros (orden A-Z) con vista de documentos?

Me pregunto si podemos usar los filtros para mostrar una categoría usando la vista de documentos y A-Z (alfabéticamente).

Tenemos un glosario que debería mostrar la vista de temas ordenada :slight_smile:

Actualmente, la vista de filtro está separada de los documentos:

Al final, terminé usando un componente de tema personalizado para ello:

// A-Z necesita toda la categoría: el núcleo no puede ordenar una lista de temas por título
// (TopicQuery::SORTABLE_MAPPING no tiene "title"), así que agotamos la lista paginada
// antes de ordenar. Limitado — las categorías de documentación son pequeñas; cualquier cosa más grande
// cae en ordenar lo que esté cargado, lo cual el MutationObserver de abajo mantiene actualizado.
// ponytail: elimina este bucle completo si el núcleo alguna vez implementa el ordenamiento por título en el servidor.
const MAX_DOC_PAGES = 12; // ~360 temas a 30 por página
let docLoadRun = 0;

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

  // `controller:discovery/topics` está deprecado en la versión actual de Discourse
  // (id de deprecación: discourse.discovery-topics-controller). Discovery ahora usa un
  // controlador dedicado por filtro; las categorías de documentación son siempre la lista de temas
// más recientes, así que buscamos ese controlador directamente. Si falla aquí, simplemente ordenamos
  // la primera página, por lo que cada paso usa encadenamiento opcional.
  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();

    // Navegamos fuera durante la carga, o la lista dejó de crecer — en cualquier caso, detenemos.
    if (run !== docLoadRun || (list.topics?.length ?? 0) === before) {
      return;
    }
  }
}

// Ordena la lista de temas de Documentación de la A a la Z, usando la misma configuración regional que la página (<html lang>).
let docSortObservers = new Map();

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

  try {
    Intl.getCanonicalLocales(lang); // lanza RangeError si la etiqueta está mal formada
    return lang;
  } catch {
    return undefined; // usa la configuración regional predeterminada del navegador
  }
}

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; // ya está ordenado — evita que el observador se alimente a sí mismo
  }

  // Inserta antes de lo que siga a las filas, manteniendo el centinela de "cargar más" al final.
  const anchor = rows[rows.length - 1].nextSibling;

  // Fija la primera fila visible para que las inserciones por encima no hagan saltar el desplazamiento.
  const pinned = rows.find((row) => row.getBoundingClientRect().bottom > 0);
  const pinnedTop = pinned?.getBoundingClientRect().top;

  // Observador pausado para que nuestros propios movimientos no vuelvan a entrar en esta función.
  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();

  // No es un contenedor flex/grid, por lo que el reordenamiento debe mover nodos del DOM — CSS `order` se ignora.
  document.querySelectorAll(".topic-list.doc-simple-mode .topic-list-body").forEach((body) => {
    // try/finally para que la lista siempre se muestre, incluso si el ordenamiento lanza una excepción.
    try {
      sortDocTopicList(body, locale);
    } finally {
      body.classList.add("docs-sorted");
    }

    // Ordena solo una vez que la carga se calme — el reordenamiento durante la carga detenía el cargador.
    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);
  });
}

Creo que el código probablemente se puede mejorar, acepto con gusto sugerencias y/o PRs.

Hmm, por desgracia, obtengo un error 404 al hacer clic en el enlace de tu publicación: https://github.com/somos-criptonautas/discourse-horizon-mod-nautas/blob/main/javascripts/api-initializers/init.gjs

Ay, perdón por eso. He actualizado la URL, es interna y todavía estoy trabajando en ella, así que considérala experimental.

Sigo atento a cualquier comentario, sugerencia o error que encuentres si revisas el código o lo pruebas. Sé que no es la solución más elegante, pero funciona para nuestro caso de uso.