diff --git a/.eslintrc.js b/.eslintrc.js
index b3a9bd5a..124d2546 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -1,6 +1,9 @@
module.exports = {
extends: ['expensify', 'prettier'],
parser: '@typescript-eslint/parser',
+ env: {
+ jest: true,
+ },
overrides: [
{
files: ['*.js', '*.jsx'],
diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js
index 3eef3952..3266ce07 100644
--- a/__tests__/ExpensiMark-HTML-test.js
+++ b/__tests__/ExpensiMark-HTML-test.js
@@ -1,4 +1,4 @@
-/* eslint-disable max-len */
+/* eslint-disable max-len,no-useless-concat */
import ExpensiMark from '../lib/ExpensiMark';
const parser = new ExpensiMark();
@@ -693,9 +693,9 @@ test('Test url replacements', () => {
'http://example.com/foo/*/bar/*/test.txt ' +
'test-.com ' +
'-test.com ' +
- '@test.com ' +
- '@test.com test.com ' +
- '@test.com @test.com ';
+ '@test.com ' +
+ '@test.com test.com ' +
+ '@test.com @test.com ';
expect(parser.replace(urlTestStartString)).toBe(urlTestReplacedString);
});
@@ -876,7 +876,7 @@ test('Test urls autolinks correctly', () => {
{
testString: 'expensify.com -expensify.com @expensify.com',
resultString:
- 'expensify.com -expensify.com @expensify.com',
+ 'expensify.com -expensify.com @expensify.com',
},
{
testString: 'https//www.expensify.com',
@@ -1376,7 +1376,7 @@ test('Test for user mention without leading whitespace', () => {
test('Test for user mention with @username@expensify', () => {
const testString = '@username@expensify';
- const resultString = '@username@expensify';
+ const resultString = '@username@expensify';
expect(parser.replace(testString)).toBe(resultString);
});
@@ -1452,6 +1452,26 @@ test('Test for @here mention with inlineCodeBlock style', () => {
expect(parser.replace(testString)).toBe(resultString);
});
+describe('Tests for short mentions', () => {
+ test('short mentions should work for @username', () => {
+ const testString = '@johnny';
+ const resultString = '@johnny';
+ expect(parser.replace(testString)).toBe(resultString);
+ });
+
+ test('short mentions should work for @firstname.lastname', () => {
+ const testString = '@john.doe';
+ const resultString = '@john.doe';
+ expect(parser.replace(testString)).toBe(resultString);
+ });
+
+ test('short mentions should work and not break @here after mention', () => {
+ const testString = '@john.doe@here';
+ const resultString = '@john.doe@here';
+ expect(parser.replace(testString)).toBe(resultString);
+ });
+});
+
// Examples that should match for here mentions:
test('Test for here mention with @here', () => {
const testString = '@here';
@@ -1650,13 +1670,13 @@ test('Skip rendering invalid markdown', () => {
test('Test for email with test+1@gmail.com@gmail.com', () => {
const testString = 'test+1@gmail.com@gmail.com';
- const resultString = 'test+1@gmail.com@gmail.com';
+ const resultString = 'test+1@gmail.com@gmail.com';
expect(parser.replace(testString)).toBe(resultString);
});
test('Test for email with test@gmail.com@gmail.com', () => {
const testString = 'test@gmail.com@gmail.com';
- const resultString = 'test@gmail.com@gmail.com';
+ const resultString = 'test@gmail.com@gmail.com';
expect(parser.replace(testString)).toBe(resultString);
});
diff --git a/__tests__/ExpensiMark-test.js b/__tests__/ExpensiMark-test.js
index 41c0a2c9..befe1958 100644
--- a/__tests__/ExpensiMark-test.js
+++ b/__tests__/ExpensiMark-test.js
@@ -1,7 +1,6 @@
/* eslint-disable max-len */
import ExpensiMark from '../lib/ExpensiMark';
import * as Utils from '../lib/utils';
-import {any, string} from "prop-types";
const parser = new ExpensiMark();
diff --git a/lib/CONST.ts b/lib/CONST.ts
index 431b7b4b..7c920706 100644
--- a/lib/CONST.ts
+++ b/lib/CONST.ts
@@ -419,6 +419,11 @@ const CONST = {
*/
EMOJI_RULE:
/[\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,
+
+ /**
+ * Regex to match a piece of text or @here, needed for both shortMention and userMention
+ */
+ PRE_MENTION_TEXT_PART: '(@here|[a-zA-Z0-9.!$%&+=?^\\`{|}-]?)',
},
REPORT: {
diff --git a/lib/ExpensiMark.ts b/lib/ExpensiMark.ts
index 61413e0d..543c0f50 100644
--- a/lib/ExpensiMark.ts
+++ b/lib/ExpensiMark.ts
@@ -365,7 +365,7 @@ export default class ExpensiMark {
{
name: 'userMentions',
regex: new RegExp(
- `(@here|[a-zA-Z0-9.!$%&+=?^\`{|}-]?)(@${Constants.CONST.REG_EXP.EMAIL_PART}|@${Constants.CONST.REG_EXP.PHONE_PART})(?!((?:(?!|[^<]*(<\\/pre>|<\\/code>))`,
+ `${Constants.CONST.REG_EXP.PRE_MENTION_TEXT_PART}(@${Constants.CONST.REG_EXP.EMAIL_PART}|@${Constants.CONST.REG_EXP.PHONE_PART})(?!((?:(?!|[^<]*(<\\/pre>|<\\/code>))`,
'gim',
),
replacement: (_extras, match, g1, g2) => {
@@ -484,6 +484,41 @@ export default class ExpensiMark {
rawInputReplacement: '$1$2',
},
+ /**
+ * This regex matches a short user mention in a string.
+ * 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
+ * Ex: @john.doe, @user12345, but NOT @user@email.com
+ *
+ * Notes:
+ * Phone is not a valid short mention.
+ * 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.
+ * The actual verification whether these mentions are pointing to real users is done in specific projects using ExpensiMark.
+ * Nevertheless, "@john.examplename" is a correct possible short-mention, and so would be parsed.
+ * This behaviour is similar to treating every user@something as valid user login.
+ *
+ * This regex will correctly preserve any @here mentions, the same way as "userMention" rule.
+ */
+ {
+ name: 'shortMentions',
+
+ regex: new RegExp(
+ `${Constants.CONST.REG_EXP.PRE_MENTION_TEXT_PART}(@(?=((?=[\\w]+[\\w'#%+-]+(?:\\.[\\w'#%+-]+)*)[\\w\\.'#%+-]{1,64}(?= |_|\\b))(?!([:\\/\\\\]))(?.*))(?!here)\\S{3,254}(?=\\k$))(?!((?:(?!|[^<]*(<\\/pre>|<\\/code>|<\\/mention-user>|<\\/mention-here>))`,
+ 'gim',
+ ),
+ replacement: (_extras, match, g1, g2) => {
+ if (!Str.isValidMention(match)) {
+ return match;
+ }
+ return `${g1}${g2}`;
+ },
+ },
+
+ {
+ name: 'hereMentionAfterShortMentions',
+ regex: /(<\/mention-short>)(@here)(?=\b)/gm,
+ replacement: '$1$2',
+ },
+
{
// Use \B in this case because \b doesn't match * or ~.
// \B will match everything that \b doesn't, so it works