@nodebb/nodebb-plugin-reactions 2.0.1 → 2.1.0
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.
- package/README.md +2 -4
- package/assets/acp.png +0 -0
- package/assets/demo.png +0 -0
- package/languages/en-GB/reactions.json +3 -0
- package/library.js +221 -33
- package/package.json +2 -2
- package/plugin.json +4 -1
- package/public/client.js +161 -64
- package/scss/reactions.scss +2 -2
- package/templates/admin/plugins/reactions.tpl +48 -29
- package/templates/partials/chats/add-reaction.tpl +4 -0
- package/templates/partials/chats/reaction.tpl +4 -0
- package/templates/partials/chats/reactions.tpl +5 -0
- package/templates/partials/topic/reactions.tpl +1 -1
package/README.md
CHANGED
|
@@ -4,9 +4,7 @@ Reactions plugin for NodeBB
|
|
|
4
4
|
# Screenshots
|
|
5
5
|
|
|
6
6
|
## Reactions:
|
|
7
|
-

|
|
9
8
|
|
|
10
9
|
## ACP:
|
|
11
|
-

|
package/assets/acp.png
ADDED
|
Binary file
|
package/assets/demo.png
ADDED
|
Binary file
|
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
"settings.max-reactions-per-post": "Maximum unique reactions per post (0 for unlimited, default: 4)",
|
|
9
9
|
"settings.max-reactions-per-user-per-post": "Maximum reactions per user per post (0 for unlimited)",
|
|
10
10
|
"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.",
|
|
11
|
+
"settings.max-reactions-per-message": "Maximum unique reactions per message (0 for unlimited, default: 4)",
|
|
12
|
+
"settings.max-reactions-per-user-per-message": "Maximum reactions per user per message (0 for unlimited)",
|
|
13
|
+
"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.",
|
|
11
14
|
"settings.reaction-reputations": "Reaction Reputations (Optional)",
|
|
12
15
|
"settings.reaction-reputations-help": "You can assign a reputation to individual reactions. When a reaction is applied to a post, the owner of that post will get this reputation.",
|
|
13
16
|
"settings.reaction-reputations.add": "Add Rule",
|
package/library.js
CHANGED
|
@@ -4,6 +4,7 @@ const _ = require.main.require('lodash');
|
|
|
4
4
|
const meta = require.main.require('./src/meta');
|
|
5
5
|
const user = require.main.require('./src/user');
|
|
6
6
|
const posts = require.main.require('./src/posts');
|
|
7
|
+
const messaging = require.main.require('./src/messaging');
|
|
7
8
|
const db = require.main.require('./src/database');
|
|
8
9
|
const routesHelpers = require.main.require('./src/routes/helpers');
|
|
9
10
|
const websockets = require.main.require('./src/socket.io/index');
|
|
@@ -19,13 +20,14 @@ function parse(name) {
|
|
|
19
20
|
return emojiParser.buildEmoji(emojiTable[name] || emojiTable[emojiAliases[name]], '');
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
const ReactionsPlugin =
|
|
23
|
+
const ReactionsPlugin = module.exports;
|
|
23
24
|
|
|
24
25
|
ReactionsPlugin.init = async function (params) {
|
|
25
|
-
|
|
26
|
-
res.render('admin/plugins/reactions', {
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
routesHelpers.setupAdminPageRoute(params.router, '/admin/plugins/reactions', (req, res) => {
|
|
27
|
+
res.render('admin/plugins/reactions', {
|
|
28
|
+
title: '[[reactions:reactions]]',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
29
31
|
};
|
|
30
32
|
|
|
31
33
|
ReactionsPlugin.addAdminNavigation = async function (header) {
|
|
@@ -41,13 +43,15 @@ ReactionsPlugin.getPluginConfig = async function (config) {
|
|
|
41
43
|
try {
|
|
42
44
|
const settings = await meta.settings.get('reactions');
|
|
43
45
|
config.maximumReactions = settings.maximumReactions ? parseInt(settings.maximumReactions, 10) : DEFAULT_MAX_EMOTES;
|
|
46
|
+
config.maximumReactionsPerMessage = settings.maximumReactionsPerMessage ?
|
|
47
|
+
parseInt(settings.maximumReactionsPerMessage, 10) : DEFAULT_MAX_EMOTES;
|
|
44
48
|
} catch (e) {
|
|
45
49
|
console.error(e);
|
|
46
50
|
}
|
|
47
51
|
return config;
|
|
48
52
|
};
|
|
49
53
|
|
|
50
|
-
ReactionsPlugin.
|
|
54
|
+
ReactionsPlugin.getPostReactions = async function (data) {
|
|
51
55
|
if (data.uid === 0) {
|
|
52
56
|
return data;
|
|
53
57
|
}
|
|
@@ -126,6 +130,81 @@ ReactionsPlugin.getReactions = async function (data) {
|
|
|
126
130
|
return data;
|
|
127
131
|
};
|
|
128
132
|
|
|
133
|
+
ReactionsPlugin.getMessageReactions = async function (data) {
|
|
134
|
+
if (data.uid === 0) {
|
|
135
|
+
return data;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
try {
|
|
139
|
+
const settings = await meta.settings.get('reactions');
|
|
140
|
+
const maximumReactionsPerMessage = settings.maximumReactionsPerMessage || DEFAULT_MAX_EMOTES;
|
|
141
|
+
|
|
142
|
+
const mids = data.messages.map(message => message && parseInt(message.mid, 10));
|
|
143
|
+
const allReactionsForMids = await db.getSetsMembers(mids.map(pid => `mid:${pid}:reactions`));
|
|
144
|
+
|
|
145
|
+
const midToIsMaxReactionsReachedMap = new Map(); // mid -> IsMaxReactionsReached (boolean)
|
|
146
|
+
const midToReactionsMap = new Map(); // mid -> reactions (string[])
|
|
147
|
+
let reactionSets = [];
|
|
148
|
+
|
|
149
|
+
for (let i = 0, len = mids.length; i < len; i++) {
|
|
150
|
+
const mid = mids[i];
|
|
151
|
+
const reactionsList = allReactionsForMids[i];
|
|
152
|
+
const reactionsCount = reactionsList.length;
|
|
153
|
+
|
|
154
|
+
if (reactionsList && reactionsList.length > 0) {
|
|
155
|
+
midToReactionsMap.set(mid, reactionsList);
|
|
156
|
+
midToIsMaxReactionsReachedMap.set(mid, reactionsCount > maximumReactionsPerMessage);
|
|
157
|
+
reactionSets = reactionSets.concat(reactionsList.map(reaction => `mid:${mid}:reaction:${reaction}`));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
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
|
+
}
|
|
176
|
+
|
|
177
|
+
for (const msg of data.messages) {
|
|
178
|
+
msg.maxReactionsReached = midToIsMaxReactionsReachedMap.get(msg.mid);
|
|
179
|
+
msg.reactions = [];
|
|
180
|
+
|
|
181
|
+
if (midToReactionsMap.has(msg.mid)) {
|
|
182
|
+
for (const reaction of midToReactionsMap.get(msg.mid)) {
|
|
183
|
+
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
|
+
|
|
190
|
+
msg.reactions.push({
|
|
191
|
+
mid: msg.mid,
|
|
192
|
+
reacted: reactedUids.includes(data.uid),
|
|
193
|
+
reaction,
|
|
194
|
+
usernames: reactedUsernames,
|
|
195
|
+
reactionImage: parse(reaction),
|
|
196
|
+
reactionCount,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
} catch (e) {
|
|
203
|
+
console.error(e);
|
|
204
|
+
}
|
|
205
|
+
return data;
|
|
206
|
+
};
|
|
207
|
+
|
|
129
208
|
ReactionsPlugin.onReply = async function (data) {
|
|
130
209
|
if (data.uid !== 0) {
|
|
131
210
|
data.reactions = [];
|
|
@@ -148,7 +227,7 @@ ReactionsPlugin.deleteReactions = async function (hookData) {
|
|
|
148
227
|
await db.deleteAll(keys);
|
|
149
228
|
};
|
|
150
229
|
|
|
151
|
-
async function
|
|
230
|
+
async function sendPostEvent(data, eventName) {
|
|
152
231
|
try {
|
|
153
232
|
const [reactionCount, totalReactions, uids] = await Promise.all([
|
|
154
233
|
db.setCount(`pid:${data.pid}:reaction:${data.reaction}`),
|
|
@@ -177,6 +256,35 @@ async function sendEvent(data, eventName) {
|
|
|
177
256
|
}
|
|
178
257
|
}
|
|
179
258
|
|
|
259
|
+
async function sendMessageEvent(data, eventName) {
|
|
260
|
+
try {
|
|
261
|
+
const [reactionCount, totalReactions, uids] = await Promise.all([
|
|
262
|
+
db.setCount(`mid:${data.mid}:reaction:${data.reaction}`),
|
|
263
|
+
db.setCount(`mid:${data.mid}:reactions`),
|
|
264
|
+
db.getSetMembers(`mid:${data.mid}:reaction:${data.reaction}`),
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
const userdata = await user.getUsersFields(uids, ['uid', 'username']);
|
|
268
|
+
const usernames = userdata.map(user => user.username).join(', ');
|
|
269
|
+
|
|
270
|
+
if (parseInt(reactionCount, 10) === 0) {
|
|
271
|
+
await db.setRemove(`mid:${data.mid}:reactions`, data.reaction);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
await websockets.in(`chat_room_${data.roomId}`).emit(eventName, {
|
|
275
|
+
mid: data.mid,
|
|
276
|
+
uid: data.uid,
|
|
277
|
+
reaction: data.reaction,
|
|
278
|
+
reactionCount,
|
|
279
|
+
totalReactions,
|
|
280
|
+
usernames,
|
|
281
|
+
reactionImage: parse(data.reaction),
|
|
282
|
+
});
|
|
283
|
+
} catch (e) {
|
|
284
|
+
console.error(e);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
180
288
|
async function getReactionReputation(reaction) {
|
|
181
289
|
const settings = await meta.settings.get('reactions');
|
|
182
290
|
const reactionsReps = settings['reaction-reputations'] || [];
|
|
@@ -201,23 +309,27 @@ SocketPlugins.reactions = {
|
|
|
201
309
|
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
202
310
|
}
|
|
203
311
|
|
|
204
|
-
data.uid = socket.uid;
|
|
205
|
-
|
|
206
312
|
const settings = await meta.settings.get('reactions');
|
|
207
313
|
const maximumReactions = settings.maximumReactions || DEFAULT_MAX_EMOTES;
|
|
208
|
-
const [totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([
|
|
314
|
+
const [tid, totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([
|
|
315
|
+
posts.getPostField(data.pid, 'tid'),
|
|
209
316
|
db.setCount(`pid:${data.pid}:reactions`),
|
|
210
317
|
db.isSetMember(`pid:${data.pid}:reactions`, data.reaction),
|
|
211
318
|
db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid),
|
|
212
319
|
getReactionReputation(data.reaction),
|
|
213
320
|
]);
|
|
214
|
-
|
|
321
|
+
if (!tid) {
|
|
322
|
+
throw new Error('[[error:no-post]]');
|
|
323
|
+
}
|
|
324
|
+
data.uid = socket.uid;
|
|
325
|
+
data.tid = tid;
|
|
215
326
|
if (!emojiIsAlreadyExist) {
|
|
216
327
|
if (totalReactions > maximumReactions) {
|
|
217
328
|
throw new Error(`[[reactions:error.maximum-reached]] (${maximumReactions})`);
|
|
218
329
|
}
|
|
219
|
-
|
|
220
|
-
const maximumReactionsPerUserPerPost = settings.maximumReactionsPerUserPerPost ?
|
|
330
|
+
|
|
331
|
+
const maximumReactionsPerUserPerPost = settings.maximumReactionsPerUserPerPost ?
|
|
332
|
+
parseInt(settings.maximumReactionsPerUserPerPost, 10) : 0;
|
|
221
333
|
if (maximumReactionsPerUserPerPost > 0) {
|
|
222
334
|
const emojiesInPost = await db.getSetMembers(`pid:${data.pid}:reactions`);
|
|
223
335
|
const userPostReactions = await db.isMemberOfSets(emojiesInPost.map(emojiName => `pid:${data.pid}:reaction:${emojiName}`), socket.uid);
|
|
@@ -227,7 +339,6 @@ SocketPlugins.reactions = {
|
|
|
227
339
|
}
|
|
228
340
|
}
|
|
229
341
|
}
|
|
230
|
-
|
|
231
342
|
|
|
232
343
|
await Promise.all([
|
|
233
344
|
db.setAdd(`pid:${data.pid}:reactions`, data.reaction),
|
|
@@ -238,7 +349,7 @@ SocketPlugins.reactions = {
|
|
|
238
349
|
await giveOwnerReactionReputation(reactionReputation, data.pid);
|
|
239
350
|
}
|
|
240
351
|
|
|
241
|
-
await
|
|
352
|
+
await sendPostEvent(data, 'event:reactions.addPostReaction');
|
|
242
353
|
},
|
|
243
354
|
removePostReaction: async function (socket, data) {
|
|
244
355
|
if (!socket.uid) {
|
|
@@ -249,30 +360,107 @@ SocketPlugins.reactions = {
|
|
|
249
360
|
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
250
361
|
}
|
|
251
362
|
|
|
363
|
+
const [tid, hasReacted, reactionReputation] = await Promise.all([
|
|
364
|
+
posts.getPostField(data.pid, 'tid'),
|
|
365
|
+
db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid),
|
|
366
|
+
getReactionReputation(data.reaction),
|
|
367
|
+
]);
|
|
368
|
+
if (!tid) {
|
|
369
|
+
throw new Error('[[error:no-post]]');
|
|
370
|
+
}
|
|
252
371
|
data.uid = socket.uid;
|
|
372
|
+
data.tid = tid;
|
|
253
373
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
374
|
+
if (hasReacted) {
|
|
375
|
+
await db.setRemove(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid);
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const reactionCount = await db.setCount(`pid:${data.pid}:reaction:${data.reaction}`);
|
|
379
|
+
if (reactionCount === 0) {
|
|
380
|
+
await db.setRemove(`pid:${data.pid}:reactions`, data.reaction);
|
|
381
|
+
}
|
|
382
|
+
if (hasReacted && reactionReputation > 0) {
|
|
383
|
+
await giveOwnerReactionReputation(-reactionReputation, data.pid);
|
|
384
|
+
}
|
|
262
385
|
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
386
|
+
await sendPostEvent(data, 'event:reactions.removePostReaction');
|
|
387
|
+
},
|
|
388
|
+
addMessageReaction: async function (socket, data) {
|
|
389
|
+
if (!socket.uid) {
|
|
390
|
+
throw new Error('[[error:not-logged-in]]');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!emojiTable[data.reaction]) {
|
|
394
|
+
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const settings = await meta.settings.get('reactions');
|
|
398
|
+
const maximumReactionsPerMessage = settings.maximumReactionsPerMessage || DEFAULT_MAX_EMOTES;
|
|
399
|
+
const [roomId, totalReactions, emojiIsAlreadyExist] = await Promise.all([
|
|
400
|
+
messaging.getMessageField(data.mid, 'roomId'),
|
|
401
|
+
db.setCount(`mid:${data.mid}:reactions`),
|
|
402
|
+
db.isSetMember(`mid:${data.mid}:reactions`, data.reaction),
|
|
403
|
+
]);
|
|
404
|
+
|
|
405
|
+
if (!roomId) {
|
|
406
|
+
throw new Error('[[error:no-message]]');
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
data.uid = socket.uid;
|
|
410
|
+
data.roomId = roomId;
|
|
411
|
+
|
|
412
|
+
if (!emojiIsAlreadyExist) {
|
|
413
|
+
if (totalReactions > maximumReactionsPerMessage) {
|
|
414
|
+
throw new Error(`[[reactions:error.maximum-reached]] (${maximumReactionsPerMessage})`);
|
|
266
415
|
}
|
|
267
|
-
|
|
268
|
-
|
|
416
|
+
|
|
417
|
+
const maximumReactionsPerUserPerMessage = settings.maximumReactionsPerUserPerMessage ?
|
|
418
|
+
parseInt(settings.maximumReactionsPerUserPerMessage, 10) : 0;
|
|
419
|
+
if (maximumReactionsPerUserPerMessage > 0) {
|
|
420
|
+
const emojiesInMessage = await db.getSetMembers(`mid:${data.mid}:reactions`);
|
|
421
|
+
const userPostReactions = await db.isMemberOfSets(emojiesInMessage.map(emojiName => `mid:${data.mid}:reaction:${emojiName}`), socket.uid);
|
|
422
|
+
const userPostReactionCount = userPostReactions.filter(Boolean).length;
|
|
423
|
+
if (userPostReactionCount > maximumReactionsPerUserPerMessage) {
|
|
424
|
+
throw new Error(`[[reactions:error.maximum-per-user-per-post-reached]] (${maximumReactionsPerUserPerMessage})`);
|
|
425
|
+
}
|
|
269
426
|
}
|
|
270
|
-
await sendEvent(data, 'event:reactions.removePostReaction');
|
|
271
|
-
} catch (e) {
|
|
272
|
-
console.error(e);
|
|
273
427
|
}
|
|
428
|
+
|
|
429
|
+
await Promise.all([
|
|
430
|
+
db.setAdd(`mid:${data.mid}:reactions`, data.reaction),
|
|
431
|
+
db.setAdd(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid),
|
|
432
|
+
]);
|
|
433
|
+
|
|
434
|
+
await sendMessageEvent(data, 'event:reactions.addMessageReaction');
|
|
274
435
|
},
|
|
275
|
-
|
|
436
|
+
removeMessageReaction: async function (socket, data) {
|
|
437
|
+
if (!socket.uid) {
|
|
438
|
+
throw new Error('[[error:not-logged-in]]');
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
if (!emojiTable[data.reaction]) {
|
|
442
|
+
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const [roomId, hasReacted] = await Promise.all([
|
|
446
|
+
messaging.getMessageField(data.mid, 'roomId'),
|
|
447
|
+
db.isSetMember(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid),
|
|
448
|
+
]);
|
|
449
|
+
if (!roomId) {
|
|
450
|
+
throw new Error('[[error:no-message]]');
|
|
451
|
+
}
|
|
452
|
+
data.uid = socket.uid;
|
|
453
|
+
data.roomId = roomId;
|
|
454
|
+
if (hasReacted) {
|
|
455
|
+
await db.setRemove(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid);
|
|
456
|
+
}
|
|
276
457
|
|
|
458
|
+
const reactionCount = await db.setCount(`mid:${data.mid}:reaction:${data.reaction}`);
|
|
459
|
+
if (reactionCount === 0) {
|
|
460
|
+
await db.setRemove(`mid:${data.mid}:reactions`, data.reaction);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
await sendMessageEvent(data, 'event:reactions.removeMessageReaction');
|
|
464
|
+
},
|
|
465
|
+
};
|
|
277
466
|
|
|
278
|
-
module.exports = ReactionsPlugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -26,7 +26,10 @@
|
|
|
26
26
|
"hook": "filter:config.get", "method": "getPluginConfig"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
"hook": "filter:post.getPosts", "method": "
|
|
29
|
+
"hook": "filter:post.getPosts", "method": "getPostReactions"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"hook": "filter:messaging.getMessages", "method": "getMessageReactions"
|
|
30
33
|
},
|
|
31
34
|
{
|
|
32
35
|
"hook": "filter:post.get", "method": "onReply"
|
package/public/client.js
CHANGED
|
@@ -1,55 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
$(document).ready(function () {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
function
|
|
7
|
-
|
|
4
|
+
setupReactions();
|
|
5
|
+
let alerts;
|
|
6
|
+
function setupReactions() {
|
|
7
|
+
createReactionTooltips();
|
|
8
|
+
require(['hooks', 'alerts'], function (hooks, _alerts) {
|
|
9
|
+
alerts = _alerts;
|
|
8
10
|
hooks.on('action:ajaxify.end', function () {
|
|
9
11
|
if (ajaxify.data.template.topic) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
var tid = $('[component="topic"]').attr('data-tid');
|
|
14
|
-
var reactionElement = $(this);
|
|
15
|
-
var pid = reactionElement.attr('data-pid');
|
|
16
|
-
var reaction = reactionElement.attr('data-reaction');
|
|
17
|
-
var reacted = reactionElement.hasClass('reacted');
|
|
18
|
-
var event = 'plugins.reactions.' + (reacted ? 'removePostReaction' : 'addPostReaction');
|
|
19
|
-
socket.emit(event, {
|
|
20
|
-
tid: tid,
|
|
21
|
-
pid: pid,
|
|
22
|
-
reaction: reaction,
|
|
23
|
-
}, function (err) {
|
|
24
|
-
if (err) {
|
|
25
|
-
alerts.error(err.message);
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
$('[component="topic"]').on('click', '[component="post/reaction/add"]', function () {
|
|
31
|
-
var reactionAddEl = $(this);
|
|
32
|
-
var tid = $('[component="topic"]').attr('data-tid');
|
|
33
|
-
var pid = reactionAddEl.attr('data-pid');
|
|
34
|
-
require(['emoji-dialog'], function (emojiDialog) {
|
|
35
|
-
emojiDialog.toggle(reactionAddEl[0], function (_, name, dialog) {
|
|
36
|
-
emojiDialog.dialogActions.close(dialog);
|
|
37
|
-
|
|
38
|
-
socket.emit('plugins.reactions.addPostReaction', {
|
|
39
|
-
tid: tid,
|
|
40
|
-
pid: pid,
|
|
41
|
-
reaction: name,
|
|
42
|
-
}, function (err) {
|
|
43
|
-
if (err) {
|
|
44
|
-
alerts.error(err.message);
|
|
45
|
-
throw err;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
$('[component="post/reaction"][data-pid="' + pid + '"][data-reaction="' + name + '"]').addClass('reacted');
|
|
49
|
-
});
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
});
|
|
12
|
+
setupPostReactions();
|
|
13
|
+
} else if (ajaxify.data.template.chats && ajaxify.data.roomId) {
|
|
14
|
+
setupMessageReactions();
|
|
53
15
|
}
|
|
54
16
|
});
|
|
55
17
|
});
|
|
@@ -61,19 +23,122 @@ $(document).ready(function () {
|
|
|
61
23
|
socket.on('event:post_restored', function (data) {
|
|
62
24
|
$('[component="post/reactions"][data-pid="' + data.pid + '"]').removeClass('hidden');
|
|
63
25
|
});
|
|
26
|
+
|
|
27
|
+
socket.on('event:chats.delete', function (mid) {
|
|
28
|
+
$('[component="message/reactions"][data-mid="' + mid + '"]').addClass('hidden');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
socket.on('event:chats.restore', function (msg) {
|
|
32
|
+
$('[component="message/reactions"][data-mid="' + msg.mid + '"]').removeClass('hidden');
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function setupPostReactions() {
|
|
37
|
+
setupPostReactionSockets();
|
|
38
|
+
|
|
39
|
+
$('[component="topic"]').on('click', '[component="post/reaction"]', function () {
|
|
40
|
+
var reactionElement = $(this);
|
|
41
|
+
var pid = reactionElement.attr('data-pid');
|
|
42
|
+
var reaction = reactionElement.attr('data-reaction');
|
|
43
|
+
var reacted = reactionElement.hasClass('reacted');
|
|
44
|
+
var event = 'plugins.reactions.' + (reacted ? 'removePostReaction' : 'addPostReaction');
|
|
45
|
+
socket.emit(event, {
|
|
46
|
+
pid: pid,
|
|
47
|
+
reaction: reaction,
|
|
48
|
+
}, function (err) {
|
|
49
|
+
if (err) {
|
|
50
|
+
alerts.error(err.message);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
$('[component="topic"]').on('click', '[component="post/reaction/add"]', function () {
|
|
56
|
+
var reactionAddEl = $(this);
|
|
57
|
+
var pid = reactionAddEl.attr('data-pid');
|
|
58
|
+
require(['emoji-dialog'], function (emojiDialog) {
|
|
59
|
+
emojiDialog.toggle(reactionAddEl[0], function (_, name, dialog) {
|
|
60
|
+
emojiDialog.dialogActions.close(dialog);
|
|
61
|
+
|
|
62
|
+
socket.emit('plugins.reactions.addPostReaction', {
|
|
63
|
+
pid: pid,
|
|
64
|
+
reaction: name,
|
|
65
|
+
}, function (err) {
|
|
66
|
+
if (err) {
|
|
67
|
+
alerts.error(err.message);
|
|
68
|
+
throw err;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
$('[component="post/reaction"][data-pid="' + pid + '"][data-reaction="' + name + '"]').addClass('reacted');
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function setupMessageReactions() {
|
|
79
|
+
setupMessageReactionSockets();
|
|
80
|
+
|
|
81
|
+
const messageContent = $('[component="chat/message/content"]');
|
|
82
|
+
messageContent.on('click', '[component="message/reaction"]', function () {
|
|
83
|
+
var reactionElement = $(this);
|
|
84
|
+
var mid = reactionElement.attr('data-mid');
|
|
85
|
+
var reaction = reactionElement.attr('data-reaction');
|
|
86
|
+
var reacted = reactionElement.hasClass('reacted');
|
|
87
|
+
var event = 'plugins.reactions.' + (reacted ? 'removeMessageReaction' : 'addMessageReaction');
|
|
88
|
+
socket.emit(event, {
|
|
89
|
+
mid: mid,
|
|
90
|
+
reaction: reaction,
|
|
91
|
+
}, function (err) {
|
|
92
|
+
if (err) {
|
|
93
|
+
alerts.error(err.message);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
messageContent.on('click', '[component="message/reaction/add"]', function () {
|
|
99
|
+
const reactionAddEl = $(this);
|
|
100
|
+
const mid = reactionAddEl.attr('data-mid');
|
|
101
|
+
|
|
102
|
+
require(['emoji-dialog'], function (emojiDialog) {
|
|
103
|
+
emojiDialog.toggle(reactionAddEl[0], function (_, name, dialog) {
|
|
104
|
+
emojiDialog.dialogActions.close(dialog);
|
|
105
|
+
|
|
106
|
+
socket.emit('plugins.reactions.addMessageReaction', {
|
|
107
|
+
mid: mid,
|
|
108
|
+
reaction: name,
|
|
109
|
+
}, function (err) {
|
|
110
|
+
if (err) {
|
|
111
|
+
return alerts.error(err.message);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
$('[component="message/reaction"][data-mid="' + mid + '"][data-reaction="' + name + '"]').addClass('reacted');
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
});
|
|
64
119
|
}
|
|
65
120
|
|
|
66
|
-
function
|
|
121
|
+
function setupPostReactionSockets() {
|
|
67
122
|
socket.off('event:reactions.addPostReaction').on('event:reactions.addPostReaction', function (data) {
|
|
68
|
-
|
|
123
|
+
updatePostReactionCount(data, 'add');
|
|
69
124
|
});
|
|
70
125
|
|
|
71
126
|
socket.off('event:reactions.removePostReaction').on('event:reactions.removePostReaction', function (data) {
|
|
72
|
-
|
|
127
|
+
updatePostReactionCount(data, 'remove');
|
|
73
128
|
});
|
|
74
129
|
}
|
|
75
130
|
|
|
76
|
-
function
|
|
131
|
+
function setupMessageReactionSockets() {
|
|
132
|
+
socket.off('event:reactions.addMessageReaction').on('event:reactions.addMessageReaction', function (data) {
|
|
133
|
+
updateMessageReactionCount(data, 'add');
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
socket.off('event:reactions.removeMessageReaction').on('event:reactions.removeMessageReaction', function (data) {
|
|
137
|
+
updateMessageReactionCount(data, 'remove');
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function updatePostReactionCount(data, type) {
|
|
77
142
|
var maxReactionsReached = parseInt(data.totalReactions, 10) > config.maximumReactions;
|
|
78
143
|
$('[component="post/reaction/add"][data-pid="' + data.pid + '"]').toggleClass('max-reactions', maxReactionsReached);
|
|
79
144
|
|
|
@@ -83,14 +148,14 @@ $(document).ready(function () {
|
|
|
83
148
|
reactionEl.tooltip('dispose');
|
|
84
149
|
reactionEl.remove();
|
|
85
150
|
}
|
|
86
|
-
|
|
151
|
+
const isSelf = parseInt(data.uid, 10) === app.user.uid;
|
|
87
152
|
if (reactionEl.length === 0) {
|
|
88
153
|
app.parseAndTranslate('partials/topic/reaction', {
|
|
89
154
|
pid: data.pid,
|
|
90
155
|
reaction: data.reaction,
|
|
91
156
|
reactionCount: data.reactionCount,
|
|
92
157
|
usernames: data.usernames,
|
|
93
|
-
reacted:
|
|
158
|
+
reacted: isSelf && type === 'add',
|
|
94
159
|
reactionImage: data.reactionImage,
|
|
95
160
|
}, function (html) {
|
|
96
161
|
$('[component="post/reactions"][data-pid="' + data.pid + '"]').append(html);
|
|
@@ -99,20 +164,52 @@ $(document).ready(function () {
|
|
|
99
164
|
reactionEl.find('.reaction-emoji-count').attr('data-count', data.reactionCount);
|
|
100
165
|
reactionEl.attr('data-bs-original-title', data.usernames);
|
|
101
166
|
reactionEl.attr('aria-label', data.usernames);
|
|
102
|
-
|
|
167
|
+
if (isSelf) {
|
|
168
|
+
reactionEl.toggleClass('reacted', type === 'add');
|
|
169
|
+
}
|
|
103
170
|
}
|
|
104
|
-
createReactionTooltips();
|
|
105
171
|
}
|
|
106
172
|
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
173
|
+
function updateMessageReactionCount(data, type) {
|
|
174
|
+
var maxReactionsReached = parseInt(data.totalReactions, 10) > config.maximumReactionsPerMessage;
|
|
175
|
+
$('[component="message/reaction/add"][data-mid="' + data.mid + '"]').toggleClass('max-reactions', maxReactionsReached);
|
|
176
|
+
|
|
177
|
+
var reactionEl = $(`[component="message/reaction"][data-mid="${data.mid}"][data-reaction="${data.reaction}"]`);
|
|
178
|
+
|
|
179
|
+
if (parseInt(data.reactionCount, 10) === 0) {
|
|
180
|
+
reactionEl.tooltip('dispose');
|
|
181
|
+
reactionEl.remove();
|
|
182
|
+
}
|
|
183
|
+
const isSelf = (parseInt(data.uid, 10) === app.user.uid);
|
|
184
|
+
if (reactionEl.length === 0) {
|
|
185
|
+
app.parseAndTranslate('partials/chats/reaction', {
|
|
186
|
+
mid: data.mid,
|
|
187
|
+
reaction: data.reaction,
|
|
188
|
+
reactionCount: data.reactionCount,
|
|
189
|
+
usernames: data.usernames,
|
|
190
|
+
reacted: isSelf && type === 'add',
|
|
191
|
+
reactionImage: data.reactionImage,
|
|
192
|
+
}, function (html) {
|
|
193
|
+
$('[component="message/reactions"][data-mid="' + data.mid + '"]').append(html);
|
|
194
|
+
});
|
|
195
|
+
} else {
|
|
196
|
+
reactionEl.find('.reaction-emoji-count').attr('data-count', data.reactionCount);
|
|
197
|
+
reactionEl.attr('data-bs-original-title', data.usernames);
|
|
198
|
+
reactionEl.attr('aria-label', data.usernames);
|
|
199
|
+
if (isSelf) {
|
|
200
|
+
reactionEl.toggleClass('reacted', type === 'add');
|
|
115
201
|
}
|
|
116
|
-
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function createReactionTooltips() {
|
|
206
|
+
if (!utils.isTouchDevice()) {
|
|
207
|
+
$('#content').tooltip({
|
|
208
|
+
selector: '.reaction',
|
|
209
|
+
placement: 'top',
|
|
210
|
+
container: '#content',
|
|
211
|
+
animation: false,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
117
214
|
}
|
|
118
215
|
});
|
package/scss/reactions.scss
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
.reactions {
|
|
2
2
|
.reaction {
|
|
3
|
-
border: 1px solid
|
|
3
|
+
border: 1px solid var(--bs-primary-bg-subtle);
|
|
4
4
|
display: inline-block;
|
|
5
5
|
border-radius: 4px;
|
|
6
6
|
padding: 2px 4px;
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
height: 18px;
|
|
11
11
|
}
|
|
12
12
|
&.reacted {
|
|
13
|
-
border: 1px solid
|
|
13
|
+
border: 1px solid var(--bs-primary);
|
|
14
14
|
}
|
|
15
15
|
.reaction-emoji-count {
|
|
16
16
|
margin-left: 5px;
|
|
@@ -1,33 +1,52 @@
|
|
|
1
|
-
<form role="form" class="reactions-settings">
|
|
2
|
-
<div class="row">
|
|
3
|
-
<div class="col-sm-2 col-xs-12 settings-header">[[reactions:settings.title]]</div>
|
|
4
|
-
<div class="col-sm-10 col-xs-12">
|
|
5
|
-
<div class="form-group">
|
|
6
|
-
<label>[[reactions:settings.max-reactions-per-post]]</label>
|
|
7
|
-
<input type="number" min="0" class="form-control" id="maximumReactions" name="maximumReactions">
|
|
8
1
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
<p class="help-text">
|
|
12
|
-
[[reactions:settings.max-reactions-per-user-per-post-help]]
|
|
13
|
-
</p>
|
|
14
|
-
</div>
|
|
15
|
-
</div>
|
|
16
|
-
</div>
|
|
2
|
+
<div class="acp-page-container">
|
|
3
|
+
<!-- IMPORT admin/partials/settings/header.tpl -->
|
|
17
4
|
|
|
18
|
-
<div class="row
|
|
19
|
-
<div class="col-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
5
|
+
<div class="row m-0">
|
|
6
|
+
<div id="spy-container" class="col-12 col-md-8 px-0 mb-4" tabindex="0">
|
|
7
|
+
<form role="form" class="reactions-settings">
|
|
8
|
+
<div class="mb-3">
|
|
9
|
+
<h5 class="fw-bold tracking-tight settings-header">[[reactions:settings.title]]</h5>
|
|
10
|
+
<div class="mb-3">
|
|
11
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-post]]</label>
|
|
12
|
+
<input type="number" min="0" class="form-control" id="maximumReactions" name="maximumReactions">
|
|
13
|
+
|
|
14
|
+
</div>
|
|
15
|
+
<div class="mb-3">
|
|
16
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-user-per-post]]</label>
|
|
17
|
+
<input type="number" min="0" class="form-control" id="maximumReactionsPerUserPerPost" name="maximumReactionsPerUserPerPost">
|
|
18
|
+
<p class="form-text">
|
|
19
|
+
[[reactions:settings.max-reactions-per-user-per-post-help]]
|
|
20
|
+
</p>
|
|
21
|
+
</div>
|
|
22
|
+
<div class="mb-3">
|
|
23
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-message]]</label>
|
|
24
|
+
<input type="number" min="0" class="form-control" id="maximumReactionsPerMessage" name="maximumReactionsPerMessage">
|
|
31
25
|
|
|
26
|
+
</div>
|
|
27
|
+
<div class="">
|
|
28
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-user-per-message]]</label>
|
|
29
|
+
<input type="number" min="0" class="form-control" id="maximumReactionsPerUserPerMessage" name="maximumReactionsPerUserPerMessage">
|
|
30
|
+
<p class="form-text">
|
|
31
|
+
[[reactions:settings.max-reactions-per-user-per-message-help]]
|
|
32
|
+
</p>
|
|
33
|
+
</div>
|
|
34
|
+
</div>
|
|
32
35
|
|
|
33
|
-
|
|
36
|
+
<div class="mb-3">
|
|
37
|
+
<h5 class="fw-bold tracking-tight settings-header">[[reactions:settings.reaction-reputations]]</h5>
|
|
38
|
+
|
|
39
|
+
<p class="form-text">
|
|
40
|
+
[[reactions:settings.reaction-reputations-help]]
|
|
41
|
+
</p>
|
|
42
|
+
<div class="form-group" data-type="sorted-list" data-sorted-list="reaction-reputations" data-item-template="admin/plugins/reactions/partials/sorted-list/emoji-item" data-form-template="admin/plugins/reactions/partials/sorted-list/emoji-form">
|
|
43
|
+
<ul data-type="list" class="list-group"></ul>
|
|
44
|
+
<button type="button" data-type="add" class="btn btn-info mt-2">[[reactions:settings.reaction-reputations.add]]</button>
|
|
45
|
+
</div>
|
|
46
|
+
</div>
|
|
47
|
+
</form>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
<!-- IMPORT admin/partials/settings/toc.tpl -->
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
@@ -0,0 +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">
|
|
2
|
+
{./reactionImage}
|
|
3
|
+
<small class="reaction-emoji-count" data-count="{./reactionCount}"></small>
|
|
4
|
+
</span>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<span class="reactions" component="post/reactions" data-pid="{./pid}">
|
|
2
2
|
<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
|
-
<i class="fa fa-
|
|
3
|
+
<i class="fa fa-face-smile text-primary"></i>
|
|
4
4
|
</span>
|
|
5
5
|
{{{ each ./reactions }}}
|
|
6
6
|
<!-- IMPORT partials/topic/reaction.tpl -->
|