使用来自主题或插件的 Plugin Outlet Connectors

Discourse 包含数百个插件插槽(Plugin Outlet),可用于将新内容注入 Discourse UI 或替换现有内容。“插槽参数”(Outlet arguments)可供使用,以便可以根据上下文定制内容。

选择插槽

要查找插件插槽的名称,请在 Discourse 核心代码中搜索 “<!--PluginOutlet”,或使用 插件插槽位置 主题组件。(例如 topic-above-posts)。

包装插槽

核心中的一些插槽看起来像 <!--PluginOutlet @name="foo" /-->。这些允许您注入新内容。其他插槽将“包装”现有的核心实现,如下所示:

<!--PluginOutlet @name="foo"-->
  核心实现
<!--/PluginOutlet-->

为这类“包装”插槽定义一个连接器(connector)将替换核心实现。只有一个活动的 theme/plugin 可以为一个包装插件插槽贡献一个连接器。

对于包装插件插槽,您可以使用 {{yield}} 关键字来渲染原始的核心实现。如果您只希望在特定条件下替换核心实现,或者希望将其包装在某些内容中,这会很有帮助。

定义模板

选择插槽后,决定连接器的名称。这在给定社区安装的所有主题/插件中必须是唯一的。例如 brand-official-topics

在您的主题/插件中,定义一个新的 Handlebars 模板,其路径格式如下:

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

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

这些文件的内容将被渲染为一个 Ember 组件。有关 Ember/Handlebars 的一般信息,请参阅 Ember 指南

对于我们假设的“品牌官方话题”连接器,模板可能如下所示:

<div class="alert alert-info">
  此话题由
  <a href="https://discourse.org/team">Discourse 团队</a>
  的成员创建
</div>

一些插件插槽会自动将您的内容包装在 HTML 元素中。元素类型由 <!--PluginOutlet--> 上的 @connectorTagName 定义。

使用插槽参数

插件插槽通过 @outletArgs 提供有关周围上下文的信息。传递给每个插槽的参数各不相同。查看参数的简单方法是将此添加到您的模板中:

{{log @outletArgs}}

这将把参数记录到您浏览器的开发者控制台。它们将显示为一个 Proxy 对象——要查看参数列表,请展开代理的 [[Target]]

在我们的 topic-above-posts 示例中,渲染的主题可在 @outletArgs.model 下找到。因此,我们可以像这样添加团队成员的用户名:

<div class="alert alert-info">
  此话题由
  {{@outletArgs.model.details.created_by.username}}
  (<a href="https://discourse.org/team">Discourse 团队</a>的成员)创建
</div>

添加更复杂的逻辑

有时,简单的 Handlebars 模板是不够的。要向连接器添加 Javascript 逻辑,您可以定义一个与 Handlebars 模板相邻的 Javascript 文件。该文件应导出一个组件定义。这与任何其他组件定义的功能相同,可以包括服务注入。

以这种方式定义组件将移除自动的 connectorTagName 包装元素,因此您可能希望在 hbs 文件中重新引入相同类型的元素。

在我们的 topic-above-posts 示例中,我们可能希望根据“在用户体验中优先显示用户名”站点设置以不同方式渲染用户。该组件定义可能如下所示:

.../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;
    }
  }
}

然后我们可以更新模板以引用新的 getter:

<div class="alert alert-info">
  此话题由
  {{this.displayName}}
  (<a href="https://discourse.org/team">Discourse 团队</a>的成员)创建
</div>

条件渲染

如果您只希望在特定条件下渲染内容,通常只需用 Handlebars 的 {{#if}} 块包装模板即可。如果这还不够,您可能希望使用 shouldRender 钩子来控制是否完全渲染连接器模板。

首先,确保您有一个如上所述的 .js 连接器定义。然后,添加一个静态的 static shouldRender() 函数。扩展我们的示例:

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";
  }
  // ... (任何其他逻辑)
}

现在,只有当话题的第一篇帖子是由团队成员创建时,连接器才会被渲染。

shouldRender 在 Glimmer 自动跟踪(autotracking)上下文中进行评估。对任何引用的属性(例如 outletArgs)的未来更改将导致函数重新评估。

引入新插槽

如果您需要一个尚不存在的插槽,请随时提交 pull request,或在 Dev 频道中开一个话题。


本文档是版本控制的——请在 github 上建议更改。

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

现在已经被弃用了,对吧?
弃用通知:通过 registerConnectorClass 定义连接器类已被弃用。请参阅 https://meta.discourse.org/t/32727 以了解更现代的模式。[deprecation id: discourse.register-connector-class-legacy]

没错;你可以改用 api.renderInOutlet:slight_smile:

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

1 个赞

我认为这比那要复杂一点:sweat_smile:
https://github.com/Firepup6500/discourse-custom-profile-link/blob/master/common/head_tag.html

别担心;我稍后会尽力帮助您(我现在需要睡觉)。 :smile:

1 个赞

谢谢! (抱歉代码一团糟,上次修改时我只想让它能工作 :sweat_smile:

1 个赞

我有点力不从心,我的主题 HEAD 文件中使用的组件收到了一个通知。我不确定如何用 api.renderInOutlet 重写。\n\n\n\n const ajax = require('discourse/lib/ajax').ajax;\n const Topic = require('discourse/models/topic').default;\n // 我们正在使用 Discourse 的 ajax 和 Topic 模型\n\n api.registerConnectorClass('above-main-container', 'featured-topics', {\n // above-main-container 是插件出口,\n // featured-topics 是你的自定义组件名称\n\n setupComponent(args, component) {\n\n // 剩余代码如下\n\n我猜我尝试用 api.renderInOutlet 替换 api.registerConnectorClass,但它出错了。我在这方面不是主题编码专家。感谢任何帮助。

您可以在此处看到一个示例:

StatBanner 是在 components 目录中定义的本地类:

在您的情况下,那将是 api.renderInOutlet("above-main-container", YourClass)

我不认为您可以在 HEAD 文件中执行此操作。您应该将代码拆分成多个文件

我鼓励您使用Discourse Theme CLI,因为它将使开发 Theme component 更加容易!

您的 Theme Component 是公开的吗?

3 个赞

我的 .js 文件中有什么方法可以获取当前的 outlet 吗?

我认为那是不可能的。

以前在经典组件中可以检查 parentView

但是该属性已被弃用。

1 个赞

您说的“获取当前插座”是什么意思?您想要插座的名称吗?还是其他什么?

我在我的问题 (这里) 中找到了一个解决方案,它大致是这样的:

  • 为 3 个不同的出口创建 .hbs.js 文件。
  • 在每个 JS 文件中,检查它正在使用的出口是否是设置 banner_location 的值。
  • 如果是,则显示横幅。如果不是,则隐藏横幅。
1 个赞

太棒了!看起来你决定使用 api.renderInOutlet,并为 outlet 名称使用了动态值 :chefs_kiss:

1 个赞