summaryrefslogtreecommitdiff
path: root/addons/mail/static/src/components/thread_viewer
diff options
context:
space:
mode:
authorSébastien Theys <seb@odoo.com>2020-08-28 10:16:37 +0000
committerSébastien Theys <seb@odoo.com>2020-08-28 13:43:54 +0000
commitf9ee1cf5f904116e4d1d70a8eaf4ee1d58b18a3a (patch)
tree89425d0677083bb9489e42d47521c4bcf1e4ff44 /addons/mail/static/src/components/thread_viewer
parent98e12901c97a67dc87f21647473754cd6f384d4a (diff)
[FIX] mail, *: rename thread viewer to thread view
The "viewer" should be the "parent" not the view itself. This distinction will become important in a future commit to allow viewers to not display the view in some situations. Rename done as a standalone commit to ease diff readability. Part of task-2310623 closes odoo/odoo#56737 X-original-commit: 1b811e6a972d72de02965adda67c5b039bee023f Related: odoo/enterprise#12756 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/thread_viewer')
-rw-r--r--addons/mail/static/src/components/thread_viewer/thread_viewer.js191
-rw-r--r--addons/mail/static/src/components/thread_viewer/thread_viewer.scss38
-rw-r--r--addons/mail/static/src/components/thread_viewer/thread_viewer.xml46
-rw-r--r--addons/mail/static/src/components/thread_viewer/thread_viewer_tests.js419
4 files changed, 0 insertions, 694 deletions
diff --git a/addons/mail/static/src/components/thread_viewer/thread_viewer.js b/addons/mail/static/src/components/thread_viewer/thread_viewer.js
deleted file mode 100644
index 8e8f8f14dbb..00000000000
--- a/addons/mail/static/src/components/thread_viewer/thread_viewer.js
+++ /dev/null
@@ -1,191 +0,0 @@
-odoo.define('mail/static/src/components/thread_viewer/thread_viewer.js', function (require) {
-'use strict';
-
-const components = {
- Composer: require('mail/static/src/components/composer/composer.js'),
- MessageList: require('mail/static/src/components/message_list/message_list.js'),
-};
-const useStore = require('mail/static/src/component_hooks/use_store/use_store.js');
-
-const { Component } = owl;
-const { useRef } = owl.hooks;
-
-class ThreadViewer extends Component {
-
- /**
- * @param {...any} args
- */
- constructor(...args) {
- super(...args);
- useStore((...args) => this._useStoreSelector(...args));
- /**
- * Reference of the composer. Useful to set focus on composer when
- * thread has the focus.
- */
- this._composerRef = useRef('composer');
- /**
- * Reference of the message list. Useful to determine scroll positions.
- */
- this._messageListRef = useRef('messageList');
- }
-
- mounted() {
- this._update();
- }
-
- patched() {
- this._update();
- }
-
- //--------------------------------------------------------------------------
- // Public
- //--------------------------------------------------------------------------
-
- /**
- * Focus the thread. If it has a composer, focus it.
- */
- focus() {
- if (!this._composerRef.comp) {
- return;
- }
- this._composerRef.comp.focus();
- }
-
- /**
- * Focusout the thread.
- */
- focusout() {
- if (!this._composerRef.comp) {
- return;
- }
- this._composerRef.comp.focusout();
- }
-
- /**
- * Get the scroll position in the message list.
- *
- * @returns {integer|undefined}
- */
- getScrollTop() {
- if (!this._messageListRef.comp) {
- return undefined;
- }
- return this._messageListRef.comp.getScrollTop();
- }
-
- /**
- * @returns {mail.thread_viewer}
- */
- get threadViewer() {
- return this.env.models['mail.thread_viewer'].get(this.props.threadViewerLocalId);
- }
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Called when thread component is mounted or patched.
- *
- * @private
- */
- _update() {
- const messageList = this._messageListRef.comp;
- this.trigger('o-rendered');
- /**
- * Control panel may offset scrolling position of message list due to
- * height of buttons. To prevent this, control panel re-render is
- * triggered before message list. Correct way should be to adjust
- * scroll positions after everything has been rendered, but OWL doesn't
- * have such an API for the moment.
- */
- if (messageList) {
- messageList.adjustFromComponentHints();
- }
- }
-
- /**
- * Returns data selected from the store.
- *
- * @private
- * @param {Object} props
- * @returns {Object}
- */
- _useStoreSelector(props) {
- const threadViewer = this.env.models['mail.thread_viewer'].get(props.threadViewerLocalId);
- const thread = threadViewer ? threadViewer.thread : undefined;
- const threadCache = threadViewer ? threadViewer.threadCache : undefined;
- return {
- isDeviceMobile: this.env.messaging.device.isMobile,
- thread: thread ? thread.__state : undefined,
- threadCache: threadCache ? threadCache.__state : undefined,
- threadViewer: threadViewer ? threadViewer.__state : undefined,
- };
- }
-
-}
-
-Object.assign(ThreadViewer, {
- components,
- defaultProps: {
- composerAttachmentsDetailsMode: 'auto',
- hasComposer: false,
- hasMessageCheckbox: false,
- hasSquashCloseMessages: false,
- haveMessagesAuthorRedirect: false,
- haveMessagesMarkAsReadIcon: false,
- haveMessagesReplyIcon: false,
- order: 'asc',
- showComposerAttachmentsExtensions: true,
- showComposerAttachmentsFilenames: true,
- },
- props: {
- composerAttachmentsDetailsMode: {
- type: String,
- validate: prop => ['auto', 'card', 'hover', 'none'].includes(prop),
- },
- hasComposer: Boolean,
- hasComposerCurrentPartnerAvatar: {
- type: Boolean,
- optional: true,
- },
- hasComposerSendButton: {
- type: Boolean,
- optional: true,
- },
- /**
- * If set, determines whether the composer should display status of
- * members typing on related thread. When this prop is not provided,
- * it defaults to composer component default value.
- */
- hasComposerThreadTyping: {
- type: Boolean,
- optional: true,
- },
- hasMessageCheckbox: Boolean,
- hasScrollAdjust: {
- type: Boolean,
- optional: true,
- },
- hasSquashCloseMessages: Boolean,
- haveMessagesAuthorRedirect: Boolean,
- haveMessagesMarkAsReadIcon: Boolean,
- haveMessagesReplyIcon: Boolean,
- order: {
- type: String,
- validate: prop => ['asc', 'desc'].includes(prop),
- },
- selectedMessageLocalId: {
- type: String,
- optional: true,
- },
- showComposerAttachmentsExtensions: Boolean,
- showComposerAttachmentsFilenames: Boolean,
- threadViewerLocalId: String,
- },
- template: 'mail.ThreadViewer',
-});
-
-return ThreadViewer;
-
-});
diff --git a/addons/mail/static/src/components/thread_viewer/thread_viewer.scss b/addons/mail/static/src/components/thread_viewer/thread_viewer.scss
deleted file mode 100644
index 9b39a493979..00000000000
--- a/addons/mail/static/src/components/thread_viewer/thread_viewer.scss
+++ /dev/null
@@ -1,38 +0,0 @@
-// ------------------------------------------------------------------
-// Layout
-// ------------------------------------------------------------------
-
-.o_ThreadViewer {
- display: flex;
- position: relative;
- flex-flow: column;
- overflow: auto;
-}
-
-.o_ThreadViewer_composer {
- flex: 0 0 auto;
-}
-
-.o_ThreadViewer_loading {
- display: flex;
- align-self: center;
- flex: 1 1 auto;
- align-items: center;
-}
-
-.o_ThreadViewer_messageList {
- flex: 1 1 auto;
-}
-
-// ------------------------------------------------------------------
-// Style
-// ------------------------------------------------------------------
-
-.o_ThreadViewer {
- background-color: gray('100');
-
-}
-
-.o_ThreadViewer_loadingIcon {
- margin-right: 3px;
-}
diff --git a/addons/mail/static/src/components/thread_viewer/thread_viewer.xml b/addons/mail/static/src/components/thread_viewer/thread_viewer.xml
deleted file mode 100644
index 93202f6e423..00000000000
--- a/addons/mail/static/src/components/thread_viewer/thread_viewer.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<templates xml:space="preserve">
-
- <t t-name="mail.ThreadViewer" owl="1">
- <div class="o_ThreadViewer" t-att-data-thread-local-id="threadViewer and threadViewer.thread and threadViewer.thread.localId">
- <t t-if="threadViewer">
- <t t-if="threadViewer.isShowingLoading" name="loadingCondition">
- <div class="o_ThreadViewer_loading">
- <span><i class="o_ThreadViewer_loadingIcon fa fa-spinner fa-spin" title="Loading..." role="img"/>Loading...</span>
- </div>
- </t>
- <t t-elif="threadViewer.threadCache.isLoaded">
- <MessageList
- class="o_ThreadViewer_messageList"
- hasMessageCheckbox="props.hasMessageCheckbox"
- hasScrollAdjust="props.hasScrollAdjust"
- hasSquashCloseMessages="props.hasSquashCloseMessages"
- haveMessagesAuthorRedirect="props.haveMessagesAuthorRedirect"
- haveMessagesMarkAsReadIcon="props.haveMessagesMarkAsReadIcon"
- haveMessagesReplyIcon="props.haveMessagesReplyIcon"
- order="props.order"
- selectedMessageLocalId="props.selectedMessageLocalId"
- threadViewerLocalId="threadViewer.localId"
- t-ref="messageList"
- />
- </t>
- <t t-if="props.hasComposer">
- <Composer
- class="o_ThreadViewer_composer"
- attachmentsDetailsMode="props.composerAttachmentsDetailsMode"
- composerLocalId="threadViewer.thread.composer.localId"
- hasCurrentPartnerAvatar="props.hasComposerCurrentPartnerAvatar"
- hasSendButton="props.hasComposerSendButton"
- hasTextInputSendOnEnterEnabled="(threadViewer.thread.model === 'mail.channel' and threadViewer.thread.mass_mailing) ? false : undefined"
- hasThreadTyping="props.hasComposerThreadTyping"
- isCompact="(threadViewer.thread.model === 'mail.channel' and threadViewer.thread.mass_mailing) ? false : undefined"
- showAttachmentsExtensions="props.showComposerAttachmentsExtensions"
- showAttachmentsFilenames="props.showComposerAttachmentsFilenames"
- t-ref="composer"
- />
- </t>
- </t>
- </div>
- </t>
-
-</templates>
diff --git a/addons/mail/static/src/components/thread_viewer/thread_viewer_tests.js b/addons/mail/static/src/components/thread_viewer/thread_viewer_tests.js
deleted file mode 100644
index 31a56bfb1f1..00000000000
--- a/addons/mail/static/src/components/thread_viewer/thread_viewer_tests.js
+++ /dev/null
@@ -1,419 +0,0 @@
-odoo.define('mail/static/src/components/thread_viewer/thread_viewer_tests.js', function (require) {
-'use strict';
-
-const components = {
- ThreadViewer: require('mail/static/src/components/thread_viewer/thread_viewer.js'),
-};
-const {
- afterEach,
- afterNextRender,
- beforeEach,
- createRootComponent,
- dragenterFiles,
- start,
-} = require('mail/static/src/utils/test_utils.js');
-
-QUnit.module('mail', {}, function () {
-QUnit.module('components', {}, function () {
-QUnit.module('thread_viewer', {}, function () {
-QUnit.module('thread_viewer_tests.js', {
- beforeEach() {
- beforeEach(this);
-
- /**
- * @param {mail.thread_viewer} threadViewer
- * @param {Object} [otherProps={}]
- * @param {Object} [param2={}]
- * @param {boolean} [param2.isFixedSize=false]
- */
- this.createThreadViewerComponent = async (threadViewer, otherProps = {}, { isFixedSize = false } = {}) => {
- let target;
- if (isFixedSize) {
- // needed to allow scrolling in some tests
- const div = document.createElement('div');
- Object.assign(div.style, {
- display: 'flex',
- 'flex-flow': 'column',
- height: '300px',
- });
- this.widget.el.append(div);
- target = div;
- } else {
- target = this.widget.el;
- }
- const props = Object.assign({ threadViewerLocalId: threadViewer.localId }, otherProps);
- await createRootComponent(this, components.ThreadViewer, { props, target });
- };
-
- 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('dragover files on thread with composer', async function (assert) {
- assert.expect(1);
-
- await this.start();
- const thread = this.env.models['mail.thread'].create({
- channel_type: 'channel',
- id: 100,
- members: [['insert', [
- {
- email: "john@example.com",
- id: 9,
- name: "John",
- },
- {
- email: "fred@example.com",
- id: 10,
- name: "Fred",
- },
- ]]],
- model: 'mail.channel',
- name: "General",
- public: 'public',
- });
- const threadViewer = this.env.models['mail.thread_viewer'].create({ thread: [['link', thread]] });
- await this.createThreadViewerComponent(threadViewer, { hasComposer: true });
- await afterNextRender(() =>
- dragenterFiles(document.querySelector('.o_ThreadViewer'))
- );
- assert.ok(
- document.querySelector('.o_Composer_dropZone'),
- "should have dropzone when dragging file over the thread"
- );
-});
-
-QUnit.test('message list desc order', async function (assert) {
- assert.expect(5);
-
- for (let i = 0; i <= 60; i++) {
- this.data['mail.message'].records.push({
- channel_ids: [100],
- model: 'mail.channel',
- res_id: 100,
- });
- }
- await this.start();
- const thread = this.env.models['mail.thread'].create({
- channel_type: 'channel',
- id: 100,
- members: [['insert', [
- {
- email: "john@example.com",
- id: 9,
- name: "John",
- },
- {
- email: "fred@example.com",
- id: 10,
- name: "Fred",
- },
- ]]],
- model: 'mail.channel',
- name: "General",
- public: 'public',
- });
- const threadViewer = this.env.models['mail.thread_viewer'].create({ thread: [['link', thread]] });
- await this.createThreadViewerComponent(threadViewer, { order: 'desc' }, { isFixedSize: true });
- const messageItems = document.querySelectorAll(`.o_MessageList_item`);
- assert.notOk(
- messageItems[0].classList.contains("o_MessageList_loadMore"),
- "load more link should NOT be before messages"
- );
- assert.ok(
- messageItems[messageItems.length - 1].classList.contains("o_MessageList_loadMore"),
- "load more link should be after messages"
- );
- assert.strictEqual(
- document.querySelectorAll(`.o_Message`).length,
- 30,
- "should have 30 messages at the beginning"
- );
-
- // scroll to bottom
- await afterNextRender(() => {
- document.querySelector(`.o_ThreadViewer_messageList`).scrollTop =
- document.querySelector(`.o_ThreadViewer_messageList`).scrollHeight;
- });
- assert.strictEqual(
- document.querySelectorAll(`.o_Message`).length,
- 60,
- "should have 60 messages after scrolled to bottom"
- );
-
- await afterNextRender(() => {
- document.querySelector(`.o_ThreadViewer_messageList`).scrollTop = 0;
- });
- assert.strictEqual(
- document.querySelectorAll(`.o_Message`).length,
- 60,
- "scrolling to top should not trigger any message fetching"
- );
-});
-
-QUnit.test('message list asc order', async function (assert) {
- assert.expect(5);
-
- for (let i = 0; i <= 60; i++) {
- this.data['mail.message'].records.push({
- channel_ids: [100],
- model: 'mail.channel',
- res_id: 100,
- });
- }
- await this.start();
- const thread = this.env.models['mail.thread'].create({
- channel_type: 'channel',
- id: 100,
- members: [['insert', [
- {
- email: "john@example.com",
- id: 9,
- name: "John",
- },
- {
- email: "fred@example.com",
- id: 10,
- name: "Fred",
- },
- ]]],
- model: 'mail.channel',
- name: "General",
- public: 'public',
- });
- const threadViewer = this.env.models['mail.thread_viewer'].create({ thread: [['link', thread]] });
- await this.createThreadViewerComponent(threadViewer, { order: 'asc' }, { isFixedSize: true });
- const messageItems = document.querySelectorAll(`.o_MessageList_item`);
- assert.notOk(
- messageItems[messageItems.length - 1].classList.contains("o_MessageList_loadMore"),
- "load more link should be before messages"
- );
- assert.ok(
- messageItems[0].classList.contains("o_MessageList_loadMore"),
- "load more link should NOT be after messages"
- );
- assert.strictEqual(
- document.querySelectorAll(`.o_Message`).length,
- 30,
- "should have 30 messages at the beginning"
- );
-
- // scroll to top
- await afterNextRender(() => {
- document.querySelector(`.o_ThreadViewer_messageList`).scrollTop = 0;
- });
- assert.strictEqual(
- document.querySelectorAll(`.o_Message`).length,
- 60,
- "should have 60 messages after scrolled to top"
- );
-
- // scroll to bottom
- await afterNextRender(() => {
- document.querySelector(`.o_ThreadViewer_messageList`).scrollTop =
- document.querySelector(`.o_ThreadViewer_messageList`).scrollHeight;
- });
- assert.strictEqual(
- document.querySelectorAll(`.o_Message`).length,
- 60,
- "scrolling to bottom should not trigger any message fetching"
- );
-});
-
-QUnit.test('mark channel as fetched when a new message is loaded and as seen when focusing composer [REQUIRE FOCUS]', async function (assert) {
- assert.expect(8);
-
- await this.start({
- mockRPC(route, args) {
- if (args.method === 'channel_fetched') {
- assert.strictEqual(
- args.args[0][0],
- 100,
- 'channel_fetched is called on the right channel id'
- );
- assert.strictEqual(
- args.model,
- 'mail.channel',
- 'channel_fetched is called on the right channel model'
- );
- assert.step('rpc:channel_fetch');
- } else if (args.method === 'channel_seen') {
- assert.strictEqual(
- args.args[0][0],
- 100,
- 'channel_seen is called on the right channel id'
- );
- assert.strictEqual(
- args.model,
- 'mail.channel',
- 'channel_seeb is called on the right channel model'
- );
- assert.step('rpc:channel_seen');
- }
- return this._super(...arguments);
- }
- });
- const thread = this.env.models['mail.thread'].create({
- // FIXME should not be needed, see task-2277537
- composer: [['create']], // avoid initial focus
- id: 100,
- isServerPinned: true, // just to avoid joinChannel to be called
- members: [['insert', [
- {
- email: "john@example.com",
- id: this.env.messaging.currentPartner.id,
- name: "John",
- },
- {
- email: "fred@example.com",
- id: 10,
- name: "Fred",
- },
- ]]],
- message_unread_counter: 1, // seen would not be called if not > 0
- model: 'mail.channel',
- });
-
- const threadViewer = this.env.models['mail.thread_viewer'].create({ thread: [['link', thread]] });
- await this.createThreadViewerComponent(threadViewer, { hasComposer: true });
- const notifications = [
- [['myDB', 'mail.channel', 100], {
- channelId: 100,
- id: 1,
- body: "<p>fdsfsd</p>",
- author_id: [10, "Fred"],
- model: "mail.channel",
- channel_ids: [100],
- }]
- ];
-
- await afterNextRender(() =>
- this.widget.call('bus_service', 'trigger', 'notification', notifications)
- );
- assert.verifySteps(
- ['rpc:channel_fetch'],
- "Channel should have been fetched but not seen yet"
- );
-
- await afterNextRender(() => thread.composer.focus());
- assert.verifySteps(
- ['rpc:channel_seen'],
- "Channel should have been marked as seen after threadViewer got the focus"
- );
-});
-
-QUnit.test('mark channel as fetched and seen when a new message is loaded if composer is focused [REQUIRE FOCUS]', async function (assert) {
- assert.expect(4);
-
- await this.start({
- mockRPC(route, args) {
- if (args.method === 'channel_fetched' && args.args[0] === 100) {
- throw new Error("'channel_fetched' RPC must not be called for created channel as message is directly seen");
- } else if (args.method === 'channel_seen') {
- assert.strictEqual(
- args.args[0][0],
- 100,
- 'channel_seen is called on the right channel id'
- );
- assert.strictEqual(
- args.model,
- 'mail.channel',
- 'channel_seen is called on the right channel model'
- );
- assert.step('rpc:channel_seen');
- }
- return this._super(...arguments);
- }
- });
- const thread = this.env.models['mail.thread'].create({
- id: 100,
- isServerPinned: true, // just to avoid joinChannel to be called
- members: [['insert', [
- {
- email: "john@example.com",
- id: this.env.messaging.currentPartner.id,
- name: "John",
- },
- {
- email: "fred@example.com",
- id: 10,
- name: "Fred",
- },
- ]]],
- message_unread_counter: 1, // seen would not be called if not > 0
- model: 'mail.channel',
- });
-
- const threadViewer = this.env.models['mail.thread_viewer'].create({ thread: [['link', thread]] });
- await this.createThreadViewerComponent(threadViewer, { hasComposer: true });
- const notifications = [
- [['myDB', 'mail.channel', 100], {
- channelId: 100,
- id: 1,
- body: "<p>fdsfsd</p>",
- author_id: [10, "Fred"],
- model: "mail.channel",
- channel_ids: [100],
- }]
- ];
- await afterNextRender(() =>
- this.widget.call('bus_service', 'trigger', 'notification', notifications)
- );
- assert.verifySteps(
- ['rpc:channel_seen'],
- "Channel should have been mark as seen directly"
- );
-});
-
-QUnit.test('show message subject if thread is mailing channel', async function (assert) {
- assert.expect(3);
-
- this.data['mail.message'].records.push({
- channel_ids: [100],
- model: 'mail.channel',
- res_id: 100,
- subject: "Salutations, voyageur",
- });
- await this.start();
- const thread = this.env.models['mail.thread'].create({
- channel_type: 'channel',
- id: 100,
- mass_mailing: true,
- model: 'mail.channel',
- name: "General",
- public: 'public',
- });
- const threadViewer = this.env.models['mail.thread_viewer'].create({ thread: [['link', thread]] });
- await this.createThreadViewerComponent(threadViewer);
-
- assert.containsOnce(
- document.body,
- '.o_Message',
- "should display a single message"
- );
- assert.containsOnce(
- document.body,
- '.o_Message_subject',
- "should display subject of the message"
- );
- assert.strictEqual(
- document.querySelector('.o_Message_subject').textContent,
- "Subject: Salutations, voyageur",
- "Subject of the message should be 'Salutations, voyageur'"
- );
-});
-
-});
-});
-});
-
-});