FastComments.com

编写扩展

扩展开发

背景

FastComments 提供通过我们称为扩展的脚本来扩展核心功能的能力。

一个 Extension 可以向评论小部件添加额外的标记、事件监听器,并运行任意代码。

在这里你会找到我们在生产环境中使用的扩展示例,以及关于如何编写扩展的文档。

扩展的生命周期 Internal Link

在评论小部件开始获取第一批评论并呈现 UI 之前,会获取并调用每个扩展的脚本。

在初次加载时,以下数据将附加到扩展对象上:

  • config - 对 config 对象的引用。
  • translations - 对 translations 对象的引用。
  • commentsById - 对按 id 存储的所有评论的引用。
  • root - 对根 DOM 节点的引用。

扩展应覆盖所需的函数,评论小部件将在适当的时候调用这些函数。

定义扩展 Internal Link

最小可行的扩展如下:

一个简单的扩展
Copy CopyRun External Link
1
2(function () {
3 const extension = FastCommentsUI.extensions.find((extension) => {
4 return extension.id === 'my-extension';
5 });
6})();
7

为了这个示例,将其保存为 my-extension.js,并使其可以通过 https://example.com/my-extension.min.js 访问。

此扩展不会执行任何操作,除了在加载时它会获取由核心评论库创建的扩展对象。

该 Extension 对象是单例的,不会与其他扩展共享。

接下来,要加载我们的扩展,必须告知评论小部件有关它。例如:

Using a Custom Extension
Copy CopyRun External Link
1
2<script async src="https://cdn.fastcomments.com/js/embed-v2-async.min.js"></script>
3<div id="fastcomments-widget"></div>
4<script>
5window.fcConfigs = [{
6 "tenantId": "demo",
7 "extensions": [
8 {
9 "id": "my-extension",
10 "path": "https://example.com/my-extension.min.js"
11 }
12 ]
13}];
14</script>
15

有关功能示例,请参见下一节。

扩展对象 Internal Link

扩展对象由以下定义组成:

扩展对象 JSDoc
Copy CopyRun External Link
1
2/**
3 * FastCommentsUI 扩展对象。用于懒加载某些组件。例如,评论系统并非所有客户都会使用,因此我们只在需要时才加载该扩展。
4 *
5 * @typedef {Object} FastCommentsUIExtension
6 * @property {string} id
7 * @property {Element} scriptNode
8 * @property {Element} root - 组件的根 DOM 节点。
9 * @property {string} [css]
10 * @property {Object} config - FastComments 配置对象。
11 * @property {Object} commentsById - 对一个按 id 存储所有评论的对象的引用,该对象会保持最新。
12 * @property {Object} translations - 对所有翻译的引用。
13 * @property {Function} reRenderComment - 对一个函数的引用,调用该函数可重新渲染一条评论。
14 * @property {Function} removeCommentAndReRender - 对一个函数的引用,调用该函数可将评论从内存中移除并重新渲染 DOM 的相应部分。
15 * @property {Function} newBroadcastId - 对一个函数的引用,调用该函数可创建新的广播 id 并将其添加到本地要忽略的广播 id 列表中。
16 * @property {Function} [withSessionId] - 传入请求 URL 和回调函数进行调用。当第三方 Cookie 被阻止时(与评论和投票请求使用的路径相同),会以追加了查看者会话 id 的 URL 调用回调;否则 URL 保持不变。当无法通过该方式获取会话 id 时,会追加最后一次通过 rememberAnonSessionId 提供的匿名会话 id(如果有的话)。不用于 SSO。
17 * @property {Function} [rememberAnonSessionId] - 传入响应返回的 anonSessionId 进行调用。服务器只会向请求中没有会话 Cookie 的访客返回该 id,绝不会为已登录会话返回。它保存在 localStorage 中(当存储不可用时保存在内存中),以便 withSessionId 在后续请求中发送它,从而使访客保持同一身份。
18 * @property {FastCommentsUIExtensionSetupEventHandlers} [setupEventHandlers]
19 * @property {FastCommentsUIExtensionPrepareCommentForSavingCallback} [prepareCommentForSaving] - 在即将发布评论时调用。返回 false 可取消提交(例如当附加的投票未完成时)。
20 * @property {FastCommentsUIExtensionNewCommentCallback} [newComment]
21 * @property {FastCommentsUIExtensionReplyAreaFilter} [replyAreaFilter] - 过滤评论区域的 HTML。
22 * @property {FastCommentsUIExtensionWidgetFilter} [widgetFilter] - 在渲染时过滤整个组件的 HTML。
23 * @property {FastCommentsUIExtensionCommentTopFilter} [commentFilter] - 在渲染前过滤每条评论的 HTML。
24 * @property {FastCommentsUIExtensionReplyAreaFilter} [commentMenuFilter] - 在渲染前过滤每条评论菜单的 HTML。
25 * @property {FastCommentsUIExtensionMenuFilter} [menuFilter] - 在渲染时过滤整个组件的 HTML。
26 * @property {FastCommentsUIExtensionReplyAreaTop} [replyAreaTop] - (旧版)返回要添加到回复区域顶部的 HTML。
27 * @property {FastCommentsUIExtensionWidgetTopCallback} [widgetTop] - (旧版)返回要添加到组件顶部的 HTML。
28 * @property {FastCommentsUIExtensionCommentTopCallback} [commentTop] - (旧版)返回要添加到评论元素顶部的 HTML。
29 * @property {FastCommentsUIExtensionCommentBottomCallback} [commentBottom] - (旧版)返回要添加到评论元素底部的 HTML。
30 * @property {FastCommentsUIExtensionCommentBottomCallback} [commentContentBottom] - 返回要添加到评论文本之后、评论内容元素内部的 HTML(由投票功能使用)。
31 * @property {Function} [replyAreaInputBottom] - 返回要添加到评论输入框内部、文本输入框下方的 HTML(由投票功能用于就地投票编辑器)。接收父评论 id,对于根回复框则为 null。
32 * @property {Function} [onPollUpdate] - 当页面上某个投票的票数发生变化时,随实时事件一起调用。
33 * @property {Function} isSiteAdmin - 返回查看者是否是该租户的管理员或版主。在首次获取后可知。
34 * @property {FastCommentsUIExtensionMenuBottomCallback} [menuBottom] - (旧版)返回要添加到每条评论的菜单元素底部的 HTML。
35 * @property {FastCommentsUIExtensionRenderCallback} [onRender]
36 * @property {FastCommentsUIExtensionConnectionStatusCallback} [onLiveConnectionStatusUpdate]
37 * @property {FastCommentsUIExtensionInitialRenderCallback} [onInitialRenderComplete]
38 * @property {FastCommentsUIExtensionPresenceUpdateCallback} [onPresenceUpdate]
39 */
40
41/**
42 * @callback FastCommentsUIExtensionSetupEventHandlers
43 * @param {Element} element - 根元素。
44 * @param {Object.<string, Function>} clickListeners - 按类名组织的点击事件处理器,可通过引用进行修改。
45 * @returns void
46 */
47
48/**
49 * @callback FastCommentsUIExtensionWidgetTopCallback
50 * @param {Object} moduleData
51 * @returns {string}
52 */
53
54/**
55 * @callback FastCommentsUIExtensionWidgetFilter
56 * @param {Object} moduleData
57 * @param {Object} html
58 * @returns {string}
59 */
60
61/**
62 * @callback FastCommentsUIExtensionCommentTopCallback
63 * @param {Object} comment
64 * @returns {string}
65 */
66
67/**
68 * @callback FastCommentsUIExtensionCommentTopFilter
69 * @param {Object} comment
70 * @param {string} html
71 * @returns {string}
72 */
73
74/**
75 * @callback FastCommentsUIExtensionCommentBottomCallback
76 * @param {Object} comment
77 * @returns {string}
78 */
79
80/**
81 * @callback FastCommentsUIExtensionMenuBottomCallback
82 * @param {Object} comment
83 * @returns {string}
84 */
85
86/**
87 * @callback FastCommentsUIExtensionMenuFilter
88 * @param {Object} comment
89 * @param {string} html
90 * @returns {string}
91 */
92
93/**
94 * @callback FastCommentsUIExtensionRenderCallback
95 * @returns {string}
96 */
97
98/**
99 * @callback FastCommentsUIExtensionConnectionStatusCallback
100 * @param {boolean} isConnected
101 * @returns {void}
102 */
103
104/**
105 * @callback FastCommentsUIExtensionInitialRenderCallback
106 * @returns {void}
107 */
108
109/**
110 * @callback FastCommentsUIExtensionReplyAreaTop
111 * @param {Object|null} currentUser
112 * @param {boolean} isSaving
113 * @param {boolean} isReplyOpen
114 * @param {string|null} parentId
115 * @returns {string}
116 */
117
118/**
119 * @callback FastCommentsUIExtensionReplyAreaFilter
120 * @param {Object|null} currentUser
121 * @param {boolean} isSaving
122 * @param {boolean} isReplyOpen
123 * @param {string|null} parentId
124 * @param {string|null} html
125 * @returns {string}
126 */
127
128/**
129 * @callback FastCommentsUIExtensionPrepareCommentForSavingCallback
130 * @param {Object} comment
131 * @param {string} parentId
132 */
133
134/**
135 * @callback FastCommentsUIExtensionNewCommentCallback
136 * @param {Object} comment
137 */
138
139/**
140 * @callback FastCommentsUIExtensionPresenceUpdateCallback
141 * @param {Object} update
142 */
143

扩展 API Internal Link

与 Extension 交互很简单,因为我们只需定义要调用的函数的引用。

基于前面的示例,假设我们想在每条评论的顶部添加 HTML:

一个简单的扩展 - 续篇
Copy CopyRun External Link
1
2(function () {
3 const extension = FastCommentsUI.extensions.find((extension) => {
4 return extension.id === 'my-extension';
5 });
6
7 extension.commentFilter = function(comment, html) {
8 return `<h3>The user's name is ${comment.commenterName}!</h3>` + html;
9 }
10})();
11

每当你以这种方式返回 HTML 时,它会通过一个 DOM 差分算法合并到 UI 中。

手动触发评论的重新渲染

我们可以等待初始页面加载完成,然后通过调用 reRenderComment 手动重新渲染某条评论:

重新渲染评论
Copy CopyRun External Link
1
2(function () {
3 const extension = FastCommentsUI.extensions.find((extension) => {
4 return extension.id === 'my-extension';
5 });
6
7 let renderCount = 0;
8
9 extension.commentFilter = function(comment, html) {
10 renderCount++;
11 return `<h3>The render count is ${renderCount}!</h3>` + html;
12 }
13
14 extension.onInitialRenderComplete = function() {
15 setInterval(function() {
16 extension.reRenderComment(extension.commentsById[Object.keys(extension.commentsById)[0]], function renderDone() {
17 console.log('Comment re-render done.');
18 });
19 }, 2000); // timeout not required, just an example.
20 }
21})();
22