Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Mentions v2] Add new rule in ExpensiMark for room mentions #680

Merged
merged 5 commits into from
Apr 10, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
173 changes: 139 additions & 34 deletions __tests__/ExpensiMark-HTML-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -302,14 +302,14 @@ test('Test markdown replacement for emojis with emails', () => {
+ '[😄] abc@gmail.com '
+ '[😄]((abc@gmail.com)) '
+ '[😄abc@gmail.com](abc@gmail.com) '
+ '[😄 abc@gmail.com ](abc@gmail.com) '
+ '[😄 abc@gmail.com ](abc@gmail.com) ';
const result = 'Do not replace the emoji with link '
+ '[<emoji>😄</emoji>](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji>]( <a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji>] <a href="mailto:abc@gmail.com">abc@gmail.com</a> '
+ '[<emoji>😄</emoji>]((<a href="mailto:abc@gmail.com">abc@gmail.com</a>)) '
+ '[<emoji>😄</emoji><a href="mailto:abc@gmail.com">abc@gmail.com</a>](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji> <a href="mailto:abc@gmail.com">abc@gmail.com</a> ](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) '
+ '[<emoji>😄</emoji> <a href="mailto:abc@gmail.com">abc@gmail.com</a> ](<a href="mailto:abc@gmail.com">abc@gmail.com</a>) ';
expect(parser.replace(testString)).toBe(result);
});

Expand All @@ -318,14 +318,14 @@ test('Test markdown replacement for composite emoji', () => {
+ '😶‍🌫️ '
+ '🧑‍🔧 '
+ '👨‍🏫 '
+ '👨🏾‍❤️‍👨🏽 '
+ '👨🏾‍❤️‍👨🏽 ';
const result = 'Replace composite emoji with only one emoji tag '
+ '<emoji>😶‍🌫️</emoji> '
+ '<emoji>🧑‍🔧</emoji> '
+ '<emoji>👨‍🏫</emoji> '
+ '<emoji>👨🏾‍❤️‍👨🏽</emoji> '
+ '<emoji>👨🏾‍❤️‍👨🏽</emoji> ';
expect(parser.replace(testString)).toBe(result);
})
});


// Markdown style links replaced successfully
Expand Down Expand Up @@ -550,30 +550,30 @@ test('Test wrapped URLs', () => {
});

test('Test Url, where double quote is not allowed', () => {
const urlTestStartString = '"om https://www.she.com/"\n' +
'"om https://www.she.com"\n' +
'"https://www.she.com/ end"\n' +
'"https://www.she.com end"\n' +
'https://www.she.com/path?test="123"\n' +
'https://www.she.com/path?test="123/"\n' +
'"https://www.she.com/path?test="123"\n' +
'"https://www.she.com/path?test="123/"\n' +
'https://www.she.com/path?test=123"\n' +
'https://www.she.com/path?test=123/"\n' +
'https://www.she.com/path?test=/"123"\n' +
'https://www.she.com/path?test=/"123/"';
const urlTestReplacedString = '&quot;om <a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a>&quot;<br />' +
'&quot;om <a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a>&quot;<br />' +
'&quot;<a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a> end&quot;<br />' +
'&quot;<a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a> end&quot;<br />' +
'<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />' +
'<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />' +
'&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />' +
'&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />' +
'<a href=\"https://www.she.com/path?test=123\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123</a>&quot;<br />' +
'<a href=\"https://www.she.com/path?test=123/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123/</a>&quot;<br />' +
'<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123&quot;<br />' +
'<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123/&quot;';
const urlTestStartString = '"om https://www.she.com/"\n'
+ '"om https://www.she.com"\n'
+ '"https://www.she.com/ end"\n'
+ '"https://www.she.com end"\n'
+ 'https://www.she.com/path?test="123"\n'
+ 'https://www.she.com/path?test="123/"\n'
+ '"https://www.she.com/path?test="123"\n'
+ '"https://www.she.com/path?test="123/"\n'
+ 'https://www.she.com/path?test=123"\n'
+ 'https://www.she.com/path?test=123/"\n'
+ 'https://www.she.com/path?test=/"123"\n'
+ 'https://www.she.com/path?test=/"123/"';
const urlTestReplacedString = '&quot;om <a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a>&quot;<br />'
+ '&quot;om <a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a>&quot;<br />'
+ '&quot;<a href=\"https://www.she.com/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/</a> end&quot;<br />'
+ '&quot;<a href=\"https://www.she.com\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com</a> end&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />'
+ '&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123&quot;<br />'
+ '&quot;<a href=\"https://www.she.com/path?test=\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=</a>&quot;123/&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=123\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123</a>&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=123/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=123/</a>&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123&quot;<br />'
+ '<a href=\"https://www.she.com/path?test=/\" target=\"_blank\" rel=\"noreferrer noopener\">https://www.she.com/path?test=/</a>&quot;123/&quot;';
expect(parser.replace(urlTestStartString)).toBe(urlTestReplacedString);
});

Expand Down Expand Up @@ -732,7 +732,7 @@ test('Test urls with unmatched closing parentheses autolinks correctly', () => {
resultString: '<a href="https://google.com/(toto)" target="_blank" rel="noreferrer noopener">google.com/(toto)</a>)titi)',
},
];
testCases.forEach(testCase => {
testCases.forEach((testCase) => {
expect(parser.replace(testCase.testString)).toBe(testCase.resultString);
});
});
Expand Down Expand Up @@ -867,7 +867,7 @@ test('Test urls autolinks correctly', () => {
},
];

testCases.forEach(testCase => {
testCases.forEach((testCase) => {
expect(parser.replace(testCase.testString)).toBe(testCase.resultString);
});
});
Expand Down Expand Up @@ -1137,7 +1137,7 @@ test('Test for link with no content', () => {

test('Test for link with emoji', () => {
const testString = '[😀](www.link.com)';
const resultString = '[<emoji>😀</emoji>](<a href="https://www.link.com" target="_blank" rel="noreferrer noopener">www.link.com</a>)';;
const resultString = '[<emoji>😀</emoji>](<a href="https://www.link.com" target="_blank" rel="noreferrer noopener">www.link.com</a>)';
expect(parser.replace(testString)).toBe(resultString);
});
test('Test quotes markdown replacement with heading inside', () => {
Expand Down Expand Up @@ -1472,7 +1472,7 @@ test('Test for mention inside link and email markdown', () => {
expect(parser.replace(testString)).toBe(resultString);
});

test('Skip rendering invalid markdown',() => {
test('Skip rendering invalid markdown', () => {
let testString = '_*test_*';
expect(parser.replace(testString)).toBe('<em>*test</em>*');

Expand Down Expand Up @@ -1959,5 +1959,110 @@ describe('Image markdown conversion to html tag', () => {
const testString = '![`code`](https://example.com/image.png)';
const resultString = '<img src="https://example.com/image.png" alt="<code>code</code>" />';
expect(parser.replace(testString)).toBe(resultString);
})
});
});

describe('room mentions', () => {
robertKozik marked this conversation as resolved.
Show resolved Hide resolved
test('simple room mention', () => {
const testString = '#room';
const resultString = '<mention-report>#room</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with leading word and space', () => {
const testString = 'hi all #room';
const resultString = 'hi all <mention-report>#room</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with leading word and no space', () => {
const testString = 'hi all#room';
const resultString = 'hi all#room';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with space between hash and room name', () => {
const testString = '# room';
const resultString = '<h1>room</h1>';
expect(parser.replace(testString)).toBe(resultString);
});

test.only('room mention with markdown syntax before the # prefix', () => {
Beamanator marked this conversation as resolved.
Show resolved Hide resolved
const testString = 'hello *#room';
const resultString = 'hello *<mention-report>#room</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with italic, bold and strikethrough styles', () => {
const testString = '#room'
+ ' _#room_'
+ ' *#room*'
+ ' ~#room~'
+ ' [#room](google.com)'
+ ' #room abc'
+ ' #room*'
+ ' #room~'
+ ' #room#'
+ ' #room@'
+ ' #room$'
+ ' #room^'
+ ' #room('
+ ' #room.'
+ ' #room!'
+ ' #room?';

const resultString = '<mention-report>#room</mention-report>'
+ ' <em><mention-report>#room</mention-report></em>'
+ ' <strong><mention-report>#room</mention-report></strong>'
+ ' <del><mention-report>#room</mention-report></del>'
+ ' <a href="https://google.com" target="_blank" rel="noreferrer noopener">#room</a>'
+ ' <mention-report>#room</mention-report> abc'
+ ' <mention-report>#room</mention-report>*'
+ ' <mention-report>#room</mention-report>~'
+ ' <mention-report>#room</mention-report>#'
+ ' <mention-report>#room</mention-report>@'
+ ' <mention-report>#room</mention-report>$'
+ ' <mention-report>#room</mention-report>^'
+ ' <mention-report>#room</mention-report>('
+ ' <mention-report>#room</mention-report>.'
+ ' <mention-report>#room</mention-report>!'
+ ' <mention-report>#room</mention-report>?';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention inside link should not be rendered', () => {
const testString = '[#room](google.com)';
const resultString = '<a href="https://google.com" target="_blank" rel="noreferrer noopener">#room</a>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with space inside link should not be rendered', () => {
const testString = '[ #room](google.com/sub#111)';
const resultString = '<a href="https://google.com/sub#111" target="_blank" rel="noreferrer noopener">#room</a>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention inside code block should not be rendered', () => {
const testString = '```\n#room\n```';
const resultString = '<pre>#room<br /></pre>';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention without room name', () => {
const testString = '#';
const resultString = '#';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with numbers', () => {
const testString = '#room123';
const resultString = '#room123';
expect(parser.replace(testString)).toBe(resultString);
});

test('room mention with hyphens', () => {
const testString = '#room-name';
const resultString = '<mention-report>#room-name</mention-report>';
expect(parser.replace(testString)).toBe(resultString);
});
});
18 changes: 9 additions & 9 deletions __tests__/ExpensiMark-Markdown-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,7 @@ test('Test heading1 markdown replacement when # is followed by multiple spaces',

test('Test heading1 markdown when # is not followed by a space', () => {
const testString = '#This is not a heading1 because starts with a # but has no space after it\n';
const resultString = '#This is not a heading1 because starts with a # but has no space after it<br />';
const resultString = '<mention-report>#This</mention-report> is not a heading1 because starts with a # but has no space after it<br />';
expect(parser.replace(testString)).toBe(resultString);
});

Expand Down Expand Up @@ -688,7 +688,7 @@ test('Test codeFence copy from selection does not add extra new line', () => {
expect(parser.htmlToMarkdown(testString)).toBe('```\ncode\n```\ntext');

testString = '<h3>test heading</h3><div><pre class=\"notranslate\"><code class=\"notranslate\">Code snippet\n</code></pre></div><blockquote><p><a href=\"https://www.example.com\">link</a></p></blockquote>';
expect(parser.htmlToMarkdown(testString)).toBe('test heading\n```\nCode snippet\n```\n> [link](https://www.example.com)')
expect(parser.htmlToMarkdown(testString)).toBe('test heading\n```\nCode snippet\n```\n> [link](https://www.example.com)');
});

test('Linebreak should be remained for text between code block', () => {
Expand Down Expand Up @@ -758,17 +758,17 @@ test('Mention html to markdown', () => {
});

describe('Image tag conversion to markdown', () => {
test('Image with alt attribute', () => {
const testString = '<img src="https://example.com/image.png" alt="image" />';
const resultString = '![image](https://example.com/image.png)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});
test('Image with alt attribute', () => {
const testString = '<img src="https://example.com/image.png" alt="image" />';
const resultString = '![image](https://example.com/image.png)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});

test('Image without alt attribute', () => {
test('Image without alt attribute', () => {
const testString = '<img src="https://example.com/image.png" />';
const resultString = '![https://example.com/image.png](https://example.com/image.png)';
expect(parser.htmlToMarkdown(testString)).toBe(resultString);
});
});

test('Image with alt text containing escaped markdown', () => {
const testString = '<img src="https://example.com/image.png" alt="&ast;bold&ast; &lowbar;italic&lowbar; &#126;strike&#126;" />';
Expand Down
13 changes: 13 additions & 0 deletions lib/ExpensiMark.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ export default class ExpensiMark {
},
},

/**
* A room mention is a string that starts with the '#' symbol and is followed by a valid room name.
*
* Note: We are allowing mentions in a format of #room-name The room name can contain any
* combination of letters and hyphens
*/
{
robertKozik marked this conversation as resolved.
Show resolved Hide resolved
name: 'roomMentions',

regex: /(?<![^ \n*~_])(#[\p{Ll}0-9-]{1,80})/gmiu,
replacement: '<mention-report>$1</mention-report>',
},

/**
* This regex matches a valid user mention in a string.
* A user mention is a string that starts with the '@' symbol and is followed by a valid user's primary
Expand Down
Loading