diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/mail/static/src/components/thread_preview | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/mail/static/src/components/thread_preview')
4 files changed, 424 insertions, 0 deletions
diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.js b/addons/mail/static/src/components/thread_preview/thread_preview.js new file mode 100644 index 00000000..94df29e0 --- /dev/null +++ b/addons/mail/static/src/components/thread_preview/thread_preview.js @@ -0,0 +1,130 @@ +odoo.define('mail/static/src/components/thread_preview/thread_preview.js', function (require) { +'use strict'; + +const components = { + MessageAuthorPrefix: require('mail/static/src/components/message_author_prefix/message_author_prefix.js'), + PartnerImStatusIcon: require('mail/static/src/components/partner_im_status_icon/partner_im_status_icon.js'), +}; +const useShouldUpdateBasedOnProps = require('mail/static/src/component_hooks/use_should_update_based_on_props/use_should_update_based_on_props.js'); +const useStore = require('mail/static/src/component_hooks/use_store/use_store.js'); +const mailUtils = require('mail.utils'); + +const { Component } = owl; +const { useRef } = owl.hooks; + +class ThreadPreview extends Component { + + /** + * @override + */ + constructor(...args) { + super(...args); + useShouldUpdateBasedOnProps(); + useStore(props => { + const thread = this.env.models['mail.thread'].get(props.threadLocalId); + let lastMessageAuthor; + let lastMessage; + if (thread) { + const orderedMessages = thread.orderedMessages; + lastMessage = orderedMessages[orderedMessages.length - 1]; + } + if (lastMessage) { + lastMessageAuthor = lastMessage.author; + } + return { + isDeviceMobile: this.env.messaging.device.isMobile, + lastMessage: lastMessage ? lastMessage.__state : undefined, + lastMessageAuthor: lastMessageAuthor + ? lastMessageAuthor.__state + : undefined, + thread: thread ? thread.__state : undefined, + threadCorrespondent: thread && thread.correspondent + ? thread.correspondent.__state + : undefined, + }; + }); + /** + * Reference of the "mark as read" button. Useful to disable the + * top-level click handler when clicking on this specific button. + */ + this._markAsReadRef = useRef('markAsRead'); + } + + //-------------------------------------------------------------------------- + // Public + //-------------------------------------------------------------------------- + + /** + * Get the image route of the thread. + * + * @returns {string} + */ + image() { + if (this.thread.correspondent) { + return this.thread.correspondent.avatarUrl; + } + return `/web/image/mail.channel/${this.thread.id}/image_128`; + } + + /** + * Get inline content of the last message of this conversation. + * + * @returns {string} + */ + get inlineLastMessageBody() { + if (!this.thread.lastMessage) { + return ''; + } + return mailUtils.htmlToTextContentInline(this.thread.lastMessage.prettyBody); + } + + /** + * @returns {mail.thread} + */ + get thread() { + return this.env.models['mail.thread'].get(this.props.threadLocalId); + } + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @private + * @param {MouseEvent} ev + */ + _onClick(ev) { + const markAsRead = this._markAsReadRef.el; + if (markAsRead && markAsRead.contains(ev.target)) { + // handled in `_onClickMarkAsRead` + return; + } + this.thread.open(); + if (!this.env.messaging.device.isMobile) { + this.env.messaging.messagingMenu.close(); + } + } + + /** + * @private + * @param {MouseEvent} ev + */ + _onClickMarkAsRead(ev) { + if (this.thread.lastNonTransientMessage) { + this.thread.markAsSeen(this.thread.lastNonTransientMessage); + } + } + +} + +Object.assign(ThreadPreview, { + components, + props: { + threadLocalId: String, + }, + template: 'mail.ThreadPreview', +}); + +return ThreadPreview; + +}); diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.scss b/addons/mail/static/src/components/thread_preview/thread_preview.scss new file mode 100644 index 00000000..772d63e2 --- /dev/null +++ b/addons/mail/static/src/components/thread_preview/thread_preview.scss @@ -0,0 +1,117 @@ +// ------------------------------------------------------------------ +// Layout +// ------------------------------------------------------------------ + +.o_ThreadPreview { + @include o-mail-notification-list-item-layout(); + + &:hover .o_ThreadPreview_markAsRead { + opacity: 1; + } +} + +.o_ThreadPreview_content { + @include o-mail-notification-list-item-content-layout(); +} + +.o_ThreadPreview_core { + @include o-mail-notification-list-item-core-layout(); +} + +.o_ThreadPreview_coreItem { + @include o-mail-notification-list-item-core-item-layout(); +} + +.o_ThreadPreview_counter { + @include o-mail-notification-list-item-counter-layout(); +} + +.o_ThreadPreview_date { + @include o-mail-notification-list-item-date-layout(); +} + +.o_ThreadPreview_header { + @include o-mail-notification-list-item-header-layout(); +} + +.o_ThreadPreview_image { + @include o-mail-notification-list-item-image-layout(); +} + +.o_ThreadPreview_imageContainer { + @include o-mail-notification-list-item-image-container-layout(); +} + +.o_ThreadPreview_inlineText { + @include o-mail-notification-list-item-inline-text-layout(); +} + +.o_ThreadPreview_markAsRead { + @include o-mail-notification-list-item-mark-as-read-layout(); +} + +.o_ThreadPreview_name { + @include o-mail-notification-list-item-name-layout(); +} + +.o_ThreadPreview_partnerImStatusIcon { + @include o-mail-notification-list-item-partner-im-status-icon-layout(); +} + +.o_ThreadPreview_sidebar { + @include o-mail-notification-list-item-sidebar-layout(); +} + +// ------------------------------------------------------------------ +// Style +// ------------------------------------------------------------------ + +.o_ThreadPreview { + @include o-mail-notification-list-item-style(); + + &:hover { + .o_ThreadPreview_partnerImStatusIcon { + @include o-mail-notification-list-item-hover-partner-im-status-icon-style(); + } + } + + &.o-muted { + &:hover { + .o_ThreadPreview_partnerImStatusIcon { + @include o-mail-notification-list-item-muted-hover-partner-im-status-icon-style(); + } + } + } +} + +.o_ThreadPreview_core { + @include o-mail-notification-list-item-core-style(); +} + +.o_ThreadPreview_counter { + @include o-mail-notification-list-item-bold-style(); +} + +.o_ThreadPreview_date { + @include o-mail-notification-list-item-date-style(); + + &.o-muted { + color: gray('500'); + } +} + +.o_ThreadPreview_image { + @include o-mail-notification-list-item-image-style(); +} + +.o_ThreadPreview_markAsRead { + @include o-mail-notification-list-item-mark-as-read-style(); +} + +.o_ThreadPreview_name { + @include o-mail-notification-list-item-bold-style(); +} + +.o_ThreadPreview_partnerImStatusIcon { + @include o-mail-notification-list-item-partner-im-status-icon-style(); +} diff --git a/addons/mail/static/src/components/thread_preview/thread_preview.xml b/addons/mail/static/src/components/thread_preview/thread_preview.xml new file mode 100644 index 00000000..8a4baf3d --- /dev/null +++ b/addons/mail/static/src/components/thread_preview/thread_preview.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="UTF-8"?> +<templates xml:space="preserve"> + + <t t-name="mail.ThreadPreview" owl="1"> + <!-- + The preview template is used by the discuss in mobile, and by the systray + menu in order to show preview of threads. + --> + <div class="o_ThreadPreview" t-att-class="{ 'o-muted': thread and thread.localMessageUnreadCounter === 0 }" t-on-click="_onClick" t-att-data-thread-local-id="thread ? thread.localId : undefined"> + <t t-if="thread"> + <div class="o_ThreadPreview_sidebar"> + <div class="o_ThreadPreview_imageContainer o_ThreadPreview_sidebarItem"> + <img class="o_ThreadPreview_image rounded-circle" t-att-src="image()" alt="Thread Image"/> + <t t-if="thread.correspondent and thread.correspondent.im_status"> + <PartnerImStatusIcon + class="o_ThreadPreview_partnerImStatusIcon" + t-att-class="{ + 'o-mobile': env.messaging.device.isMobile, + 'o-muted': thread.localMessageUnreadCounter === 0, + }" + partnerLocalId="thread.correspondent.localId" + /> + </t> + </div> + </div> + <div class="o_ThreadPreview_content"> + <div class="o_ThreadPreview_header"> + <span class="o_ThreadPreview_name" t-att-class="{ 'o-mobile': env.messaging.device.isMobile, 'o-muted': thread.localMessageUnreadCounter === 0 }"> + <t t-esc="thread.displayName"/> + </span> + <t t-if="thread.localMessageUnreadCounter > 0"> + <span class="o_ThreadPreview_counter" t-att-class="{ 'o-muted': thread.localMessageUnreadCounter === 0 }"> + (<t t-esc="thread.localMessageUnreadCounter"/>) + </span> + </t> + <span class="o-autogrow"/> + <t t-if="thread.lastMessage"> + <span class="o_ThreadPreview_date" t-att-class="{ 'o-muted': thread.localMessageUnreadCounter === 0 }"> + <t t-esc="thread.lastMessage.date.fromNow()"/> + </span> + </t> + </div> + <div class="o_ThreadPreview_core"> + <span class="o_ThreadPreview_coreItem o_ThreadPreview_inlineText" t-att-class="{ 'o-empty': inlineLastMessageBody.length === 0 }"> + <t t-if="thread.lastMessage and thread.lastMessage.author"> + <MessageAuthorPrefix + messageLocalId="thread.lastMessage.localId" + threadLocalId="thread.localId" + /> + </t> + <t t-esc="inlineLastMessageBody"/> + </span> + <span class="o-autogrow"/> + <t t-if="thread.localMessageUnreadCounter > 0"> + <span class="o_ThreadPreview_coreItem o_ThreadPreview_markAsRead fa fa-check" title="Mark as Read" t-on-click="_onClickMarkAsRead" t-ref="markAsRead"/> + </t> + </div> + </div> + </t> + </div> + </t> + +</templates> diff --git a/addons/mail/static/src/components/thread_preview/thread_preview_tests.js b/addons/mail/static/src/components/thread_preview/thread_preview_tests.js new file mode 100644 index 00000000..981abf6b --- /dev/null +++ b/addons/mail/static/src/components/thread_preview/thread_preview_tests.js @@ -0,0 +1,114 @@ +odoo.define('mail/static/src/components/thread_preview/thread_preview_tests.js', function (require) { +'use strict'; + +const components = { + ThreadPreview: require('mail/static/src/components/thread_preview/thread_preview.js'), +}; + +const { + afterEach, + afterNextRender, + beforeEach, + createRootComponent, + start, +} = require('mail/static/src/utils/test_utils.js'); + +QUnit.module('mail', {}, function () { +QUnit.module('components', {}, function () { +QUnit.module('thread_preview', {}, function () { +QUnit.module('thread_preview_tests.js', { + beforeEach() { + beforeEach(this); + + this.createThreadPreviewComponent = async props => { + await createRootComponent(this, components.ThreadPreview, { + props, + target: this.widget.el, + }); + }; + + this.start = async params => { + const { env, widget } = await start(Object.assign({}, params, { + data: this.data, + })); + this.env = env; + this.widget = widget; + }; + }, + afterEach() { + afterEach(this); + }, +}); + +QUnit.test('mark as read', async function (assert) { + assert.expect(8); + this.data['mail.channel'].records.push({ + id: 11, + message_unread_counter: 1, + }); + this.data['mail.message'].records.push({ + channel_ids: [11], + id: 100, + model: 'mail.channel', + res_id: 11, + }); + + await this.start({ + hasChatWindow: true, + async mockRPC(route, args) { + if (route.includes('channel_seen')) { + assert.step('channel_seen'); + } + return this._super(...arguments); + }, + }); + const thread = this.env.models['mail.thread'].findFromIdentifyingData({ + id: 11, + model: 'mail.channel', + }); + await this.createThreadPreviewComponent({ threadLocalId: thread.localId }); + assert.containsOnce( + document.body, + '.o_ThreadPreview_markAsRead', + "should have the mark as read button" + ); + assert.containsOnce( + document.body, + '.o_ThreadPreview_counter', + "should have an unread counter" + ); + + await afterNextRender(() => + document.querySelector('.o_ThreadPreview_markAsRead').click() + ); + assert.verifySteps( + ['channel_seen'], + "should have marked the thread as seen" + ); + assert.hasClass( + document.querySelector('.o_ThreadPreview'), + 'o-muted', + "should be muted once marked as read" + ); + assert.containsNone( + document.body, + '.o_ThreadPreview_markAsRead', + "should no longer have the mark as read button" + ); + assert.containsNone( + document.body, + '.o_ThreadPreview_counter', + "should no longer have an unread counter" + ); + assert.containsNone( + document.body, + '.o_ChatWindow', + "should not have opened the thread" + ); +}); + +}); +}); +}); + +}); |
