Próximos cambios en encabezados: preparando temas y plugins

We’ve recently been working on updating Discourse’s header from the legacy ‘widget’ rendering system to modern Glimmer components. This change is now available in Discourse core behind the glimmer header mode site setting.

:timer_clock: Approximate Timeline

(very rough estimates - subject to change in either direction)

Q1 2024:

  • :white_check_mark: core implementation finished & enabled on Meta

  • :white_check_mark: upgrade advice published; console deprecation messages enabled

  • :white_check_mark: work begins to update all official and third-party plugins/themes

Q2 2024:

  • :white_check_mark: start enabling new header implementation by default

  • :white_check_mark: official and third-party themes/plugins are ready for the upgrade

  • :white_check_mark: Deprecation messages start triggering an admin warning banner for any remaining issues

Q3 2024:

  • :white_check_mark: Announcement topic posted for wider visibility: Preparing your community for behind-the-scenes header changes

  • :white_check_mark: w/c 5th August 2024 (v3.4.0.beta1): new header enabled for all sites by default. It will still be possible for admins to switch back to the old header by toggling the ‘glimmer header mode’ site setting.

  • :white_check_mark: w/c 2nd September 2024: final removal of feature flag and legacy code

:eyes: What Does it Mean for Me?

If your plugin or theme uses any ‘widget’ APIs to customize the header, those will need to be updated for compatibility with the new header.

:person_tipping_hand: How Do I Try the New Header?

In the latest version of Discourse, the new header is automatically enabled when all your themes/plugins are compatible.

If your themes/plugins are not compatible, then the legacy header will still be used, and a warning will be printed to the console alongside the existing deprecation messages. A warning banner will also be shown to admins in the UI.

In the unlikely event that this automatic system does not work as expected, you can temporarily override this ‘automatic feature flag’ via the glimmer header mode site setting. If you do that, please let us know the reason in this topic.

:technologist: Do I Need to Update My Plugin/Theme?

To determine whether your customization needs to be updated, check if it uses decorateWidget, changeWidgetSetting, reopenWidget or attachWidgetAction on any of these widgets:

  • header
  • site-header
  • header-contents
  • header-buttons
  • user-status-bubble
  • sidebar-toggle
  • header-icons
  • header-topic-info
  • header-notifications
  • home-logo
  • user-dropdown

or uses one of these plugin API methods:

  • addToHeaderIcons
  • addHeaderPanel

All of these things will now cause deprecation messages to be printed to the console. Deprecation IDs are:

  • discourse.add-header-panel
  • discourse.header-widget-overrides

:warning: If you use more than one theme in your instance, be sure to check all of them.

Admin notice

As of June 20, 2024, we’ve enabled the admin notice for the deprecations above.

If your instance was deployed after this date and your instance’s current plugins, theme, or theme components triggers one of the deprecation warnings, the following message will be displayed only for the admins*:

This message is just to alert the admins that they need to take action soon to modernize the affected customizations: the old customizations will still work until we remove the legacy codebase.

:twisted_rightwards_arrows: What Are the Replacements?

Each theme/plugin is different, but here is some guidance for the most common use cases:

addToHeaderIcons

:information_source: For custom header icons, we recommend removing your code and installing the official Custom Header Links (Icons) Theme Component. If that doesn’t meet your requirements, see below for information for details on the required code changes:

The addToHeaderIcons plugin API has been deprecated in favor of the new headerIcons API. It exists to allow adding, removing, or re-ordering of icons in the header. It requires a Component to be passed.

The component can be passed as so:

Before After
api.addToHeaderIcons(“widget-foo”) api.headerIcons.add(“foo”, FooIcon)
api.decorateWidget(“header-icons:before”, () => return helper.h(“div”, “widget-foo”)) api.headerIcons.add(“foo”, FooIcon, { before: “search” })
api.decorateWidget(“header-icons:after”, () => return helper.h(“div”, “widget-foo”)) api.headerIcons.add(“foo”, FooComponent, { after: “search” })

This example uses Ember’s Template Tag Format (gjs) to define a component inline and pass it to the headerButtons.add API:

// .../discourse/api-initializers/add-my-button.gjs

import DButton from "discourse/components/d-button";
import { apiInitializer } from "discourse/lib/api";

export default apiInitializer("1.0", (api) => {
  api.headerIcons.add("some-unique-name", <template>
    <li><DButton class="icon btn-flat" @href="/u" @icon="address-book" /></li>
  </template>);
});

Or for a dropdown, you could use <DMenu instead of <DButton:

import DButton from "discourse/components/d-button";
import { apiInitializer } from "discourse/lib/api";
import DMenu from "float-kit/components/d-menu";

export default apiInitializer("1.0", (api) => {
  api.headerIcons.add("some-unique-name", <template>
    <li>
      <DMenu class="icon btn-flat" @icon="address-book">
        <DButton @translatedLabel="User 1" @href="/u/user1" />
        <DButton @translatedLabel="User 2" @href="/u/user2" />
        <DButton @translatedLabel="User 3" @href="/u/user3" />
      </DMenu>
    </li>
  </template>);
});

Example upgrade commits:

decorateWidget("header-buttons:*")

:information_source: For custom header links, we recommend removing your code and installing the official Custom Header Links Theme Component. If that doesn’t meet your requirements, see below for information for details on the required code changes:

The header-buttons widget has been deprecated and we have introduced a headerButtons plugin API. It exists to allow adding, removing, or re-ordering of buttons in the header. It requires a Component to be passed.

Before After
api.decorateWidget(“header-buttons:before”) api.headerButtons(“button-name”, ButtonComponent, { before: “auth” })
api.decorateWidget(“header-buttons:after”) api.headerButtons(“button-name”, ButtonComponent, { after: “auth” })

changeWidgetSetting(...) for the header widgets

:information_source: The most common uses of changeWidgetSetting can be achieved using these theme components:

If these don’t fit your use-case, read on…

Some customizations on the header widgets were using the changeWidgetSetting API.

Although, there is no direct replacement for customizations like the one above, due to how the Glimmer components fields work, we introduced a new plugin API on Discourse 3.3.0.beta3 to handle some of these cases.

registerValueTransformer can be used to override values that were tagged in the source code as overridable, this is a similar approach to how plugin outlets work.

We already added two transformers for the use cases we found to be common in our source code base:

  • home-logo-href: can be used to override the URL in the home logo anchor. See the section home-logo below for examples.

  • header-notifications-avatar-size: can be used to change the size of the image fetched to the user avatar in the header. Example:

The code below:

api.changeWidgetSetting(
  "header-notifications",
  "avatarSize",
  settings.header_avatars_size
);

Would be converted to:

api.registerValueTransformer(
  "header-notifications-avatar-size",
  () => settings.header_avatars_size
);

These transformers need to be added to the Discourse source code. If you need a different one, please let us know posting your use case below.

More details about the new value transformer APIs can be found here.

home-logo

We have introduced a home-logo plugin outlet in replacement of home-logo:before or home-logo:after widget decorations. You can utilize the automatic __before and __after naming in your connector file to specify where your custom content should be placed.

More details on before/after connector file naming can be found here.

Before After
api.decorateWidget(“home-logo:before”) Move content to /connectors/home-logo__before
api.decorateWidget(“header-buttons:after”) Move content to /connectors/home-logo__after)

Altering the home-logo anchor URL:

A very common need is altering the URL the home-logo links to. We introduced the home-logo-href value transformer to address this. Examples:

  • to change the link to a static URL

    api.registerValueTransformer("home-logo-href", () => "https://example.com");
    
  • to return a dynamic URL based on the current user

    api.registerValueTransformer("home-logo-href", () => {
      const currentUser = api.getCurrentUser();
      return `https://example.com/${currentUser.username}`;
    });
    
  • to return a URL based on a theme-component setting

    api.registerValueTransformer("home-logo-href", () => {
      return settings.example_logo_url_setting;
    });
    

:sos: What about other customizations?

If your customization cannot be achieved using CSS, PluginOutlets, or the new APIs we’ve introduced, please let us know by creating a new Dev topic to discuss.

:sparkles: How do I update a theme/plugin to support both old and new header?

All the new APIs and plugin outlets listed in this document are supported on both the new and the old header. So you only need to make one update to your theme/plugin now, and users will be ready for the switch.

31 Me gusta

Pero, ¿cómo defino un FooIcon?

En un plugin, intenté crear /assets/javascripts/discourse/components/server-link.js (como para otros componentes que uso en un archivo hbs)

import Component from "@ember/component";
import discourseComputed from "discourse-common/utils/decorators";

export default Component.extend({
// ¿tiene que ir algo aquí?
});

Y un assets/javascripts/discourse/templates/components/server-link.hbs con
esto es un enlace (Creo que puedo hacer que sea un enlace si consigo que funcione este “Hola, mundo”)

El ejemplo anterior tiene const IconWithDropdown = ..., pero ¿dónde iría eso? Intenté ponerlo en un inicializador (donde había estado api.decorateWidget), pero no me parece javascript válido ni para ember, según lo que entiendo.

Antes tenía un array headerlinks y solía

        headerLinks.push(
          h(
            `li.headerLink.no-servers`,
            h("a", anchorAttributes, I18n.t("pfaffmanager.no_servers_title"))
          )
        );

para añadir los enlaces que quería. Creo que si consigo que

      api.headerIcons.add("foo", ServerLink, { before: "search" });

funcione, entonces podré poner eso en el bucle que construyó ese array.

1 me gusta

¡¡¡OMG!!! ¿Los componentes de Glimmer van en assets/javascripts/discourse/component y los componentes de Ember van en assets/javascripts/discourse/components?!?

Ahora tengo un server-link.gjs

import Component from "@ember/component";
export default class ServerLink extends Component {
  // Argumento requerido para la URL
  url = null;
  // Argumento opcional para el texto del enlace
  text = 'asdf';
  click() {
    console.log('ServerLink clicked!',this);

  }
  // Plantilla para el componente
  <template>
    {{log "my template" this}}
    LINK!
    <a href={{this.url}}>{{this.text}}</a>
  </template>
}

Y en mi inicializador esto:

      api.headerIcons.add("foo", ServerLink, { param: "url, yo", before: "search" });

Ahora tengo algo en la cabecera.

Pero, ¿cómo paso cosas a ServerLink? Necesito llamarlo varias veces con diferentes URLs y diferente texto para hacer clic. No puedo ver las cosas en ese {} en el componente.

Y en realidad no quieres poner JavaScript antes de la <template>, ¡ya que mi console.log("") no se analizará!

También intenté hacer:

      const x = new ServerLink({
        url: "mylink",
        text: "my-text",
        name: 'Bob',
        message: 'Generated from JavaScript',
      });

y luego pasar x en lugar de ServerLink, pero todavía no hay suerte.

2 Me gusta

¿Quieres decir que quieres varios botones en las cabeceras con diferentes iconos/texto/URL o el mismo botón, pero dependiendo del contexto, el texto/URL puede cambiar?

Sí, estás en una clase; ¡declararías variables, funciones o plantillas allí!

Sí. Los enlaces cambian para diferentes usuarios. El código antiguo enlazaba a través de una matriz de servers y los añadía a esta matriz:

            headerLinks.push(
              h(
                `li.headerLink${deviceClass}${newClass}`,
                h("a", anchorAttributes, linkText)
              )
            );

Y luego hizo esto:

      // api.decorateWidget("header-buttons:before", (helper) => {
      //   return helper.h("ul.pfaffmanager-header-links", headerLinks);
      // });

Así que tenía hasta 3 enlaces que se añadían a la cabecera, cada uno enlazando a una URL de servidor separada.

Ajá. Ahora lo entiendo.

No, no te preocupes, la convención sigue siendo /components/ :sweat_smile:

(Técnicamente, puedes definir y pasar componentes gjs como quieras, así que puedes elegir el nombre de directorio que prefieras. Pero nos ceñimos a /components/.)

Sí, esa es una pregunta justa. Trabajaré en la redacción de algunos documentos ‘desde cero’ sobre cómo añadir iconos a la cabecera para que tengamos un mejor punto de referencia.

Mientras tanto, podrías echar un vistazo a la actualización de discourse-icon-header-links para inspirarte. Lo bueno de usar GJS es que puedes definir componentes en cualquier lugar y tienen acceso a las variables del ámbito local.

Así que, si cambias el nombre de tu inicializador a .gjs, puedes hacer cosas como

servers.forEach((server) => {
  api.headerIcons.add(`server-${server.id}`, 
    <li><DButton @translatedLabel={{server.name}} @icon="server" /></li>
  );
});

O puedes definir un componente antes en el mismo archivo y usarlo como

class ServerButton extends Component {
  get icon(){
    // alguna lógica para decidir el icono
  }
  
    <li><DButton @translatedLabel={{@server.name}} @icon={{this.icon}} /></li>
  
}

...

servers.forEach((server) => {
  api.headerIcons.add(`server-${server.id}`, 
    <ServerButton @server={{server}} />
  );
});

O podrías mover la iteración dentro de la plantilla (¡útil si la lista de servidores es un TrackedArray que puede cambiar en tiempo de ejecución!)

api.headerIcons.add("server-buttons",  
  {{#each servers as |server|}}
    <ServerButton @server={{server}} />
  {{/each}}
);
7 Me gusta

Mientras trabajamos en un código de ejemplo más detallado

Oh. Genial. Pensé que lo había probado en components y no había funcionado.

¡Gracias! Creo que puedo hacer que una de estas cosas funcione. El enlace a los enlaces de encabezado es de gran ayuda. Estoy bastante seguro de que cuando escribí mi código, tuve suficiente sentido como para mirar ese mismo componente para resolverlo entonces.

4 Me gusta

¡Viendo un rayo de esperanza!

¡Hola @david y @Arkshine! ¡Lo logré!

¿Quuuuuuuuuuuuuué? ¿Solo cambiarle el nombre? Eso es una locura. Pero, efectivamente. Lo hice, y ahora

          servers.filter(Boolean).map((server) => {
            const linkHref = `/pfaffmanager/servers/${server.id}`;
            const linkTitle = `click to configure server ${server.id}`;
            let host = String(server.hostname);
            const linkText = host.replace(
              /www.|community.|forums?.|talk.|discourse./,
              ""
            );
            const serverLink = <template>
              <li class="headerLink">
                <a class="btn-flat" href={{linkHref}} title={{linkTitle}}>
                  {{host}}
                </a>
              </li>
            </template>;
            const beforeIcon = ["chat", "search", "hamburger", "user-menu"];
            api.headerIcons.add(host, serverLink, { before: beforeIcon });
          });

¡Y está haciendo lo que hacía antes, que es lo que quería!

Sí, ahora eso suena muy bien, y aún más lo que quiero, pero esas cosas no cambian mucho, así que lo dejaré por hoy. Si cambian un nombre de host, tendrán que recargar la página para que el enlace cambie.

Supongo que borrarás mi código extra aquí cuando actualices lo anterior (o quizás no habría sido tan hablador en un tema de documentación…).

5 Me gusta

He actualizado el OP con algunos ejemplos completos de gjs e incluido un enlace a la documentación original de Ember. ¿Qué te parece @pfaffman? ¿Hay algo más que creas que valdría la pena añadir?

1 me gusta

Es mejor porque tiene un ejemplo funcional. Pero, ¿entiendo correctamente que existen componentes de ember y componentes de glimmer? Y si es así, ¿deberías decir que se requiere un componente de glimmer, creo?

¿Y tal vez enlazar a la documentación de glimmer sobre cómo funcionan?

Parece que puedes tener componentes en línea como en tu ejemplo, y otro tipo donde lo asignas a algo como una variable en el mismo archivo o lo pones en otro archivo que pones en el directorio de componentes y luego incluyes? Creo que todo eso puede ser demasiado para este tema, pero me encantaría un tema dedicado a eso.

2 Me gusta

Son totalmente intercambiables: puedes usar un componente clásico de Ember o un componente de Glimmer. Y para cualquiera de ellos, puedes optar por crearlos utilizando el formato antiguo de .js/.hbs o el nuevo formato de .gjs.

Veré si puedo incluir algunos enlaces a la documentación de Ember :+1:

4 Me gusta

:mega: Hoy fusionamos este cambio, que habilitará automáticamente la nueva implementación de la cabecera para sitios con temas/plugins compatibles.

Si tus temas/plugins no son compatibles, se seguirá utilizando la cabecera heredada y se imprimirá una advertencia en la consola junto con los mensajes de deprecación existentes. En un futuro cercano, esta advertencia de consola se actualizará a un banner de advertencia en la interfaz de usuario.

En el improbable caso de que este cambio automático cause problemas, puedes anular temporalmente esta ‘bandera de función automática’ a través de la configuración del sitio glimmer header mode. Si lo haces, por favor, infórmanos el motivo en este tema.

3 Me gusta

No buscaba hacer ningún cambio, pero los avisos de depreciación me dicen lo contrario.

Entonces, ¿hay una opción y quizás una forma fácil de mantener el status quo?

o

¿Qué me estaría perdiendo al optar por mantener un encabezado antiguo? No entiendo qué significa el nuevo. Veo configuraciones de grupo, la personalización para diferentes grupos es intrigante, pero ¿qué se puede personalizar?

Esto es lo que encontré hoy:

No soy un gurú ni un genio con estos cambios, llevan tiempo y no los hago con la frecuencia suficiente como para desear aprender las técnicas que los usuarios aquí parecen comprender/conocer fácilmente.

De alguna manera me molesta tener que hacerlos en primer lugar, pero antes de gritar como un viejo que se quedó sin pudín, me gustaría saber, ¿qué, por qué y hacia dónde va esto?

Me dedico a esto profesionalmente y aun así encuentro que estas cosas de JavaScript están lejos de ser fáciles.

Soy un anciano y entiendo tu dolor.

Es solo el progreso, me temo. Esta actualización de Ember rompió un montón de cosas y aún no ha terminado.

“Lo pediste” cuando hiciste esa personalización. Apuesto a que en los últimos cinco años has comprado un teléfono o portátil nuevo.

Si yo fuera tú (y tú fueras como yo, sin el trabajo a tiempo completo en Discourse), publicaría en Marketplace. Si fuera yo, probablemente no respondería por menos de $300, pero hay una posibilidad razonable de que alguien más lo haga por $100 o $200. Supongo que no se volverá a romper en otros 5 años o más.

Creo que puedes deshacerte del selector de temas de hamburguesa y usar la barra lateral.

3 Me gusta

Respuesta honesta y agradable, la aprecio pero no hay mucho con lo que trabajar, quizás haya más por venir (espero)

Ni siquiera sabía que estábamos tratando con Java aquí :man_shrugging:

Tampoco quiero que nadie te quite tu pudín :face_with_hand_over_mouth:

Claro, pero ¿cuál es el objetivo deseado? Este software se dedica a tantas cosas que me pregunto quién ve qué final.

¿Es esto simplemente necesario por la actualización de Ember?

Tampoco sé por qué se hizo Ember, pero si funciona, ¿por qué arreglarlo? Estoy seguro de que hay una explicación larga y profunda que conduce al futuro de las cosas, pero ¿no hay una visión real que compartir?

Visito otros foros que usan software muy antiguo, personalmente veo Discourse como mucho mejor que cualquiera de ellos, pero no parecen sufrir en comparación, tienen los mismos problemas de crecimiento, la mayoría son personalidad vs software en mi opinión, demasiados veteranos que perdieron su pudín, me pregunto, ¿hay un futuro IOT que hará que todos esos foros queden obsoletos donde no funcionarán en absoluto y Discourse lo sabe y se está preparando?

Hay más de esa honestidad que proporcionas :grin: es cierto, y yo estaba más ansioso por aprender, más ambicioso, sentí que valía la pena más, he sido maltratado, atropellado y dejado por muerto desde entonces.

ok, aceptas, acepto esa apuesta y como ya has perdido me ayudas con esto, seremos amigos.

Entonces probablemente te habrías rendido hace mucho tiempo, lo de ser maltratado, atropellado y dejado por muerto fue una descripción un tanto eufemística, solo quedo yo al volante, supongo que cualquiera que quede está en algún panel tratando de arreglar el hiperimpulsor, no lo sé ya que no me comunico a menudo con otros, no tenemos financiación, nosotros (FULL30) fuimos des-plataformados de las redes sociales y también de Discourse, me pregunto cuántos otros clientes de pago cortó Discourse voluntariamente o cuántos otros fueron considerados tan ofensivos en sus creencias que Discourse públicamente invirtió dinero en su contra.

Sin embargo, mientras digo la verdad, no me ofendo, vive y deja vivir, sé que se acerca un futuro, lo que no sé es por qué sigo aquí y sigo intentándolo, pero lo hago, así que seguiré intentándolo, como AA, solo por hoy :hugs:

Pero estaba de moda cuando lo implementé :expressionless:

La barra lateral (aquí) se puede cerrar con un menú de hamburguesa, no hay mucha diferencia en la función, abre y cierra una ventana de navegación, pero ¿la mía no se puede guardar fácilmente?

Sí, me encantaría y preferiría pagarle a alguien para que limpie mi código personalizado y haga que las cosas funcionen bien, y pagaría felizmente, disfruto empleando a otros, compartiendo la riqueza, cuando sea mayor quiero ser un filántropo, pero hoy necesito un filántropo :innocent: y nuevamente aprecio cualquier ayuda que otros puedan ofrecer.

La otra forma de jugarlo es pedir ayuda a tu comunidad, dejar de hacer lo que sea que fuera la personalización, iniciar un nuevo tema que comparta tu código y pedir ayuda. He recibido mucha ayuda en tales asuntos recientemente.

2 Me gusta

Desafortunadamente no. La capacidad de mantener la ‘cabecera antigua’ es solo algo temporal durante el período de transición. Pronto, la nueva cabecera será la única opción.

¡Sí! Siempre estamos contentos de ayudar con preguntas en Dev. Además, compartir el código y las soluciones públicamente crea un recurso útil para otros.

4 Me gusta

uf, mi comunidad está más en sintonía con otros problemas

Seguramente podría compartir aquí, pero entonces va en sentido contrario, ¿a qué programador le interesa ayudarnos?

La ironía, la programación bien podría ser el arma de fuego del futuro, bien podría causar mucha más muerte y destrucción, me desvío

Muy bien, ¿qué significa eso exactamente para mí, crear un grupo de usuarios, público y no registrado, quizás?

Estas configuraciones de grupo, percibo que se basan en niveles de confianza en lugar de grupos reales diferentes, ¿como un grupo de caza y un grupo de pesca?

Primero necesito entender el objetivo y dónde mis esfuerzos como lobo solitario tendrán el mayor impacto, ahorrando tiempo y conservando la sensación personalizada de mi foro.


No deseo descarrilar el hilo de nadie, si se considera que esto debería ser su propio hilo, estoy de acuerdo

pero, ¿cómo habrá alguna vez una relación verdaderamente cohesiva cuando la gente sienta la necesidad de eliminar algo que le ofende?

Se necesita paciencia para entender a los demás, se eliminó el enlace, mostró un logotipo faltante después pero no mientras se publicaba, ¿otro problema de cabecera a discutir?

Era una publicación en mi foro escrita por un hombre que creo que tiene más de 80 años, podría preguntarle con seguridad pero se niega a hablar conmigo, ¿lo reprendo, lo prohíbo o lo evito?

No, ¿por qué? Porque hay una mejor manera, pero significa tolerar a los demás y cómo piensan, encuentro gente buena en áreas malas, gente buena que parece mala, y justo lo contrario en ambos casos.

Exactamente, acabo de encontrar los errores, deseo abordarlos pero no entiendo la causa raíz, aparte de que el futuro avanza, necesitamos una nueva cabecera, está bien, ¿cuál es el curso correcto para fijar mi objetivo, una simple puesta a punto, una corrección completa del rumbo?

¿Estamos discutiendo solo la necesidad de trabajar en estas tres áreas?

Tengo un uso mixto de componentes, comencé sin ninguno y luego aprendí que pueden ser beneficiosos, nunca fui completamente de componentes y tengo una mezcla.

Aquí está mi tema por lo que vale la pena sin los componentes
discourse-full30-ii.zip (10,1 KB)
También puedo publicar esos, algunos, los modales, ya no funcionan recientemente