Skip to content

Commit c73d705

Browse files
authored
Merge pull request #824 from software-mansion-labs/kicu/38025-short-mentions
Add handling short mentions tags to ExpensiMark
2 parents e759ac2 + f7e27c7 commit c73d705

File tree

5 files changed

+72
-10
lines changed

5 files changed

+72
-10
lines changed

.eslintrc.js

+3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
module.exports = {
22
extends: ['expensify', 'prettier'],
33
parser: '@typescript-eslint/parser',
4+
env: {
5+
jest: true,
6+
},
47
overrides: [
58
{
69
files: ['*.js', '*.jsx'],

__tests__/ExpensiMark-HTML-test.js

+28-8
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
/* eslint-disable max-len */
1+
/* eslint-disable max-len,no-useless-concat */
22
import ExpensiMark from '../lib/ExpensiMark';
33

44
const parser = new ExpensiMark();
@@ -693,9 +693,9 @@ test('Test url replacements', () => {
693693
'<a href="http://example.com/foo/*/bar/*/test.txt" target="_blank" rel="noreferrer noopener">http://example.com/foo/*/bar/*/test.txt</a> ' +
694694
'test-.com ' +
695695
'-<a href="https://test.com" target="_blank" rel="noreferrer noopener">test.com</a> ' +
696-
'@test.com ' +
697-
'@test.com <a href="https://test.com" target="_blank" rel="noreferrer noopener">test.com</a> ' +
698-
'@test.com @test.com ';
696+
'<mention-short>@test.com</mention-short> ' +
697+
'<mention-short>@test.com</mention-short> <a href="https://test.com" target="_blank" rel="noreferrer noopener">test.com</a> ' +
698+
'<mention-short>@test.com</mention-short> <mention-short>@test.com</mention-short> ';
699699

700700
expect(parser.replace(urlTestStartString)).toBe(urlTestReplacedString);
701701
});
@@ -876,7 +876,7 @@ test('Test urls autolinks correctly', () => {
876876
{
877877
testString: 'expensify.com -expensify.com @expensify.com',
878878
resultString:
879-
'<a href="https://expensify.com" target="_blank" rel="noreferrer noopener">expensify.com</a> -<a href="https://expensify.com" target="_blank" rel="noreferrer noopener">expensify.com</a> @expensify.com',
879+
'<a href="https://expensify.com" target="_blank" rel="noreferrer noopener">expensify.com</a> -<a href="https://expensify.com" target="_blank" rel="noreferrer noopener">expensify.com</a> <mention-short>@expensify.com</mention-short>',
880880
},
881881
{
882882
testString: 'https//www.expensify.com',
@@ -1376,7 +1376,7 @@ test('Test for user mention without leading whitespace', () => {
13761376

13771377
test('Test for user mention with @username@expensify', () => {
13781378
const testString = '@username@expensify';
1379-
const resultString = '@username@expensify';
1379+
const resultString = '<mention-short>@username</mention-short><mention-short>@expensify</mention-short>';
13801380
expect(parser.replace(testString)).toBe(resultString);
13811381
});
13821382

@@ -1452,6 +1452,26 @@ test('Test for @here mention with inlineCodeBlock style', () => {
14521452
expect(parser.replace(testString)).toBe(resultString);
14531453
});
14541454

1455+
describe('Tests for short mentions', () => {
1456+
test('short mentions should work for @username', () => {
1457+
const testString = '@johnny';
1458+
const resultString = '<mention-short>@johnny</mention-short>';
1459+
expect(parser.replace(testString)).toBe(resultString);
1460+
});
1461+
1462+
test('short mentions should work for @firstname.lastname', () => {
1463+
const testString = '@john.doe';
1464+
const resultString = '<mention-short>@john.doe</mention-short>';
1465+
expect(parser.replace(testString)).toBe(resultString);
1466+
});
1467+
1468+
test('short mentions should work and not break @here after mention', () => {
1469+
const testString = '@john.doe@here';
1470+
const resultString = '<mention-short>@john.doe</mention-short><mention-here>@here</mention-here>';
1471+
expect(parser.replace(testString)).toBe(resultString);
1472+
});
1473+
});
1474+
14551475
// Examples that should match for here mentions:
14561476
test('Test for here mention with @here', () => {
14571477
const testString = '@here';
@@ -1650,13 +1670,13 @@ test('Skip rendering invalid markdown', () => {
16501670

16511671
test('Test for email with test+1@gmail.com@gmail.com', () => {
16521672
const testString = 'test+1@gmail.com@gmail.com';
1653-
const resultString = '<a href="mailto:test+1@gmail.com">test+1@gmail.com</a>@gmail.com';
1673+
const resultString = '<a href="mailto:test+1@gmail.com">test+1@gmail.com</a><mention-short>@gmail.com</mention-short>';
16541674
expect(parser.replace(testString)).toBe(resultString);
16551675
});
16561676

16571677
test('Test for email with test@gmail.com@gmail.com', () => {
16581678
const testString = 'test@gmail.com@gmail.com';
1659-
const resultString = '<a href="mailto:test@gmail.com">test@gmail.com</a>@gmail.com';
1679+
const resultString = '<a href="mailto:test@gmail.com">test@gmail.com</a><mention-short>@gmail.com</mention-short>';
16601680
expect(parser.replace(testString)).toBe(resultString);
16611681
});
16621682

__tests__/ExpensiMark-test.js

-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* eslint-disable max-len */
22
import ExpensiMark from '../lib/ExpensiMark';
33
import * as Utils from '../lib/utils';
4-
import {any, string} from "prop-types";
54

65
const parser = new ExpensiMark();
76

lib/CONST.ts

+5
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,11 @@ const CONST = {
419419
*/
420420
EMOJI_RULE:
421421
/[\p{Extended_Pictographic}](\u200D[\p{Extended_Pictographic}]|[\u{1F3FB}-\u{1F3FF}]|[\u{E0020}-\u{E007F}]|\uFE0F|\u20E3)*|[\u{1F1E6}-\u{1F1FF}]{2}|[#*0-9]\uFE0F?\u20E3/gu,
422+
423+
/**
424+
* Regex to match a piece of text or @here, needed for both shortMention and userMention
425+
*/
426+
PRE_MENTION_TEXT_PART: '(@here|[a-zA-Z0-9.!$%&+=?^\\`{|}-]?)',
422427
},
423428

424429
REPORT: {

lib/ExpensiMark.ts

+36-1
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,7 @@ export default class ExpensiMark {
366366
{
367367
name: 'userMentions',
368368
regex: new RegExp(
369-
`(@here|[a-zA-Z0-9.!$%&+=?^\`{|}-]?)(@${Constants.CONST.REG_EXP.EMAIL_PART}|@${Constants.CONST.REG_EXP.PHONE_PART})(?!((?:(?!<a).)+)?<\\/a>|[^<]*(<\\/pre>|<\\/code>))`,
369+
`${Constants.CONST.REG_EXP.PRE_MENTION_TEXT_PART}(@${Constants.CONST.REG_EXP.EMAIL_PART}|@${Constants.CONST.REG_EXP.PHONE_PART})(?!((?:(?!<a).)+)?<\\/a>|[^<]*(<\\/pre>|<\\/code>))`,
370370
'gim',
371371
),
372372
replacement: (_extras, match, g1, g2) => {
@@ -485,6 +485,41 @@ export default class ExpensiMark {
485485
rawInputReplacement: '$1<a href="mailto:$2" data-raw-href="$2" data-link-variant="auto">$2</a>',
486486
},
487487

488+
/**
489+
* This regex matches a short user mention in a string.
490+
* A short-mention is a string that starts with the '@' symbol and is followed by a valid user's primary login without the email domain part
491+
* Ex: @john.doe, @user12345, but NOT @user@email.com
492+
*
493+
* Notes:
494+
* Phone is not a valid short mention.
495+
* In reality these "short-mentions" are just possible candidates, because the parser has no way of verifying if there exists a user named ex: @john.examplename.
496+
* The actual verification whether these mentions are pointing to real users is done in specific projects using ExpensiMark.
497+
* Nevertheless, "@john.examplename" is a correct possible short-mention, and so would be parsed.
498+
* This behaviour is similar to treating every user@something as valid user login.
499+
*
500+
* This regex will correctly preserve any @here mentions, the same way as "userMention" rule.
501+
*/
502+
{
503+
name: 'shortMentions',
504+
505+
regex: new RegExp(
506+
`${Constants.CONST.REG_EXP.PRE_MENTION_TEXT_PART}(@(?=((?=[\\w]+[\\w'#%+-]+(?:\\.[\\w'#%+-]+)*)[\\w\\.'#%+-]{1,64}(?= |_|\\b))(?!([:\\/\\\\]))(?<end>.*))(?!here)\\S{3,254}(?=\\k<end>$))(?!((?:(?!<a).)+)?<\\/a>|[^<]*(<\\/pre>|<\\/code>|<\\/mention-user>|<\\/mention-here>))`,
507+
'gim',
508+
),
509+
replacement: (_extras, match, g1, g2) => {
510+
if (!Str.isValidMention(match)) {
511+
return match;
512+
}
513+
return `${g1}<mention-short>${g2}</mention-short>`;
514+
},
515+
},
516+
517+
{
518+
name: 'hereMentionAfterShortMentions',
519+
regex: /(<\/mention-short>)(@here)(?=\b)/gm,
520+
replacement: '$1<mention-here>$2</mention-here>',
521+
},
522+
488523
{
489524
// Use \B in this case because \b doesn't match * or ~.
490525
// \B will match everything that \b doesn't, so it works

0 commit comments

Comments
 (0)