作为我们持续改进 Discourse 代码库工作的一部分,我们正在移除对旧版“widget”渲染系统的使用,并将其替换为 Glimmer 组件。
最近,我们 对帖子菜单进行了现代化改造,目前该功能已在 Discourse 中通过 glimmer_post_menu_mode 设置提供。
此设置接受三个可能的值:
disabled:使用旧版“widget”系统auto:将检测您当前插件和主题的兼容性。如果有任何不兼容项,它将使用旧版系统;否则将使用新菜单。enabled:将使用新菜单。如果您有任何不兼容的插件或主题,您的站点可能会出错。
我们已经更新了官方插件以兼容新菜单,但如果您仍有与新版菜单不兼容的第三方插件、主题或主题组件,则必须对其进行升级。
浏览器控制台中将打印警告信息,指出不兼容性的来源。
推出时间表
这些是粗略估计,可能会发生变化
2024 年第四季度:
核心实现已完成
官方插件已更新
已在 Meta 站点上启用
glimmer_post_menu_mode默认值为auto;已启用控制台弃用消息
已发布升级建议
2025 年第一季度:
第三方插件和主题应已完成更新
弃用消息开始生效,任何剩余问题都将触发管理员警告横幅
默认启用新帖子菜单
2025 年第二季度
4 月 1 日 - 移除功能标志设置和旧代码
这对您意味着什么?
如果您的插件或主题使用任何“widget”API 来自定义帖子菜单,则需要进行更新以兼容新版本。
我如何尝试新帖子菜单?
在最新版本的 Discourse 中,如果您没有安装不兼容的插件或主题,新帖子菜单将自动启用。
如果您已安装了不兼容的扩展,作为管理员,您仍然可以将设置更改为 enabled 以强制使用新菜单。请谨慎使用此功能,因为根据您安装的自定义内容,您的站点可能会出错。
如果这种自动系统未按预期工作(这种情况很少见),您可以使用上述设置暂时覆盖此“自动功能标志”。如果您需要这样做,请在此主题中告知我们。
我需要更新我的插件和主题吗?
如果您的插件或主题执行了以下任何自定义操作,则需要进行更新:
-
在这些 widget 上使用
decorateWidget、changeWidgetSetting、reopenWidget或attachWidgetAction:post-menupost-user-tip-shimsmall-user-list
-
使用以下任何 API 方法:
addPostMenuButtonremovePostMenuButtonreplacePostMenuButton
如果您有执行上述自定义操作的扩展,当您访问主题页面时,控制台中将打印警告信息,指出需要升级的插件或组件。
弃用 ID 为:
discourse.post-menu-widget-overrides
如果您在实例中使用了多个主题,请务必检查所有主题,因为警告信息仅针对当前启用的插件以及正在使用的主题和主题组件打印。
替代方案是什么?
我们引入了值转换器 post-menu-buttons 作为自定义帖子菜单的新 API。
该值转换器提供一个 DAG 对象,允许添加、替换、删除或重新排序按钮。它还提供上下文信息,例如与菜单关联的帖子、正在显示的帖子状态以及按钮键,以便更轻松地放置项目。
DAG API 期望接收 Ember 组件,如果 API 需要新的按钮定义(如 .add 和 .replace)。
每个自定义操作都不同,但以下是针对最常见用例的一些指导:
addPostMenuButton
之前:
withPluginApi("1.34.0", (api) => {
api.addPostMenuButton("solved", (attrs) => {
if (attrs.can_accept_answer) {
const isOp = currentUser?.id === attrs.topicCreatedById;
return {
action: "acceptAnswer",
icon: "far-check-square",
className: "unaccepted",
title: "solved.accept_answer",
label: isOp ? "solved.solution" : null,
position: attrs.topic_accepted_answer ? "second-last-hidden" : "first",
};
}
});
});
之后:
以下示例使用 Ember 的 模板标签格式 (gjs)
// components/solved-accept-answer-button.gjs
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { inject as service } from "@ember/service";
import DButton from "discourse/components/d_button";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
export default class SolvedAcceptAnswerButton extends Component {
// 指示按钮是直接显示还是隐藏在“显示更多”按钮之后
static hidden(args) {
return args.post.topic_accepted_answer;
}
...
<template>
<DButton
class="post-action-menu__solved-unaccepted unaccepted"
...attributes
@action={{this.acceptAnswer}}
@icon="far-check-square"
@label={{if this.showLabel "solved.solution"}}
@title="solved.accept_answer"
/>
</template>
}
// initializer.js
import SolvedAcceptAnswerButton from "../components/solved-accept-answer-button";
...
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"post-menu-buttons",
({
value: dag,
context: {
post,
firstButtonKey, // 第一个按钮的键
secondLastHiddenButtonKey, // 倒数第二个隐藏按钮的键
lastHiddenButtonKey, // 最后一个隐藏按钮的键
},
}) => {
dag.add(
"solved",
SolvedAcceptAnswerButton,
post.topic_accepted_answer
? {
before: lastHiddenButtonKey,
after: secondLastHiddenButtonKey,
}
: {
before: [
"assign", // 由 assign 插件添加的按钮
firstButtonKey,
],
}
);
}
);
});
为按钮设置样式
建议在您的组件中包含
...attributes,如上例所示。结合使用组件
DButton或DMenu时,这将处理样板类,并确保您的按钮遵循帖子菜单中其他按钮的格式。可以使用自定义类指定额外的格式。
replacePostMenuButton
- 之前:
withPluginApi("1.34.0", (api) => {
api.replacePostMenuButton("like", {
name: "discourse-reactions-actions",
buildAttrs: (widget) => {
return { post: widget.findAncestorModel() };
},
shouldRender: (widget) => {
const post = widget.findAncestorModel();
return post && !post.deleted_at;
},
});
});
- 之后:
import ReactionsActionButton from "../components/discourse-reactions-actions-button";
...
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"post-menu-buttons",
({ value: dag, context: { buttonKeys } }) => {
// ReactionsActionButton 是新的按钮组件
dag.replace(buttonKeys.LIKE, ReactionsActionButton);
}
);
});
removePostMenuButton
- 之前:
withPluginApi("1.34.0", (api) => {
api.removePostMenuButton('like', (attrs, state, siteSettings, settings, currentUser) => {
if (attrs.post_number === 1) {
return true;
}
});
});
- 之后:
withPluginApi("1.34.0", (api) => {
api.registerValueTransformer(
"post-menu-buttons",
({ value: dag, context: { post, buttonKeys } }) => {
if (post.post_number === 1) {
dag.delete(buttonKeys.LIKE);
}
}
);
});
其他自定义操作怎么办?
如果您的自定义操作无法使用我们引入的新 API 实现,请创建一个新的开发主题进行讨论。
我是插件/主题作者。如何在过渡期间更新主题/插件以同时支持旧版和新版帖子菜单?
我们在插件中使用了以下模式来同时支持旧版和新版帖子菜单:
function customizePostMenu(api) {
const transformerRegistered = api.registerValueTransformer(
"post-menu-buttons",
({ value: dag, context }) => {
// 新帖子菜单自定义操作
...
}
);
const silencedKey =
transformerRegistered && "discourse.post-menu-widget-overrides";
withSilencedDeprecations(silencedKey, () => customizeWidgetPostMenu(api));
}
function customizeWidgetPostMenu(api) {
// 旧版“widget”代码自定义操作放在这里
...
}
export default {
name: "my-plugin",
initialize(container) {
withPluginApi("1.34.0", customizePostMenu);
}
};
更多示例
您可以查看我们的官方插件,了解如何使用新 API 的示例: