diff --git a/addons/im_livechat/models/mail_channel.py b/addons/im_livechat/models/mail_channel.py
index b62a7c570aa..adb8d59ef0b 100644
--- a/addons/im_livechat/models/mail_channel.py
+++ b/addons/im_livechat/models/mail_channel.py
@@ -87,7 +87,9 @@ class MailChannel(models.Model):
def _execute_command_help_message_extra(self):
msg = super(MailChannel, self)._execute_command_help_message_extra()
- return msg + _("Type :shortcut to insert a canned response in your message.
")
+ if self.channel_type == 'livechat':
+ return msg + _("Type :shortcut to insert a canned response in your message.
")
+ return msg
def execute_command_history(self, **kwargs):
self.env['bus.bus']._sendone(self.uuid, 'im_livechat.history_command', {'id': self.id})
diff --git a/addons/im_livechat/static/src/composer/suggestion_service_patch.js b/addons/im_livechat/static/src/composer/suggestion_service_patch.js
new file mode 100644
index 00000000000..83adf2ba677
--- /dev/null
+++ b/addons/im_livechat/static/src/composer/suggestion_service_patch.js
@@ -0,0 +1,72 @@
+/* @odoo-module */
+
+import { patch } from "@web/core/utils/patch";
+import { SuggestionService } from "@mail/composer/suggestion_service";
+import { cleanTerm } from "@mail/utils/format";
+import { _t } from "@web/core/l10n/translation";
+
+patch(SuggestionService.prototype, "im_livechat", {
+ getSupportedDelimiters(thread) {
+ return thread?.type === "livechat"
+ ? [...this._super(...arguments), ":"]
+ : this._super(...arguments);
+ },
+ /**
+ * Returns suggestions that match the given search term from specified type.
+ *
+ * @param {Object} [param0={}]
+ * @param {String} [param0.delimiter] can be one one of the following: ["@", ":", "#", "/"]
+ * @param {String} [param0.term]
+ * @param {Object} [options={}]
+ * @param {Integer} [options.thread] prioritize and/or restrict
+ * result in the context of given thread
+ * @returns {[mainSuggestion[], extraSuggestion[]]}
+ */
+ searchSuggestions({ delimiter, term }, { thread } = {}, sort = false) {
+ if (delimiter === ":") {
+ return this.searchCannedResponseSuggestions(cleanTerm(term), sort);
+ }
+ return this._super(...arguments);
+ },
+
+ searchCannedResponseSuggestions(cleanedSearchTerm, sort) {
+ const cannedResponses = this.store.cannedResponses
+ .filter((cannedResponse) => {
+ return cleanTerm(cannedResponse.name).includes(cleanedSearchTerm);
+ })
+ .map(({ id, name, substitution }) => {
+ return {
+ id,
+ name,
+ substitution: _t(substitution),
+ };
+ });
+ const sortFunc = (c1, c2) => {
+ const cleanedName1 = cleanTerm(c1.name ?? "");
+ const cleanedName2 = cleanTerm(c2.name ?? "");
+ if (
+ cleanedName1.startsWith(cleanedSearchTerm) &&
+ !cleanedName2.startsWith(cleanedSearchTerm)
+ ) {
+ return -1;
+ }
+ if (
+ !cleanedName1.startsWith(cleanedSearchTerm) &&
+ cleanedName2.startsWith(cleanedSearchTerm)
+ ) {
+ return 1;
+ }
+ if (cleanedName1 < cleanedName2) {
+ return -1;
+ }
+ if (cleanedName1 > cleanedName2) {
+ return 1;
+ }
+ return c1.id - c2.id;
+ };
+ return {
+ type: "CannedResponse",
+ mainSuggestions: sort ? cannedResponses.sort(sortFunc) : cannedResponses,
+ };
+ },
+});
diff --git a/addons/im_livechat/static/tests/composer_patch_tests.js b/addons/im_livechat/static/tests/composer_patch_tests.js
index 51b8fa263b5..b2d2808e892 100644
--- a/addons/im_livechat/static/tests/composer_patch_tests.js
+++ b/addons/im_livechat/static/tests/composer_patch_tests.js
@@ -1,7 +1,13 @@
/** @odoo-module */
import { startServer } from "@bus/../tests/helpers/mock_python_environment";
-import { afterNextRender, click, dragenterFiles, start } from "@mail/../tests/helpers/test_utils";
+import {
+ afterNextRender,
+ insertText,
+ click,
+ dragenterFiles,
+ start,
+} from "@mail/../tests/helpers/test_utils";
import { editInput, nextTick, triggerHotkey } from "@web/../tests/helpers/utils";
QUnit.module("composer (patch)");
@@ -87,3 +93,120 @@ QUnit.test('Receives visitor typing status "is typing"', async (assert) => {
);
assert.containsOnce($, ".o-mail-Typing:contains(Visitor 20 is typing...)");
});
+
+QUnit.test('display canned response suggestions on typing ":"', async (assert) => {
+ const pyEnv = await startServer();
+ const channelId = pyEnv["mail.channel"].create({
+ anonymous_name: "Mario",
+ channel_member_ids: [
+ [0, 0, { partner_id: pyEnv.currentPartnerId }],
+ [0, 0, { partner_id: pyEnv.publicPartnerId }],
+ ],
+ channel_type: "livechat",
+ livechat_operator_id: pyEnv.currentPartnerId,
+ });
+ pyEnv["mail.shortcode"].create({
+ source: "hello",
+ substitution: "Hello! How are you?",
+ });
+ const { openDiscuss } = await start();
+ await openDiscuss(channelId);
+ assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
+ await insertText(".o-mail-Composer-input", ":");
+ assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
+});
+
+QUnit.test("use a canned response", async (assert) => {
+ const pyEnv = await startServer();
+ const channelId = pyEnv["mail.channel"].create({
+ anonymous_name: "Mario",
+ channel_member_ids: [
+ [0, 0, { partner_id: pyEnv.currentPartnerId }],
+ [0, 0, { partner_id: pyEnv.publicPartnerId }],
+ ],
+ channel_type: "livechat",
+ livechat_operator_id: pyEnv.currentPartnerId,
+ });
+ pyEnv["mail.shortcode"].create({
+ source: "hello",
+ substitution: "Hello! How are you?",
+ });
+ const { openDiscuss } = await start();
+ await openDiscuss(channelId);
+ assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
+ assert.strictEqual($(".o-mail-Composer-input").val(), "");
+ await insertText(".o-mail-Composer-input", ":");
+ assert.containsOnce($, ".o-mail-Composer-suggestion");
+ await click(".o-mail-Composer-suggestion");
+ assert.strictEqual(
+ $(".o-mail-Composer-input").val().replace(/\s/, " "),
+ "Hello! How are you? ",
+ "canned response + additional whitespace afterwards"
+ );
+});
+
+QUnit.test("use a canned response some text", async (assert) => {
+ const pyEnv = await startServer();
+ const channelId = pyEnv["mail.channel"].create({
+ anonymous_name: "Mario",
+ channel_member_ids: [
+ [0, 0, { partner_id: pyEnv.currentPartnerId }],
+ [0, 0, { partner_id: pyEnv.publicPartnerId }],
+ ],
+ channel_type: "livechat",
+ livechat_operator_id: pyEnv.currentPartnerId,
+ });
+ pyEnv["mail.shortcode"].create({
+ source: "hello",
+ substitution: "Hello! How are you?",
+ });
+ const { openDiscuss } = await start();
+ await openDiscuss(channelId);
+ assert.containsNone($, ".o-mail-Composer-suggestion");
+ assert.strictEqual($(".o-mail-Composer-input").val(), "");
+ await insertText(".o-mail-Composer-input", "bluhbluh ");
+ assert.strictEqual($(".o-mail-Composer-input").val(), "bluhbluh ");
+ await insertText(".o-mail-Composer-input", ":");
+ assert.containsOnce($, ".o-mail-Composer-suggestion");
+ await click(".o-mail-Composer-suggestion");
+ assert.strictEqual(
+ $(".o-mail-Composer-input").val().replace(/\s/, " "),
+ "bluhbluh Hello! How are you? ",
+ "previous content + canned response substitution + additional whitespace afterwards"
+ );
+});
+
+QUnit.test("add an emoji after a canned response", async (assert) => {
+ const pyEnv = await startServer();
+ const channelId = pyEnv["mail.channel"].create({
+ anonymous_name: "Visitor 20",
+ channel_member_ids: [
+ [0, 0, { partner_id: pyEnv.currentPartnerId }],
+ [0, 0, { partner_id: pyEnv.publicPartnerId }],
+ ],
+ channel_type: "livechat",
+ livechat_operator_id: pyEnv.currentPartnerId,
+ });
+ pyEnv["mail.shortcode"].create({
+ source: "hello",
+ substitution: "Hello! How are you?",
+ });
+ const { openDiscuss } = await start();
+ await openDiscuss(channelId);
+ assert.containsNone($, ".o-mail-Composer-suggestion");
+ assert.strictEqual($(".o-mail-Composer-input").val(), "");
+ await insertText(".o-mail-Composer-input", ":");
+ assert.containsOnce($, ".o-mail-Composer-suggestion");
+ await click(".o-mail-Composer-suggestion");
+ assert.strictEqual(
+ $(".o-mail-Composer-input").val().replace(/\s/, " "),
+ "Hello! How are you? ",
+ "previous content + canned response substitution + additional whitespace afterwards"
+ );
+ await click("button[aria-label='Emojis']");
+ await click(".o-mail-Emoji:contains(😊)");
+ assert.strictEqual(
+ $(".o-mail-Composer-input").val().replace(/\s/, " "),
+ "Hello! How are you? 😊"
+ );
+});
diff --git a/addons/mail/static/src/composer/suggestion_hook.js b/addons/mail/static/src/composer/suggestion_hook.js
index a6eec7b3e51..e696e8fc547 100644
--- a/addons/mail/static/src/composer/suggestion_hook.js
+++ b/addons/mail/static/src/composer/suggestion_hook.js
@@ -38,7 +38,6 @@ export function useSuggestion() {
if (selectionStart > 0) {
candidatePositions.push(selectionStart - 1);
}
- const suggestionDelimiters = ["@", ":", "#", "/"];
for (const candidatePosition of candidatePositions) {
if (candidatePosition < 0 || candidatePosition >= content.length) {
continue;
@@ -47,7 +46,11 @@ export function useSuggestion() {
if (candidateChar === "/" && candidatePosition !== 0) {
continue;
}
- if (!suggestionDelimiters.includes(candidateChar)) {
+ if (
+ !suggestionService
+ .getSupportedDelimiters(comp.props.composer.thread)
+ .includes(candidateChar)
+ ) {
continue;
}
const charBeforeCandidate = content[candidatePosition - 1];
diff --git a/addons/mail/static/src/composer/suggestion_service.js b/addons/mail/static/src/composer/suggestion_service.js
index abd5d2bc7ac..0d59344ab0c 100644
--- a/addons/mail/static/src/composer/suggestion_service.js
+++ b/addons/mail/static/src/composer/suggestion_service.js
@@ -2,7 +2,6 @@
import { cleanTerm } from "@mail/utils/format";
import { registry } from "@web/core/registry";
-import { _t } from "@web/core/l10n/translation";
const commandRegistry = registry.category("mail.channel_commands");
@@ -19,6 +18,10 @@ export class SuggestionService {
this.channelMemberService = services["mail.channel.member"];
}
+ getSupportedDelimiters(thread) {
+ return ["@", "#", "/"];
+ }
+
async fetchSuggestions({ delimiter, term }, { thread, onFetched } = {}) {
const cleanedSearchTerm = cleanTerm(term);
switch (delimiter) {
@@ -26,8 +29,6 @@ export class SuggestionService {
this.fetchPartners(cleanedSearchTerm, thread).then(onFetched);
break;
}
- case ":":
- break;
case "#":
this.fetchThreads(cleanedSearchTerm).then(onFetched);
break;
@@ -79,7 +80,7 @@ export class SuggestionService {
* Returns suggestions that match the given search term from specified type.
*
* @param {Object} [param0={}]
- * @param {String} [param0.delimiter] can be one one of the following: ["@", ":", "#", "/"]
+ * @param {String} [param0.delimiter] can be one one of the following: ["@", "#", "/"]
* @param {String} [param0.term]
* @param {Object} [options={}]
* @param {Integer} [options.thread] prioritize and/or restrict
@@ -92,8 +93,6 @@ export class SuggestionService {
case "@": {
return this.searchPartnerSuggestions(cleanedSearchTerm, thread, sort);
}
- case ":":
- return this.searchCannedResponseSuggestions(cleanedSearchTerm, sort);
case "#":
return this.searchChannelSuggestions(cleanedSearchTerm, thread, sort);
case "/":
@@ -285,47 +284,6 @@ export class SuggestionService {
};
}
- searchCannedResponseSuggestions(cleanedSearchTerm, sort) {
- const cannedResponses = this.store.cannedResponses
- .filter((cannedResponse) => {
- return cleanTerm(cannedResponse.name).includes(cleanedSearchTerm);
- })
- .map(({ id, name, substitution }) => {
- return {
- id,
- name,
- substitution: _t(substitution),
- };
- });
- const sortFunc = (c1, c2) => {
- const cleanedName1 = cleanTerm(c1.name ?? "");
- const cleanedName2 = cleanTerm(c2.name ?? "");
- if (
- cleanedName1.startsWith(cleanedSearchTerm) &&
- !cleanedName2.startsWith(cleanedSearchTerm)
- ) {
- return -1;
- }
- if (
- !cleanedName1.startsWith(cleanedSearchTerm) &&
- cleanedName2.startsWith(cleanedSearchTerm)
- ) {
- return 1;
- }
- if (cleanedName1 < cleanedName2) {
- return -1;
- }
- if (cleanedName1 > cleanedName2) {
- return 1;
- }
- return c1.id - c2.id;
- };
- return {
- type: "CannedResponse",
- mainSuggestions: sort ? cannedResponses.sort(sortFunc) : cannedResponses,
- };
- }
-
searchChannelSuggestions(cleanedSearchTerm, thread, sort) {
let threads;
if (
diff --git a/addons/mail/static/tests/composer/composer_tests.js b/addons/mail/static/tests/composer/composer_tests.js
index 72f25014727..e9405d594b7 100644
--- a/addons/mail/static/tests/composer/composer_tests.js
+++ b/addons/mail/static/tests/composer/composer_tests.js
@@ -393,36 +393,6 @@ QUnit.test("add an emoji after a command", async (assert) => {
assert.strictEqual($(".o-mail-Composer-input").val().replace(/\s/, " "), "/who 😊");
});
-QUnit.test("add an emoji after a canned response", async (assert) => {
- const pyEnv = await startServer();
- const channelId = pyEnv["mail.channel"].create({
- name: "Mario Party",
- });
- pyEnv["mail.shortcode"].create({
- source: "hello",
- substitution: "Hello! How are you?",
- });
- const { openDiscuss } = await start();
- await openDiscuss(channelId);
- assert.containsNone($, ".o-mail-Composer-suggestion");
- assert.strictEqual($(".o-mail-Composer-input").val(), "");
- await insertText(".o-mail-Composer-input", ":");
- assert.containsOnce($, ".o-mail-Composer-suggestion");
- await click(".o-mail-Composer-suggestion");
- assert.strictEqual(
- $(".o-mail-Composer-input").val().replace(/\s/, " "),
- "Hello! How are you? ",
- "previous content + canned response substitution + additional whitespace afterwards"
- );
-
- await click("button[aria-label='Emojis']");
- await click(".o-mail-Emoji:contains(😊)");
- assert.strictEqual(
- $(".o-mail-Composer-input").val().replace(/\s/, " "),
- "Hello! How are you? 😊"
- );
-});
-
QUnit.test("add an emoji after a partner mention", async (assert) => {
const pyEnv = await startServer();
const partnerId = pyEnv["res.partner"].create({
diff --git a/addons/mail/static/tests/suggestion/suggestion_tests.js b/addons/mail/static/tests/suggestion/suggestion_tests.js
index bff5f6a4561..ddc256a2735 100644
--- a/addons/mail/static/tests/suggestion/suggestion_tests.js
+++ b/addons/mail/static/tests/suggestion/suggestion_tests.js
@@ -177,64 +177,6 @@ QUnit.test(
}
);
-QUnit.test('display canned response suggestions on typing ":"', async (assert) => {
- const pyEnv = await startServer();
- const channelId = pyEnv["mail.channel"].create({ name: "Mario Party" });
- pyEnv["mail.shortcode"].create({
- source: "hello",
- substitution: "Hello! How are you?",
- });
- const { openDiscuss } = await start();
- await openDiscuss(channelId);
- assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
- await insertText(".o-mail-Composer-input", ":");
- assert.containsOnce($, ".o-mail-Composer-suggestionList .o-open");
-});
-
-QUnit.test("use a canned response", async (assert) => {
- const pyEnv = await startServer();
- const channelId = pyEnv["mail.channel"].create({ name: "Mario Party" });
- pyEnv["mail.shortcode"].create({
- source: "hello",
- substitution: "Hello! How are you?",
- });
- const { openDiscuss } = await start();
- await openDiscuss(channelId);
- assert.containsNone($, ".o-mail-Composer-suggestionList .o-open");
- assert.strictEqual($(".o-mail-Composer-input").val(), "");
- await insertText(".o-mail-Composer-input", ":");
- assert.containsOnce($, ".o-mail-Composer-suggestion");
- await click(".o-mail-Composer-suggestion");
- assert.strictEqual(
- $(".o-mail-Composer-input").val().replace(/\s/, " "),
- "Hello! How are you? ",
- "canned response + additional whitespace afterwards"
- );
-});
-
-QUnit.test("use a canned response some text", async (assert) => {
- const pyEnv = await startServer();
- const channelId = pyEnv["mail.channel"].create({ name: "Mario Party" });
- pyEnv["mail.shortcode"].create({
- source: "hello",
- substitution: "Hello! How are you?",
- });
- const { openDiscuss } = await start();
- await openDiscuss(channelId);
- assert.containsNone($, ".o-mail-Composer-suggestion");
- assert.strictEqual($(".o-mail-Composer-input").val(), "");
- await insertText(".o-mail-Composer-input", "bluhbluh ");
- assert.strictEqual($(".o-mail-Composer-input").val(), "bluhbluh ");
- await insertText(".o-mail-Composer-input", ":");
- assert.containsOnce($, ".o-mail-Composer-suggestion");
- await click(".o-mail-Composer-suggestion");
- assert.strictEqual(
- $(".o-mail-Composer-input").val().replace(/\s/, " "),
- "bluhbluh Hello! How are you? ",
- "previous content + canned response substitution + additional whitespace afterwards"
- );
-});
-
QUnit.test('display channel mention suggestions on typing "#"', async (assert) => {
const pyEnv = await startServer();
const channelId = pyEnv["mail.channel"].create({