diff --git a/__tests__/ExpensiMark-HTML-test.js b/__tests__/ExpensiMark-HTML-test.js
index 75e7daee6..db8893afc 100644
--- a/__tests__/ExpensiMark-HTML-test.js
+++ b/__tests__/ExpensiMark-HTML-test.js
@@ -304,15 +304,29 @@ test('Test markdown replacement for emojis with emails', () => {
+ '[πabc@gmail.com](abc@gmail.com) '
+ '[π abc@gmail.com ](abc@gmail.com) '
const result = 'Do not replace the emoji with link '
- + '[π](abc@gmail.com) '
- + '[π]( abc@gmail.com) '
- + '[π] abc@gmail.com '
- + '[π]((abc@gmail.com)) '
- + '[πabc@gmail.com](abc@gmail.com) '
- + '[π abc@gmail.com ](abc@gmail.com) '
+ + '[π](abc@gmail.com) '
+ + '[π]( abc@gmail.com) '
+ + '[π] abc@gmail.com '
+ + '[π]((abc@gmail.com)) '
+ + '[πabc@gmail.com](abc@gmail.com) '
+ + '[π abc@gmail.com ](abc@gmail.com) '
expect(parser.replace(testString)).toBe(result);
});
+test('Test markdown replacement for composite emoji', () => {
+ const testString = 'Replace composite emoji with only one emoji tag '
+ + 'πΆβπ«οΈ '
+ + 'π§βπ§ '
+ + 'π¨βπ« '
+ + 'π¨πΎββ€οΈβπ¨π½ '
+ const result = 'Replace composite emoji with only one emoji tag '
+ + 'πΆβπ«οΈ '
+ + 'π§βπ§ '
+ + 'π¨βπ« '
+ + 'π¨πΎββ€οΈβπ¨π½ '
+ expect(parser.replace(testString)).toBe(result);
+})
+
// Markdown style links replaced successfully
test('Test markdown style links', () => {
@@ -1123,7 +1137,7 @@ test('Test for link with no content', () => {
test('Test for link with emoji', () => {
const testString = '[π](www.link.com)';
- const resultString = '[π](www.link.com)';
+ const resultString = '[π](www.link.com)';;
expect(parser.replace(testString)).toBe(resultString);
});
test('Test quotes markdown replacement with heading inside', () => {
diff --git a/lib/CONST.d.ts b/lib/CONST.d.ts
index aea62aae2..02eb3fe2c 100644
--- a/lib/CONST.d.ts
+++ b/lib/CONST.d.ts
@@ -289,6 +289,12 @@ export declare const CONST: {
* Regex matching an text containing an Emoji
*/
readonly EMOJIS: RegExp;
+ /**
+ * Regex matching an text containing an Emoji that can be a single emoji or made up by some different emojis
+ *
+ * @type RegExp
+ */
+ readonly EMOJI_RULE: RegExp;
};
readonly REPORT: {
/**
diff --git a/lib/CONST.jsx b/lib/CONST.jsx
index b40225615..abdc31b3a 100644
--- a/lib/CONST.jsx
+++ b/lib/CONST.jsx
@@ -357,6 +357,12 @@ export const CONST = {
* @type RegExp
*/
EMOJIS: /[\p{Extended_Pictographic}\u200d\u{1f1e6}-\u{1f1ff}\u{1f3fb}-\u{1f3ff}\u{e0020}-\u{e007f}\u20E3\uFE0F]|[#*0-9]\uFE0F?\u20E3/gu,
+ /**
+ * Regex matching an text containing an Emoji that can be a single emoji or made up by some different emojis
+ *
+ * @type RegExp
+ */
+ 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,
},
REPORT: {
diff --git a/lib/ExpensiMark.js b/lib/ExpensiMark.js
index 690e8ac4a..e3822d20d 100644
--- a/lib/ExpensiMark.js
+++ b/lib/ExpensiMark.js
@@ -18,8 +18,15 @@ export default class ExpensiMark {
* @type {Object[]}
*/
this.rules = [
+ // Apply the emoji first avoid applying any other formatting rules inside of it
+ {
+ name: 'emoji',
+ regex: CONST.REG_EXP.EMOJI_RULE,
+ replacement: match => `${match}`
+ },
+
/**
- * Apply the code-fence first so that we avoid replacing anything inside of it that we're not supposed to
+ * Apply the code-fence to avoid replacing anything inside of it that we're not supposed to
* (aka any rule with the '(?![^<]*<\/pre>)' avoidance in it
*/
{
diff --git a/package-lock.json b/package-lock.json
index 074b0b93f..ae5d8679c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -24,7 +24,7 @@
"underscore": "1.13.6"
},
"devDependencies": {
- "@babel/preset-env": "^7.23.9",
+ "@babel/preset-env": "^7.24.0",
"babel-jest": "^29.0.0",
"babelify": "10.0.0",
"eslint-config-expensify": "^2.0.16",
@@ -353,9 +353,9 @@
}
},
"node_modules/@babel/helper-plugin-utils": {
- "version": "7.22.5",
- "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz",
- "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==",
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.0.tgz",
+ "integrity": "sha512-9cUznXMG0+FxRuJfvL82QlTqIzhVW9sL0KjMPHhAOOvpQGL8QtdxnBKILjBqxlHyliz0yCa1G903ZXI/FuHy2w==",
"dev": true,
"engines": {
"node": ">=6.9.0"
@@ -1320,16 +1320,15 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.23.4",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.23.4.tgz",
- "integrity": "sha512-9x9K1YyeQVw0iOXJlIzwm8ltobIIv7j2iLyP2jIhEbqPRQ7ScNgwQufU2I0Gq11VjyG4gI4yMXt2VFags+1N3g==",
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.1.tgz",
+ "integrity": "sha512-XjD5f0YqOtebto4HGISLNfiNMTTs6tbkFf2TOqJlYKYmbo+mN9Dnpl4SRoofiziuOWMIyq3sZEUqLo3hLITFEA==",
"dev": true,
"dependencies": {
- "@babel/compat-data": "^7.23.3",
- "@babel/helper-compilation-targets": "^7.22.15",
- "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-compilation-targets": "^7.23.6",
+ "@babel/helper-plugin-utils": "^7.24.0",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
- "@babel/plugin-transform-parameters": "^7.23.3"
+ "@babel/plugin-transform-parameters": "^7.24.1"
},
"engines": {
"node": ">=6.9.0"
@@ -1388,12 +1387,12 @@
}
},
"node_modules/@babel/plugin-transform-parameters": {
- "version": "7.23.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.23.3.tgz",
- "integrity": "sha512-09lMt6UsUb3/34BbECKVbVwrT9bO6lILWln237z7sLaWnMsTi7Yc9fhX5DLpkJzAGfaReXI22wP41SZmnAA3Vw==",
+ "version": "7.24.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.1.tgz",
+ "integrity": "sha512-8Jl6V24g+Uw5OGPeWNKrKqXPDw2YDjLc53ojwfMcKwlEoETKU9rU0mHUtcg9JntWI/QYzGAXNWEcVHZ+fR+XXg==",
"dev": true,
"dependencies": {
- "@babel/helper-plugin-utils": "^7.22.5"
+ "@babel/helper-plugin-utils": "^7.24.0"
},
"engines": {
"node": ">=6.9.0"
@@ -1622,14 +1621,14 @@
}
},
"node_modules/@babel/preset-env": {
- "version": "7.23.9",
- "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.23.9.tgz",
- "integrity": "sha512-3kBGTNBBk9DQiPoXYS0g0BYlwTQYUTifqgKTjxUwEUkduRT2QOa0FPGBJ+NROQhGyYO5BuTJwGvBnqKDykac6A==",
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.0.tgz",
+ "integrity": "sha512-ZxPEzV9IgvGn73iK0E6VB9/95Nd7aMFpbE0l8KQFDG70cOV9IxRP7Y2FUPmlK0v6ImlLqYX50iuZ3ZTVhOF2lA==",
"dev": true,
"dependencies": {
"@babel/compat-data": "^7.23.5",
"@babel/helper-compilation-targets": "^7.23.6",
- "@babel/helper-plugin-utils": "^7.22.5",
+ "@babel/helper-plugin-utils": "^7.24.0",
"@babel/helper-validator-option": "^7.23.5",
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.23.3",
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.23.3",
@@ -1682,7 +1681,7 @@
"@babel/plugin-transform-new-target": "^7.23.3",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.23.4",
"@babel/plugin-transform-numeric-separator": "^7.23.4",
- "@babel/plugin-transform-object-rest-spread": "^7.23.4",
+ "@babel/plugin-transform-object-rest-spread": "^7.24.0",
"@babel/plugin-transform-object-super": "^7.23.3",
"@babel/plugin-transform-optional-catch-binding": "^7.23.4",
"@babel/plugin-transform-optional-chaining": "^7.23.4",
diff --git a/package.json b/package.json
index 61be7b9a7..cfa587744 100644
--- a/package.json
+++ b/package.json
@@ -33,7 +33,7 @@
"underscore": "1.13.6"
},
"devDependencies": {
- "@babel/preset-env": "^7.23.9",
+ "@babel/preset-env": "^7.24.0",
"babel-jest": "^29.0.0",
"babelify": "10.0.0",
"eslint-config-expensify": "^2.0.16",