为特定用户个人资料添加背景视频?

如果你想在特定页面上添加内容,最佳选择是使用 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>

现在我们在控制台中看到了以下内容:

你可以浏览这些数据,看看是否包含你需要的内容。应该包含,因为它包含了该页面上其他元素使用的关于用户的所有数据。这是该特定用户的用户页面的“模型”。

其中一个可用的属性是……鼓声 :drum: ……用户所属的群组。

所以,如果你执行:

{{log args.model.groups}}

你会在控制台中看到该用户所属的所有群组。

好的,现在我们有了所需的数据,剩下的就是基于此添加一些条件。

你可能会想,我们可以在同一个代码片段中完成这一点,但不幸的是,我们无法做到。Handlebars 是一种模板语言。它对逻辑的支持非常、非常基础——仅限于简单的真/假条件和循环。你无法进行比较和其他类似操作。

那么确切在哪里可以做呢?在连接器类(connector class)中,听起来很高级……我知道。

简而言之,连接器类本质上是一段附加到插槽的 JavaScript 代码。实际上它要复杂得多,但你现在只需要知道这些就够了。

那么,让我们创建一个。我们这样做:

<script type="text/discourse-plugin" version="0.8">
api.registerConnectorClass('OUTLET_NAME', 'SOME_NAME', {

});
</script>

这里的 OUTLET_NAMESOME_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。所以,现在我们要做的最后一件事是将该值传递给插槽模板。以下是操作方法:

传递给 setupComponentcomponent 在连接器和插槽之间是共享的。你可以通过将其作为属性设置在组件上来向插槽传递内容,如下所示:

  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 修改。

  1. 找到插槽名称
  2. 获取数据
  3. 在连接器中处理数据
  4. 将属性传回模板