[FIX] mail: remove regex from mentions
That should fix all detection issues once and for all. task-2366680 closes odoo/odoo#61528 X-original-commit: cf6961fba38ed619bf7bfb908c3aadde0f694648 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com>
This commit is contained in:
@@ -1166,7 +1166,7 @@ QUnit.test('Post a message containing an email address followed by a mention on
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
})
|
||||
});
|
||||
});
|
||||
await afterNextRender(() =>
|
||||
document.querySelector('.o_ComposerSuggestion').click()
|
||||
@@ -1181,6 +1181,66 @@ QUnit.test('Post a message containing an email address followed by a mention on
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('mention 2 different partners that have the same name', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.channel'].records.push({ id: 11 });
|
||||
this.data['res.partner'].records.push(
|
||||
{
|
||||
id: 25,
|
||||
email: "partner1@example.com",
|
||||
name: "TestPartner",
|
||||
}, {
|
||||
id: 26,
|
||||
email: "partner2@example.com",
|
||||
name: "TestPartner",
|
||||
},
|
||||
);
|
||||
await this.start();
|
||||
const thread = this.env.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
const threadViewer = this.env.models['mail.thread_viewer'].create({
|
||||
hasThreadView: true,
|
||||
thread: [['link', thread]],
|
||||
});
|
||||
await this.createThreadViewComponent(threadViewer.threadView, { hasComposer: true });
|
||||
document.querySelector('.o_ComposerTextInput_textarea').focus();
|
||||
await afterNextRender(() => {
|
||||
["@", "T", "e"].forEach((char)=>{
|
||||
document.execCommand('insertText', false, char);
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
});
|
||||
});
|
||||
await afterNextRender(() => document.querySelectorAll('.o_ComposerSuggestion')[0].click());
|
||||
await afterNextRender(() => {
|
||||
["@", "T", "e"].forEach((char)=>{
|
||||
document.execCommand('insertText', false, char);
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
});
|
||||
});
|
||||
await afterNextRender(() => document.querySelectorAll('.o_ComposerSuggestion')[1].click());
|
||||
await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
|
||||
assert.containsOnce(document.body, '.o_Message_content', 'should have one message after posting it');
|
||||
assert.containsOnce(
|
||||
document.querySelector(`.o_Message_content`),
|
||||
`.o_mail_redirect[data-oe-id="25"][data-oe-model="res.partner"]:contains("@TestPartner")`,
|
||||
"message should contain the first partner mention"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.querySelector(`.o_Message_content`),
|
||||
`.o_mail_redirect[data-oe-id="26"][data-oe-model="res.partner"]:contains("@TestPartner")`,
|
||||
"message should also contain the second partner mention"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('mention a channel with space in the name', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
@@ -1216,12 +1276,12 @@ QUnit.test('mention a channel with space in the name', async function (assert) {
|
||||
assert.containsOnce(
|
||||
document.querySelector('.o_Message_content'),
|
||||
'.o_channel_redirect',
|
||||
"message must contain a link the the mentionned channel"
|
||||
"message must contain a link to the mentioned channel"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_channel_redirect').textContent,
|
||||
'#General good boy',
|
||||
"link to the channel must containt # + the channel name"
|
||||
"link to the channel must contains # + the channel name"
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1260,16 +1320,16 @@ QUnit.test('mention a channel with "&" in the name', async function (assert) {
|
||||
assert.containsOnce(
|
||||
document.querySelector('.o_Message_content'),
|
||||
'.o_channel_redirect',
|
||||
"message should contain a link the the mentionned channel"
|
||||
"message should contain a link to the mentioned channel"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_channel_redirect').textContent,
|
||||
'#General & good',
|
||||
"link to the channel must containt # + the channel name"
|
||||
"link to the channel must contains # + the channel name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('start to mention a channel and add a new line after the #', async function (assert) {
|
||||
QUnit.test('mention a channel on a second line when the first line contains #', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
@@ -1289,7 +1349,7 @@ QUnit.test('start to mention a channel and add a new line after the #', async fu
|
||||
|
||||
await afterNextRender(() => {
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`).focus();
|
||||
document.execCommand('insertText', false, "#\n #");
|
||||
document.execCommand('insertText', false, "#blabla\n#");
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
@@ -1304,16 +1364,16 @@ QUnit.test('start to mention a channel and add a new line after the #', async fu
|
||||
assert.containsOnce(
|
||||
document.querySelector('.o_Message_content'),
|
||||
'.o_channel_redirect',
|
||||
"message should contain a link the the mentionned channel"
|
||||
"message should contain a link to the mentioned channel"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_channel_redirect').textContent,
|
||||
'#General good',
|
||||
"link to the channel must containt # + the channel name"
|
||||
"link to the channel must contains # + the channel name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('mention a channel and add a new line and text after the mention', async function (assert) {
|
||||
QUnit.test('mention a channel when replacing the space after the mention by another char', async function (assert) {
|
||||
assert.expect(2);
|
||||
|
||||
this.data['mail.channel'].records.push({
|
||||
@@ -1345,7 +1405,7 @@ QUnit.test('mention a channel and add a new line and text after the mention', as
|
||||
await afterNextRender(() => {
|
||||
const text = document.querySelector(`.o_ComposerTextInput_textarea`).value;
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`).value = text.slice(0, -1);
|
||||
document.execCommand('insertText', false, "\ntest");
|
||||
document.execCommand('insertText', false, ", test");
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
@@ -1357,12 +1417,70 @@ QUnit.test('mention a channel and add a new line and text after the mention', as
|
||||
assert.containsOnce(
|
||||
document.querySelector('.o_Message_content'),
|
||||
'.o_channel_redirect',
|
||||
"message should contain a link the the mentionned channel"
|
||||
"message should contain a link to the mentioned channel"
|
||||
);
|
||||
assert.strictEqual(
|
||||
document.querySelector('.o_channel_redirect').textContent,
|
||||
'#General good',
|
||||
"link to the channel must containt # + the channel name"
|
||||
"link to the channel must contains # + the channel name"
|
||||
);
|
||||
});
|
||||
|
||||
QUnit.test('mention 2 different channels that have the same name', async function (assert) {
|
||||
assert.expect(3);
|
||||
|
||||
this.data['mail.channel'].records.push(
|
||||
{
|
||||
id: 11,
|
||||
name: "my channel",
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: "my channel",
|
||||
},
|
||||
);
|
||||
await this.start();
|
||||
const thread = this.env.models['mail.thread'].findFromIdentifyingData({
|
||||
id: 11,
|
||||
model: 'mail.channel',
|
||||
});
|
||||
const threadViewer = this.env.models['mail.thread_viewer'].create({
|
||||
hasThreadView: true,
|
||||
thread: [['link', thread]],
|
||||
});
|
||||
await this.createThreadViewComponent(threadViewer.threadView, { hasComposer: true });
|
||||
document.querySelector('.o_ComposerTextInput_textarea').focus();
|
||||
await afterNextRender(() => {
|
||||
["#", "m", "y"].forEach((char)=>{
|
||||
document.execCommand('insertText', false, char);
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
});
|
||||
});
|
||||
await afterNextRender(() => document.querySelectorAll('.o_ComposerSuggestion')[0].click());
|
||||
await afterNextRender(() => {
|
||||
["#", "m", "y"].forEach((char)=>{
|
||||
document.execCommand('insertText', false, char);
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keydown'));
|
||||
document.querySelector(`.o_ComposerTextInput_textarea`)
|
||||
.dispatchEvent(new window.KeyboardEvent('keyup'));
|
||||
});
|
||||
});
|
||||
await afterNextRender(() => document.querySelectorAll('.o_ComposerSuggestion')[1].click());
|
||||
await afterNextRender(() => document.querySelector('.o_Composer_buttonSend').click());
|
||||
assert.containsOnce(document.body, '.o_Message_content', 'should have one message after posting it');
|
||||
assert.containsOnce(
|
||||
document.querySelector(`.o_Message_content`),
|
||||
`.o_channel_redirect[data-oe-id="11"][data-oe-model="mail.channel"]:contains("#my channel")`,
|
||||
"message should contain the first channel mention"
|
||||
);
|
||||
assert.containsOnce(
|
||||
document.querySelector(`.o_Message_content`),
|
||||
`.o_channel_redirect[data-oe-id="12"][data-oe-model="mail.channel"]:contains("#my channel")`,
|
||||
"message should also contain the second channel mention"
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@@ -161,7 +161,7 @@ function factory(dependencies) {
|
||||
recordReplacement = this[this.activeSuggestedRecordName].substitution;
|
||||
break;
|
||||
case 'activeSuggestedChannel':
|
||||
recordReplacement = this[this.activeSuggestedRecordName].name.replace(/ /g, '\u00a0');
|
||||
recordReplacement = this[this.activeSuggestedRecordName].name;
|
||||
this.update({
|
||||
mentionedChannels: [['link', this[this.activeSuggestedRecordName]]],
|
||||
});
|
||||
@@ -170,7 +170,7 @@ function factory(dependencies) {
|
||||
recordReplacement = this[this.activeSuggestedRecordName].name;
|
||||
break;
|
||||
case 'activeSuggestedPartner':
|
||||
recordReplacement = this[this.activeSuggestedRecordName].name.replace(/ /g, '\u00a0');
|
||||
recordReplacement = this[this.activeSuggestedRecordName].name;
|
||||
this.update({
|
||||
mentionedPartners: [['link', this[this.activeSuggestedRecordName]]],
|
||||
});
|
||||
@@ -510,15 +510,18 @@ function factory(dependencies) {
|
||||
* @returns {mail.partner[]}
|
||||
*/
|
||||
_computeMentionedPartners() {
|
||||
const inputMentions = this.textInputContent.replace(/\n/g, " \n ").match(
|
||||
new RegExp("@[^ ]+(?= | |$)", 'g')
|
||||
) || [];
|
||||
const unmentionedPartners = [];
|
||||
// ensure the same mention is not used multiple times if multiple
|
||||
// partners have the same name
|
||||
const namesIndex = {};
|
||||
for (const partner of this.mentionedPartners) {
|
||||
let inputMention = inputMentions.find(item => {
|
||||
return item === ("@" + partner.name).replace(/ /g, '\u00a0');
|
||||
});
|
||||
if (!inputMention) {
|
||||
const fromIndex = namesIndex[partner.name] !== undefined
|
||||
? namesIndex[partner.name] + 1 :
|
||||
0;
|
||||
const index = this.textInputContent.indexOf(`@${partner.name}`, fromIndex);
|
||||
if (index !== -1) {
|
||||
namesIndex[partner.name] = index;
|
||||
} else {
|
||||
unmentionedPartners.push(partner);
|
||||
}
|
||||
}
|
||||
@@ -533,15 +536,18 @@ function factory(dependencies) {
|
||||
* @returns {mail.partner[]}
|
||||
*/
|
||||
_computeMentionedChannels() {
|
||||
const inputMentions = this.textInputContent.replace(/\n/g, " \n ").match(
|
||||
new RegExp("#[^ ]+(?= | |$)", 'g')
|
||||
) || [];
|
||||
const unmentionedChannels = [];
|
||||
// ensure the same mention is not used multiple times if multiple
|
||||
// channels have the same name
|
||||
const namesIndex = {};
|
||||
for (const channel of this.mentionedChannels) {
|
||||
let inputMention = inputMentions.find(item => {
|
||||
return item === ("#" + channel.name).replace(/ /g, '\u00a0');
|
||||
});
|
||||
if (!inputMention) {
|
||||
const fromIndex = namesIndex[channel.name] !== undefined
|
||||
? namesIndex[channel.name] + 1 :
|
||||
0;
|
||||
const index = this.textInputContent.indexOf(`#${channel.name}`, fromIndex);
|
||||
if (index !== -1) {
|
||||
namesIndex[channel.name] = index;
|
||||
} else {
|
||||
unmentionedChannels.push(channel);
|
||||
}
|
||||
}
|
||||
@@ -577,45 +583,45 @@ function factory(dependencies) {
|
||||
* @returns {string}
|
||||
*/
|
||||
_generateMentionsLinks(body) {
|
||||
if (this.mentionedPartners.length === 0 && this.mentionedChannels.length === 0) {
|
||||
return body;
|
||||
// List of mention data to insert in the body.
|
||||
// Useful to do the final replace after parsing to avoid using the
|
||||
// same tag twice if two different mentions have the same name.
|
||||
const mentions = [];
|
||||
for (const partner of this.mentionedPartners) {
|
||||
const placeholder = `@-mention-partner-${partner.id}`;
|
||||
const text = `@${owl.utils.escape(partner.name)}`;
|
||||
mentions.push({
|
||||
class: 'o_mail_redirect',
|
||||
id: partner.id,
|
||||
model: 'res.partner',
|
||||
placeholder,
|
||||
text,
|
||||
});
|
||||
body = body.replace(text, placeholder);
|
||||
}
|
||||
const inputMentions = body.replace(new RegExp('<br/>', 'g'), " <br/> ")
|
||||
.match(new RegExp("(@|#)" + '[^ ]+(?= | |$)', 'g'))
|
||||
.filter(match => !match.endsWith("<br/>"));
|
||||
const substrings = [];
|
||||
let startIndex = 0;
|
||||
for (const match of inputMentions) {
|
||||
const suggestionDelimiter = match[0];
|
||||
const matchNameEscaped = match.substring(1).replace(new RegExp('\u00a0', 'g'), ' ');
|
||||
const endIndex = body.indexOf(match, startIndex) + match.length;
|
||||
let field = "mentionedPartners";
|
||||
let model = "res.partner";
|
||||
let cssClass = "o_mail_redirect";
|
||||
if (suggestionDelimiter === "#") {
|
||||
field = "mentionedChannels";
|
||||
model = "mail.channel";
|
||||
cssClass = "o_channel_redirect";
|
||||
}
|
||||
const mention = this[field].find(mention =>
|
||||
owl.utils.escape(mention.name) === matchNameEscaped
|
||||
);
|
||||
let mentionLink = suggestionDelimiter + matchNameEscaped;
|
||||
if (mention) {
|
||||
const baseHREF = this.env.session.url('/web');
|
||||
const href = `href='${baseHREF}#model=${model}&id=${mention.id}'`;
|
||||
const attClass = `class='${cssClass}'`;
|
||||
const dataOeId = `data-oe-id='${mention.id}'`;
|
||||
const dataOeModel = `data-oe-model='${model}'`;
|
||||
const target = `target='_blank'`;
|
||||
mentionLink = `<a ${href} ${attClass} ${dataOeId} ${dataOeModel} ${target} >${suggestionDelimiter}${matchNameEscaped}</a>`;
|
||||
}
|
||||
substrings.push(body.substring(startIndex, body.indexOf(match, startIndex)));
|
||||
substrings.push(mentionLink);
|
||||
startIndex = endIndex;
|
||||
for (const channel of this.mentionedChannels) {
|
||||
const placeholder = `#-mention-channel-${channel.id}`;
|
||||
const text = `#${owl.utils.escape(channel.name)}`;
|
||||
mentions.push({
|
||||
class: 'o_channel_redirect',
|
||||
id: channel.id,
|
||||
model: 'mail.channel',
|
||||
placeholder,
|
||||
text,
|
||||
});
|
||||
body = body.replace(text, placeholder);
|
||||
}
|
||||
substrings.push(body.substring(startIndex, body.length));
|
||||
return substrings.join('');
|
||||
const baseHREF = this.env.session.url('/web');
|
||||
for (const mention of mentions) {
|
||||
const href = `href='${baseHREF}#model=${mention.model}&id=${mention.id}'`;
|
||||
const attClass = `class='${mention.class}'`;
|
||||
const dataOeId = `data-oe-id='${mention.id}'`;
|
||||
const dataOeModel = `data-oe-model='${mention.model}'`;
|
||||
const target = `target='_blank'`;
|
||||
const link = `<a ${href} ${attClass} ${dataOeId} ${dataOeModel} ${target}>${mention.text}</a>`;
|
||||
body = body.replace(mention.placeholder, link);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user