# Título alfabético duplicado en la categoría

**URL:** https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045
**Category:** Feature
**Created:** [27 Agosto, 2026 09:05 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045 "2026-08-27T09:05:32Z")
**Posts on this page:** 1
**Showing post:** 9

<div class="post-metadata">

### Author: ![satonotdead](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/satonotdead/32/447830_2.png) [@satonotdead](https://meta.discourse.org/u/satonotdead)
#### Post date: [13 Septiembre, 2026 23:28 UTC](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045/9 "2026-09-13T23:28:56Z")

</div>

Solo comparto una solución que estaba usando desde hace unas semanas:

> [@satonotdead](#):
>
> Al final, usé un [componente de tema personalizado](https://github.com/somos-criptonautas/discourse-horizon-mod-nautas/blob/main/javascripts/api-initializers/init.gjs) para ello:
> 
> ```plaintext
> // 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; si son más grandes,
> // se recurre a ordenar lo que esté cargado, lo que el MutationObserver de abajo mantiene actualizado.
> // ponytail: elimina este bucle completo si el núcleo implementa algún día la ordenación de títulos 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();
> 
> // Se navegó 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, colacionando con el <html lang> de la página.
> let docSortObservers = new Map();
> 
> function docSortLocale() {
> const lang = document.documentElement.lang;
> if (!lang) {
> return undefined;
> }
> 
> try {
> Intl.getCanonicalLocales(lang); // lanza RangeError con una etiqueta malformada
> return lang;
> } catch {
> return undefined; // recurre a 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 — detiene que el observador se alimente a sí mismo
> }
> 
> // Inserta antes de lo que siga a las filas, manteniendo cualquier marcador de carga 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 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 revele 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 pueda mejorar, acepto con gusto sugerencias y/o PRs.

---

_[View the full topic](https://meta.discourse.org/t/tri-titre-alphabetique-dans-categorie/411045)._
