nodebb-plugin-mentions 4.5.2 → 4.6.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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "user-mentioned-you-in": "<strong>%1</strong> กล่าวถึงคุณใน <strong>%2</strong>"
3
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "mentions": "การถูกกล่าวถึง",
3
+ "user-mentioned-you-in": "<strong>%1</strong> ได้กล่าวถึงคุณใน <strong>%2</strong>",
4
+ "user-mentioned-you-in-room": "<strong>%1</strong> ได้กล่าวถึงคุณใน <strong class=\"text-nowrap\"><i class=\"fa %2\"></i>%3</strong>",
5
+ "user-mentioned-group-in": "<strong>%1</strong> ได้กล่าวถึง <strong>%2</strong> ใน <strong>%3</strong>",
6
+ "notificationType-mention": "เมื่อบางคนได้กล่าวถึงคุณ"
7
+ }
package/library.js CHANGED
@@ -5,6 +5,7 @@
5
5
  const _ = require('lodash');
6
6
  const validator = require('validator');
7
7
  const entitiesDecode = require('html-entities').decode;
8
+ const cheerio = require('cheerio');
8
9
 
9
10
  const nconf = require.main.require('nconf');
10
11
  const winston = require.main.require('winston');
@@ -93,7 +94,11 @@ Mentions.notify = async function ({ post }) {
93
94
  }
94
95
 
95
96
  const noMentionGroups = getNoMentionGroups();
96
- matches = _.uniq(matches.map(match => slugify(match))).filter(match => match && !noMentionGroups.includes(match));
97
+ matches = _.uniq(
98
+ matches
99
+ .map(match => slugify(match.slice(1)))
100
+ .filter(match => match && !noMentionGroups.includes(match))
101
+ );
97
102
  if (!matches.length) {
98
103
  return;
99
104
  }
@@ -185,7 +190,8 @@ Mentions.notifyMessage = async (hookData) => {
185
190
  }
186
191
  const { message } = hookData;
187
192
  const { roomId } = message;
188
- matches = _.uniq(matches.map(slugify));
193
+ matches = _.uniq(matches.map(match => slugify(match.slice(1))));
194
+
189
195
  const [matchedUids, roomData] = await Promise.all([
190
196
  getUidsToNotify(matches),
191
197
  Messaging.getRoomData(roomId),
@@ -327,11 +333,12 @@ async function createNotification(postData, nidType, notificationText) {
327
333
  }
328
334
 
329
335
  Mentions.parsePost = async (data) => {
330
- if (!data || !data.postData || !data.postData.content) {
336
+ const { postData, type } = data;
337
+ if (!postData.content) {
331
338
  return data;
332
339
  }
333
340
 
334
- const parsed = await Mentions.parseRaw(data.postData.content);
341
+ const parsed = await Mentions.parseRaw(postData.content, type);
335
342
  data.postData.content = parsed;
336
343
  return data;
337
344
  };
@@ -340,7 +347,7 @@ function removePunctuationSuffix(string) {
340
347
  return string.replace(/[!?.]*$/, '');
341
348
  }
342
349
 
343
- function getMatches(content, isMarkdown = false) {
350
+ async function getMatches(content, isMarkdown = false) {
344
351
  const splitContent = utility.split(content, isMarkdown, false, true);
345
352
  let matches = [];
346
353
  splitContent.forEach((cleanedContent, i) => {
@@ -349,12 +356,38 @@ function getMatches(content, isMarkdown = false) {
349
356
  }
350
357
  });
351
358
 
352
- return { splitContent, matches };
359
+ const $ = cheerio.load(splitContent.join(''));
360
+ const anchors = $('a');
361
+ const urls = new Set();
362
+ Array.from(anchors).forEach((anchor) => {
363
+ const text = $(anchor).prop('innerText');
364
+ const match = text.match(regex);
365
+ if (match) {
366
+ urls.add($(anchor).attr('href'));
367
+ }
368
+ });
369
+
370
+ // Filter out urls that don't backreference to a remote id
371
+ const backrefs = await db.getObjectFields('remoteUrl:uid', Array.from(urls));
372
+ const urlAsIdExists = await db.isSortedSetMembers('usersRemote:lastCrawled', Array.from(urls));
373
+ const urlMap = new Map();
374
+ Array.from(urls).map(async (url, index) => {
375
+ if (backrefs[url] || urlAsIdExists[index]) {
376
+ urlMap.set(url, backrefs[url] || url);
377
+ }
378
+ });
379
+ let slugs = await User.getUsersFields(Array.from(urlMap.values()), ['userslug']);
380
+ slugs = slugs.map(({ userslug }) => userslug);
381
+ Array.from(urlMap.keys()).forEach((url, idx) => {
382
+ urlMap.set(url, `/user/${encodeURIComponent(slugs[idx])}`);
383
+ });
384
+
385
+ return { splitContent, matches, urlMap };
353
386
  }
354
387
 
355
388
  Mentions.getMatches = async (content) => {
356
389
  // Exported method only accepts markdown, also filters out dupes and matches to ensure slugs exist
357
- let { matches } = getMatches(content, true);
390
+ let { matches } = await getMatches(content, true);
358
391
  matches = await filterMatches(matches);
359
392
  const ids = await Promise.all(matches.map(async m => User.getUidByUserslug(m.slice(1).toLowerCase())));
360
393
  matches = matches.map((slug, idx) => (ids[idx] ? {
@@ -372,11 +405,15 @@ async function filterMatches(matches) {
372
405
  return matches.filter((m, i) => exists[[i]]);
373
406
  }
374
407
 
375
- Mentions.parseRaw = async (content) => {
408
+ Mentions.parseRaw = async (content, type = 'default') => {
409
+ if (type === 'plaintext') {
410
+ return content;
411
+ }
412
+
376
413
  // Note: Mentions.clean explicitly can't be called here because I need the content unstripped
377
- let { splitContent, matches } = getMatches(content);
414
+ let { splitContent, matches, urlMap } = await getMatches(content);
378
415
 
379
- if (!matches.length) {
416
+ if (!matches.length && !urlMap.size) {
380
417
  return content;
381
418
  }
382
419
 
@@ -390,6 +427,7 @@ Mentions.parseRaw = async (content) => {
390
427
  return atIndex !== 0 ? match.slice(atIndex) : match;
391
428
  });
392
429
 
430
+ // Convert matches to anchor html
393
431
  await Promise.all(matches.map(async (match) => {
394
432
  const slug = slugify(match.slice(1));
395
433
  match = removePunctuationSuffix(match);
@@ -397,7 +435,7 @@ Mentions.parseRaw = async (content) => {
397
435
  const cid = await categories.getCidByHandle(slug);
398
436
  const { groupExists, user, category } = await utils.promiseParallel({
399
437
  groupExists: Groups.existsBySlug(slug),
400
- user: User.getUserFields(uid, ['uid', 'username', 'fullname', 'url']),
438
+ user: User.getUserFields(uid, ['uid', 'username', 'userslug', 'fullname', 'url']),
401
439
  category: categories.getCategoryFields(cid, ['slug']),
402
440
  });
403
441
 
@@ -406,17 +444,20 @@ Mentions.parseRaw = async (content) => {
406
444
 
407
445
  switch (true) {
408
446
  case !!uid: {
409
- url = utils.isNumber(user.uid) ? `${nconf.get('url')}/user/${slug}` : (user.url || user.uid);
447
+ url = `/user/${encodeURIComponent(user.userslug)}`;
448
+ if (type.startsWith('activitypub') && !utils.isNumber(uid)) {
449
+ url = user.url || user.uid;
450
+ }
410
451
  break;
411
452
  }
412
453
 
413
454
  case !!cid: {
414
- url = `${nconf.get('url')}/category/${category.slug}`;
455
+ url = `/category/${category.slug}`;
415
456
  break;
416
457
  }
417
458
 
418
459
  case !!groupExists: {
419
- url = `${nconf.get('url')}/groups/${slug}`;
460
+ url = `/groups/${slug}`;
420
461
  break;
421
462
  }
422
463
  }
@@ -456,7 +497,20 @@ Mentions.parseRaw = async (content) => {
456
497
  }
457
498
  }));
458
499
 
459
- return splitContent.join('');
500
+ const parsed = splitContent.join('');
501
+
502
+ // Modify existing anchors to local profile
503
+ const $ = cheerio.load(parsed);
504
+ const anchors = $('a');
505
+ Array.from(anchors).forEach((anchor) => {
506
+ const $anchor = $(anchor);
507
+ const url = $anchor.attr('href');
508
+ if (urlMap.has(url)) {
509
+ $anchor.attr('href', urlMap.get(url));
510
+ }
511
+ });
512
+
513
+ return $.html();
460
514
  };
461
515
 
462
516
  Mentions.clean = function (input, isMarkdown, stripBlockquote, stripCode) {
package/package.json CHANGED
@@ -1,11 +1,8 @@
1
1
  {
2
2
  "name": "nodebb-plugin-mentions",
3
- "version": "4.5.2",
3
+ "version": "4.6.0",
4
4
  "description": "NodeBB Plugin that allows users to mention other users by prepending an '@' sign to their username",
5
5
  "main": "library.js",
6
- "scripts": {
7
- "test": "echo \"Error: no test specified\" && exit 1"
8
- },
9
6
  "repository": {
10
7
  "type": "git",
11
8
  "url": "https://github.com/julianlam/nodebb-plugin-mentions"
@@ -16,22 +13,24 @@
16
13
  "username",
17
14
  "mentions"
18
15
  ],
19
- "author": "Julian Lam <julian@designcreateplay.com>",
20
- "license": "BSD-2-Clause",
16
+ "author": "Julian Lam <julian@nodebb.org>",
17
+ "license": "MIT",
21
18
  "bugs": {
22
19
  "url": "https://github.com/julianlam/nodebb-plugin-mentions/issues"
23
20
  },
24
21
  "nbbpm": {
25
- "compatibility": "^3.3.0"
22
+ "compatibility": "^4.0.0"
26
23
  },
27
24
  "dependencies": {
25
+ "cheerio": "^1.0.0-rc.12",
28
26
  "html-entities": "^2.3.2",
29
27
  "lodash": "4.17.21",
28
+ "sanitize-html": "^2.13.0",
30
29
  "validator": "^13.0.0"
31
30
  },
32
31
  "devDependencies": {
33
32
  "mocha": "10.4.0",
34
- "eslint": "9.0.0",
33
+ "eslint": "9.3.0",
35
34
  "eslint-config-nodebb": "0.2.1",
36
35
  "eslint-plugin-import": "2.29.1"
37
36
  }