@nodebb/nodebb-plugin-reactions 2.1.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -5,9 +5,11 @@
5
5
  "error.maximum-reached": "Maximum reactions reached",
6
6
  "error.maximum-per-user-per-post-reached": "Maximum reactions per user per post reached",
7
7
  "settings.title": "Reactions plugin settings",
8
+ "settings.enable-post-reactions": "Enable post reactions",
8
9
  "settings.max-reactions-per-post": "Maximum unique reactions per post (0 for unlimited, default: 4)",
9
10
  "settings.max-reactions-per-user-per-post": "Maximum reactions per user per post (0 for unlimited)",
10
11
  "settings.max-reactions-per-user-per-post-help": "The limit is enforced only when adding a new reaction and not when joining an existing reaction.",
12
+ "settings.enable-message-reactions": "Enable message reactions",
11
13
  "settings.max-reactions-per-message": "Maximum unique reactions per message (0 for unlimited, default: 4)",
12
14
  "settings.max-reactions-per-user-per-message": "Maximum reactions per user per message (0 for unlimited)",
13
15
  "settings.max-reactions-per-user-per-message-help": "The limit is enforced only when adding a new reaction and not when joining an existing reaction.",
package/library.js CHANGED
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const _ = require.main.require('lodash');
4
3
  const meta = require.main.require('./src/meta');
5
4
  const user = require.main.require('./src/user');
6
5
  const posts = require.main.require('./src/posts');
7
6
  const messaging = require.main.require('./src/messaging');
7
+ const privileges = require.main.require('./src/privileges');
8
8
  const db = require.main.require('./src/database');
9
9
  const routesHelpers = require.main.require('./src/routes/helpers');
10
10
  const websockets = require.main.require('./src/socket.io/index');
@@ -45,12 +45,27 @@ ReactionsPlugin.getPluginConfig = async function (config) {
45
45
  config.maximumReactions = settings.maximumReactions ? parseInt(settings.maximumReactions, 10) : DEFAULT_MAX_EMOTES;
46
46
  config.maximumReactionsPerMessage = settings.maximumReactionsPerMessage ?
47
47
  parseInt(settings.maximumReactionsPerMessage, 10) : DEFAULT_MAX_EMOTES;
48
+ config.enablePostReactions = settings.enablePostReactions === 'on';
49
+ config.enableMessageReactions = settings.enableMessageReactions === 'on';
48
50
  } catch (e) {
49
51
  console.error(e);
50
52
  }
51
53
  return config;
52
54
  };
53
55
 
56
+ ReactionsPlugin.filterSettingsGet = async function (hookData) {
57
+ if (hookData.plugin === 'reactions') {
58
+ const { values } = hookData;
59
+ if (!values.hasOwnProperty('enablePostReactions')) {
60
+ values.enablePostReactions = 'on';
61
+ }
62
+ if (!values.hasOwnProperty('enableMessageReactions')) {
63
+ values.enableMessageReactions = 'on';
64
+ }
65
+ }
66
+ return hookData;
67
+ };
68
+
54
69
  ReactionsPlugin.getPostReactions = async function (data) {
55
70
  if (data.uid === 0) {
56
71
  return data;
@@ -83,42 +98,24 @@ ReactionsPlugin.getPostReactions = async function (data) {
83
98
  }
84
99
  }
85
100
 
86
- const reactionSetToUsersMap = new Map(); // reactionSet -> { uid, username }
87
- if (reactionSets.length > 0) {
88
- const uidsForReactions = await db.getSetsMembers(reactionSets);
89
- const allUids = _.union(...uidsForReactions).filter(Boolean);
90
- const usersData = await user.getUsersFields(allUids, ['uid', 'username']);
91
- const uidToUserdataMap = _.keyBy(usersData, 'uid');
92
-
93
- for (let i = 0, len = reactionSets.length; i < len; i++) {
94
- const uidsForReaction = uidsForReactions[i];
95
- if (uidsForReaction && uidsForReaction.length > 0) {
96
- const usersData = uidsForReaction.map(uid => uidToUserdataMap[uid]).filter(Boolean);
97
- reactionSetToUsersMap.set(reactionSets[i], usersData);
98
- }
99
- }
100
- }
101
+ const reactionSetToUsersMap = await getReactionSetsUidsMap(reactionSets);
101
102
 
102
103
  for (const post of data.posts) {
103
104
  post.maxReactionsReached = pidToIsMaxReactionsReachedMap.get(post.pid);
104
105
  post.reactions = [];
105
106
 
106
- if (pidToReactionsMap.has(post.pid)) {
107
- for (const reaction of pidToReactionsMap.get(post.pid)) {
107
+ const reactions = pidToReactionsMap.get(post.pid);
108
+ if (reactions) {
109
+ for (const reaction of reactions) {
108
110
  const reactionSet = `pid:${post.pid}:reaction:${reaction}`;
109
- if (reactionSetToUsersMap.has(reactionSet)) {
110
- const usersData = reactionSetToUsersMap.get(reactionSet);
111
- const reactionCount = usersData.length;
112
- const reactedUsernames = usersData.map(userData => userData.username).join(', ');
113
- const reactedUids = usersData.map(userData => userData.uid);
114
-
111
+ const uids = reactionSetToUsersMap.get(reactionSet);
112
+ if (Array.isArray(uids)) {
115
113
  post.reactions.push({
116
114
  pid: post.pid,
117
- reacted: reactedUids.includes(data.uid),
115
+ reacted: uids.includes(String(data.uid)),
118
116
  reaction,
119
- usernames: reactedUsernames,
120
117
  reactionImage: parse(reaction),
121
- reactionCount,
118
+ reactionCount: uids.length,
122
119
  });
123
120
  }
124
121
  }
@@ -158,42 +155,23 @@ ReactionsPlugin.getMessageReactions = async function (data) {
158
155
  }
159
156
  }
160
157
 
161
- const reactionSetToUsersMap = new Map(); // reactionSet -> { uid, username }
162
- if (reactionSets.length > 0) {
163
- const uidsForReactions = await db.getSetsMembers(reactionSets);
164
- const allUids = _.union(...uidsForReactions).filter(Boolean);
165
- const usersData = await user.getUsersFields(allUids, ['uid', 'username']);
166
- const uidToUserdataMap = _.keyBy(usersData, 'uid');
167
-
168
- for (let i = 0, len = reactionSets.length; i < len; i++) {
169
- const uidsForReaction = uidsForReactions[i];
170
- if (uidsForReaction && uidsForReaction.length > 0) {
171
- const usersData = uidsForReaction.map(uid => uidToUserdataMap[uid]).filter(Boolean);
172
- reactionSetToUsersMap.set(reactionSets[i], usersData);
173
- }
174
- }
175
- }
158
+ const reactionSetToUsersMap = await getReactionSetsUidsMap(reactionSets);
176
159
 
177
160
  for (const msg of data.messages) {
178
161
  msg.maxReactionsReached = midToIsMaxReactionsReachedMap.get(msg.mid);
179
162
  msg.reactions = [];
180
-
181
- if (midToReactionsMap.has(msg.mid)) {
182
- for (const reaction of midToReactionsMap.get(msg.mid)) {
163
+ const reactions = midToReactionsMap.get(msg.mid);
164
+ if (reactions) {
165
+ for (const reaction of reactions) {
183
166
  const reactionSet = `mid:${msg.mid}:reaction:${reaction}`;
184
- if (reactionSetToUsersMap.has(reactionSet)) {
185
- const usersData = reactionSetToUsersMap.get(reactionSet);
186
- const reactionCount = usersData.length;
187
- const reactedUsernames = usersData.map(userData => userData.username).join(', ');
188
- const reactedUids = usersData.map(userData => userData.uid);
189
-
167
+ const uids = reactionSetToUsersMap.get(reactionSet);
168
+ if (Array.isArray(uids)) {
190
169
  msg.reactions.push({
191
170
  mid: msg.mid,
192
- reacted: reactedUids.includes(data.uid),
171
+ reacted: uids.includes(String(data.uid)),
193
172
  reaction,
194
- usernames: reactedUsernames,
195
173
  reactionImage: parse(reaction),
196
- reactionCount,
174
+ reactionCount: uids.length,
197
175
  });
198
176
  }
199
177
  }
@@ -205,6 +183,22 @@ ReactionsPlugin.getMessageReactions = async function (data) {
205
183
  return data;
206
184
  };
207
185
 
186
+
187
+ async function getReactionSetsUidsMap(reactionSets) {
188
+ const reactionSetToUsersMap = new Map(); // reactionSet -> uids
189
+ if (reactionSets.length > 0) {
190
+ const uidsForReactions = await db.getSetsMembers(reactionSets);
191
+
192
+ for (let i = 0, len = reactionSets.length; i < len; i++) {
193
+ const uidsForReaction = uidsForReactions[i];
194
+ if (uidsForReaction && uidsForReaction.length > 0) {
195
+ reactionSetToUsersMap.set(reactionSets[i], uidsForReaction);
196
+ }
197
+ }
198
+ }
199
+ return reactionSetToUsersMap;
200
+ }
201
+
208
202
  ReactionsPlugin.onReply = async function (data) {
209
203
  if (data.uid !== 0) {
210
204
  data.reactions = [];
@@ -229,15 +223,11 @@ ReactionsPlugin.deleteReactions = async function (hookData) {
229
223
 
230
224
  async function sendPostEvent(data, eventName) {
231
225
  try {
232
- const [reactionCount, totalReactions, uids] = await Promise.all([
226
+ const [reactionCount, totalReactions] = await Promise.all([
233
227
  db.setCount(`pid:${data.pid}:reaction:${data.reaction}`),
234
228
  db.setCount(`pid:${data.pid}:reactions`),
235
- db.getSetMembers(`pid:${data.pid}:reaction:${data.reaction}`),
236
229
  ]);
237
230
 
238
- const userdata = await user.getUsersFields(uids, ['uid', 'username']);
239
- const usernames = userdata.map(user => user.username).join(', ');
240
-
241
231
  if (parseInt(reactionCount, 10) === 0) {
242
232
  await db.setRemove(`pid:${data.pid}:reactions`, data.reaction);
243
233
  }
@@ -248,7 +238,6 @@ async function sendPostEvent(data, eventName) {
248
238
  reaction: data.reaction,
249
239
  reactionCount,
250
240
  totalReactions,
251
- usernames,
252
241
  reactionImage: parse(data.reaction),
253
242
  });
254
243
  } catch (e) {
@@ -258,15 +247,11 @@ async function sendPostEvent(data, eventName) {
258
247
 
259
248
  async function sendMessageEvent(data, eventName) {
260
249
  try {
261
- const [reactionCount, totalReactions, uids] = await Promise.all([
250
+ const [reactionCount, totalReactions] = await Promise.all([
262
251
  db.setCount(`mid:${data.mid}:reaction:${data.reaction}`),
263
252
  db.setCount(`mid:${data.mid}:reactions`),
264
- db.getSetMembers(`mid:${data.mid}:reaction:${data.reaction}`),
265
253
  ]);
266
254
 
267
- const userdata = await user.getUsersFields(uids, ['uid', 'username']);
268
- const usernames = userdata.map(user => user.username).join(', ');
269
-
270
255
  if (parseInt(reactionCount, 10) === 0) {
271
256
  await db.setRemove(`mid:${data.mid}:reactions`, data.reaction);
272
257
  }
@@ -277,7 +262,6 @@ async function sendMessageEvent(data, eventName) {
277
262
  reaction: data.reaction,
278
263
  reactionCount,
279
264
  totalReactions,
280
- usernames,
281
265
  reactionImage: parse(data.reaction),
282
266
  });
283
267
  } catch (e) {
@@ -310,6 +294,9 @@ SocketPlugins.reactions = {
310
294
  }
311
295
 
312
296
  const settings = await meta.settings.get('reactions');
297
+ if (settings.enablePostReactions === 'off') {
298
+ throw new Error('[[error:post-reactions-disabled]]');
299
+ }
313
300
  const maximumReactions = settings.maximumReactions || DEFAULT_MAX_EMOTES;
314
301
  const [tid, totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([
315
302
  posts.getPostField(data.pid, 'tid'),
@@ -360,11 +347,15 @@ SocketPlugins.reactions = {
360
347
  throw new Error('[[reactions:error.invalid-reaction]]');
361
348
  }
362
349
 
363
- const [tid, hasReacted, reactionReputation] = await Promise.all([
350
+ const [settings, tid, hasReacted, reactionReputation] = await Promise.all([
351
+ meta.settings.get('reactions'),
364
352
  posts.getPostField(data.pid, 'tid'),
365
353
  db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid),
366
354
  getReactionReputation(data.reaction),
367
355
  ]);
356
+ if (settings.enablePostReactions === 'off') {
357
+ throw new Error('[[error:post-reactions-disabled]]');
358
+ }
368
359
  if (!tid) {
369
360
  throw new Error('[[error:no-post]]');
370
361
  }
@@ -395,6 +386,9 @@ SocketPlugins.reactions = {
395
386
  }
396
387
 
397
388
  const settings = await meta.settings.get('reactions');
389
+ if (settings.enableMessageReactions === 'off') {
390
+ throw new Error('[[error:post-reactions-disabled]]');
391
+ }
398
392
  const maximumReactionsPerMessage = settings.maximumReactionsPerMessage || DEFAULT_MAX_EMOTES;
399
393
  const [roomId, totalReactions, emojiIsAlreadyExist] = await Promise.all([
400
394
  messaging.getMessageField(data.mid, 'roomId'),
@@ -442,10 +436,14 @@ SocketPlugins.reactions = {
442
436
  throw new Error('[[reactions:error.invalid-reaction]]');
443
437
  }
444
438
 
445
- const [roomId, hasReacted] = await Promise.all([
439
+ const [settings, roomId, hasReacted] = await Promise.all([
440
+ meta.settings.get('reactions'),
446
441
  messaging.getMessageField(data.mid, 'roomId'),
447
442
  db.isSetMember(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid),
448
443
  ]);
444
+ if (settings.enableMessageReactions === 'off') {
445
+ throw new Error('[[error:post-reactions-disabled]]');
446
+ }
449
447
  if (!roomId) {
450
448
  throw new Error('[[error:no-message]]');
451
449
  }
@@ -462,5 +460,43 @@ SocketPlugins.reactions = {
462
460
 
463
461
  await sendMessageEvent(data, 'event:reactions.removeMessageReaction');
464
462
  },
463
+ getReactionUsernames: async function (socket, data) {
464
+ if (!socket.uid) {
465
+ throw new Error('[[error:not-logged-in]]');
466
+ }
467
+ if (!emojiTable[data.reaction]) {
468
+ throw new Error('[[reactions:error.invalid-reaction]]');
469
+ }
470
+ let set = '';
471
+ if (data.type === 'post') {
472
+ if (!await privileges.posts.can('topics:read', data.pid, socket.uid)) {
473
+ throw new Error('[[error:not-allowed]]');
474
+ }
475
+ set = `pid:${data.pid}:reaction:${data.reaction}`;
476
+ } else if (data.type === 'message') {
477
+ const roomId = await messaging.getMessageField(data.mid, 'roomId');
478
+ if (!await messaging.canViewMessage(data.mid, roomId, socket.uid)) {
479
+ throw new Error('[[error:not-allowed]]');
480
+ }
481
+ set = `mid:${data.mid}:reaction:${data.reaction}`;
482
+ } else {
483
+ throw new Error('[[error:invalid-data]]');
484
+ }
485
+ let uids = await db.getSetMembers(set);
486
+ const cutoff = 6;
487
+
488
+ let otherCount = 0;
489
+ if (uids.length > cutoff) {
490
+ otherCount = uids.length - (cutoff - 1);
491
+ uids = uids.slice(0, cutoff - 1);
492
+ }
493
+
494
+ const usernames = await user.getUsernamesByUids(uids);
495
+ return {
496
+ cutoff: cutoff,
497
+ otherCount,
498
+ usernames,
499
+ };
500
+ },
465
501
  };
466
502
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nodebb/nodebb-plugin-reactions",
3
- "version": "2.1.0",
3
+ "version": "2.1.1",
4
4
  "nbbpm": {
5
5
  "compatibility": "^3.3.0"
6
6
  },
package/plugin.json CHANGED
@@ -25,6 +25,9 @@
25
25
  {
26
26
  "hook": "filter:config.get", "method": "getPluginConfig"
27
27
  },
28
+ {
29
+ "hook": "filter:settings.get", "method": "filterSettingsGet"
30
+ },
28
31
  {
29
32
  "hook": "filter:post.getPosts", "method": "getPostReactions"
30
33
  },
package/public/client.js CHANGED
@@ -3,6 +3,8 @@
3
3
  $(document).ready(function () {
4
4
  setupReactions();
5
5
  let alerts;
6
+ let mouseOverReactionEl;
7
+ let tooltipTimeoutId = 0;
6
8
  function setupReactions() {
7
9
  createReactionTooltips();
8
10
  require(['hooks', 'alerts'], function (hooks, _alerts) {
@@ -10,7 +12,11 @@ $(document).ready(function () {
10
12
  hooks.on('action:ajaxify.end', function () {
11
13
  if (ajaxify.data.template.topic) {
12
14
  setupPostReactions();
13
- } else if (ajaxify.data.template.chats && ajaxify.data.roomId) {
15
+ }
16
+ });
17
+ // switchChat uses action:chat.loaded and not action:ajaxify.end
18
+ hooks.on('action:chat.loaded', function () {
19
+ if (ajaxify.data.template.chats && ajaxify.data.roomId) {
14
20
  setupMessageReactions();
15
21
  }
16
22
  });
@@ -186,11 +192,18 @@ $(document).ready(function () {
186
192
  mid: data.mid,
187
193
  reaction: data.reaction,
188
194
  reactionCount: data.reactionCount,
189
- usernames: data.usernames,
190
195
  reacted: isSelf && type === 'add',
191
196
  reactionImage: data.reactionImage,
192
197
  }, function (html) {
193
- $('[component="message/reactions"][data-mid="' + data.mid + '"]').append(html);
198
+ require(['forum/chats/messages'], function (messages) {
199
+ const reactionEl = $('[component="message/reactions"][data-mid="' + data.mid + '"]');
200
+ const chatContentEl = reactionEl.parents('[component="chat/message/content"]');
201
+ const isAtBottom = messages.isAtBottom(chatContentEl);
202
+ reactionEl.append(html);
203
+ if (isAtBottom || isSelf) {
204
+ messages.scrollToBottom(chatContentEl);
205
+ }
206
+ });
194
207
  });
195
208
  } else {
196
209
  reactionEl.find('.reaction-emoji-count').attr('data-count', data.reactionCount);
@@ -203,13 +216,65 @@ $(document).ready(function () {
203
216
  }
204
217
 
205
218
  function createReactionTooltips() {
206
- if (!utils.isTouchDevice()) {
207
- $('#content').tooltip({
208
- selector: '.reaction',
209
- placement: 'top',
210
- container: '#content',
211
- animation: false,
212
- });
213
- }
219
+ require(['bootstrap', 'translator'], function (bootstrap, translator) {
220
+ async function createTooltip(data) {
221
+ if (!mouseOverReactionEl || !mouseOverReactionEl.length) {
222
+ return;
223
+ }
224
+ const el = mouseOverReactionEl;
225
+ let usernames = data.usernames.filter(name => name !== '[[global:former_user]]');
226
+ if (!usernames.length) {
227
+ return;
228
+ }
229
+ if (usernames.length + data.otherCount > data.cutoff) {
230
+ usernames = usernames.join(', ').replace(/,/g, '|');
231
+ usernames = await translator.translate('[[topic:users_and_others, ' + usernames + ', ' + data.otherCount + ']]');
232
+ usernames = usernames.replace(/\|/g, ',');
233
+ } else {
234
+ usernames = usernames.join(', ');
235
+ }
236
+
237
+ el.attr('title', usernames);
238
+ (new bootstrap.Tooltip(el, {
239
+ container: '#content',
240
+ html: true,
241
+ placement: 'top',
242
+ animation: false,
243
+ })).show();
244
+ }
245
+
246
+ if (!utils.isTouchDevice()) {
247
+ $('#content').on('mouseenter', '.reaction', function () {
248
+ const $this = $(this);
249
+ mouseOverReactionEl = $this;
250
+ const mid = $this.attr('data-mid');
251
+ const pid = $this.attr('data-pid');
252
+ tooltipTimeoutId = setTimeout(async () => {
253
+ if (mouseOverReactionEl && mouseOverReactionEl.length) {
254
+ const d = await socket.emit('plugins.reactions.getReactionUsernames', {
255
+ type: pid ? 'post' : 'message',
256
+ mid: mid,
257
+ pid: pid,
258
+ reaction: $this.attr('data-reaction'),
259
+ });
260
+ createTooltip(d);
261
+ }
262
+ }, 200);
263
+ });
264
+ $('#content').on('mouseleave', '.reaction', function () {
265
+ if (tooltipTimeoutId) {
266
+ clearTimeout(tooltipTimeoutId);
267
+ tooltipTimeoutId = 0;
268
+ }
269
+ mouseOverReactionEl = null;
270
+ const $this = $(this);
271
+ const tooltip = bootstrap.Tooltip.getInstance(this);
272
+ if (tooltip) {
273
+ tooltip.dispose();
274
+ $this.attr('title', '');
275
+ }
276
+ });
277
+ }
278
+ });
214
279
  }
215
280
  });
@@ -7,10 +7,13 @@
7
7
  <form role="form" class="reactions-settings">
8
8
  <div class="mb-3">
9
9
  <h5 class="fw-bold tracking-tight settings-header">[[reactions:settings.title]]</h5>
10
+ <div class="form-check form-switch mb-3">
11
+ <input id="enablePostReactions" name="enablePostReactions" type="checkbox" class="form-check-input">
12
+ <label for="enablePostReactions" class="form-check-label">[[reactions:settings.enable-post-reactions]]</label>
13
+ </div>
10
14
  <div class="mb-3">
11
15
  <label class="form-label">[[reactions:settings.max-reactions-per-post]]</label>
12
16
  <input type="number" min="0" class="form-control" id="maximumReactions" name="maximumReactions">
13
-
14
17
  </div>
15
18
  <div class="mb-3">
16
19
  <label class="form-label">[[reactions:settings.max-reactions-per-user-per-post]]</label>
@@ -19,6 +22,11 @@
19
22
  [[reactions:settings.max-reactions-per-user-per-post-help]]
20
23
  </p>
21
24
  </div>
25
+ <hr/>
26
+ <div class="form-check form-switch mb-3">
27
+ <input id="enableMessageReactions" name="enableMessageReactions" type="checkbox" class="form-check-input">
28
+ <label for="enableMessageReactions" class="form-check-label">[[reactions:settings.enable-message-reactions]]</label>
29
+ </div>
22
30
  <div class="mb-3">
23
31
  <label class="form-label">[[reactions:settings.max-reactions-per-message]]</label>
24
32
  <input type="number" min="0" class="form-control" id="maximumReactionsPerMessage" name="maximumReactionsPerMessage">
@@ -1,4 +1,6 @@
1
+ {{{ if config.enableMessageReactions }}}
1
2
  <button class="reaction-add btn btn-sm btn-link {{{ if ./maxReactionsReached }}}max-reactions{{{ end }}}" component="message/reaction/add" data-mid="{./mid}" title="[[reactions:add-reaction]]">
2
3
  <i class="fa fa-face-smile"></i>
3
4
  </button>
5
+ {{{ end }}}
4
6
 
@@ -1,4 +1,4 @@
1
- <span class="reaction mb-2 {{{ if ./reacted }}}reacted{{{ end }}}" component="message/reaction" data-mid="{./mid}" data-reaction="{./reaction}" title="{./usernames}" data-bs-toggle="tooltip">
1
+ <span class="reaction mb-2 {{{ if ./reacted }}}reacted{{{ end }}}" component="message/reaction" data-mid="{./mid}" data-reaction="{./reaction}">
2
2
  {./reactionImage}
3
3
  <small class="reaction-emoji-count" data-count="{./reactionCount}"></small>
4
4
  </span>
@@ -1,5 +1,7 @@
1
+ {{{ if config.enableMessageReactions }}}
1
2
  <div class="reactions {{{ if ./deleted}}}hidden{{{ end }}}" component="message/reactions" data-mid="{./mid}">
2
3
  {{{ each ./reactions }}}
3
4
  <!-- IMPORT partials/chats/reaction.tpl -->
4
5
  {{{ end }}}
5
- </div>
6
+ </div>
7
+ {{{ end }}}
@@ -1,4 +1,4 @@
1
- <span class="reaction {{{ if ./reacted }}}reacted{{{ end }}}" component="post/reaction" data-pid="{./pid}" data-reaction="{./reaction}" title="{./usernames}">
1
+ <span class="reaction {{{ if ./reacted }}}reacted{{{ end }}}" component="post/reaction" data-pid="{./pid}" data-reaction="{./reaction}">
2
2
  {./reactionImage}
3
3
  <small class="reaction-emoji-count" data-count="{./reactionCount}"></small>
4
4
  </span>
@@ -1,3 +1,4 @@
1
+ {{{ if config.enablePostReactions }}}
1
2
  <span class="reactions" component="post/reactions" data-pid="{./pid}">
2
3
  <span class="reaction-add d-inline-block px-2 mx-1 btn-ghost-sm {{{ if ./maxReactionsReached }}}max-reactions{{{ end }}}" component="post/reaction/add" data-pid="{./pid}" title="[[reactions:add-reaction]]">
3
4
  <i class="fa fa-face-smile text-primary"></i>
@@ -5,4 +6,5 @@
5
6
  {{{ each ./reactions }}}
6
7
  <!-- IMPORT partials/topic/reaction.tpl -->
7
8
  {{{ end }}}
8
- </span>
9
+ </span>
10
+ {{{ end }}}