主题组件与插件:区别是什么

有人能澄清一下 主题组件插件插件 API 这三个 Discourse 概念之间的区别吗?

尤其是主题组件与插件之间的区别。如果我想自定义我的论坛,该如何判断应该构建哪一个?

(抱歉,如果我在 简介 中遗漏了这一点,但我在那里没找到相关说明。)

3 个赞

我对 Discourse 并非初学者,但也算不上专家。

  1. 主题组件(Theme-component)使用 HTML、CSS 和 JavaScript 来增强基础主题。
    我特意提到“基础主题”,因为它通常简称为“主题”,有时人们会忽略这两者的区别,需要自行推断。主题和/或主题组件可以由管理员在不关闭网站的情况下安装;如果您是 Discourse 客户,也可以添加这些内容。(列表)另见:Discourse 主题使用入门指南

  2. 插件使用 Ruby 编写,几乎可以实现任何功能。如果您是 Discourse 客户,只能激活有限的一组插件;但如果您是自己托管(self-hosting),则可以添加任意数量的插件。不过请注意,我见过很多帖子提到自定义插件在升级时会导致网站崩溃。激活插件通常不需要重启服务器;我怀疑首次安装时可能需要重启。其他人可以补充说明,因为我对插件的唯一经验就是从管理菜单中激活它们。(列表)另见:Discourse 插件开发入门指南 - 第一部分

  3. 我尚未开发过插件,因此我猜您指的是 Discourse API Ruby Gem。参见:Use the Discourse API ruby gem

  4. 此外还有 API,它包含 Webhooks,通常配合 curl 或其他编程语言使用。这样做的好处是您可以不必依赖 Ruby。

  5. 虽然我也未曾涉足这一领域,但您也可以在 PostgreSQL 数据库层面进行编程。不过,除非您技术非常娴熟且对自己的能力充满信心,否则我不推荐这样做。

希望以上信息对您有帮助(HTH)。


编辑

如果您想全面投入成为 Discourse 开发者,这里还有一个加分项:

参见:新手(如我)如何开始为 Discourse 构建功能

7 个赞

@EricGT 的回答基础上补充一点,该回答已经很好地解释了相关内容:

  • 主题(theme)或主题组件本质上是一种修改 Discourse 前端 EmberJS 应用任意部分的方式。这可以简单到自定义 HTML 或 CSS,也可以复杂到添加新功能。如果出现问题,主题通常更加优雅,意味着即使某些部分无法工作,您的整个网站也不一定会瘫痪。
  • 插件主要影响 Rails 服务器端应用,但也具备主题的所有功能,并能影响 EmberJS 应用,尽管实现起来更为复杂。插件故障通常不如主题那样优雅,因此如果您能用主题实现某项功能,建议优先从主题入手。但如果您需要自定义路由或存储数据,则必须使用插件。
  • pluginAPI 是客户端提供的一个 API,主题或主题组件可以利用它更轻松地修改 Discourse 客户端的特定部分。

自定义网站的起点最好是主题。以下是一些资源:

Discourse 主题设计师指南
Discourse 主题开发者指南
初学者使用主题创建器和主题 CLI 开始构建 Discourse 主题的指南

10 个赞

谢谢,各位。这很有帮助。我想我理解的关键区别是:
– 如果你只想修改前端显示的内容,就构建一个主题。
– 如果你需要修改涉及后端交互的内容,就构建一个插件。

这样理解对吗?

这里有一个我具体想实现的例子:我希望让每个分类的版主都能在该分类中置顶话题。大体思路如下:

  1. 检查用户是否为该分类的版主(这需要从后端获取用户和分类的相关信息)

  2. 如果用户是版主,则显示置顶按钮(这是前端部分)

  3. 如果用户点击置顶按钮,将该话题置顶(目前还不确定这在 Discourse 代码中具体发生在哪里——可能同时涉及前端和后端?)

在这里,因为我需要与后端进行交互(可能在第 1 步,也可能在第 3 步?),所以我应该使用插件。这样理解对吗?

2 个赞

表面部分可能如此,但这将涉及后端,因为它关系到权限和安全。你不能让前端自行决定某人拥有哪些特权。

1 个赞

你的意思是,从程序层面回答“该用户是否是此分类的管理员”这一问题,会涉及前后端的多份文件吗?嗯……

1 个赞

在主题中,你应该能够判断用户是否为版主,并且如果 Discourse API 提供了可以从前端调用的端点(毕竟主题可以使用 JavaScript),你也可以进行后端调用。因此,对于 [1],你可能不需要插件。只有当你需要更改后端行为或暴露 API 时,才需要插件。

但对于 [3],你可能需要插件,因为正如 @merefield 所说,这涉及到权限和安全问题(如果后端阻止版主置顶主题,你就需要修改它以允许这样做)。

正如我上面所说,仅仅进行该验证(判断用户是否为版主)可能不需要插件,但由于涉及允许其置顶分类的操作,很可能需要插件。如果 Discourse 有允许任何版主置顶主题的选项(我不确定是否有),那么你就不需要插件(但你可能也不需要主题,除非置顶按钮对版主不显示,而你使用主题只是为了向版主显示该按钮,并在他们点击置顶按钮时通过 JavaScript 调用端点)。

2 个赞

关于主题与插件的讨论,以及我提到的具体示例,这非常有帮助。谢谢。

目前,我处理变更的方式是逐一梳理 Discourse 代码的具体细节(这个对象定义在哪里?哪个模板控制它?处理该操作的逻辑位于何处?等等)。但这进展缓慢(链接)。

我认为,专注于 API可能是更高效的途径。这样,我就不必梳理成熟 Discourse 代码的所有细节,还能专注于构建主题而非插件——或者仅仅通过“自定义”仪表盘进行变更。

基本上,弄懂 API 如何运作似乎比研究 Discourse 代码库要容易得多。

继续以我提到的示例为例:如果用户是某个分类的管理员,则允许该用户在分类页面置顶话题。

我能否在不使用插件的情况下实现这一点?让我试着勾勒一下思路:

1. 用户是否是某个分类的管理员?

我目前在 API 文档中没看到任何能告诉我用户是否为某分类管理员的内容。我原本以为在获取分类的 GET 请求中会有相关信息,但并未找到。或者可能在获取用户的 GET 请求中,但那里也没有列出该用户担任管理员的分类。

我能添加这些功能吗?

或者,我是否可以在用户或分类上创建一个 custom_field 来标识管理员身份,然后在加载分类页面时通过 API 调用该自定义字段?

2. 如果用户是管理员,显示置顶按钮。

如果能解决第 1 点,我假设只需通过前端 JavaScript 和 CSS 添加该按钮,并在用户是管理员时显示即可。

3. 用户(作为管理员)点击按钮,从而置顶话题。

在 API 中,话题似乎确实具有“置顶”(pinned)属性(布尔值)。我推测这与它们在分类中的置顶状态相关,因为看起来每个话题只属于一个分类。

因此,当管理员点击“置顶”按钮时,我可能只需将话题的“置顶”状态更新为 True。如果这行不通,自定义字段也可能是解决方案(尽管我还没看到如何为话题添加自定义字段)。


因此,通过这种方式或类似方法,我似乎可以通过 API 完成这项任务;而如果使用插件,则需要在 Discourse 代码库的文件中进行大量梳理。

我的理解正确吗?

1 个赞

您是否找到了:如何逆向工程 Discourse API

1 个赞

我之前见过这个,但现在我会仔细查看。谢谢提醒。

我正在尝试梳理:
–在 API 的哪个位置可以获取有关某个类别版主的信息(我在类别或用户返回的信息中没看到——显然它一定在某个地方)

–能否使用 API 为条目添加新字段?例如,能否使用 API 为话题添加自定义字段?(我认为话题通常不带自定义字段)

1 个赞

我会去数据库里查找。

数据库里有一个 posts 表,你可以在那里添加一个字段,但那样的话你就脱离官方支持范围了,无法获得帮助。

1 个赞

谢谢——这非常有帮助。

你指的是哪个数据库?


为了确认一下,我这里的想法是利用 API 来完成一些原本可能需要插件才能实现的功能。例如,我的网站本身会调用 API 来判断某个用户(或用户组,视情况而定)是否是某个分类的管理员。这个调用可以硬编码在自定义面板中,或者放在主题或插件里。

要进行这类调用,我需要先进行身份验证,据我所知这需要从管理面板创建 API 密钥。管理面板中创建 API 密钥的过程需要填写“描述”和“用户级别”——我不确定这两项在这里该如何填写。在我的情况下,我希望我的应用来发起 API 调用,而不是代表某个特定用户。因此,我可能对此有误解。

你知道针对这类 API 调用应该选择什么样的“用户级别”,或者在那里应该输入什么内容吗?

1 个赞

按照 标准 安装 Discourse 时,它运行在 Docker 容器 中。数据的持久化是通过 PostgreSQL 数据库实现的。

参见:Data Explorer 插件

如果你拥有管理员权限,可以获取其中一个备份,该备份是 SQL 数据压缩后的 tar.gz 文件。你可以使用其中的 SQL 语句将数据重新加载到另一个 PostgreSQL 数据库中,并进行更多操作。

2 个赞

我从未创建过主题或插件,也从未使用过 Ruby 或其他任何编程语言调用过该 API。我在生产环境网站上拥有管理员权限,正是通过该权限访问了备份数据。此外,我使用 Prolog 进行编程,正是通过它来 访问 数据,并利用 DCG( definite clause grammar,确定子句文法)解析帖子。如果你熟悉 BNF,那么 DCG 与之相差无几,但在处理更复杂的部分时,你需要理解语法合一(syntactic unification)和逆向链式推理(backward chaining)。

1 个赞

谢谢。我将单独跟进此事项。

1 个赞

不行,因为每个操作的访问权限均由服务器强制执行。

你需要查看如何重写 lib/guardian/ 目录和 lib/guardian.rb 文件中的函数,以允许特定分类的管理员置顶主题,然后使用与主题 JavaScript 相同的机制(但在插件中实现)来修改用户界面,使“置顶主题”选项在适当的时候显示出来。

1 个赞

啊——这就说得通了。所以听起来用 API 来实现那个功能可能行不通(你的回复可能帮我省了不少时间)。

我可能会尝试一种与常规置顶行为略有不同的方式。具体来说,我会给某些用户赋予对某个分类的“所有权”权限,这样他们就能在该分类中高亮显示特定主题。

如果通过 JSON API 来实现,我会从我的自定义仪表板中,为相关用户添加一个自定义字段(例如 category-name: owner)之类的内容。然后,当分类页面加载时,调用 API 来评估该用户;如果他们拥有该自定义字段,就显示“高亮”按钮;接着,如果他们点击某个主题的“高亮”按钮,就将该主题分配到该分类的高亮组中(这也是该分类的一个自定义字段)。

这只是一个粗略的草图——没必要一步步确认这些步骤(我在编码时可能还需要调整)。但我现在的问题是:这种使用 JSON API 的方式——尤其是从我的 Discourse 应用内部创建和检索自定义字段——是否可行?

1 个赞

亲爱的 @JQ331

但我现在的问题是:这种使用 JSON API 的方式——特别是从我的 Discourse 应用中创建和检索自定义字段——是否可行?

关于主题组件、插件与 Discourse API 之间区别的问题,您已经收到了许多出色的回复,我恐怕很难再补充更多内容;不过,这里提供另一种看待问题的角度,或许对您有所帮助:

主题组件和插件都使用模板钩子(plugin hooks)在 Ember.js 生命周期中执行代码(顺便提一下,了解这一点很有价值)。

此外,Discourse API 对主题组件和插件均可用。

该 API 主要暴露了底层 PostgreSQL 数据库中的部分数据,而非全部。

在开发功能时,建议先从 API 入手,确认您所需的数据是否可通过 API 获取。

如果您需要的某些数据不在 API 中,则需要检查 PostgreSQL 数据库,确认该数据是否存在于数据库中。

如果所需的其他数据确实存在于数据库中,那么您需要暴露这些数据。通常情况下,这意味着需要向 Discourse 数据序列化器添加数据,并扩展 API。

数据序列化器本质上就是生成由 API 暴露的 JSON 对象的过程。该过程可以扩展以添加更多对象。

关于如何检查 PostgreSQL 数据库,我假设有多种方法(例如阅读 GitHub 上的代码),但我通常的做法是直接登录数据库,查看其中的表,研究这些表的结构以及每个表包含哪些字段(使用基础 SQL)。

因此,简而言之(尽量保持简洁),我们需要将 API 和数据库表都作为参考。一般来说,当您希望“扩展 API”,即添加 API 开箱即用(OOTB)未提供的数据时,我们需要为此创建一个插件;然后,这些数据将随扩展后的 API 一同暴露,我们便可以在主题组件和插件中使用这些数据。

希望这一视角能在某种程度上对您有所帮助。

6 个赞

解释得非常出色。非常感谢你的回复。这指出了我之前未曾意识到的一点:

从这些回复中我了解到(正如你所说),在许多情况下,与 JSON API 交互是一个不错的起点,可能避免需要编写新的主题或插件。但确实存在一些 API 未暴露的数据类型。要访问并处理这些数据类型,你需要使用 Discourse 数据序列化器来暴露相关数据;而要进行这种序列化,你需要编写一个插件。

看起来一个很好的例子是:通过 API 无法获取群组的所有者信息。我之所以这么说,是因为(关于访问群组所有者):

有一点令人困惑——在 Discourse API 中,当你获取某个特定群组时,返回的一个属性被标记为 "is_group_owner": true,所以不确定这究竟是什么意思……

但看起来要获取群组所有者,我需要序列化“群组所有者”这一属性。


有没有使用 Discourse 序列化器的好例子?我见过 这个,但鉴于其重要性,如果能提供带有几个示例的操作指南,将非常有帮助。

我目前找到的最接近的例子是:

这很有帮助,但还不够正确(至少它给了我“无效插件”的错误)。我不确定该如何调整它,以便在群组索引页面上能够访问每个群组的群组所有者。

3 个赞

我不确定你尝试使用插件示例的方式,但在我之前发布的文件结构和代码中,该插件在开发实例上已成功运行。

is_group_owner 是在当前用户查看群组时的上下文中使用的。

可以通过 AJAX 调用获取所有者信息,但据我所知,这需要针对列表中的每个群组单独进行,可能会产生大量请求。我认为总体而言,使用这种方法实现起来颇具挑战性。无论如何,如果你想尝试,可以在主题中运行以下概念验证代码片段。只需将 GROUP_NAME 替换为你其中一个群组的名称即可。(编辑:这里还有一个主题组件示例,展示了如何利用 AJAX 调用:https://github.com/awesomerobot/discourse-featured-topics/blob/master/common/head_tag.html)

<script type="text/discourse-plugin" version="0.8.40">
  const { ajax } = require("discourse/lib/ajax");
  ajax(`/groups/GROUP_NAME/members.json`).then(response => {
    console.log(response.owners.map(owner => owner.username))
  });
</script>

综上所述,使用插件绝对是简单、最整洁的解决方案。

1 个赞