Notificación de tema nuevo o actualizado fijo

Hola,

Esto podría ser útil si el elemento Ver x temas nuevos o actualizados se mantuviera fijo en la parte superior, justo debajo del encabezado, para que se vea inmediatamente al hacer scroll en la lista de temas. Al hacer clic en la barra, saltaría a la parte superior para cargar los temas nuevos o actualizados.

La parte fija funciona muy bien con esto :arrow_down:

#list-area .show-more.has-topics {
  position: sticky;
  top: var(--header-offset);
}

La otra parte (JS) sería una función de clic para saltar al principio o hacer scroll hacia arriba, pero no sé cómo hacerlo o cuál es la mejor manera de hacerlo.

Encontré esta sección en las plantillas discovery/topics.hbs y discovery/categories.hbs. Si modifico el <a href a <a href="/", podría funcionar (no estoy seguro, no lo he probado), pero con esto se recarga cada vez, como al hacer clic en el logotipo.

Gracias por la ayuda :slightly_smiling_face:

Sí, agregar una URL causará navegación ya que no hay lógica para interceptar el evento.

El enlace está llamando a una acción de Ember. Todas las acciones en Discourse son extensibles. Por lo tanto, actúan como ganchos de personalización de cierta manera. Entonces, ¿cómo modificas una acción? Bueno, primero veamos qué hace.

Busca en GitHub o localmente por el nombre de la acción. Las acciones siempre se definen en archivos JS y se referencian en Handlebars. Queremos ver la definición, así que reducimos la búsqueda a archivos JS.

Repository search results · GitHub

Obtienes cuatro archivos. ¿Cuál deberías revisar? Quieres personalizar la plantilla

discovery/topics.hbs

Así que

discovery/topics.js

es lo que quieres revisar.

¿Alguna parte de este código es útil? No, pero ahora sabemos dónde se define la acción. Así que, modifiquémosla.

discovery/topics.js es una clase de Ember. Puedes modificar las clases de Ember con un método en la API de plugins llamado… modifyClass :stuck_out_tongue:

https://github.com/discourse/discourse/blob/main/app/assets/javascripts/discourse/app/lib/plugin-api.js#L166-L195

Ya sabemos la clase que queremos modificar. Es discovery/topics. Necesitamos saber qué tipo de clase es. Así que, revisemos el directorio de archivos.

discourse/app/controllers/discovery/topics.js

Es un controlador.

También sabemos que queremos modificar la acción showInserted en esa clase. Así que, comenzamos con esto.

api.modifyClass("controller:discovery/topics", {
  pluginId: 'sticky-new-topics-banner',
  actions: {
    showInserted() {
      // hagamos algo
    }
  }
});

Luego puedes agregar cualquier código que desees para hacer scroll en la ventana cuando el usuario hace clic en el banner de “nuevos temas”. Yo opté por algo así.

const listControls = document.querySelector(".list-controls");
listControls.scrollIntoView();

Puedes leer más sobre scrollIntoView() aquí.

Luego agregas eso al método de la API de plugins de la siguiente manera.

api.modifyClass("controller:discovery/topics", {
  pluginId: 'sticky-new-topics-banner',
  actions: {
    showInserted() {
+     const listControls = document.querySelector(".list-controls");
+     listControls.scrollIntoView();
    }
  }
});

Entonces, ¿ya terminamos? No. Esto rompe Discourse porque estás sobrescribiendo completamente la acción. Al hacer clic en el enlace ahora se hará scroll hasta el elemento list-controls, pero no se cargarán los nuevos temas. ¿Por qué? Porque el código principal ya no se está aplicando. Me refiero a esto

Entonces, ¿cómo lo arreglas? Con esta línea simple

this._super(...arguments);

No necesitas copiar ningún código del núcleo si solo quieres agregarle algo. Haz tu trabajo y luego agrega esa línea. Todo lo que hace es asegurarse de que el código del núcleo se aplique.

api.modifyClass("controller:discovery/topics", {
  pluginId: 'sticky-new-topics-banner',
  actions: {
    showInserted() {
     const listControls = document.querySelector(".list-controls");
     listControls.scrollIntoView();
+
+    this._super(...arguments);
    }
  }
});

Si pruebas esto, verás que casi todo funciona muy bien, excepto… el encabezado se superpone con list-controls. ¿Por qué? Porque el encabezado está configurado como sticky.

Puedes solucionarlo de varias maneras en JS: calcular la altura, obtener el desplazamiento, importar un helper de Discourse, etc. No entraré en esos detalles.

La forma más fácil es con CSS usando scroll-margin-top, sobre lo cual puedes leer aquí.

Así que, agregamos esto

.list-controls {
  scroll-margin-top: calc(var(--header-offset) * 2);
}

En inglés: Cuando se hace clic en el enlace, haz scroll hasta la parte superior de list-controls menos 2 veces la altura del encabezado, para que no se superponga y tenga un poco de espacio debajo.

Así que, pongamos todo esto junto.

Pestaña de encabezado común

<script type="text/discourse-plugin" version="0.8">
api.modifyClass("controller:discovery/topics", {
  pluginId: "sticky-new-topics-banner",
  actions: {
    showInserted() {
      const listControls = document.querySelector(".list-controls");
      listControls.scrollIntoView();
      this._super(...arguments);
    }
  }
});
</script>

CSS común

#list-area {
  // móvil tiene un diseño diferente
  .alert-info,
  .show-more.has-topics {
    position: sticky;
    // safari a veces es exigente sin el prefijo
    position: -webkit-sticky;
    top: var(--header-offset);
    // el banner debe estar encima del contenido
    z-index: z("header");
  }
}

.list-controls {
  scroll-margin-top: calc(var(--header-offset) * 2);
}

¡Gracias @Johani! Es muy útil para mí y creo que finalmente entendí muchas cosas. Me encantan tus respuestas detalladas porque podemos aprender mucho de ellas: cómo funcionan las cosas y, por supuesto, que funcionan perfectamente. ¡Gracias de nuevo! :slightly_smiling_face: