Próximas alterações no cabeçalho - preparando temas e 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 curtidas

Mas como eu defino um FooIcon?

Em um plugin, tentei criar /assets/javascripts/discourse/components/server-link.js (como para outros componentes que uso em um arquivo hbs)

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

export default Component.extend({
// algo tem que ir aqui?
});

E um assets/javascripts/discourse/templates/components/server-link.hbs com
isto é um link (acho que posso torná-lo um link se conseguir fazer este “Olá, mundo” funcionar)

O exemplo acima tem const IconWithDropdown = ..., mas onde isso iria? Tentei colocá-lo em um inicializador (onde estava o api.decorateWidget), mas não parece ser um JavaScript válido para mim ou para o Ember, pelo que pude apurar.

Antes, eu tinha um array headerlinks e faria

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

para adicionar os links que eu queria. Acho que se eu conseguir fazer

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

funcionar, então posso simplesmente colocá-lo no loop que construiu esse array.

1 curtida

OMG. Então os componentes Glimmer vão em assets/javascripts/discourse/component e os componentes Ember vão em assets/javascripts/discourse/components?!?!\n\nAgora tenho um server-link.gjs

import Component from "@ember/component";
export default class ServerLink extends Component {
  // Argumento obrigatório para a URL
  url = null;
  // Argumento opcional para o texto do link
  text = 'asdf';
  click() {
    console.log('ServerLink clicado!',this);

  }
  // Template para o componente
  <template>
    {{log "meu template" this}}
    LINK!
    <a href={{this.url}}>{{this.text}}</a>
  </template>
}

e no meu inicializador isto:

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

Agora tenho algo no cabeçalho.

Mas como passo coisas para ServerLink? Preciso chamá-lo várias vezes com URLs diferentes e textos para clicar diferentes. Não consigo ver as coisas naquele {} no componente.

E você realmente não quer colocar javascript antes do <template>, pois meu console.log("") não será analisado!

Também tentei fazer:

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

e depois passar x em vez de ServerLink, mas ainda assim sem sucesso.

2 curtidas

Você quer vários botões nos cabeçalhos com ícones/textos/URLs diferentes ou o mesmo botão, mas dependendo do contexto, o texto/URL pode mudar?

Sim, você está em uma classe — você declararia variáveis, funções ou modelos lá!

Sim. Os links mudam para usuários diferentes. O código antigo passava por um array de servers e os adicionava a este array:

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

E então fazia isso:

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

Então eu tinha até 3 links que eram adicionados ao cabeçalho, cada um linkando para uma URL de servidor separada.

Aha. Agora eu entendi.

Não, não se preocupe - a convenção ainda é /components/ :sweat_smile:

(Tecnicamente, você pode definir e passar componentes gjs como quiser, então pode escolher qualquer nome de diretório que preferir. Mas nós vamos manter /components/.)

Sim, essa é uma pergunta justa - vou trabalhar para escrever alguns documentos ‘do zero’ sobre como adicionar ícones ao cabeçalho para que tenhamos um ponto de referência melhor.

Enquanto isso, você pode gostar de dar uma olhada na atualização do discourse-icon-header-links para inspiração. A coisa legal sobre o uso de GJS é que você pode definir componentes em qualquer lugar, e eles têm acesso a variáveis no escopo local.

Então, se você renomear seu inicializador para .gjs, você pode fazer coisas como

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

Ou você pode definir um componente anteriormente no mesmo arquivo e usá-lo como

class ServerButton extends Component {
  get icon(){
    // alguma lógica para decidir o ícone
  }
  <li><DButton @translatedLabel={{@server.name}} @icon={{this.icon}} /></li>
}

...

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

Ou você poderia mover a iteração para dentro do template (útil se a lista de servidores for um TrackedArray que pode mudar em tempo de execução!)

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

Enquanto trabalhamos em exemplos de código mais detalhados

Ah. Que bom. Achei que tinha tentado em components e não tinha funcionado.

Obrigado! Acho que consigo fazer uma dessas coisas funcionar. O link para os links do cabeçalho é uma grande ajuda. Tenho certeza de que, quando escrevi meu código, tive bom senso para olhar para esse componente para descobrir.

4 curtidas

Vendo um raio de esperança!

Olá @david e @Arkshine! Consegui!

O q u e ? Apenas renomear? Isso é loucura. Mas, com certeza. Eu fiz isso, e agora

          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 });
          });

E está fazendo o que fazia antes, que era o que eu queria!

Sim, agora isso parece muito legal, e ainda mais o que eu quero, mas essas coisas não mudam muito, então vou parar por aqui. Se eles mudarem um nome de host, eles terão que recarregar a página para que o link mude.

Presumo que você excluirá minha “tralha” extra aqui quando atualizar as coisas acima (ou talvez eu não tivesse sido tão falante em um tópico de documentação…).

5 curtidas

Atualizei o OP com alguns exemplos completos de gjs e incluí um link para a documentação upstream do Ember. O que você acha, @pfaffman? Há mais alguma coisa que você acha que valeria a pena adicionar?

1 curtida

É melhor, pois tem um exemplo funcional. Mas eu entendi corretamente que existem componentes Ember e componentes Glimmer? E se isso estiver certo, você deveria dizer que um componente Glimmer é necessário, eu acho?

E talvez vincular à documentação do Glimmer sobre como eles funcionam?

Parece que você pode ter componentes inline como no seu exemplo, e outro tipo onde você o atribui a algo como uma variável no mesmo arquivo ou o coloca em outro arquivo que você coloca no diretório de componentes e depois inclui? Acho que tudo isso pode ser demais para este tópico, mas eu adoraria um tópico dedicado sobre isso.

2 curtidas

Eles são totalmente intercambiáveis - você pode usar um Componente Ember Clássico ou um Componente Glimmer. E para qualquer um deles, você pode optar por criá-los usando o formato antigo .js/.hbs ou o novo formato .gjs.

Vou ver se consigo incluir alguns links para a documentação do Ember :+1:

4 curtidas

:mega: Hoje mesclamos esta alteração, que habilitará automaticamente a nova implementação do cabeçalho para sites com temas/plugins compatíveis.

Se seus temas/plugins não forem compatíveis, o cabeçalho legado ainda será usado e um aviso será impresso no console junto com as mensagens de descontinuação existentes. Em um futuro próximo, este aviso do console será atualizado para um banner de aviso na interface do usuário.

No improvável caso de essa alteração automática causar problemas, você pode substituir temporariamente este ‘sinalizador de recurso automático’ através da configuração do site glimmer header mode. Se fizer isso, por favor, informe-nos o motivo neste tópico.

3 curtidas

Eu não estava procurando fazer nenhuma alteração, mas os avisos de depreciação me dizem o contrário,

Então há uma escolha e talvez uma maneira fácil de apenas manter o status quo?

ou

O que eu estaria perdendo ao optar por tentar manter um cabeçalho antigo, não entendo o que o novo significa, vejo configurações de grupo, personalização para diferentes grupos é intrigante, mas o que pode ser personalizado?

Isso é o que encontrei hoje,

Eu não sou um guru ou gênio com essas mudanças, elas levam tempo e eu não as faço com frequência suficiente para realmente desejar aprender as técnicas que os usuários aqui parecem facilmente compreender/saber

Eu meio que me ressinto de ter que fazê-las em primeiro lugar, mas antes que eu grite como um velho que ficou sem pudim, eu gostaria de saber, o quê, por quê e para onde isso está indo?

Eu faço isso como profissão e ainda acho esse negócio de JavaScript muito longe de ser fácil.

Eu sou um velho e sinto sua dor.

É apenas progresso, receio. Essa atualização do Ember quebrou um monte de coisas e ainda não acabou.

Você “pediu por isso” quando fez essa personalização. Aposto que nos últimos cinco anos você comprou um telefone ou laptop novo.

Se eu fosse você (e você fosse como eu, sem o trabalho em tempo integral no Discourse), eu postaria em Marketplace. Se eu fosse eu, provavelmente não responderia por menos de US$ 300, mas há uma chance razoável de que outra pessoa o faça por US$ 100 ou US$ 200. Eu estimaria que não quebrará novamente por mais 5 anos ou mais.

Acho que o seletor de tema de hambúrguer pode ser removido e você pode usar a barra lateral.

3 curtidas

Resposta honesta e agradável, aprecio, mas não há muito com o que trabalhar, talvez haja mais por vir (espero)

Eu nem sabia que estávamos lidando com Java aqui :man_shrugging:

Não quero que ninguém pegue seu pudim também :face_with_hand_over_mouth:

Claro, mas qual é o objetivo desejado? Este software mexe com tantas coisas que me pergunto quem vê o quê no final?

Isso é simplesmente necessário pela atualização do Ember?

Eu também não sei por que o Ember foi feito, mas se funciona, por que consertar? Tenho certeza de que há uma explicação longa e profunda que leva ao futuro das coisas, mas não há uma visão verdadeira a ser compartilhada?

Eu visito outros fóruns que usam software muito antigo. Pessoalmente, vejo o Discourse como muito melhor do que qualquer um deles, mas eles não parecem sofrer em comparação, eles têm os mesmos problemas de crescimento, a maioria é personalidade vs software, na minha opinião, muitos veteranos que perderam seu pudim. Estou me perguntando, existe um futuro de IOT que tornará literalmente todos esses fóruns obsoletos, onde eles não funcionarão mais e o Discourse está ciente e se preparando?

Há mais dessa honestidade que você oferece :grin: Verdade, e eu estava mais ansioso para aprender, mais ambicioso, senti o que valia mais, fui espancado, atropelado e deixado para morrer desde então.

Ok, você está certo, aceito essa aposta e, como você já perdeu, me ajuda com isso, seremos amigos.

Então você provavelmente teria desistido há muito tempo, o espancado, atropelado e deixado para morrer foi uma descrição um tanto eufemística, sou só eu que restou ao volante, acho que qualquer outra pessoa está em algum painel tentando consertar o hiperdrive, sei lá, pois não me comunico muito com os outros, não temos financiamento, nós (FULL30) fomos desativados das mídias sociais e do Discourse também, me pergunto quantos outros clientes pagantes o Discourse cortou voluntariamente ou quantos outros foram considerados tão ofensivos em suas crenças que o Discourse publicamente investiu dinheiro contra eles?

No entanto, enquanto falo a verdade, não me ofendo, viva e deixe viver, sei que um futuro está chegando, o que não sei é por que ainda estou aqui e ainda tentando, mas estou, então, continuarei tentando, como AA, apenas por hoje :hugs:

Mas estava na moda quando o usei :expressionless:

A barra lateral (aqui) pode ser fechada com um menu “hamburger”, não há muita diferença na função, ela abre e fecha uma janela de navegação, mas a minha não pode ser salva facilmente?

Sim, eu adoraria e preferiria pagar alguém para limpar meu código personalizado e fazer as coisas funcionarem bem, e eu pagaria feliz, gosto de empregar outras pessoas, compartilhar a riqueza, quando eu crescer quero ser um filantropo, mas hoje preciso de um filantropo :innocent: e novamente aprecio qualquer ajuda que outros possam oferecer.

A outra forma de jogar é pedir ajuda à sua comunidade, parar de fazer o que quer que seja a personalização, iniciar um novo tópico que compartilhe seu código e pedir ajuda. Tenho recebido muita ajuda nesses assuntos recentemente.

2 curtidas

Infelizmente não. A capacidade de manter o ‘cabeçalho antigo’ é apenas uma coisa temporária durante o período de transição. Em breve, o novo cabeçalho será a única opção.

Sim! Estamos sempre felizes em ajudar com perguntas em Dev. Além disso, compartilhar o código e as soluções publicamente cria um recurso útil para outras pessoas.

4 curtidas

Ufa, minha comunidade está mais sintonizada com outras questões

Certamente eu poderia compartilhar aqui, mas então vai para o lado oposto, que programador se interessa em nos ajudar?

A ironia, programar pode muito bem ser as armas de fogo do futuro, pode muito bem causar muito mais morte e destruição também, divago

Muito bem, isso significa o quê exatamente para mim, criar um grupo de usuários, público e não logado, talvez?

Essas configurações de grupo, percebo que são baseadas em níveis de confiança versus grupos realmente diferentes, como um grupo de caça e um grupo de pesca?

Primeiro, preciso entender o objetivo e onde meus esforços como lobo solitário terão o maior impacto, economizando tempo e preservando a sensação personalizada do meu fórum.


Não quero descarrilar o tópico de ninguém, se for considerado que isso deveria ser um tópico próprio, tudo bem

mas como haverá um relacionamento verdadeiramente coeso quando as pessoas sentem a necessidade de remover algo que as ofende?

É preciso paciência para entender os outros, o link removido, mostrou um logo faltando depois, mas não enquanto postava, outro problema de cabeçalho para discutir?

Foi uma postagem no meu fórum escrita por um homem que acredito ter mais de 80 anos, eu poderia perguntar com certeza, mas ele se recusa a falar comigo, devo repreendê-lo, bani-lo ou evitá-lo?

Não, por quê, porque há um jeito melhor, mas isso significa aturar os outros e como eles pensam, encontro pessoas boas em lugares ruins, pessoas boas que parecem ruins, e exatamente o oposto em ambos.

Exatamente, acabei de encontrar os erros, desejo corrigi-los, mas não entendo a causa raiz, exceto que o futuro está avançando, precisamos de um novo cabeçalho, ok, tudo bem, qual é o curso correto para definir minhas metas, ajuste simples, uma correção de curso completa?

Estamos discutindo apenas a necessidade de trabalhar nessas três áreas?

Tenho uso misto de componentes, comecei sem nenhum e depois aprendi que eles podem ser benéficos, nunca usei componentes totalmente e tenho uma mistura.

Aqui está meu tema, pelo que vale, sem os componentes
discourse-full30-ii.zip (10,1 KB)
Posso postar esses também, alguns, os modais, já não estão funcionando recentemente