有人能澄清一下 主题组件、插件 和 插件 API 这三个 Discourse 概念之间的区别吗?
尤其是主题组件与插件之间的区别。如果我想自定义我的论坛,该如何判断应该构建哪一个?
(抱歉,如果我在 简介 中遗漏了这一点,但我在那里没找到相关说明。)
有人能澄清一下 主题组件、插件 和 插件 API 这三个 Discourse 概念之间的区别吗?
尤其是主题组件与插件之间的区别。如果我想自定义我的论坛,该如何判断应该构建哪一个?
(抱歉,如果我在 简介 中遗漏了这一点,但我在那里没找到相关说明。)
我对 Discourse 并非初学者,但也算不上专家。
主题组件(Theme-component)使用 HTML、CSS 和 JavaScript 来增强基础主题。
我特意提到“基础主题”,因为它通常简称为“主题”,有时人们会忽略这两者的区别,需要自行推断。主题和/或主题组件可以由管理员在不关闭网站的情况下安装;如果您是 Discourse 客户,也可以添加这些内容。(列表)另见:Discourse 主题使用入门指南
插件使用 Ruby 编写,几乎可以实现任何功能。如果您是 Discourse 客户,只能激活有限的一组插件;但如果您是自己托管(self-hosting),则可以添加任意数量的插件。不过请注意,我见过很多帖子提到自定义插件在升级时会导致网站崩溃。激活插件通常不需要重启服务器;我怀疑首次安装时可能需要重启。其他人可以补充说明,因为我对插件的唯一经验就是从管理菜单中激活它们。(列表)另见:Discourse 插件开发入门指南 - 第一部分
我尚未开发过插件,因此我猜您指的是 Discourse API Ruby Gem。参见:Use the Discourse API ruby gem
此外还有 API,它包含 Webhooks,通常配合 curl 或其他编程语言使用。这样做的好处是您可以不必依赖 Ruby。
虽然我也未曾涉足这一领域,但您也可以在 PostgreSQL 数据库层面进行编程。不过,除非您技术非常娴熟且对自己的能力充满信心,否则我不推荐这样做。
希望以上信息对您有帮助(HTH)。
编辑
如果您想全面投入成为 Discourse 开发者,这里还有一个加分项:
在 @EricGT 的回答基础上补充一点,该回答已经很好地解释了相关内容:
自定义网站的起点最好是主题。以下是一些资源:
Discourse 主题设计师指南
Discourse 主题开发者指南
初学者使用主题创建器和主题 CLI 开始构建 Discourse 主题的指南
谢谢,各位。这很有帮助。我想我理解的关键区别是:
– 如果你只想修改前端显示的内容,就构建一个主题。
– 如果你需要修改涉及后端交互的内容,就构建一个插件。
这样理解对吗?
这里有一个我具体想实现的例子:我希望让每个分类的版主都能在该分类中置顶话题。大体思路如下:
检查用户是否为该分类的版主(这需要从后端获取用户和分类的相关信息)
如果用户是版主,则显示置顶按钮(这是前端部分)
如果用户点击置顶按钮,将该话题置顶(目前还不确定这在 Discourse 代码中具体发生在哪里——可能同时涉及前端和后端?)
在这里,因为我需要与后端进行交互(可能在第 1 步,也可能在第 3 步?),所以我应该使用插件。这样理解对吗?
表面部分可能如此,但这将涉及后端,因为它关系到权限和安全。你不能让前端自行决定某人拥有哪些特权。
你的意思是,从程序层面回答“该用户是否是此分类的管理员”这一问题,会涉及前后端的多份文件吗?嗯……
在主题中,你应该能够判断用户是否为版主,并且如果 Discourse API 提供了可以从前端调用的端点(毕竟主题可以使用 JavaScript),你也可以进行后端调用。因此,对于 [1],你可能不需要插件。只有当你需要更改后端行为或暴露 API 时,才需要插件。
但对于 [3],你可能需要插件,因为正如 @merefield 所说,这涉及到权限和安全问题(如果后端阻止版主置顶主题,你就需要修改它以允许这样做)。
正如我上面所说,仅仅进行该验证(判断用户是否为版主)可能不需要插件,但由于涉及允许其置顶分类的操作,很可能需要插件。如果 Discourse 有允许任何版主置顶主题的选项(我不确定是否有),那么你就不需要插件(但你可能也不需要主题,除非置顶按钮对版主不显示,而你使用主题只是为了向版主显示该按钮,并在他们点击置顶按钮时通过 JavaScript 调用端点)。
关于主题与插件的讨论,以及我提到的具体示例,这非常有帮助。谢谢。
目前,我处理变更的方式是逐一梳理 Discourse 代码的具体细节(这个对象定义在哪里?哪个模板控制它?处理该操作的逻辑位于何处?等等)。但这进展缓慢(链接)。
我认为,专注于 API可能是更高效的途径。这样,我就不必梳理成熟 Discourse 代码的所有细节,还能专注于构建主题而非插件——或者仅仅通过“自定义”仪表盘进行变更。
基本上,弄懂 API 如何运作似乎比研究 Discourse 代码库要容易得多。
继续以我提到的示例为例:如果用户是某个分类的管理员,则允许该用户在分类页面置顶话题。
我能否在不使用插件的情况下实现这一点?让我试着勾勒一下思路:
1. 用户是否是某个分类的管理员?
我目前在 API 文档中没看到任何能告诉我用户是否为某分类管理员的内容。我原本以为在获取分类的 GET 请求中会有相关信息,但并未找到。或者可能在获取用户的 GET 请求中,但那里也没有列出该用户担任管理员的分类。
我能添加这些功能吗?
或者,我是否可以在用户或分类上创建一个 custom_field 来标识管理员身份,然后在加载分类页面时通过 API 调用该自定义字段?
2. 如果用户是管理员,显示置顶按钮。
如果能解决第 1 点,我假设只需通过前端 JavaScript 和 CSS 添加该按钮,并在用户是管理员时显示即可。
3. 用户(作为管理员)点击按钮,从而置顶话题。
在 API 中,话题似乎确实具有“置顶”(pinned)属性(布尔值)。我推测这与它们在分类中的置顶状态相关,因为看起来每个话题只属于一个分类。
因此,当管理员点击“置顶”按钮时,我可能只需将话题的“置顶”状态更新为 True。如果这行不通,自定义字段也可能是解决方案(尽管我还没看到如何为话题添加自定义字段)。
因此,通过这种方式或类似方法,我似乎可以通过 API 完成这项任务;而如果使用插件,则需要在 Discourse 代码库的文件中进行大量梳理。
我的理解正确吗?
您是否找到了:如何逆向工程 Discourse API
我之前见过这个,但现在我会仔细查看。谢谢提醒。
我正在尝试梳理:
–在 API 的哪个位置可以获取有关某个类别版主的信息(我在类别或用户返回的信息中没看到——显然它一定在某个地方)
–能否使用 API 为条目添加新字段?例如,能否使用 API 为话题添加自定义字段?(我认为话题通常不带自定义字段)
我会去数据库里查找。
数据库里有一个 posts 表,你可以在那里添加一个字段,但那样的话你就脱离官方支持范围了,无法获得帮助。
谢谢——这非常有帮助。
你指的是哪个数据库?
为了确认一下,我这里的想法是利用 API 来完成一些原本可能需要插件才能实现的功能。例如,我的网站本身会调用 API 来判断某个用户(或用户组,视情况而定)是否是某个分类的管理员。这个调用可以硬编码在自定义面板中,或者放在主题或插件里。
要进行这类调用,我需要先进行身份验证,据我所知这需要从管理面板创建 API 密钥。管理面板中创建 API 密钥的过程需要填写“描述”和“用户级别”——我不确定这两项在这里该如何填写。在我的情况下,我希望我的应用来发起 API 调用,而不是代表某个特定用户。因此,我可能对此有误解。
你知道针对这类 API 调用应该选择什么样的“用户级别”,或者在那里应该输入什么内容吗?
按照 标准 安装 Discourse 时,它运行在 Docker 容器 中。数据的持久化是通过 PostgreSQL 数据库实现的。
如果你拥有管理员权限,可以获取其中一个备份,该备份是 SQL 数据压缩后的 tar.gz 文件。你可以使用其中的 SQL 语句将数据重新加载到另一个 PostgreSQL 数据库中,并进行更多操作。
我从未创建过主题或插件,也从未使用过 Ruby 或其他任何编程语言调用过该 API。我在生产环境网站上拥有管理员权限,正是通过该权限访问了备份数据。此外,我使用 Prolog 进行编程,正是通过它来 访问 数据,并利用 DCG( definite clause grammar,确定子句文法)解析帖子。如果你熟悉 BNF,那么 DCG 与之相差无几,但在处理更复杂的部分时,你需要理解语法合一(syntactic unification)和逆向链式推理(backward chaining)。
谢谢。我将单独跟进此事项。
不行,因为每个操作的访问权限均由服务器强制执行。
你需要查看如何重写 lib/guardian/ 目录和 lib/guardian.rb 文件中的函数,以允许特定分类的管理员置顶主题,然后使用与主题 JavaScript 相同的机制(但在插件中实现)来修改用户界面,使“置顶主题”选项在适当的时候显示出来。
啊——这就说得通了。所以听起来用 API 来实现那个功能可能行不通(你的回复可能帮我省了不少时间)。
我可能会尝试一种与常规置顶行为略有不同的方式。具体来说,我会给某些用户赋予对某个分类的“所有权”权限,这样他们就能在该分类中高亮显示特定主题。
如果通过 JSON API 来实现,我会从我的自定义仪表板中,为相关用户添加一个自定义字段(例如 category-name: owner)之类的内容。然后,当分类页面加载时,调用 API 来评估该用户;如果他们拥有该自定义字段,就显示“高亮”按钮;接着,如果他们点击某个主题的“高亮”按钮,就将该主题分配到该分类的高亮组中(这也是该分类的一个自定义字段)。
这只是一个粗略的草图——没必要一步步确认这些步骤(我在编码时可能还需要调整)。但我现在的问题是:这种使用 JSON API 的方式——尤其是从我的 Discourse 应用内部创建和检索自定义字段——是否可行?
亲爱的 @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 一同暴露,我们便可以在主题组件和插件中使用这些数据。
希望这一视角能在某种程度上对您有所帮助。
解释得非常出色。非常感谢你的回复。这指出了我之前未曾意识到的一点:
从这些回复中我了解到(正如你所说),在许多情况下,与 JSON API 交互是一个不错的起点,可能避免需要编写新的主题或插件。但确实存在一些 API 未暴露的数据类型。要访问并处理这些数据类型,你需要使用 Discourse 数据序列化器来暴露相关数据;而要进行这种序列化,你需要编写一个插件。
看起来一个很好的例子是:通过 API 无法获取群组的所有者信息。我之所以这么说,是因为(关于访问群组所有者):
有一点令人困惑——在 Discourse API 中,当你获取某个特定群组时,返回的一个属性被标记为 "is_group_owner": true,所以不确定这究竟是什么意思……
但看起来要获取群组所有者,我需要序列化“群组所有者”这一属性。
有没有使用 Discourse 序列化器的好例子?我见过 这个,但鉴于其重要性,如果能提供带有几个示例的操作指南,将非常有帮助。
我目前找到的最接近的例子是:
这很有帮助,但还不够正确(至少它给了我“无效插件”的错误)。我不确定该如何调整它,以便在群组索引页面上能够访问每个群组的群组所有者。
我不确定你尝试使用插件示例的方式,但在我之前发布的文件结构和代码中,该插件在开发实例上已成功运行。
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>
综上所述,使用插件绝对是最简单、最整洁的解决方案。