Usando conectores de salida de plugin desde un tema o plugin

Discourse incluye cientos de ‘Plugin Outlets’ (Salidas de Complemento) que se pueden usar para inyectar contenido nuevo o reemplazar contenido existente en la interfaz de usuario de Discourse. Los ‘Argumentos de Salida’ (‘Outlet arguments’) se ponen a disposición para que el contenido pueda personalizarse según el contexto.

Elección de una salida

Para encontrar el nombre de una salida de complemento, busca en el núcleo de Discourse "<code><PluginOutlet</code>", o utiliza el componente de tema ubicaciones de salidas de complemento. (ej. topic-above-posts).

Salidas envolventes (Wrapper outlets)

Algunas salidas en el núcleo se ven como <code><PluginOutlet @name="foo" /></code>. Estas te permiten inyectar contenido nuevo. Otras salidas ‘envolverán’ una implementación del núcleo existente de esta manera:

<PluginOutlet @name="foo">
  implementación del núcleo
</PluginOutlet>

Definir un conector para este tipo de salida de complemento ‘envolvente’ reemplazará la implementación del núcleo. Solo un tema/complemento activo puede contribuir con un conector para una salida de complemento envolvente.

Para las salidas de complemento envolventes, puedes renderizar la implementación original del núcleo usando la palabra clave {{yield}}. Esto puede ser útil si solo deseas reemplazar la implementación del núcleo bajo ciertas condiciones, o si deseas envolverla en algo.

Definición de la plantilla

Una vez que hayas elegido una salida, decide un nombre para tu conector. Este debe ser único en todos los temas/complementos instalados en una comunidad determinada. ej. brand-official-topics

En tu tema/complemento, define una nueva plantilla handlebars con una ruta formateada de esta manera:

:art: {theme}/javascripts/discourse/connectors/{outlet-name}/{connector-name}.hbs

:electric_plug: {plugin}/assets/javascripts/discourse/connectors/{outlet-name}/{connector-name}.hbs

El contenido de estos archivos se renderizará como un Componente Ember. Para obtener información general sobre Ember/Handlebars, consulta las guías de Ember.

Para nuestro hipotético conector de “temas oficiales de la marca”, la plantilla podría verse así:

<div class="alert alert-info">
  Este tema fue creado por un miembro del
  <a href="https://discourse.org/team">Equipo de Discourse</a>
</div>

Algunas salidas de complemento envolverán automáticamente tu contenido en un elemento HTML. El tipo de elemento se define mediante @connectorTagName en el <code><PluginOutlet></code>.

Uso de argumentos de salida

Las Salidas de Complemento proporcionan información sobre el contexto circundante a través de @outletArgs. Los argumentos pasados a cada salida varían. Una forma fácil de ver los argumentos es agregar esto a tu plantilla:

{{log @outletArgs}}

Esto registrará los argumentos en la consola de desarrollador de tu navegador. Aparecerán como un objeto Proxy - para explorar la lista de argumentos, expande el [[Target]] del proxy.

En nuestro ejemplo topic-above-posts, el tema renderizado está disponible bajo @outletArgs.model. Por lo tanto, podemos agregar el nombre de usuario del miembro del equipo de esta manera:

<div class="alert alert-info">
  Este tema fue creado por
  {{@outletArgs.model.details.created_by.username}}
  (un miembro del
  <a href="https://discourse.org/team">Equipo de Discourse</a>)
</div>

Adición de lógica más compleja

A veces, una plantilla handlebars simple no es suficiente. Para agregar lógica Javascript a tu conector, puedes definir un archivo Javascript adyacente a tu plantilla handlebars. Este archivo debe exportar una definición de componente. Esto funciona igual que cualquier otra definición de componente y puede incluir inyecciones de servicio.

Definir un componente de esta manera eliminará el elemento envolvente automático connectorTagName, por lo que es posible que desees reintroducir un elemento del mismo tipo en tu archivo hbs.

En nuestro ejemplo topic-above-posts, es posible que queramos renderizar al usuario de manera diferente según la configuración del sitio ‘prioritize username in ux’ (priorizar nombre de usuario en la experiencia de usuario). Una definición de componente para eso podría verse algo así:

.../connectors/topic-above-posts/brand-official-topic.js:

import Component from "@glimmer/component";
import { service } from "@ember/service";

export default class BrandOfficialTopics extends Component {
  @service siteSettings;

  get displayName() {
    const user = this.args.outletArgs.model.details.created_by;
    if (this.siteSettings.prioritize_username_in_ux) {
      return user.username;
    } else {
      return user.name;
    }
  }
}

Luego podemos actualizar la plantilla para hacer referencia al nuevo getter:

<div class="alert alert-info">
  Este tema fue creado por
  {{this.displayName}}
  (un miembro del
  <a href="https://discourse.org/team">Equipo de Discourse</a>)
</div>

Renderizado condicional

Si solo deseas que tu contenido se renderice bajo ciertas condiciones, a menudo es suficiente envolver tu plantilla con un bloque {{#if}} de handlebars. Si eso no es suficiente, es posible que desees usar el hook shouldRender para controlar si tu plantilla de conector se renderiza o no.

Primero, asegúrate de tener una definición de conector .js como se describe anteriormente. Luego, agrega una función estática static shouldRender(). Extendiendo nuestro ejemplo:

import Component from "@glimmer/component";
import { getOwner } from "discourse-common/lib/get-owner";

export default class BrandOfficialTopics extends Component {
  static shouldRender(outletArgs, helper) {
    const firstPost = outletArgs.model.postStream.posts[0];
    return firstPost.primary_group_name === "team";
  }
  // ... (cualquier otra lógica)
}

Ahora el conector solo se renderizará cuando la primera publicación del tema haya sido creada por un miembro del equipo.

shouldRender se evalúa en un contexto de autoseguimiento de Glimmer. Los cambios futuros en cualquier propiedad referenciada (ej. outletArgs) harán que la función se reevalúe.

Introducción de nuevas salidas

Si necesitas una salida que aún no existe, no dudes en hacer una solicitud de extracción (pull request) o abrir un tema en Dev.


Este documento está controlado por versiones: sugiere cambios en github.

39 Me gusta
Using discourse's plugin outlets
What is the best way to integrate member applications?
Add HTML (Link) Next To Logo
Group Semantics
Can I put the search form at the top of our 404 page?
How to show user total post count beside name
Native theme support
Developing Discourse Plugins - Part 2 - Connect to a plugin outlet
Feedback on "on-discourse" javascript for setting up custom JS for each page?
Topic-timeline api.decorateWidget call has stopped working
Developing Discourse Plugins - Part 2 - Connect to a plugin outlet
Developing Discourse Themes & Theme Components
How to add btn before "sign in"
Minimizing Maintenance on Theme Customizations
How to add custom fields to models
Tags at the top of the topic list in a Category
I want to insert images (banner) between the topic answers. How do I start?
How to add a link shortcut to the area under the title
Baidu Search
Add Banner/HTML (Widget) before reply button
Upcoming Header Changes - Preparing Themes and Plugins
Upgrading Discourse to Ember 4
Converting modals from legacy controllers to new DModal component API
Need help integrating code wrote on Edittext to the Discourse
Settings not appearing
How to add a custom button in user profile card?
How to add a custom button in user profile card?
Customizing the topic list
(not recommended) Overriding Discourse templates from a Theme or Plugin
How to override the site-header.hbs file from custom theme?
Upcoming topic-list changes - how to prepare themes and plugins
Working with .erb templates in a plugin
How to Integrate a Custom Plugin in discourse UI
Modernizing inline script tags for templates & JS API
Custom Components -- add button or text at any plugin outlet
(not recommended) Overriding Discourse templates from a Theme or Plugin
Display Tags inline with thread title, instead of being on the bottom line
Add link to external SSO profile to profile page
Discourse view file update does not reflect in browser
Discourse view file update does not reflect in browser
Add likes and views to search display
Using template hbs to add HTML content to a plugin outlet
Adding a billing section in the member section
Adding a billing section in the member section
How to modify the header HTML, but still remaining the default founctions
Removing support for "template overrides" and mobile-specific templates
How can i add image in login and register box
Most “traditional” or classic forum Category listing
Newbie help accessing code
How to add custom html next to logo using discourse plugin methods
Using the DModal API to render Modal windows (aka popups/dialogs) in Discourse
Air Theme
How to create a plugin with backend API calls to populate composer while drafting?
How to add a custom url text link on the login page
Add Text In Header Beside Logo
Split up theme Javascript into multiple files

Esto ahora está obsoleto, ¿verdad?
Aviso de obsolescencia: Definir clases de conector a través de registerConnectorClass está obsoleto. Consulte https://meta.discourse.org/t/32727 para ver patrones más modernos. [deprecation id: discourse.register-connector-class-legacy]

Es correcto; puedes usar api.renderInOutlet en su lugar. :slight_smile:

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

1 me gusta

Creo que es un poco más complicado que eso :sweat_smile:
https://github.com/Firepup6500/discourse-custom-profile-link/blob/master/common/head_tag.html

No te preocupes; veré qué puedo hacer para ayudarte más tarde (necesito dormir ahora mismo). :smile:

1 me gusta

¡Gracias! (Lamento que el código sea un desastre, solo quería que funcionara la última vez que lo toqué :sweat_smile:)

1 me gusta

Estoy un poco abrumado aquí, recibí un aviso sobre mi componente utilizado en el archivo HEAD de mi tema. No estoy seguro de cómo reescribirlo con api.renderInOutlet.

  const ajax = require('discourse/lib/ajax').ajax;
  const Topic = require('discourse/models/topic').default;
  // Usamos ajax y el modelo Topic de Discourse

  api.registerConnectorClass('above-main-container', 'featured-topics', {
    // above-main-container es el outlet del plugin,
    // featured-topics es el nombre de tu componente personalizado

    setupComponent(args, component) {

   // el resto del código continúa

Supongo que intenté reemplazar api.registerConnectorClass con api.renderInOutlet pero falló. Realmente no soy un experto en codificación de temas. Gracias por cualquier ayuda.

Puedes ver un ejemplo aquí:

StatBanner es una clase nativa definida en el directorio components:

En tu caso, sería api.renderInOutlet("above-main-container", YourClass)

No creo que puedas hacerlo en el archivo HEAD. Deberías dividir tu código en varios archivos.

Te animo a usar Discourse Theme CLI, ya que será mucho más fácil desarrollar un componente de tema.

¿Es tu componente de tema público?

3 Me gusta

¿Hay alguna forma de obtener el outlet actual en mi archivo .js?

No creo que eso sea posible.

Solía ser posible inspeccionar parentView en los componentes clásicos.

Pero esa propiedad ha sido obsoleta.

1 me gusta

¿Qué quieres decir con “obtener el outlet actual”? ¿Quieres el nombre del outlet? ¿O algo más?

Encontré una solución para mi problema (aquí), pero fue algo como esto:

  • Crear archivos .hbs y .js para 3 diferentes outlets.
  • En cada archivo JS, verificar si el outlet que está usando es el valor de la configuración banner_location.
  • Si lo es, mostrar el banner. Si no, ocultar el banner.
1 me gusta

¡Genial! Parece que te decidiste por usar api.renderInOutlet, con un valor dinámico para el nombre del outlet :chefs_kiss:

1 me gusta