summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-08-28 12:53:38 +0000
committerSébastien Theys <seb@odoo.com>2020-08-28 16:15:15 +0000
commit17611f4adc5e950b0a75a2a6615b1da3c956387f (patch)
treeede6cbc0bc67c1dceed3b5ca6c1bb3e08389675c /addons/mail/static/src/components
parent5ba64925fd846e9a9a4884913e3d1004d05ecdf6 (diff)
[FIX] mail, *: fix opens and redirects, adapt messaging menu close
task-2244279 task-2262862 task-2274993 task-2277537 task-2282238 closes odoo/odoo#56745 X-original-commit: 109f202 Related: odoo/enterprise#12761 Signed-off-by: Alexandre Kühn (aku) <aku@odoo.com> Signed-off-by: Sébastien Theys (seb) <seb@odoo.com>
Diffstat (limited to 'addons/mail/static/src/components')
-rw-r--r--addons/mail/static/src/components/chat_window/chat_window.js27
-rw-r--r--addons/mail/static/src/components/chat_window/chat_window.xml1
-rw-r--r--addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js4
-rw-r--r--addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js124
-rw-r--r--addons/mail/static/src/components/chatter/chatter.js29
-rw-r--r--addons/mail/static/src/components/chatter/chatter.xml2
-rw-r--r--addons/mail/static/src/components/composer/composer.js6
-rw-r--r--addons/mail/static/src/components/composer_text_input/composer_text_input.js12
-rw-r--r--addons/mail/static/src/components/discuss/discuss.js41
-rw-r--r--addons/mail/static/src/components/discuss/discuss.xml4
-rw-r--r--addons/mail/static/src/components/discuss/tests/discuss_tests.js28
-rw-r--r--addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js2
-rw-r--r--addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js2
-rw-r--r--addons/mail/static/src/components/follower/follower.js5
-rw-r--r--addons/mail/static/src/components/follower/follower_tests.js16
-rw-r--r--addons/mail/static/src/components/message/message.js66
-rw-r--r--addons/mail/static/src/components/message/message.xml4
-rw-r--r--addons/mail/static/src/components/message_list/message_list.js5
-rw-r--r--addons/mail/static/src/components/message_list/message_list.xml1
-rw-r--r--addons/mail/static/src/components/messaging_menu/messaging_menu.js47
-rw-r--r--addons/mail/static/src/components/messaging_menu/messaging_menu.xml2
-rw-r--r--addons/mail/static/src/components/notification_group/notification_group.js6
-rw-r--r--addons/mail/static/src/components/notification_list/notification_list_notification_group_tests.js1
-rw-r--r--addons/mail/static/src/components/notification_request/notification_request.js4
-rw-r--r--addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js5
-rw-r--r--addons/mail/static/src/components/thread_preview/thread_preview.js5
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view.js2
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view.xml1
-rw-r--r--addons/mail/static/src/components/thread_view/thread_view_tests.js4
29 files changed, 281 insertions, 175 deletions
diff --git a/addons/mail/static/src/components/chat_window/chat_window.js b/addons/mail/static/src/components/chat_window/chat_window.js
index 4652da4975b..7f9b53d2670 100644
--- a/addons/mail/static/src/components/chat_window/chat_window.js
+++ b/addons/mail/static/src/components/chat_window/chat_window.js
@@ -167,20 +167,15 @@ class ChatWindow extends Component {
* @param {Object} ui.item
* @param {integer} ui.item.id
*/
- _onAutocompleteSelect(ev, ui) {
- const partnerId = ui.item.id;
- const partner = this.env.models['mail.partner'].find(partner => partner.id === partnerId);
- const chat = partner.correspondentThreads.find(thread => thread.channel_type === 'chat');
- if (chat) {
- chat.open({ chatWindowMode: 'from_new_message' });
- } else {
- this.env.models['mail.thread'].createChannel({
- autoselect: true,
- autoselectChatWindowMode: 'from_new_message',
- partnerId,
- type: 'chat',
- });
+ async _onAutocompleteSelect(ev, ui) {
+ const chat = await this.env.messaging.getChat({ partnerId: ui.item.id });
+ if (!chat) {
+ return;
}
+ this.env.messaging.chatWindowManager.openThread(chat, {
+ makeActive: true,
+ replaceNewMessage: true,
+ });
}
/**
@@ -225,6 +220,12 @@ class ChatWindow extends Component {
if (this.chatWindow.isFocused) {
return;
}
+ if (isEventHandled(ev, 'Message.authorOpenChat')) {
+ return;
+ }
+ if (isEventHandled(ev, 'Message.authorOpenProfile')) {
+ return;
+ }
this.chatWindow.focus();
}
diff --git a/addons/mail/static/src/components/chat_window/chat_window.xml b/addons/mail/static/src/components/chat_window/chat_window.xml
index d711f267ac7..f92a32cf2fe 100644
--- a/addons/mail/static/src/components/chat_window/chat_window.xml
+++ b/addons/mail/static/src/components/chat_window/chat_window.xml
@@ -29,7 +29,6 @@
hasComposerCurrentPartnerAvatar="false"
hasComposerSendButton="env.messaging.device.isMobile"
hasSquashCloseMessages="chatWindow.thread.model !== 'mail.box'"
- haveMessagesAuthorRedirect="chatWindow.thread.channel_type !== 'chat'"
threadViewLocalId="chatWindow.threadView.localId"
t-on-focusin="_onFocusinThread"
t-ref="thread"
diff --git a/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js b/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js
index b598eeee4b4..c6ec0b74e9f 100644
--- a/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js
+++ b/addons/mail/static/src/components/chat_window_hidden_menu/chat_window_hidden_menu.js
@@ -123,9 +123,7 @@ class ChatWindowHiddenMenu extends Component {
*/
_onClickedChatWindow(ev) {
const chatWindow = ev.detail.chatWindow;
- chatWindow.makeVisible();
- chatWindow.unfold();
- chatWindow.focus();
+ chatWindow.makeActive();
this.env.messaging.chatWindowManager.closeHiddenMenu();
}
diff --git a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
index 3306dbed905..efba64cecde 100644
--- a/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
+++ b/addons/mail/static/src/components/chat_window_manager/chat_window_manager_tests.js
@@ -255,6 +255,128 @@ QUnit.test('chat window new message: fold', async function (assert) {
);
});
+QUnit.test('open chat from "new message" chat window should open chat in place of this "new message" chat window', async function (assert) {
+ /**
+ * InnerWith computation uses following info:
+ * ([mocked] global window width: @see `mail/static/src/utils/test_utils.js:start()` method)
+ * (others: @see mail/static/src/models/chat_window_manager/chat_window_manager.js:visual)
+ *
+ * - chat window width: 325px
+ * - start/end/between gap width: 10px/10px/5px
+ * - hidden menu width: 200px
+ * - global width: 1920px
+ *
+ * Enough space for 3 visible chat windows:
+ * 10 + 325 + 5 + 325 + 5 + 325 + 10 = 1000 < 1920
+ */
+ assert.expect(11);
+
+ this.data['res.partner'].records.push({ id: 131, name: "Partner 131" });
+ this.data['res.users'].records.push({ partner_id: 131 });
+ this.data['mail.channel'].records.push(
+ { is_minimized: true },
+ { is_minimized: true },
+ );
+ const imSearchDef = makeDeferred();
+ await this.start({
+ env: {
+ browser: {
+ innerWidth: 1920,
+ },
+ },
+ async mockRPC(route, args) {
+ const res = await this._super(...arguments);
+ if (args.method === 'im_search') {
+ imSearchDef.resolve();
+ }
+ return res;
+ }
+ });
+ assert.containsN(
+ document.body,
+ '.o_ChatWindow',
+ 2,
+ "should have 2 chat windows initially"
+ );
+ assert.containsNone(
+ document.body,
+ '.o_ChatWindow.o-new-message',
+ "should not have any 'new message' chat window initially"
+ );
+
+ // open "new message" chat window
+ await afterNextRender(() =>
+ document.querySelector(`.o_MessagingMenu_toggler`).click()
+ );
+ await afterNextRender(() =>
+ document.querySelector(`.o_MessagingMenu_newMessageButton`).click()
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ChatWindow.o-new-message',
+ "should have 'new message' chat window after clicking 'new message' in messaging menu"
+ );
+ assert.containsN(
+ document.body,
+ '.o_ChatWindow',
+ 3,
+ "should have 3 chat window after opening 'new message' chat window",
+ );
+ assert.containsOnce(
+ document.body,
+ '.o_ChatWindow_newMessageFormInput',
+ "'new message' chat window should have new message form input"
+ );
+ assert.hasClass(
+ document.querySelector('.o_ChatWindow[data-visible-index="2"]'),
+ 'o-new-message',
+ "'new message' chat window should be the last chat window initially",
+ );
+
+ await afterNextRender(() =>
+ document.querySelector('.o_ChatWindow[data-visible-index="2"] .o_ChatWindowHeader_commandShiftRight').click()
+ );
+ assert.hasClass(
+ document.querySelector('.o_ChatWindow[data-visible-index="1"]'),
+ 'o-new-message',
+ "'new message' chat window should have moved to the middle after clicking shift previous",
+ );
+
+ // search for a user in "new message" autocomplete
+ document.execCommand('insertText', false, "131");
+ document.querySelector(`.o_ChatWindow_newMessageFormInput`)
+ .dispatchEvent(new window.KeyboardEvent('keydown'));
+ document.querySelector(`.o_ChatWindow_newMessageFormInput`)
+ .dispatchEvent(new window.KeyboardEvent('keyup'));
+ // Wait for search RPC to be resolved. The following await lines are
+ // necessary because autocomplete is an external lib therefore it is not
+ // possible to use `afterNextRender`.
+ await imSearchDef;
+ await nextAnimationFrame();
+ const link = document.querySelector('.ui-autocomplete .ui-menu-item a');
+ assert.ok(
+ link,
+ "should have autocomplete suggestion after typing on 'new message' input"
+ );
+ assert.strictEqual(
+ link.textContent,
+ "Partner 131",
+ "autocomplete suggestion should target the partner matching search term"
+ );
+
+ await afterNextRender(() => link.click());
+ assert.containsNone(
+ document.body,
+ '.o_ChatWindow.o-new-message',
+ "should have removed the 'new message' chat window after selecting a partner"
+ );
+ assert.strictEqual(
+ document.querySelector('.o_ChatWindow[data-visible-index="1"] .o_ChatWindowHeader_name').textContent,
+ "Partner 131",
+ "chat window with selected partner should be opened in position where 'new message' chat window was, which is in the middle"
+ );
+});
+
QUnit.test('chat window: basic rendering', async function (assert) {
assert.expect(11);
@@ -1351,7 +1473,7 @@ QUnit.test('chat window: switch on TAB', async function (assert) {
);
});
-QUnit.test('chat window: TAB cycle with 3 open chat windows', async function (assert) {
+QUnit.test('chat window: TAB cycle with 3 open chat windows [REQUIRE FOCUS]', async function (assert) {
/**
* InnerWith computation uses following info:
* ([mocked] global window width: @see `mail/static/src/utils/test_utils.js:start()` method)
diff --git a/addons/mail/static/src/components/chatter/chatter.js b/addons/mail/static/src/components/chatter/chatter.js
index 61fbb78d619..cd1f8f8720b 100644
--- a/addons/mail/static/src/components/chatter/chatter.js
+++ b/addons/mail/static/src/components/chatter/chatter.js
@@ -37,19 +37,18 @@ class Chatter extends Component {
attachments: 1,
},
});
- this._threadRef = useRef('thread');
+ /**
+ * Reference of the composer. Useful to focus it.
+ */
+ this._composerRef = useRef('composer');
}
mounted() {
- if (this.chatter.thread) {
- this._notifyRendered();
- }
+ this._update();
}
patched() {
- if (this.chatter.thread) {
- this._notifyRendered();
- }
+ this._update();
}
//--------------------------------------------------------------------------
@@ -77,6 +76,22 @@ class Chatter extends Component {
});
}
+ /**
+ * @private
+ */
+ _update() {
+ if (this.chatter.thread) {
+ this._notifyRendered();
+ }
+ if (this.chatter.isDoFocus) {
+ this.chatter.update({ isDoFocus: false });
+ const composer = this._composerRef.comp;
+ if (composer) {
+ composer.focus();
+ }
+ }
+ }
+
//--------------------------------------------------------------------------
// Handlers
//--------------------------------------------------------------------------
diff --git a/addons/mail/static/src/components/chatter/chatter.xml b/addons/mail/static/src/components/chatter/chatter.xml
index b83c9ad733f..a48396f813c 100644
--- a/addons/mail/static/src/components/chatter/chatter.xml
+++ b/addons/mail/static/src/components/chatter/chatter.xml
@@ -39,10 +39,8 @@
class="o_Chatter_thread"
hasComposer="false"
hasScrollAdjust="chatter.hasMessageListScrollAdjust"
- haveMessagesAuthorRedirect="true"
order="'desc'"
threadViewLocalId="chatter.threadView.localId"
- t-ref="thread"
/>
</t>
</t>
diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js
index afb7d811eae..0b23ee4ec22 100644
--- a/addons/mail/static/src/components/composer/composer.js
+++ b/addons/mail/static/src/components/composer/composer.js
@@ -121,7 +121,7 @@ class Composer extends Component {
if (this.env.messaging.device.isMobile) {
this.el.scrollIntoView();
}
- this.composer.focus();
+ this._textInputRef.comp.focus();
}
/**
@@ -288,7 +288,7 @@ class Composer extends Component {
ev.stopPropagation();
this._textInputRef.comp.saveStateInStore();
this.composer.insertIntoTextInput(ev.detail.unicode);
- this.composer.focus();
+ this.focus();
}
/**
@@ -323,7 +323,7 @@ class Composer extends Component {
if (ev.key === 'Escape') {
if (this._emojisPopoverRef.comp) {
this._emojisPopoverRef.comp.close();
- this.composer.focus();
+ this.focus();
markEventHandled(ev, 'Composer.closeEmojisPopover');
}
}
diff --git a/addons/mail/static/src/components/composer_text_input/composer_text_input.js b/addons/mail/static/src/components/composer_text_input/composer_text_input.js
index 53616e112e9..6a0943f9ca8 100644
--- a/addons/mail/static/src/components/composer_text_input/composer_text_input.js
+++ b/addons/mail/static/src/components/composer_text_input/composer_text_input.js
@@ -82,6 +82,10 @@ class ComposerTextInput extends Component {
return this.env._t("Write something...");
}
+ focus() {
+ this._textareaRef.el.focus();
+ }
+
focusout() {
this.saveStateInStore();
this._textareaRef.el.blur();
@@ -155,10 +159,6 @@ class ComposerTextInput extends Component {
this.composer.textInputCursorEnd
);
this._updateHeight();
- if (this.composer.isDoFocus) {
- this._textareaRef.el.focus();
- this.composer.update({ isDoFocus: false });
- }
}
/**
@@ -273,7 +273,7 @@ class ComposerTextInput extends Component {
if (this.composer.activeSuggestedPartner) {
this.composer.insertMentionedPartner(this.composer.activeSuggestedPartner);
this.composer.closeMentionSuggestions();
- this.composer.focus();
+ this.focus();
}
}
break;
@@ -322,7 +322,7 @@ class ComposerTextInput extends Component {
_onPartnerMentionSuggestionClicked(ev) {
this.composer.insertMentionedPartner(ev.detail.partner);
this.composer.closeMentionSuggestions();
- this.composer.focus();
+ this.focus();
}
/**
diff --git a/addons/mail/static/src/components/discuss/discuss.js b/addons/mail/static/src/components/discuss/discuss.js
index 83aee1e201e..4c8c4a58ab7 100644
--- a/addons/mail/static/src/components/discuss/discuss.js
+++ b/addons/mail/static/src/components/discuss/discuss.js
@@ -47,12 +47,13 @@ class Discuss extends Component {
});
this._updateLocalStoreProps();
/**
- * Reference of the thread. Useful to update scroll position correctly
- * on patch. AKU TODO: this made sense when composer was outside of
- * thread, but this may no longer be necessary??
+ * Reference of the composer. Useful to focus it.
*/
- this._threadRef = useRef('thread');
-
+ this._composerRef = useRef('composer');
+ /**
+ * Reference of the ThreadView. Useful to focus it.
+ */
+ this._threadViewRef = useRef('threadView');
// bind since passed as props
this._onMobileAddItemHeaderInputSelect = this._onMobileAddItemHeaderInputSelect.bind(this);
this._onMobileAddItemHeaderInputSource = this._onMobileAddItemHeaderInputSource.bind(this);
@@ -66,6 +67,7 @@ class Discuss extends Component {
this.discuss.openInitThread();
}
this._updateLocalStoreProps();
+ this._update();
}
patched() {
@@ -83,6 +85,7 @@ class Discuss extends Component {
}
this._activeThreadCache = this.discuss.threadView.threadCache;
this._updateLocalStoreProps();
+ this._update();
}
willUnmount() {
@@ -142,6 +145,24 @@ class Discuss extends Component {
/**
* @private
*/
+ _update() {
+ if (this.discuss.isDoFocus) {
+ this.discuss.update({ isDoFocus: false });
+ const composer = this._composerRef.comp;
+ if (composer) {
+ composer.focus();
+ } else {
+ const threadView = this._threadViewRef.comp;
+ if (threadView) {
+ threadView.focus();
+ }
+ }
+ }
+ }
+
+ /**
+ * @private
+ */
_updateLocalStoreProps() {
/**
* Locally tracked store props `activeThreadCache`.
@@ -251,16 +272,6 @@ class Discuss extends Component {
/**
* @private
* @param {CustomEvent} ev
- * @param {Object} ev.detail
- * @param {mail.thread} ev.detail.thread
- */
- _onSelectThread(ev) {
- this.discuss.threadView.update({ thread: [['link', ev.detail.thread]] });
- }
-
- /**
- * @private
- * @param {CustomEvent} ev
*/
_onThreadRendered(ev) {
this.trigger('o-update-control-panel');
diff --git a/addons/mail/static/src/components/discuss/discuss.xml b/addons/mail/static/src/components/discuss/discuss.xml
index 94234d0fc9b..d88b4796d9c 100644
--- a/addons/mail/static/src/components/discuss/discuss.xml
+++ b/addons/mail/static/src/components/discuss/discuss.xml
@@ -58,7 +58,6 @@
hasComposerThreadTyping="true"
hasMessageCheckbox="true"
hasSquashCloseMessages="discuss.thread.model !== 'mail.box'"
- haveMessagesAuthorRedirect="discuss.thread.channel_type !== 'chat'"
haveMessagesMarkAsReadIcon="discuss.thread === env.messaging.inbox"
haveMessagesReplyIcon="discuss.thread === env.messaging.inbox"
selectedMessageLocalId="
@@ -66,6 +65,7 @@
discuss.replyingToMessage.localId"
threadViewLocalId="discuss.threadView.localId"
t-on-o-rendered="_onThreadRendered"
+ t-ref="threadView"
/>
</t>
<t t-if="!discuss.thread and (!env.messaging.device.isMobile or discuss.activeMobileNavbarTabId === 'mailbox')">
@@ -77,7 +77,6 @@
<NotificationList
class="o_Discuss_notificationList"
filter="discuss.activeMobileNavbarTabId"
- t-on-o-select-thread="_onSelectThread"
/>
</t>
<t t-if="env.messaging.device.isMobile and !discuss.isReplyingToMessage">
@@ -96,6 +95,7 @@
hasDiscardButton="true"
hasThreadName="true"
t-on-o-message-posted="_onReplyingToMessageMessagePosted"
+ t-ref="composer"
/>
</t>
</t>
diff --git a/addons/mail/static/src/components/discuss/tests/discuss_tests.js b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
index 8c437ce5b18..3f9e93d1de8 100644
--- a/addons/mail/static/src/components/discuss/tests/discuss_tests.js
+++ b/addons/mail/static/src/components/discuss/tests/discuss_tests.js
@@ -2116,7 +2116,7 @@ QUnit.test('message origin redirect to channel', async function (assert) {
});
QUnit.test('redirect to author (open chat)', async function (assert) {
- assert.expect(9);
+ assert.expect(7);
// expected correspondent, with a random unique id that will be used to link
// partner to chat and a random name that will be asserted in the test
@@ -2143,13 +2143,6 @@ QUnit.test('redirect to author (open chat)', async function (assert) {
id: 100,
model: 'mail.channel',
res_id: 1,
- },
- {
- author_id: this.data.currentPartnerId,
- channel_ids: [1],
- id: 101,
- model: 'mail.channel',
- res_id: 1,
}
);
await this.start({
@@ -2187,8 +2180,8 @@ QUnit.test('redirect to author (open chat)', async function (assert) {
);
assert.strictEqual(
document.querySelectorAll(`.o_Discuss_thread .o_Message`).length,
- 2,
- "should have 2 messages"
+ 1,
+ "should have 1 message"
);
const msg1 = document.querySelector(`
.o_Discuss_thread
@@ -2196,12 +2189,6 @@ QUnit.test('redirect to author (open chat)', async function (assert) {
this.env.models['mail.message'].find(message => message.id === 100).localId
}"]
`);
- const msg2 = document.querySelector(`
- .o_Discuss_thread
- .o_Message[data-message-local-id="${
- this.env.models['mail.message'].find(message => message.id === 101).localId
- }"]
- `);
assert.strictEqual(
msg1.querySelectorAll(`:scope .o_Message_authorAvatar`).length,
1,
@@ -2211,15 +2198,6 @@ QUnit.test('redirect to author (open chat)', async function (assert) {
msg1.querySelector(`:scope .o_Message_authorAvatar`).classList.contains('o_redirect'),
"message1 should have redirect to author"
);
- assert.strictEqual(
- msg2.querySelectorAll(`:scope .o_Message_authorAvatar`).length,
- 1,
- "message2 should have author image"
- );
- assert.notOk(
- msg2.querySelector(`:scope .o_Message_authorAvatar`).classList.contains('o_redirect'),
- "message2 should not have redirect to author (self-author)"
- );
await afterNextRender(() =>
msg1.querySelector(`:scope .o_Message_authorAvatar`).click()
diff --git a/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js b/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js
index cea920a45f9..c2185a60dc0 100644
--- a/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js
+++ b/addons/mail/static/src/components/discuss_mobile_mailbox_selection/discuss_mobile_mailbox_selection.js
@@ -74,7 +74,7 @@ class DiscussMobileMailboxSelection extends Component {
*/
_onClick(ev) {
const { mailbox } = ev.currentTarget.dataset;
- this.discuss.threadView.update({ thread: [['link', mailbox]] });
+ mailbox.open();
}
}
diff --git a/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js b/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
index daaa8b44b5d..2b84762c9dd 100644
--- a/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
+++ b/addons/mail/static/src/components/discuss_sidebar_item/discuss_sidebar_item.js
@@ -117,7 +117,7 @@ class DiscussSidebarItem extends Component {
* @param {MouseEvent} ev
*/
_onClick(ev) {
- this.discuss.threadView.update({ thread: [['link', this.thread]] });
+ this.thread.open();
}
/**
diff --git a/addons/mail/static/src/components/follower/follower.js b/addons/mail/static/src/components/follower/follower.js
index f446c098914..dc3e395f03c 100644
--- a/addons/mail/static/src/components/follower/follower.js
+++ b/addons/mail/static/src/components/follower/follower.js
@@ -43,10 +43,7 @@ class Follower extends Component {
_onClickDetails(ev) {
ev.preventDefault();
ev.stopPropagation();
- this.env.messaging.openDocument({
- id: this.follower.resId,
- model: this.follower.resModel,
- });
+ this.follower.openProfile();
}
/**
diff --git a/addons/mail/static/src/components/follower/follower_tests.js b/addons/mail/static/src/components/follower/follower_tests.js
index d93cb24b6c2..a742e899e6b 100644
--- a/addons/mail/static/src/components/follower/follower_tests.js
+++ b/addons/mail/static/src/components/follower/follower_tests.js
@@ -4,6 +4,7 @@ odoo.define('mail/static/src/components/follower/follower_tests.js', function (r
const components = {
Follower: require('mail/static/src/components/follower/follower.js'),
};
+const { makeDeferred } = require('mail/static/src/utils/deferred/deferred.js');
const {
afterEach,
afterNextRender,
@@ -141,8 +142,8 @@ QUnit.test('click on channel follower details', async function (assert) {
assert.step('do_action');
assert.strictEqual(
payload.action.res_id,
- 1,
- "The redirect action should redirect to the right res id (1)"
+ 10,
+ "The redirect action should redirect to the right res id (10)"
);
assert.strictEqual(
payload.action.res_model,
@@ -155,7 +156,8 @@ QUnit.test('click on channel follower details', async function (assert) {
"The redirect action should be of type 'ir.actions.act_window'"
);
});
-
+ this.data['res.partner'].records.push({ id: 100 });
+ this.data['mail.channel'].records.push({ id: 10 });
await this.start({
env: { bus },
});
@@ -164,7 +166,7 @@ QUnit.test('click on channel follower details', async function (assert) {
model: 'res.partner',
});
const follower = await this.env.models['mail.follower'].create({
- channel: [['insert', { id: 1, model: 'mail.channel', name: "channel" }]],
+ channel: [['insert', { id: 10, model: 'mail.channel', name: "channel" }]],
followedThread: [['link', thread]],
id: 2,
isActive: true,
@@ -192,6 +194,7 @@ QUnit.test('click on channel follower details', async function (assert) {
QUnit.test('click on partner follower details', async function (assert) {
assert.expect(7);
+ const openFormDef = makeDeferred();
const bus = new Bus();
bus.on('do-action', null, payload => {
assert.step('do_action');
@@ -210,8 +213,9 @@ QUnit.test('click on partner follower details', async function (assert) {
"ir.actions.act_window",
"The redirect action should be of type 'ir.actions.act_window'"
);
+ openFormDef.resolve();
});
-
+ this.data['res.partner'].records.push({ id: 100 });
await this.start({
env: { bus },
});
@@ -243,6 +247,7 @@ QUnit.test('click on partner follower details', async function (assert) {
);
document.querySelector('.o_Follower_details').click();
+ await openFormDef;
assert.verifySteps(
['do_action'],
"clicking on follower should redirect to partner form view"
@@ -302,6 +307,7 @@ QUnit.test('click on edit follower', async function (assert) {
QUnit.test('edit follower and close subtype dialog', async function (assert) {
assert.expect(6);
+ this.data['res.partner'].records.push({ id: 100 });
await this.start({
hasDialog: true,
async mockRPC(route, args) {
diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js
index 157f3dc2acd..7d314647bb2 100644
--- a/addons/mail/static/src/components/message/message.js
+++ b/addons/mail/static/src/components/message/message.js
@@ -11,6 +11,7 @@ const components = {
PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'),
};
const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
+const { markEventHandled } = require('mail/static/src/utils/utils.js');
const { timeFromNow } = require('mail.utils');
const { _lt } = require('web.core');
@@ -156,19 +157,18 @@ class Message extends Component {
}
/**
- * Determine whether author redirect feature is enabled on message.
- * Click on message author should redirect to author.
+ * Determines whether author open chat feature is enabled on message.
*
* @returns {boolean}
*/
- get hasAuthorRedirect() {
- if (!this.props.hasAuthorRedirect) {
- return false;
- }
+ get hasAuthorOpenChat() {
if (!this.message.author) {
return false;
}
- if (this.message.author === this.env.messaging.currentPartner) {
+ if (
+ this.message.originThread &&
+ this.message.originThread.correspondent === this.message.author
+ ) {
return false;
}
return true;
@@ -237,6 +237,13 @@ class Message extends Component {
}
/**
+ * @returns {string}
+ */
+ get OPEN_CHAT() {
+ return this.env._t("Open chat");
+ }
+
+ /**
* Make this message viewable in its enclosing scroll environment (usually
* message list).
*
@@ -245,7 +252,7 @@ class Message extends Component {
* @param {string} [param0.block='end']
* @returns {Promise}
*/
- async scrollIntoView({ behavior='auto', block='end' }={}) {
+ async scrollIntoView({ behavior = 'auto', block = 'end' } = {}) {
this.el.scrollIntoView({
behavior,
block,
@@ -419,23 +426,33 @@ class Message extends Component {
* @param {MouseEvent} ev
*/
_onClick(ev) {
+ if (ev.target.closest('.o_channel_redirect')) {
+ this.env.messaging.openProfile({
+ id: Number(ev.target.dataset.oeId),
+ model: 'mail.channel',
+ });
+ // avoid following dummy href
+ ev.preventDefault();
+ return;
+ }
if (ev.target.closest('.o_mention')) {
- this.env.messaging.redirect({
+ this.env.messaging.openProfile({
id: Number(ev.target.dataset.oeId),
model: ev.target.dataset.oeModel,
});
+ // avoid following dummy href
ev.preventDefault();
return;
}
if (ev.target.closest('.o_mail_redirect')) {
- this.env.messaging.redirect({
+ this.env.messaging.openProfile({
id: Number(ev.target.dataset.oeId),
model: ev.target.dataset.oeModel,
});
+ // avoid following dummy href
ev.preventDefault();
return;
}
- ev.stopPropagation();
this.state.isClicked = !this.state.isClicked;
}
@@ -443,17 +460,24 @@ class Message extends Component {
* @private
* @param {MouseEvent} ev
*/
- _onClickAuthor(ev) {
- if (!this.hasAuthorRedirect) {
+ _onClickAuthorAvatar(ev) {
+ if (!this.hasAuthorOpenChat) {
return;
}
+ markEventHandled(ev, 'Message.authorOpenChat');
+ this.message.author.openChat();
+ }
+
+ /**
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onClickAuthorName(ev) {
if (!this.message.author) {
return;
}
- this.env.messaging.redirect({
- id: this.message.author.id,
- model: this.message.author.model,
- });
+ markEventHandled(ev, 'Message.authorOpenProfile');
+ this.message.author.openProfile();
}
/**
@@ -514,11 +538,9 @@ class Message extends Component {
* @param {MouseEvent} ev
*/
_onClickOriginThread(ev) {
+ // avoid following dummy href
ev.preventDefault();
- this.env.messaging.redirect({
- id: this.message.originThread.id,
- model: this.message.originThread.model,
- });
+ this.message.originThread.open();
}
/**
@@ -581,7 +603,6 @@ class Message extends Component {
Object.assign(Message, {
components,
defaultProps: {
- hasAuthorRedirect: false,
hasCheckbox: false,
hasMarkAsReadIcon: false,
hasReplyIcon: false,
@@ -594,7 +615,6 @@ Object.assign(Message, {
optional: true,
validate: prop => ['auto', 'card', 'hover', 'none'].includes(prop),
},
- hasAuthorRedirect: Boolean,
hasCheckbox: Boolean,
hasMarkAsReadIcon: Boolean,
hasReplyIcon: Boolean,
diff --git a/addons/mail/static/src/components/message/message.xml b/addons/mail/static/src/components/message/message.xml
index 7739c79337a..b0a41947c88 100644
--- a/addons/mail/static/src/components/message/message.xml
+++ b/addons/mail/static/src/components/message/message.xml
@@ -18,7 +18,7 @@
<div class="o_Message_sidebar" t-att-class="{ 'o-message-squashed': props.isSquashed }">
<t t-if="!props.isSquashed">
<div class="o_Message_authorAvatarContainer o_Message_sidebarItem">
- <img class="o_Message_authorAvatar rounded-circle" t-att-class="{ o_Message_authorRedirect: hasAuthorRedirect, o_redirect: hasAuthorRedirect }" t-att-src="avatar" t-on-click="_onClickAuthor" t-att-title="hasAuthorRedirect ? 'Open chat' : ''" alt=""/>
+ <img class="o_Message_authorAvatar rounded-circle" t-att-class="{ o_Message_authorRedirect: hasAuthorOpenChat, o_redirect: hasAuthorOpenChat }" t-att-src="avatar" t-on-click="_onClickAuthorAvatar" t-att-title="hasAuthorOpenChat ? OPEN_CHAT : ''" alt="Avatar"/>
<t t-if="message.author and message.author.im_status">
<PartnerImStatusIcon
class="o_Message_partnerImStatusIcon"
@@ -58,7 +58,7 @@
<t t-if="!props.isSquashed">
<div class="o_Message_header">
<t t-if="message.author">
- <div class="o_Message_authorName" t-att-class="hasAuthorRedirect ? 'o_Message_authorRedirect o_redirect' : ''" t-on-click="_onClickAuthor" t-att-title="hasAuthorRedirect ? 'Open chat' : ''">
+ <div class="o_Message_authorName o_Message_authorRedirect o_redirect" t-on-click="_onClickAuthorName" title="Open profile">
<t t-esc="message.author.nameOrDisplayName"/>
</div>
</t>
diff --git a/addons/mail/static/src/components/message_list/message_list.js b/addons/mail/static/src/components/message_list/message_list.js
index 0cdf4893305..966bff9a5ba 100644
--- a/addons/mail/static/src/components/message_list/message_list.js
+++ b/addons/mail/static/src/components/message_list/message_list.js
@@ -141,9 +141,6 @@ class MessageList extends Component {
case 'more-messages-loaded':
this._adjustFromMoreMessagesLoaded(hint);
break;
- default:
- this.threadView.markComponentHintProcessed(hint);
- break;
}
}
this._willPatchSnapshot = undefined;
@@ -552,7 +549,6 @@ Object.assign(MessageList, {
hasMessageCheckbox: false,
hasScrollAdjust: true,
hasSquashCloseMessages: false,
- haveMessagesAuthorRedirect: false,
haveMessagesMarkAsReadIcon: false,
haveMessagesReplyIcon: false,
order: 'asc',
@@ -560,7 +556,6 @@ Object.assign(MessageList, {
props: {
hasMessageCheckbox: Boolean,
hasSquashCloseMessages: Boolean,
- haveMessagesAuthorRedirect: Boolean,
haveMessagesMarkAsReadIcon: Boolean,
haveMessagesReplyIcon: Boolean,
hasScrollAdjust: Boolean,
diff --git a/addons/mail/static/src/components/message_list/message_list.xml b/addons/mail/static/src/components/message_list/message_list.xml
index a678f7a7199..0a87558e839 100644
--- a/addons/mail/static/src/components/message_list/message_list.xml
+++ b/addons/mail/static/src/components/message_list/message_list.xml
@@ -65,7 +65,6 @@
t-att-class="{
'o-has-message-selection': props.selectedMessageLocalId,
}"
- hasAuthorRedirect="props.haveMessagesAuthorRedirect"
hasMarkAsReadIcon="props.haveMessagesMarkAsReadIcon"
hasCheckbox="props.hasMessageCheckbox"
hasReplyIcon="props.haveMessagesReplyIcon"
diff --git a/addons/mail/static/src/components/messaging_menu/messaging_menu.js b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
index ac4472d8209..4e5ede8f635 100644
--- a/addons/mail/static/src/components/messaging_menu/messaging_menu.js
+++ b/addons/mail/static/src/components/messaging_menu/messaging_menu.js
@@ -33,13 +33,6 @@ class MessagingMenu extends Component {
};
});
- /**
- * Reference of the new message input in mobile. Useful to include it
- * and autocomplete menu as "inside" the messaging menu, to prevent
- * closing the messaging menu otherwise.
- */
- this._mobileNewMessageInputRef = useRef('mobileNewMessageInput');
-
// bind since passed as props
this._onMobileNewMessageInputSelect = this._onMobileNewMessageInputSelect.bind(this);
this._onMobileNewMessageInputSource = this._onMobileNewMessageInputSource.bind(this);
@@ -121,20 +114,14 @@ class MessagingMenu extends Component {
// in mobile: keeps the messaging menu open in background
// TODO: maybe need to move this to a mobile component?
// task-2089887
- if (
- this.env.messaging.device.isMobile &&
- this.env.messaging.chatWindowManager.hasVisibleChatWindows
- ) {
+ if (this.env.messaging.device.isMobile) {
return;
}
// ignore click inside the menu
if (this.el.contains(ev.target)) {
return;
}
- const input = this._mobileNewMessageInputRef.comp;
- if (input && input.contains(ev.target)) {
- return;
- }
+ // in all other cases: close the messaging menu when clicking outside
this.messagingMenu.close();
}
@@ -194,21 +181,7 @@ class MessagingMenu extends Component {
* @param {integer} ui.item.id
*/
_onMobileNewMessageInputSelect(ev, ui) {
- const partnerId = ui.item.id;
- const partner = this.env.models['mail.partner'].find(partner => partner.id === partnerId);
- const chat = partner.correspondentThreads.find(thread => thread.channel_type === 'chat');
- if (chat) {
- chat.open();
- } else {
- this.env.models['mail.thread'].createChannel({
- autoselect: true,
- partnerId,
- type: 'chat',
- });
- }
- if (!this.env.messaging.device.isMobile) {
- this.messagingMenu.close();
- }
+ this.env.messaging.openChat({ partnerId: ui.item.id });
}
/**
@@ -246,20 +219,6 @@ class MessagingMenu extends Component {
this.messagingMenu.update({ activeTabId: ev.detail.tabId });
}
- /**
- * @private
- * @param {CustomEvent} ev
- * @param {Object} ev.detail
- * @param {mail.thread} ev.detail.thread
- */
- _onSelectThread(ev) {
- ev.stopPropagation();
- ev.detail.thread.open();
- if (!this.env.messaging.device.isMobile) {
- this.messagingMenu.close();
- }
- }
-
}
Object.assign(MessagingMenu, {
diff --git a/addons/mail/static/src/components/messaging_menu/messaging_menu.xml b/addons/mail/static/src/components/messaging_menu/messaging_menu.xml
index b743c7ca92d..fc779231dd8 100644
--- a/addons/mail/static/src/components/messaging_menu/messaging_menu.xml
+++ b/addons/mail/static/src/components/messaging_menu/messaging_menu.xml
@@ -46,7 +46,6 @@
select="_onMobileNewMessageInputSelect"
source="_onMobileNewMessageInputSource"
t-on-o-hide="_onHideMobileNewMessage"
- t-ref="mobileNewMessageInput"
/>
</t>
</div>
@@ -54,7 +53,6 @@
class="o_MessagingMenu_notificationList"
t-att-class="{ 'o-mobile': env.messaging.device.isMobile }"
filter="messagingMenu.activeTabId"
- t-on-o-select-thread="_onSelectThread"
/>
<t t-if="env.messaging.device.isMobile">
<MobileMessagingNavbar
diff --git a/addons/mail/static/src/components/notification_group/notification_group.js b/addons/mail/static/src/components/notification_group/notification_group.js
index 4020a610dc6..65cfcea3076 100644
--- a/addons/mail/static/src/components/notification_group/notification_group.js
+++ b/addons/mail/static/src/components/notification_group/notification_group.js
@@ -61,6 +61,9 @@ class NotificationGroup extends Component {
return;
}
this.group.openDocuments();
+ if (!this.env.messaging.device.isMobile) {
+ this.env.messaging.messagingMenu.close();
+ }
}
/**
@@ -69,6 +72,9 @@ class NotificationGroup extends Component {
*/
_onClickMarkAsRead(ev) {
this.group.openCancelAction();
+ if (!this.env.messaging.device.isMobile) {
+ this.env.messaging.messagingMenu.close();
+ }
}
}
diff --git a/addons/mail/static/src/components/notification_list/notification_list_notification_group_tests.js b/addons/mail/static/src/components/notification_list/notification_list_notification_group_tests.js
index fcab0251524..aa1d36593ad 100644
--- a/addons/mail/static/src/components/notification_list/notification_list_notification_group_tests.js
+++ b/addons/mail/static/src/components/notification_list/notification_list_notification_group_tests.js
@@ -364,6 +364,7 @@ QUnit.test('different mail.channel are not grouped', async function (assert) {
// they are linked to different channels, even though the model is the same.
assert.expect(6);
+ this.data['mail.channel'].records.push({ id: 31 }, { id: 32 });
this.data['mail.message'].records.push(
// first message that is expected to have a failure
{
diff --git a/addons/mail/static/src/components/notification_request/notification_request.js b/addons/mail/static/src/components/notification_request/notification_request.js
index cb1b59ccbaa..69fb5beab2f 100644
--- a/addons/mail/static/src/components/notification_request/notification_request.js
+++ b/addons/mail/static/src/components/notification_request/notification_request.js
@@ -74,9 +74,7 @@ class NotificationRequest extends Component {
if (def) {
def.then(this._handleResponseNotificationPermission.bind(this));
}
-
- const device = this.env.messaging.device;
- if (!device.isMobile) {
+ if (!this.env.messaging.device.isMobile) {
this.env.messaging.messagingMenu.close();
}
}
diff --git a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
index efcd8f5d86e..24486cfc93c 100644
--- a/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
+++ b/addons/mail/static/src/components/thread_needaction_preview/thread_needaction_preview.js
@@ -105,7 +105,10 @@ class ThreadNeedactionPreview extends Component {
// handled in `_onClickMarkAsRead`
return;
}
- this.trigger('o-select-thread', { thread: this.thread });
+ this.thread.open();
+ if (!this.env.messaging.device.isMobile) {
+ this.env.messaging.messagingMenu.close();
+ }
}
/**
diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.js b/addons/mail/static/src/components/thread_preview/thread_preview.js
index 614201e821d..6e30005225c 100644
--- a/addons/mail/static/src/components/thread_preview/thread_preview.js
+++ b/addons/mail/static/src/components/thread_preview/thread_preview.js
@@ -97,7 +97,10 @@ class ThreadPreview extends Component {
// handled in `_onClickMarkAsRead`
return;
}
- this.trigger('o-select-thread', { thread: this.thread });
+ this.thread.open();
+ if (!this.env.messaging.device.isMobile) {
+ this.env.messaging.messagingMenu.close();
+ }
}
/**
diff --git a/addons/mail/static/src/components/thread_view/thread_view.js b/addons/mail/static/src/components/thread_view/thread_view.js
index 7b1680f31a8..395fac68474 100644
--- a/addons/mail/static/src/components/thread_view/thread_view.js
+++ b/addons/mail/static/src/components/thread_view/thread_view.js
@@ -132,7 +132,6 @@ Object.assign(ThreadView, {
hasComposer: false,
hasMessageCheckbox: false,
hasSquashCloseMessages: false,
- haveMessagesAuthorRedirect: false,
haveMessagesMarkAsReadIcon: false,
haveMessagesReplyIcon: false,
order: 'asc',
@@ -168,7 +167,6 @@ Object.assign(ThreadView, {
optional: true,
},
hasSquashCloseMessages: Boolean,
- haveMessagesAuthorRedirect: Boolean,
haveMessagesMarkAsReadIcon: Boolean,
haveMessagesReplyIcon: Boolean,
order: {
diff --git a/addons/mail/static/src/components/thread_view/thread_view.xml b/addons/mail/static/src/components/thread_view/thread_view.xml
index 30844135e65..eead567fa1e 100644
--- a/addons/mail/static/src/components/thread_view/thread_view.xml
+++ b/addons/mail/static/src/components/thread_view/thread_view.xml
@@ -15,7 +15,6 @@
hasMessageCheckbox="props.hasMessageCheckbox"
hasScrollAdjust="props.hasScrollAdjust"
hasSquashCloseMessages="props.hasSquashCloseMessages"
- haveMessagesAuthorRedirect="props.haveMessagesAuthorRedirect"
haveMessagesMarkAsReadIcon="props.haveMessagesMarkAsReadIcon"
haveMessagesReplyIcon="props.haveMessagesReplyIcon"
order="props.order"
diff --git a/addons/mail/static/src/components/thread_view/thread_view_tests.js b/addons/mail/static/src/components/thread_view/thread_view_tests.js
index 06832473b99..c8f41632ae3 100644
--- a/addons/mail/static/src/components/thread_view/thread_view_tests.js
+++ b/addons/mail/static/src/components/thread_view/thread_view_tests.js
@@ -304,7 +304,7 @@ QUnit.test('mark channel as fetched when a new message is loaded and as seen whe
"Channel should have been fetched but not seen yet"
);
- await afterNextRender(() => thread.composer.focus());
+ await afterNextRender(() => document.querySelector('.o_ComposerTextInput_textarea').focus());
assert.verifySteps(
['rpc:channel_seen'],
"Channel should have been marked as seen after threadView got the focus"
@@ -355,6 +355,8 @@ QUnit.test('mark channel as fetched and seen when a new message is loaded if com
const threadView = this.env.models['mail.thread_view'].create({ thread: [['link', thread]] });
await this.createThreadViewComponent(threadView, { hasComposer: true });
+
+ document.querySelector('.o_ComposerTextInput_textarea').focus();
const notifications = [
[['myDB', 'mail.channel', 100], {
channelId: 100,