即将到来的标题更改 - 准备主题和插件

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 个赞

但是如何定义 FooIcon

在插件中,我尝试创建 /assets/javascripts/discourse/components/server-link.js(就像我在 hbs 文件中使用的其他组件一样)

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

export default Component.extend({
// 这里是否必须放些什么?
});

以及一个 assets/javascripts/discourse/templates/components/server-link.hbs 文件,内容为
this is a link(我想如果我能让这个“Hello, world”正常工作,我就可以把它变成一个链接)

上面的示例中有 const IconWithDropdown = ...,但它应该放在哪里?我尝试将其放在初始化程序中(api.decorateWidget 之前的位置),但据我所知,它看起来不像有效的 JavaScript 或 Ember 代码。

之前我有一个 headerlinks 数组,然后会执行

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

来添加我想要的链接。我认为如果我能让

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

工作,那么我就可以把它放在构建该数组的循环中。

1 个赞

OMG。Glimmer 组件放在 assets/javascripts/discourse/component,Ember 组件放在 assets/javascripts/discourse/components?!?!\n\n我现在有一个 server-link.gjs\n\nimport Component from "@ember/component";\nexport default class ServerLink extends Component {\n // URL 所需的参数\n url = null;\n // 链接文本的可选参数\n text = 'asdf';\n click() {\n console.log('ServerLink clicked!',this);\n\n }\n // 组件的模板\n <template>\n {{log "my template" this}}\n LINK!\n <a href={{this.url}}>{{this.text}}</a>\n </template>\n}\n\n\n并在我的初始化器中是这样的:\n\n api.headerIcons.add("foo", ServerLink, { param: "url, yo", before: "search" });\n\n\n现在我的标题栏里有东西了。\n\n但是如何将内容传递给 ServerLink?我需要用不同的 URL 和不同的点击文本调用它几次。我似乎看不到 {} 中的内容在组件里。\n\n而且你不想在 \u003ctemplate\u003e 之前放入 JavaScript,因为我的 console.log(\"\") 将无法解析!\n\n我也尝试过这样做:\n\n const x = new ServerLink({\n url: "mylink",\n text: "my-text",\n name: 'Bob',\n message: 'Generated from JavaScript',\n });\n \n然后传递 x 而不是 ServerLink,但仍然无效。

2 个赞

您的意思是您想要在标题中有多个具有不同图标/文本/URL 的按钮,还是同一个按钮,但根据上下文,文本/URL 可以更改?

是的,您在一个类中——您可以在那里声明变量、函数或模板!

是的。链接因用户而异。旧代码通过 servers 数组链接,并将它们推送到此数组:

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

然后执行此操作:

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

因此,我最多添加了 3 个链接到标题,每个链接指向一个单独的服务器 URL。

啊哈。我现在明白了。

不,别担心——约定仍然是 /components/ :sweat_smile:

(严格来说,你可以定义和传递 gjs 组件,随心所欲,所以你可以选择任何你喜欢的目录名称。但我们坚持使用 /components/。)

是的,这是个好问题——我将着手撰写一些关于如何向标题添加图标的“从头开始”的文档,以便我们有一个更好的参考点。

不过在此期间,你可能会喜欢看看 discourse-icon-header-links 的更新以获取灵感。使用 GJS 的好处是你可以将组件定义在任何地方,并且它们可以访问局部作用域中的变量。

所以,如果你将你的初始化器重命名为 .gjs,你可以这样做:

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

或者你可以在同一个文件中更早地定义一个组件,然后像这样使用它:

class ServerButton extends Component {
  get icon(){
    // 决定图标的一些逻辑
  }
  
    <li><DButton @translatedLabel={{@server.name}} @icon={{this.icon}} /></li>
  
}


...

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

或者你可以将迭代移到模板内部(如果服务器列表是可能在运行时更改的 TrackedArray,这很有用!)

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

在我们编写更详细的示例代码时

哦。太好了。我以为我在 components 里试过了,但没成功。

谢谢!我想我能搞定其中一个。指向 header links 的链接很有帮助。我很确定当我写代码的时候,我足够聪明地查看了那个组件来弄清楚。

4 个赞

看到一丝希望!

@david@Arkshine!我做到了!

什~~~~~~~~~~~~~~~么?只是重命名一下?这太疯狂了。但事实确实如此。我做到了,现在

          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 = 
              
                
                  
                    {{host}}
                  
                
              ;
            const beforeIcon = ["chat", "search", "hamburger", "user-menu"];
            api.headerIcons.add(host, serverLink, { before: beforeIcon });
          });

而且它正在做它以前做的事情,这正是我想要的!

是的,现在这听起来很酷,而且更符合我的想法,但那些东西变化不大,所以我今天就到此为止了。如果他们更改主机名,他们将不得不重新加载页面才能看到链接更改。

我猜当你更新上面的内容时,你会删除我这里多余的东西(或者我可能不会在一个文档主题里这么健谈……)。

5 个赞

我已经更新了 OP,其中包含一些完整的 gjs 示例,并包含指向 Ember 上游文档的链接。你觉得怎么样,@pfaffman?你觉得还有什么值得添加的吗?

1 个赞

自从有了可用的示例后,情况有所改善。但我是否正确理解了,有 Ember 组件和 Glimmer 组件?如果正确,我认为您应该说明需要 Glimmer 组件?

也许可以链接到关于 Glimmer 组件如何工作的文档?

似乎您可以拥有内联组件,就像在您的示例中一样,还有另一种类型,您将其分配给同一文件中的变量,或者将其放入 components 目录中的另一个文件中然后包含?我认为所有这些对于这个主题来说可能都太多了,但我很乐意有一个专门讨论这个的主题。

2 个赞

它们完全可以互换——你可以使用经典的 Ember 组件或 Glimmer 组件。对于这两种组件,你可以选择使用旧式 .js/.hbs 格式或新式 .gjs 格式来编写它们。

我会看看是否能加入一些指向 Ember 文档的链接 :+1:

4 个赞

:mega: 今天我们合并了此更改,它将自动为具有兼容主题/插件的站点启用新的标题实现。

如果您的主题/插件不兼容,则仍将使用旧的标题,并且会在控制台中打印警告以及现有的弃用消息。在不久的将来,此控制台警告将升级为 UI 中的警告横幅。

万一此自动更改导致问题,您可以通过 glimmer header mode 站点设置临时覆盖此“自动功能标志”。如果您这样做,请在此主题中告知我们原因。

3 个赞

我本来不想做任何更改,但折旧通知告诉我情况并非如此,

所以有一个选择,也许有一个简单的方法来维持现状?

或者

选择尝试并维护旧标题我会错过什么?我不明白新标题是什么意思。我看到群组设置,为不同群组定制很有趣,但可以定制什么?

这就是我今天发现的,

我不是这些更改的专家或大师,它们需要时间,而且我不会经常做,以至于真正想学习这里用户似乎轻易理解/知道的技巧。

我有点不愿意首先必须做这些更改,但在我像一个用完了布丁的老人一样尖叫之前,我想知道,是什么,为什么,以及这将走向何方?

我以此为生,即便如此,我仍然觉得这些 JavaScript 的东西远非易事。

我是一个老人,我理解你的痛苦。

恐怕这就是进步。这次 Ember 升级破坏了很多东西,而且还没有结束。

当你进行那项定制时,你就“自找的”。我敢打赌,在过去的五年里,你肯定换了新手机或新笔记本电脑。

如果我是你(而你又像我一样,没有全职的 discourse 工作),我会发帖到 #marketplace。如果是我,我可能不会低于 300 美元的回应,但其他人很可能会以 100 或 200 美元的价格回应。我猜它在未来五年或更长时间内不会再出问题了。

我认为你可以去掉那个汉堡包主题选择器,然后使用侧边栏。

3 个赞

很好的坦诚回复,我很感激,但没什么可操作的,也许将来会有更多(我希望)

我甚至不知道我们这里处理的是 Java :man_shrugging:

也不想别人抢你的布丁 :face_with_hand_over_mouth:

当然,但期望的目标是什么?这个软件涉猎如此之多,我想知道谁看到了最终结果?

这仅仅是 ember 升级所必需的吗?

我也不知道为什么 ember 要这样做,但如果它能正常工作,为什么还要修复它?我相信有一个漫长而深刻的解释,最终都指向事物的未来,但难道没有一个真正的愿景可以分享吗?

我访问过其他使用非常老旧软件的论坛,我个人认为 discourse 比它们任何一个都好,但它们似乎并没有因此而落后,它们也面临着同样的增长问题,在我看来,大多数是个人 vs 软件的问题,太多老家伙失去了他们的布丁,我想知道,是否有未来的物联网将使所有这些论坛变得过时,以至于它们根本无法工作,而 discourse 意识到了这一点并正在准备?

你提供了更多坦诚的话 :grin: 确实如此,而且我更渴望学习,更有野心,感觉更有价值,从那以后我一直被击垮、碾压、抛弃,生死未卜

好吧,你说了算,我接受这个赌注,既然你已经输了,你就帮我解决这个问题,我们就成为朋友。

那么你可能早就放弃了,被击垮、碾压、抛弃在路边是一种委婉的说法,现在只剩下我一个人在掌舵了,我想其他人可能在某个控制面板上试图修复超光速引擎,我不知道,因为我很少与别人交流,我们没有资金,我们(FULL30)也被社交媒体和 discourse 排斥了,我想知道 discourse 愿意解雇多少付费客户,或者有多少客户因为他们的信仰被认为太冒犯而被 discourse 公开反对?

然而,虽然我说的是实话,但我并不介意,随遇而安,我知道未来即将来临,我不知道的是我为什么还在这里并且还在努力,但我还在努力,就像匿名戒酒会一样,只为今天 :hugs:

但当我使用它的时候,它可是风靡一时 :expressionless:

侧边栏(在这里)可以用汉堡菜单关闭,功能上没有太大区别,它打开和关闭一个导航窗口,但我的无法轻松保存?

是的,我很乐意并希望有人能清理我的自定义代码,让事情顺利进行,我也很乐意付费,我喜欢雇佣别人,分享财富,等我长大了想成为一个慈善家,但今天我需要一个慈善家 :innocent: 再次感谢任何人能提供的帮助。

另一种方法是向您的社区寻求帮助,停止进行任何自定义,创建一个分享您的代码的新主题,并寻求帮助。我最近在这方面得到了很多帮助。

2 个赞

不幸的是,不行。“旧标题”只是过渡期的一个临时选项。很快,新标题将是唯一可用的选项。

是的!我们很乐意在 Dev 中解答你的问题。此外,公开分享代码和解决方案可以为他人创造有用的资源。

4 个赞

哎呀,我的社区更关注其他问题

我当然可以在这里分享,但结果适得其反,什么样的程序员会对帮助我们感兴趣?

具有讽刺意味的是,编码可能是未来的武器,可能会造成更多的死亡和破坏,我离题了

很好,这对我的意义是什么,创建一个用户组,公开且未登录?

这些组设置,我认为它们基于信任级别而不是实际的不同组,比如狩猎组和钓鱼组?

首先我需要了解目标,以及我作为一个独行侠的努力将在哪里产生最大的效益,节省时间和保持论坛的定制感觉。


我不想打断任何人的帖子,如果认为这应该是一个单独的帖子,我没意见

但当人们觉得有必要删除冒犯他们的东西时,如何才能建立真正和谐的关系?

理解他人需要耐心,删除的链接显示的是一个缺失的标志,但发帖时没有,这是另一个标题问题要讨论吗?

这是我论坛上的一篇文章,作者是一位我认为已经80多岁的人,我可以确定地问,但他拒绝和我说话,我应该责骂他、封禁他还是疏远他?

不,为什么,因为有更好的方法,但这需要容忍别人以及他们的想法,我在糟糕的地方找到好人,看起来糟糕的好人,以及反之亦然。

没错,我刚发现错误,想解决它们,但除了未来在前进之外,我不明白根本原因,我们需要一个新的标题,好的,没问题,正确的方向是什么,简单的调整,还是全面的纠正?

我们是在讨论只需要处理这三个区域吗?

我混合了组件使用,开始时没有,然后发现它们可能是有益的,我从未完全使用组件,而是混合使用。

这是我的主题,不含组件,但值得参考
discourse-full30-ii.zip (10.1 KB)
我也可以发布那些,一些,比如模态框,最近已经不起作用了