@nodebb/nodebb-plugin-reactions 2.0.1 → 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.
- package/README.md +2 -4
- package/assets/acp.png +0 -0
- package/assets/demo.png +0 -0
- package/languages/en-GB/reactions.json +5 -0
- package/library.js +289 -65
- package/package.json +2 -2
- package/plugin.json +7 -1
- package/public/client.js +222 -60
- package/scss/reactions.scss +2 -2
- package/templates/admin/plugins/reactions.tpl +56 -29
- package/templates/partials/chats/add-reaction.tpl +6 -0
- package/templates/partials/chats/reaction.tpl +4 -0
- package/templates/partials/chats/reactions.tpl +7 -0
- package/templates/partials/topic/reaction.tpl +1 -1
- package/templates/partials/topic/reactions.tpl +4 -2
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
|
|
@@ -5,9 +5,14 @@
|
|
|
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",
|
|
13
|
+
"settings.max-reactions-per-message": "Maximum unique reactions per message (0 for unlimited, default: 4)",
|
|
14
|
+
"settings.max-reactions-per-user-per-message": "Maximum reactions per user per message (0 for unlimited)",
|
|
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.",
|
|
11
16
|
"settings.reaction-reputations": "Reaction Reputations (Optional)",
|
|
12
17
|
"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
18
|
"settings.reaction-reputations.add": "Add Rule",
|
package/library.js
CHANGED
|
@@ -1,9 +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');
|
|
6
|
+
const messaging = require.main.require('./src/messaging');
|
|
7
|
+
const privileges = require.main.require('./src/privileges');
|
|
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,30 @@ 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;
|
|
48
|
+
config.enablePostReactions = settings.enablePostReactions === 'on';
|
|
49
|
+
config.enableMessageReactions = settings.enableMessageReactions === 'on';
|
|
44
50
|
} catch (e) {
|
|
45
51
|
console.error(e);
|
|
46
52
|
}
|
|
47
53
|
return config;
|
|
48
54
|
};
|
|
49
55
|
|
|
50
|
-
ReactionsPlugin.
|
|
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
|
+
|
|
69
|
+
ReactionsPlugin.getPostReactions = async function (data) {
|
|
51
70
|
if (data.uid === 0) {
|
|
52
71
|
return data;
|
|
53
72
|
}
|
|
@@ -79,42 +98,80 @@ ReactionsPlugin.getReactions = async function (data) {
|
|
|
79
98
|
}
|
|
80
99
|
}
|
|
81
100
|
|
|
82
|
-
const reactionSetToUsersMap =
|
|
83
|
-
if (reactionSets.length > 0) {
|
|
84
|
-
const uidsForReactions = await db.getSetsMembers(reactionSets);
|
|
85
|
-
const allUids = _.union(...uidsForReactions).filter(Boolean);
|
|
86
|
-
const usersData = await user.getUsersFields(allUids, ['uid', 'username']);
|
|
87
|
-
const uidToUserdataMap = _.keyBy(usersData, 'uid');
|
|
88
|
-
|
|
89
|
-
for (let i = 0, len = reactionSets.length; i < len; i++) {
|
|
90
|
-
const uidsForReaction = uidsForReactions[i];
|
|
91
|
-
if (uidsForReaction && uidsForReaction.length > 0) {
|
|
92
|
-
const usersData = uidsForReaction.map(uid => uidToUserdataMap[uid]).filter(Boolean);
|
|
93
|
-
reactionSetToUsersMap.set(reactionSets[i], usersData);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
}
|
|
101
|
+
const reactionSetToUsersMap = await getReactionSetsUidsMap(reactionSets);
|
|
97
102
|
|
|
98
103
|
for (const post of data.posts) {
|
|
99
104
|
post.maxReactionsReached = pidToIsMaxReactionsReachedMap.get(post.pid);
|
|
100
105
|
post.reactions = [];
|
|
101
106
|
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
const reactions = pidToReactionsMap.get(post.pid);
|
|
108
|
+
if (reactions) {
|
|
109
|
+
for (const reaction of reactions) {
|
|
104
110
|
const reactionSet = `pid:${post.pid}:reaction:${reaction}`;
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const reactionCount = usersData.length;
|
|
108
|
-
const reactedUsernames = usersData.map(userData => userData.username).join(', ');
|
|
109
|
-
const reactedUids = usersData.map(userData => userData.uid);
|
|
110
|
-
|
|
111
|
+
const uids = reactionSetToUsersMap.get(reactionSet);
|
|
112
|
+
if (Array.isArray(uids)) {
|
|
111
113
|
post.reactions.push({
|
|
112
114
|
pid: post.pid,
|
|
113
|
-
reacted:
|
|
115
|
+
reacted: uids.includes(String(data.uid)),
|
|
116
|
+
reaction,
|
|
117
|
+
reactionImage: parse(reaction),
|
|
118
|
+
reactionCount: uids.length,
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
} catch (e) {
|
|
125
|
+
console.error(e);
|
|
126
|
+
}
|
|
127
|
+
return data;
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
ReactionsPlugin.getMessageReactions = async function (data) {
|
|
131
|
+
if (data.uid === 0) {
|
|
132
|
+
return data;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
const settings = await meta.settings.get('reactions');
|
|
137
|
+
const maximumReactionsPerMessage = settings.maximumReactionsPerMessage || DEFAULT_MAX_EMOTES;
|
|
138
|
+
|
|
139
|
+
const mids = data.messages.map(message => message && parseInt(message.mid, 10));
|
|
140
|
+
const allReactionsForMids = await db.getSetsMembers(mids.map(pid => `mid:${pid}:reactions`));
|
|
141
|
+
|
|
142
|
+
const midToIsMaxReactionsReachedMap = new Map(); // mid -> IsMaxReactionsReached (boolean)
|
|
143
|
+
const midToReactionsMap = new Map(); // mid -> reactions (string[])
|
|
144
|
+
let reactionSets = [];
|
|
145
|
+
|
|
146
|
+
for (let i = 0, len = mids.length; i < len; i++) {
|
|
147
|
+
const mid = mids[i];
|
|
148
|
+
const reactionsList = allReactionsForMids[i];
|
|
149
|
+
const reactionsCount = reactionsList.length;
|
|
150
|
+
|
|
151
|
+
if (reactionsList && reactionsList.length > 0) {
|
|
152
|
+
midToReactionsMap.set(mid, reactionsList);
|
|
153
|
+
midToIsMaxReactionsReachedMap.set(mid, reactionsCount > maximumReactionsPerMessage);
|
|
154
|
+
reactionSets = reactionSets.concat(reactionsList.map(reaction => `mid:${mid}:reaction:${reaction}`));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const reactionSetToUsersMap = await getReactionSetsUidsMap(reactionSets);
|
|
159
|
+
|
|
160
|
+
for (const msg of data.messages) {
|
|
161
|
+
msg.maxReactionsReached = midToIsMaxReactionsReachedMap.get(msg.mid);
|
|
162
|
+
msg.reactions = [];
|
|
163
|
+
const reactions = midToReactionsMap.get(msg.mid);
|
|
164
|
+
if (reactions) {
|
|
165
|
+
for (const reaction of reactions) {
|
|
166
|
+
const reactionSet = `mid:${msg.mid}:reaction:${reaction}`;
|
|
167
|
+
const uids = reactionSetToUsersMap.get(reactionSet);
|
|
168
|
+
if (Array.isArray(uids)) {
|
|
169
|
+
msg.reactions.push({
|
|
170
|
+
mid: msg.mid,
|
|
171
|
+
reacted: uids.includes(String(data.uid)),
|
|
114
172
|
reaction,
|
|
115
|
-
usernames: reactedUsernames,
|
|
116
173
|
reactionImage: parse(reaction),
|
|
117
|
-
reactionCount,
|
|
174
|
+
reactionCount: uids.length,
|
|
118
175
|
});
|
|
119
176
|
}
|
|
120
177
|
}
|
|
@@ -126,6 +183,22 @@ ReactionsPlugin.getReactions = async function (data) {
|
|
|
126
183
|
return data;
|
|
127
184
|
};
|
|
128
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
|
+
|
|
129
202
|
ReactionsPlugin.onReply = async function (data) {
|
|
130
203
|
if (data.uid !== 0) {
|
|
131
204
|
data.reactions = [];
|
|
@@ -148,17 +221,13 @@ ReactionsPlugin.deleteReactions = async function (hookData) {
|
|
|
148
221
|
await db.deleteAll(keys);
|
|
149
222
|
};
|
|
150
223
|
|
|
151
|
-
async function
|
|
224
|
+
async function sendPostEvent(data, eventName) {
|
|
152
225
|
try {
|
|
153
|
-
const [reactionCount, totalReactions
|
|
226
|
+
const [reactionCount, totalReactions] = await Promise.all([
|
|
154
227
|
db.setCount(`pid:${data.pid}:reaction:${data.reaction}`),
|
|
155
228
|
db.setCount(`pid:${data.pid}:reactions`),
|
|
156
|
-
db.getSetMembers(`pid:${data.pid}:reaction:${data.reaction}`),
|
|
157
229
|
]);
|
|
158
230
|
|
|
159
|
-
const userdata = await user.getUsersFields(uids, ['uid', 'username']);
|
|
160
|
-
const usernames = userdata.map(user => user.username).join(', ');
|
|
161
|
-
|
|
162
231
|
if (parseInt(reactionCount, 10) === 0) {
|
|
163
232
|
await db.setRemove(`pid:${data.pid}:reactions`, data.reaction);
|
|
164
233
|
}
|
|
@@ -169,7 +238,30 @@ async function sendEvent(data, eventName) {
|
|
|
169
238
|
reaction: data.reaction,
|
|
170
239
|
reactionCount,
|
|
171
240
|
totalReactions,
|
|
172
|
-
|
|
241
|
+
reactionImage: parse(data.reaction),
|
|
242
|
+
});
|
|
243
|
+
} catch (e) {
|
|
244
|
+
console.error(e);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function sendMessageEvent(data, eventName) {
|
|
249
|
+
try {
|
|
250
|
+
const [reactionCount, totalReactions] = await Promise.all([
|
|
251
|
+
db.setCount(`mid:${data.mid}:reaction:${data.reaction}`),
|
|
252
|
+
db.setCount(`mid:${data.mid}:reactions`),
|
|
253
|
+
]);
|
|
254
|
+
|
|
255
|
+
if (parseInt(reactionCount, 10) === 0) {
|
|
256
|
+
await db.setRemove(`mid:${data.mid}:reactions`, data.reaction);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
await websockets.in(`chat_room_${data.roomId}`).emit(eventName, {
|
|
260
|
+
mid: data.mid,
|
|
261
|
+
uid: data.uid,
|
|
262
|
+
reaction: data.reaction,
|
|
263
|
+
reactionCount,
|
|
264
|
+
totalReactions,
|
|
173
265
|
reactionImage: parse(data.reaction),
|
|
174
266
|
});
|
|
175
267
|
} catch (e) {
|
|
@@ -201,23 +293,30 @@ SocketPlugins.reactions = {
|
|
|
201
293
|
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
202
294
|
}
|
|
203
295
|
|
|
204
|
-
data.uid = socket.uid;
|
|
205
|
-
|
|
206
296
|
const settings = await meta.settings.get('reactions');
|
|
297
|
+
if (settings.enablePostReactions === 'off') {
|
|
298
|
+
throw new Error('[[error:post-reactions-disabled]]');
|
|
299
|
+
}
|
|
207
300
|
const maximumReactions = settings.maximumReactions || DEFAULT_MAX_EMOTES;
|
|
208
|
-
const [totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([
|
|
301
|
+
const [tid, totalReactions, emojiIsAlreadyExist, alreadyReacted, reactionReputation] = await Promise.all([
|
|
302
|
+
posts.getPostField(data.pid, 'tid'),
|
|
209
303
|
db.setCount(`pid:${data.pid}:reactions`),
|
|
210
304
|
db.isSetMember(`pid:${data.pid}:reactions`, data.reaction),
|
|
211
305
|
db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid),
|
|
212
306
|
getReactionReputation(data.reaction),
|
|
213
307
|
]);
|
|
214
|
-
|
|
308
|
+
if (!tid) {
|
|
309
|
+
throw new Error('[[error:no-post]]');
|
|
310
|
+
}
|
|
311
|
+
data.uid = socket.uid;
|
|
312
|
+
data.tid = tid;
|
|
215
313
|
if (!emojiIsAlreadyExist) {
|
|
216
314
|
if (totalReactions > maximumReactions) {
|
|
217
315
|
throw new Error(`[[reactions:error.maximum-reached]] (${maximumReactions})`);
|
|
218
316
|
}
|
|
219
|
-
|
|
220
|
-
const maximumReactionsPerUserPerPost = settings.maximumReactionsPerUserPerPost ?
|
|
317
|
+
|
|
318
|
+
const maximumReactionsPerUserPerPost = settings.maximumReactionsPerUserPerPost ?
|
|
319
|
+
parseInt(settings.maximumReactionsPerUserPerPost, 10) : 0;
|
|
221
320
|
if (maximumReactionsPerUserPerPost > 0) {
|
|
222
321
|
const emojiesInPost = await db.getSetMembers(`pid:${data.pid}:reactions`);
|
|
223
322
|
const userPostReactions = await db.isMemberOfSets(emojiesInPost.map(emojiName => `pid:${data.pid}:reaction:${emojiName}`), socket.uid);
|
|
@@ -227,7 +326,6 @@ SocketPlugins.reactions = {
|
|
|
227
326
|
}
|
|
228
327
|
}
|
|
229
328
|
}
|
|
230
|
-
|
|
231
329
|
|
|
232
330
|
await Promise.all([
|
|
233
331
|
db.setAdd(`pid:${data.pid}:reactions`, data.reaction),
|
|
@@ -238,7 +336,7 @@ SocketPlugins.reactions = {
|
|
|
238
336
|
await giveOwnerReactionReputation(reactionReputation, data.pid);
|
|
239
337
|
}
|
|
240
338
|
|
|
241
|
-
await
|
|
339
|
+
await sendPostEvent(data, 'event:reactions.addPostReaction');
|
|
242
340
|
},
|
|
243
341
|
removePostReaction: async function (socket, data) {
|
|
244
342
|
if (!socket.uid) {
|
|
@@ -249,30 +347,156 @@ SocketPlugins.reactions = {
|
|
|
249
347
|
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
250
348
|
}
|
|
251
349
|
|
|
350
|
+
const [settings, tid, hasReacted, reactionReputation] = await Promise.all([
|
|
351
|
+
meta.settings.get('reactions'),
|
|
352
|
+
posts.getPostField(data.pid, 'tid'),
|
|
353
|
+
db.isSetMember(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid),
|
|
354
|
+
getReactionReputation(data.reaction),
|
|
355
|
+
]);
|
|
356
|
+
if (settings.enablePostReactions === 'off') {
|
|
357
|
+
throw new Error('[[error:post-reactions-disabled]]');
|
|
358
|
+
}
|
|
359
|
+
if (!tid) {
|
|
360
|
+
throw new Error('[[error:no-post]]');
|
|
361
|
+
}
|
|
252
362
|
data.uid = socket.uid;
|
|
363
|
+
data.tid = tid;
|
|
364
|
+
|
|
365
|
+
if (hasReacted) {
|
|
366
|
+
await db.setRemove(`pid:${data.pid}:reaction:${data.reaction}`, socket.uid);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const reactionCount = await db.setCount(`pid:${data.pid}:reaction:${data.reaction}`);
|
|
370
|
+
if (reactionCount === 0) {
|
|
371
|
+
await db.setRemove(`pid:${data.pid}:reactions`, data.reaction);
|
|
372
|
+
}
|
|
373
|
+
if (hasReacted && reactionReputation > 0) {
|
|
374
|
+
await giveOwnerReactionReputation(-reactionReputation, data.pid);
|
|
375
|
+
}
|
|
253
376
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
]);
|
|
259
|
-
|
|
260
|
-
|
|
377
|
+
await sendPostEvent(data, 'event:reactions.removePostReaction');
|
|
378
|
+
},
|
|
379
|
+
addMessageReaction: async function (socket, data) {
|
|
380
|
+
if (!socket.uid) {
|
|
381
|
+
throw new Error('[[error:not-logged-in]]');
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (!emojiTable[data.reaction]) {
|
|
385
|
+
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const settings = await meta.settings.get('reactions');
|
|
389
|
+
if (settings.enableMessageReactions === 'off') {
|
|
390
|
+
throw new Error('[[error:post-reactions-disabled]]');
|
|
391
|
+
}
|
|
392
|
+
const maximumReactionsPerMessage = settings.maximumReactionsPerMessage || DEFAULT_MAX_EMOTES;
|
|
393
|
+
const [roomId, totalReactions, emojiIsAlreadyExist] = await Promise.all([
|
|
394
|
+
messaging.getMessageField(data.mid, 'roomId'),
|
|
395
|
+
db.setCount(`mid:${data.mid}:reactions`),
|
|
396
|
+
db.isSetMember(`mid:${data.mid}:reactions`, data.reaction),
|
|
397
|
+
]);
|
|
398
|
+
|
|
399
|
+
if (!roomId) {
|
|
400
|
+
throw new Error('[[error:no-message]]');
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
data.uid = socket.uid;
|
|
404
|
+
data.roomId = roomId;
|
|
405
|
+
|
|
406
|
+
if (!emojiIsAlreadyExist) {
|
|
407
|
+
if (totalReactions > maximumReactionsPerMessage) {
|
|
408
|
+
throw new Error(`[[reactions:error.maximum-reached]] (${maximumReactionsPerMessage})`);
|
|
261
409
|
}
|
|
262
410
|
|
|
263
|
-
const
|
|
264
|
-
|
|
265
|
-
|
|
411
|
+
const maximumReactionsPerUserPerMessage = settings.maximumReactionsPerUserPerMessage ?
|
|
412
|
+
parseInt(settings.maximumReactionsPerUserPerMessage, 10) : 0;
|
|
413
|
+
if (maximumReactionsPerUserPerMessage > 0) {
|
|
414
|
+
const emojiesInMessage = await db.getSetMembers(`mid:${data.mid}:reactions`);
|
|
415
|
+
const userPostReactions = await db.isMemberOfSets(emojiesInMessage.map(emojiName => `mid:${data.mid}:reaction:${emojiName}`), socket.uid);
|
|
416
|
+
const userPostReactionCount = userPostReactions.filter(Boolean).length;
|
|
417
|
+
if (userPostReactionCount > maximumReactionsPerUserPerMessage) {
|
|
418
|
+
throw new Error(`[[reactions:error.maximum-per-user-per-post-reached]] (${maximumReactionsPerUserPerMessage})`);
|
|
419
|
+
}
|
|
266
420
|
}
|
|
267
|
-
|
|
268
|
-
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
await Promise.all([
|
|
424
|
+
db.setAdd(`mid:${data.mid}:reactions`, data.reaction),
|
|
425
|
+
db.setAdd(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid),
|
|
426
|
+
]);
|
|
427
|
+
|
|
428
|
+
await sendMessageEvent(data, 'event:reactions.addMessageReaction');
|
|
429
|
+
},
|
|
430
|
+
removeMessageReaction: async function (socket, data) {
|
|
431
|
+
if (!socket.uid) {
|
|
432
|
+
throw new Error('[[error:not-logged-in]]');
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (!emojiTable[data.reaction]) {
|
|
436
|
+
throw new Error('[[reactions:error.invalid-reaction]]');
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
const [settings, roomId, hasReacted] = await Promise.all([
|
|
440
|
+
meta.settings.get('reactions'),
|
|
441
|
+
messaging.getMessageField(data.mid, 'roomId'),
|
|
442
|
+
db.isSetMember(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid),
|
|
443
|
+
]);
|
|
444
|
+
if (settings.enableMessageReactions === 'off') {
|
|
445
|
+
throw new Error('[[error:post-reactions-disabled]]');
|
|
446
|
+
}
|
|
447
|
+
if (!roomId) {
|
|
448
|
+
throw new Error('[[error:no-message]]');
|
|
449
|
+
}
|
|
450
|
+
data.uid = socket.uid;
|
|
451
|
+
data.roomId = roomId;
|
|
452
|
+
if (hasReacted) {
|
|
453
|
+
await db.setRemove(`mid:${data.mid}:reaction:${data.reaction}`, socket.uid);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const reactionCount = await db.setCount(`mid:${data.mid}:reaction:${data.reaction}`);
|
|
457
|
+
if (reactionCount === 0) {
|
|
458
|
+
await db.setRemove(`mid:${data.mid}:reactions`, data.reaction);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
await sendMessageEvent(data, 'event:reactions.removeMessageReaction');
|
|
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]]');
|
|
269
474
|
}
|
|
270
|
-
|
|
271
|
-
}
|
|
272
|
-
|
|
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);
|
|
273
492
|
}
|
|
493
|
+
|
|
494
|
+
const usernames = await user.getUsernamesByUids(uids);
|
|
495
|
+
return {
|
|
496
|
+
cutoff: cutoff,
|
|
497
|
+
otherCount,
|
|
498
|
+
usernames,
|
|
499
|
+
};
|
|
274
500
|
},
|
|
275
501
|
};
|
|
276
502
|
|
|
277
|
-
|
|
278
|
-
module.exports = ReactionsPlugin;
|
package/package.json
CHANGED
package/plugin.json
CHANGED
|
@@ -26,7 +26,13 @@
|
|
|
26
26
|
"hook": "filter:config.get", "method": "getPluginConfig"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
|
-
"hook": "filter:
|
|
29
|
+
"hook": "filter:settings.get", "method": "filterSettingsGet"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"hook": "filter:post.getPosts", "method": "getPostReactions"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"hook": "filter:messaging.getMessages", "method": "getMessageReactions"
|
|
30
36
|
},
|
|
31
37
|
{
|
|
32
38
|
"hook": "filter:post.get", "method": "onReply"
|
package/public/client.js
CHANGED
|
@@ -1,55 +1,23 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
$(document).ready(function () {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
4
|
+
setupReactions();
|
|
5
|
+
let alerts;
|
|
6
|
+
let mouseOverReactionEl;
|
|
7
|
+
let tooltipTimeoutId = 0;
|
|
8
|
+
function setupReactions() {
|
|
9
|
+
createReactionTooltips();
|
|
10
|
+
require(['hooks', 'alerts'], function (hooks, _alerts) {
|
|
11
|
+
alerts = _alerts;
|
|
8
12
|
hooks.on('action:ajaxify.end', function () {
|
|
9
13
|
if (ajaxify.data.template.topic) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
});
|
|
14
|
+
setupPostReactions();
|
|
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) {
|
|
20
|
+
setupMessageReactions();
|
|
53
21
|
}
|
|
54
22
|
});
|
|
55
23
|
});
|
|
@@ -61,19 +29,122 @@ $(document).ready(function () {
|
|
|
61
29
|
socket.on('event:post_restored', function (data) {
|
|
62
30
|
$('[component="post/reactions"][data-pid="' + data.pid + '"]').removeClass('hidden');
|
|
63
31
|
});
|
|
32
|
+
|
|
33
|
+
socket.on('event:chats.delete', function (mid) {
|
|
34
|
+
$('[component="message/reactions"][data-mid="' + mid + '"]').addClass('hidden');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
socket.on('event:chats.restore', function (msg) {
|
|
38
|
+
$('[component="message/reactions"][data-mid="' + msg.mid + '"]').removeClass('hidden');
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function setupPostReactions() {
|
|
43
|
+
setupPostReactionSockets();
|
|
44
|
+
|
|
45
|
+
$('[component="topic"]').on('click', '[component="post/reaction"]', function () {
|
|
46
|
+
var reactionElement = $(this);
|
|
47
|
+
var pid = reactionElement.attr('data-pid');
|
|
48
|
+
var reaction = reactionElement.attr('data-reaction');
|
|
49
|
+
var reacted = reactionElement.hasClass('reacted');
|
|
50
|
+
var event = 'plugins.reactions.' + (reacted ? 'removePostReaction' : 'addPostReaction');
|
|
51
|
+
socket.emit(event, {
|
|
52
|
+
pid: pid,
|
|
53
|
+
reaction: reaction,
|
|
54
|
+
}, function (err) {
|
|
55
|
+
if (err) {
|
|
56
|
+
alerts.error(err.message);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
$('[component="topic"]').on('click', '[component="post/reaction/add"]', function () {
|
|
62
|
+
var reactionAddEl = $(this);
|
|
63
|
+
var pid = reactionAddEl.attr('data-pid');
|
|
64
|
+
require(['emoji-dialog'], function (emojiDialog) {
|
|
65
|
+
emojiDialog.toggle(reactionAddEl[0], function (_, name, dialog) {
|
|
66
|
+
emojiDialog.dialogActions.close(dialog);
|
|
67
|
+
|
|
68
|
+
socket.emit('plugins.reactions.addPostReaction', {
|
|
69
|
+
pid: pid,
|
|
70
|
+
reaction: name,
|
|
71
|
+
}, function (err) {
|
|
72
|
+
if (err) {
|
|
73
|
+
alerts.error(err.message);
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
$('[component="post/reaction"][data-pid="' + pid + '"][data-reaction="' + name + '"]').addClass('reacted');
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function setupMessageReactions() {
|
|
85
|
+
setupMessageReactionSockets();
|
|
86
|
+
|
|
87
|
+
const messageContent = $('[component="chat/message/content"]');
|
|
88
|
+
messageContent.on('click', '[component="message/reaction"]', function () {
|
|
89
|
+
var reactionElement = $(this);
|
|
90
|
+
var mid = reactionElement.attr('data-mid');
|
|
91
|
+
var reaction = reactionElement.attr('data-reaction');
|
|
92
|
+
var reacted = reactionElement.hasClass('reacted');
|
|
93
|
+
var event = 'plugins.reactions.' + (reacted ? 'removeMessageReaction' : 'addMessageReaction');
|
|
94
|
+
socket.emit(event, {
|
|
95
|
+
mid: mid,
|
|
96
|
+
reaction: reaction,
|
|
97
|
+
}, function (err) {
|
|
98
|
+
if (err) {
|
|
99
|
+
alerts.error(err.message);
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
messageContent.on('click', '[component="message/reaction/add"]', function () {
|
|
105
|
+
const reactionAddEl = $(this);
|
|
106
|
+
const mid = reactionAddEl.attr('data-mid');
|
|
107
|
+
|
|
108
|
+
require(['emoji-dialog'], function (emojiDialog) {
|
|
109
|
+
emojiDialog.toggle(reactionAddEl[0], function (_, name, dialog) {
|
|
110
|
+
emojiDialog.dialogActions.close(dialog);
|
|
111
|
+
|
|
112
|
+
socket.emit('plugins.reactions.addMessageReaction', {
|
|
113
|
+
mid: mid,
|
|
114
|
+
reaction: name,
|
|
115
|
+
}, function (err) {
|
|
116
|
+
if (err) {
|
|
117
|
+
return alerts.error(err.message);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
$('[component="message/reaction"][data-mid="' + mid + '"][data-reaction="' + name + '"]').addClass('reacted');
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
});
|
|
64
125
|
}
|
|
65
126
|
|
|
66
|
-
function
|
|
127
|
+
function setupPostReactionSockets() {
|
|
67
128
|
socket.off('event:reactions.addPostReaction').on('event:reactions.addPostReaction', function (data) {
|
|
68
|
-
|
|
129
|
+
updatePostReactionCount(data, 'add');
|
|
69
130
|
});
|
|
70
131
|
|
|
71
132
|
socket.off('event:reactions.removePostReaction').on('event:reactions.removePostReaction', function (data) {
|
|
72
|
-
|
|
133
|
+
updatePostReactionCount(data, 'remove');
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function setupMessageReactionSockets() {
|
|
138
|
+
socket.off('event:reactions.addMessageReaction').on('event:reactions.addMessageReaction', function (data) {
|
|
139
|
+
updateMessageReactionCount(data, 'add');
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
socket.off('event:reactions.removeMessageReaction').on('event:reactions.removeMessageReaction', function (data) {
|
|
143
|
+
updateMessageReactionCount(data, 'remove');
|
|
73
144
|
});
|
|
74
145
|
}
|
|
75
146
|
|
|
76
|
-
function
|
|
147
|
+
function updatePostReactionCount(data, type) {
|
|
77
148
|
var maxReactionsReached = parseInt(data.totalReactions, 10) > config.maximumReactions;
|
|
78
149
|
$('[component="post/reaction/add"][data-pid="' + data.pid + '"]').toggleClass('max-reactions', maxReactionsReached);
|
|
79
150
|
|
|
@@ -83,14 +154,14 @@ $(document).ready(function () {
|
|
|
83
154
|
reactionEl.tooltip('dispose');
|
|
84
155
|
reactionEl.remove();
|
|
85
156
|
}
|
|
86
|
-
|
|
157
|
+
const isSelf = parseInt(data.uid, 10) === app.user.uid;
|
|
87
158
|
if (reactionEl.length === 0) {
|
|
88
159
|
app.parseAndTranslate('partials/topic/reaction', {
|
|
89
160
|
pid: data.pid,
|
|
90
161
|
reaction: data.reaction,
|
|
91
162
|
reactionCount: data.reactionCount,
|
|
92
163
|
usernames: data.usernames,
|
|
93
|
-
reacted:
|
|
164
|
+
reacted: isSelf && type === 'add',
|
|
94
165
|
reactionImage: data.reactionImage,
|
|
95
166
|
}, function (html) {
|
|
96
167
|
$('[component="post/reactions"][data-pid="' + data.pid + '"]').append(html);
|
|
@@ -99,18 +170,109 @@ $(document).ready(function () {
|
|
|
99
170
|
reactionEl.find('.reaction-emoji-count').attr('data-count', data.reactionCount);
|
|
100
171
|
reactionEl.attr('data-bs-original-title', data.usernames);
|
|
101
172
|
reactionEl.attr('aria-label', data.usernames);
|
|
102
|
-
|
|
173
|
+
if (isSelf) {
|
|
174
|
+
reactionEl.toggleClass('reacted', type === 'add');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function updateMessageReactionCount(data, type) {
|
|
180
|
+
var maxReactionsReached = parseInt(data.totalReactions, 10) > config.maximumReactionsPerMessage;
|
|
181
|
+
$('[component="message/reaction/add"][data-mid="' + data.mid + '"]').toggleClass('max-reactions', maxReactionsReached);
|
|
182
|
+
|
|
183
|
+
var reactionEl = $(`[component="message/reaction"][data-mid="${data.mid}"][data-reaction="${data.reaction}"]`);
|
|
184
|
+
|
|
185
|
+
if (parseInt(data.reactionCount, 10) === 0) {
|
|
186
|
+
reactionEl.tooltip('dispose');
|
|
187
|
+
reactionEl.remove();
|
|
188
|
+
}
|
|
189
|
+
const isSelf = (parseInt(data.uid, 10) === app.user.uid);
|
|
190
|
+
if (reactionEl.length === 0) {
|
|
191
|
+
app.parseAndTranslate('partials/chats/reaction', {
|
|
192
|
+
mid: data.mid,
|
|
193
|
+
reaction: data.reaction,
|
|
194
|
+
reactionCount: data.reactionCount,
|
|
195
|
+
reacted: isSelf && type === 'add',
|
|
196
|
+
reactionImage: data.reactionImage,
|
|
197
|
+
}, function (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
|
+
});
|
|
207
|
+
});
|
|
208
|
+
} else {
|
|
209
|
+
reactionEl.find('.reaction-emoji-count').attr('data-count', data.reactionCount);
|
|
210
|
+
reactionEl.attr('data-bs-original-title', data.usernames);
|
|
211
|
+
reactionEl.attr('aria-label', data.usernames);
|
|
212
|
+
if (isSelf) {
|
|
213
|
+
reactionEl.toggleClass('reacted', type === 'add');
|
|
214
|
+
}
|
|
103
215
|
}
|
|
104
|
-
createReactionTooltips();
|
|
105
216
|
}
|
|
106
217
|
|
|
107
218
|
function createReactionTooltips() {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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,
|
|
112
241
|
placement: 'top',
|
|
113
|
-
|
|
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
|
+
}
|
|
114
276
|
});
|
|
115
277
|
}
|
|
116
278
|
});
|
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,60 @@
|
|
|
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="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>
|
|
14
|
+
<div class="mb-3">
|
|
15
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-post]]</label>
|
|
16
|
+
<input type="number" min="0" class="form-control" id="maximumReactions" name="maximumReactions">
|
|
17
|
+
</div>
|
|
18
|
+
<div class="mb-3">
|
|
19
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-user-per-post]]</label>
|
|
20
|
+
<input type="number" min="0" class="form-control" id="maximumReactionsPerUserPerPost" name="maximumReactionsPerUserPerPost">
|
|
21
|
+
<p class="form-text">
|
|
22
|
+
[[reactions:settings.max-reactions-per-user-per-post-help]]
|
|
23
|
+
</p>
|
|
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>
|
|
30
|
+
<div class="mb-3">
|
|
31
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-message]]</label>
|
|
32
|
+
<input type="number" min="0" class="form-control" id="maximumReactionsPerMessage" name="maximumReactionsPerMessage">
|
|
31
33
|
|
|
34
|
+
</div>
|
|
35
|
+
<div class="">
|
|
36
|
+
<label class="form-label">[[reactions:settings.max-reactions-per-user-per-message]]</label>
|
|
37
|
+
<input type="number" min="0" class="form-control" id="maximumReactionsPerUserPerMessage" name="maximumReactionsPerUserPerMessage">
|
|
38
|
+
<p class="form-text">
|
|
39
|
+
[[reactions:settings.max-reactions-per-user-per-message-help]]
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
</div>
|
|
32
43
|
|
|
33
|
-
|
|
44
|
+
<div class="mb-3">
|
|
45
|
+
<h5 class="fw-bold tracking-tight settings-header">[[reactions:settings.reaction-reputations]]</h5>
|
|
46
|
+
|
|
47
|
+
<p class="form-text">
|
|
48
|
+
[[reactions:settings.reaction-reputations-help]]
|
|
49
|
+
</p>
|
|
50
|
+
<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">
|
|
51
|
+
<ul data-type="list" class="list-group"></ul>
|
|
52
|
+
<button type="button" data-type="add" class="btn btn-info mt-2">[[reactions:settings.reaction-reputations.add]]</button>
|
|
53
|
+
</div>
|
|
54
|
+
</div>
|
|
55
|
+
</form>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<!-- IMPORT admin/partials/settings/toc.tpl -->
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
{{{ if config.enableMessageReactions }}}
|
|
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]]">
|
|
3
|
+
<i class="fa fa-face-smile"></i>
|
|
4
|
+
</button>
|
|
5
|
+
{{{ end }}}
|
|
6
|
+
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<span class="reaction {{{ if ./reacted }}}reacted{{{ end }}}" component="post/reaction" data-pid="{./pid}" data-reaction="{./reaction}"
|
|
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,8 +1,10 @@
|
|
|
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
|
-
<i class="fa fa-
|
|
4
|
+
<i class="fa fa-face-smile text-primary"></i>
|
|
4
5
|
</span>
|
|
5
6
|
{{{ each ./reactions }}}
|
|
6
7
|
<!-- IMPORT partials/topic/reaction.tpl -->
|
|
7
8
|
{{{ end }}}
|
|
8
|
-
</span>
|
|
9
|
+
</span>
|
|
10
|
+
{{{ end }}}
|