如果你想在特定页面上添加内容,最佳选择是使用 plugin-outlet(插件插槽)。简而言之,plugin-outlet 是 Discourse 模板中预留的空间,你可以利用它们来添加新内容。
你需要做的第一件事是确认目标页面上是否存在 plugin-outlet。有一个主题组件可以帮你完成这项工作。
安装该组件后,将其启用,然后前往你想要目标化的页面,检查有哪些可用的插槽。在你的案例中,确实存在这样一个 plugin-outlet(已用绿色高亮显示)
所以,我们需要的是 above-user-profile。
假设它不存在……那该怎么办?在这种情况下,最佳选择是请求添加它,或者提交 PR 将其添加到核心代码中。如果你的使用场景合理,大多数情况下都会被接受。
无论如何,正如我之前所说,在这种情况下它已经存在了。那么,让我们看看如何向其中添加标记。对于接下来的步骤,你不再需要上面的组件,既然你已经知道了插件插槽的名称,现在可以禁用它了。
你只需要在主题的“Header(头部)”选项卡中添加类似以下内容:
<script type="text/x-handlebars" data-template-name="/connectors/OUTLET_NAME/SOME_NAME">
Your markup goes here...
</script>
你需要将 OUTLET_NAME 更改为你想要目标化的插槽名称。然后将 SOME_NAME 更改为你想为此自定义功能起的名称。名称可以是任何内容,但如果可能的话,请尽量具有描述性。这是一种良好的实践。因此,我们最终得到以下内容:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
Your markup goes here...
<h1>Hello World!!</h1>
</script>
让我们试试这个,看看会发生什么……请记住,上面的代码片段应放在你主题的 common > header 选项卡中。
然后……
到目前为止一切顺利,但让我们深入探究一下。
你并不希望视频在所有个人资料页面上显示,而是希望仅根据某些条件显示。那么,该怎么做呢?你需要两样东西:一些要消费的数据和一点 JavaScript 代码。
让我们先找到数据。还记得我说过 plugin-outlet 是预留空间吗?如果没有上下文,保留它们有什么意义?这就是为什么 Discourse 会将相关的上下文信息传递给每个 plugin-outlet……但首先,让我们退一步。当你添加这段代码时:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
Your markup goes here...
<h1>Hello World!!</h1>
</script>
它看起来像 HTML——script 标签确实是——但标签内部的内容被视为 Handlebars 代码。
这意味着你可以这样做:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
{{log this}}
</script>
然后检查浏览器控制台。每当插槽被渲染时(即当你位于用户页面时),你都会看到以下内容:
现在,这些信息有帮助吗?是的……但目前还不行。我们稍后会回到这个话题。让我们再退一步,看看 Discourse 如何将上下文传递给插槽。如果你在 Github 上(或在本地)搜索插槽名称,会得到以下结果:
让我们打开该文件。你看到的第一行是:
看看该行的最后一部分:
args=(hash model=model)
你会看到 Discourse 将 model 作为参数传递给插槽。为了所有实际目的并保持简单,model = data(数据)。
因此,我们插槽的参数之一是 model,这就是我们所需数据所在的位置。所以,让我们回到我们的代码片段:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
{{log this}}
</script>
将其更改为:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
- {{log this}}
+ {{log args}}
</script>
现在我们在控制台中看到了以下内容:
你可以浏览这些数据,看看是否包含你需要的内容。应该包含,因为它包含了该页面上其他元素使用的关于用户的所有数据。这是该特定用户的用户页面的“模型”。
其中一个可用的属性是……鼓声
……用户所属的群组。
所以,如果你执行:
{{log args.model.groups}}
你会在控制台中看到该用户所属的所有群组。
好的,现在我们有了所需的数据,剩下的就是基于此添加一些条件。
你可能会想,我们可以在同一个代码片段中完成这一点,但不幸的是,我们无法做到。Handlebars 是一种模板语言。它对逻辑的支持非常、非常基础——仅限于简单的真/假条件和循环。你无法进行比较和其他类似操作。
那么确切在哪里可以做呢?在连接器类(connector class)中,听起来很高级……我知道。
简而言之,连接器类本质上是一段附加到插槽的 JavaScript 代码。实际上它要复杂得多,但你现在只需要知道这些就够了。
那么,让我们创建一个。我们这样做:
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('OUTLET_NAME', 'SOME_NAME', {
});
</script>
这里的 OUTLET_NAME 和 SOME_NAME 应该与上面使用的名称相同。所以让我们更改它们:
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
});
</script>
此代码片段也应放在你主题的 common > header 选项卡中。所以你现在应该拥有类似以下内容:
<script type="text/x-handlebars" data-template-name="/connectors/above-user-profile/add-profile-videos">
{{log args.model.groups}}
</script>
<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
});
</script>
在我们的连接器类内部,我们可以做一些工作……但是……我们需要记住,它并不像任何普通的 JavaScript 文件。由于缺乏更好的描述……把它想象成一个精简版的 Ember 组件。进一步展开这个话题超出了本文的范围,所以我们继续。
默认情况下,有四个方法与其连接:
actions 允许你定义操作,如下所示:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
actions: {
myAction() {
// do something
}
}
});
然后你可以在插槽内部调用该操作,例如当按钮被按下时。我们这里不需要这个,所以让我们继续。
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
shouldRender(args, component) {
// return true or false here
}
});
我们也不会使用这个,因为插槽仅在个人资料页面上渲染,而且我们目前没有其他要求。不过,你可以使用它在插槽渲染之前添加任何你想要测试的条件。例如,当前用户的信任级别等。继续……
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
// do something
}
});
这是我们要关注的重点。任何你想要设置的 JavaScript 条件或变量都放在这里。在深入探讨这个之前,让我们先涵盖最后一个方法,以求完整:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
teardownComponent(args, component) {
// do something
}
});
当插槽即将被移除时会触发此方法。因此,它允许你执行任何所需的清理工作,例如移除事件监听器等。
好的,让我们回到 setupComponent:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
// do something
}
});
你可以看到它接收了两个参数。首先是 args,然后是 component。
这里的 args 与我们之前查看的内容相同。它是 Discourse 传递给插槽的上下文数据。所以,如果你执行:
api.registerConnectorClass("above-user-profile", "add-profile-videos", {
setupComponent(args, component) {
console.log(args.model.groups);
}
});
你会在浏览器控制台中看到与我们之前看到相同的信息。即个人资料所有者所属的群组。有趣的部分开始了,你现在拥有了数据,并且有了正确的钩子。因此,你可以在这里做任何你想做的事。所以,如果我希望视频仅在属于特定群组的成员个人资料上显示,我可以这样做:
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
setupComponent(args, component) {
const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
const showVideo = inGroup.length ? true : false;
console.log(showVideo);
}
});
如果你在属于 staff 群组的用户的个人资料页面上尝试此操作,它将在控制台中打印 true。所以,现在我们要做的最后一件事是将该值传递给插槽模板。以下是操作方法:
传递给 setupComponent 的 component 在连接器和插槽之间是共享的。你可以通过将其作为属性设置在组件上来向插槽传递内容,如下所示:
const TARGET_GROUP = "staff"
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
setupComponent(args, component) {
const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
const showVideo = inGroup.length ? true : false;
- console.log(showVideo);
+ component.setProperties({showVideo})
}
});
现在,如果我们回到模板并执行类似以下操作:
{{log showVideo}}
它将打印相同的结果。因此,我们现在将其放入 Handlebars 条件中,并在其中添加你的标记,如下所示:
<script
type="text/x-handlebars"
data-template-name="/connectors/above-user-profile/add-profile-videos"
>
{{#if showVideo}}
<video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
<source src="[INSERT LINK]" type="video/webm">
<source src="[INSERT LINK]" type="video/mp4">
</video>
{{/if}}
</script>
然后检查 staff 用户的个人资料页面。你会看到视频已加载。
一旦你离开该 staff 成员的个人资料页面,视频就会消失。视频不会显示在非 staff 群组用户的个人资料上。
所以,让我们把所有这些整合起来。这与上面的内容相同。
以下是我使用的 CSS。位于 common > css 选项卡中:
#myVideo {
position: fixed;
top: var(--header-offset);
min-height: 100vh;
left: 0;
z-index: -1;
}
.user-content {
background: none;
}
.user-main {
padding: 0.5em;
background: rgba(var(--secondary-rgb), 0.8);
}
// 如果你也想在移动端显示
.mobile-view {
body[class*="user-"] {
background: none;
.user-main,
.user-content {
padding: 0.5em;
background: rgba(var(--secondary-rgb), 0.8);
}
}
}
HTML / JavaScript / Handlebars。这应放在你主题的 common > header 选项卡中:
<script
type="text/x-handlebars"
data-template-name="/connectors/above-user-profile/add-profile-videos"
>
{{#if showVideo}}
<video playsinline autoplay muted loop id="myVideo" poster="[INSERT LINK]">
<source src="[INSERT LINK]" type="video/webm">
<source src="[INSERT LINK]" type="video/mp4">
</video>
{{/if}}
</script>
<script type="text/discourse-plugin" version="0.8">
const TARGET_GROUP = "staff"
api.registerConnectorClass('above-user-profile', 'add-profile-videos', {
setupComponent(args, component) {
const inGroup = [...args.model.groups].filter(g => g.name === TARGET_GROUP)
const showVideo = inGroup.length ? true : false;
component.setProperties({showVideo})
}
});
</script>
将 TARGET_GROUP 更改为你想要目标化的群组名称,并添加视频的 src 属性。
这篇帖子有点长……不要被吓倒。一旦你掌握了概念,上面我们做的所有事情都可以在 3-5 分钟内完成。
这里的好处是,我们讨论的所有内容对于任何 plugin-outlet 都基本相同。唯一变化的是名称。因此,这适用于你未来想要进行的任何 plugin-outlet 修改。
- 找到插槽名称
- 获取数据
- 在连接器中处理数据
- 将属性传回模板




