From aa7bc757a2564da558c5ddeb66e0f0fcc79fb34b Mon Sep 17 00:00:00 2001 From: Zelong Lin Date: Tue, 14 Sep 2021 18:37:15 +0000 Subject: [PATCH] [IMP] mail: allow user to edit message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit task-2362167 closes odoo/odoo#76544 X-original-commit: 5d9d20748845652d2ecf527f4011522bf584807b Signed-off-by: Sébastien Theys (seb) --- addons/mail/controllers/discuss.py | 5 +- addons/mail/models/ir_attachment.py | 32 ++-- addons/mail/models/mail_channel.py | 1 + addons/mail/models/mail_message.py | 12 +- .../src/components/composer/composer.js | 5 + .../src/components/composer/composer.scss | 8 +- .../src/components/composer/composer.xml | 24 ++- .../composer_text_input.js | 12 ++ .../static/src/components/message/message.js | 3 + .../static/src/components/message/message.xml | 18 ++- .../message_action_list.xml | 1 + .../src/models/attachment/attachment.js | 8 +- .../static/src/models/composer/composer.js | 146 +++++++++++++++--- .../mail/static/src/models/message/message.js | 40 ++++- .../message_action_list.js | 14 +- .../mail/static/src/models/thread/thread.js | 18 ++- 16 files changed, 296 insertions(+), 51 deletions(-) diff --git a/addons/mail/controllers/discuss.py b/addons/mail/controllers/discuss.py index a4393cf9935..45667a943ce 100644 --- a/addons/mail/controllers/discuss.py +++ b/addons/mail/controllers/discuss.py @@ -144,15 +144,16 @@ class DiscussController(http.Controller): return thread.message_post(**{key: value for key, value in post_data.items() if key in allowed_params}).message_format()[0] @http.route('/mail/message/update_content', methods=['POST'], type='json', auth='public') - def mail_message_update_content(self, message_id, body): + def mail_message_update_content(self, message_id, body, attachment_ids): guest = request.env['mail.guest']._get_guest_from_request(request) message_sudo = guest.env['mail.message'].browse(message_id).sudo().exists() if not message_sudo.is_current_user_or_guest_author and not guest.env.user.has_group('base.group_system'): raise NotFound() - message_sudo._update_content(body=body) + message_sudo._update_content(body=body, attachment_ids=attachment_ids) return { 'id': message_sudo.id, 'body': message_sudo.body, + 'attachments': [('insert-and-replace', message_sudo.attachment_ids._attachment_format(commands=True))], } @http.route('/mail/attachment/upload', methods=['POST'], type='http', auth='public') diff --git a/addons/mail/models/ir_attachment.py b/addons/mail/models/ir_attachment.py index 6cf408a8a26..7dcadd89683 100644 --- a/addons/mail/models/ir_attachment.py +++ b/addons/mail/models/ir_attachment.py @@ -36,14 +36,26 @@ class IrAttachment(models.Model): except AccessError: pass - def _attachment_format(self): + def _attachment_format(self, commands=False): safari = request and request.httprequest.user_agent and request.httprequest.user_agent.browser == 'safari' - return [{ - 'checksum': attachment.checksum, - 'id': attachment.id, - 'filename': attachment.name, - 'name': attachment.name, - 'mimetype': 'application/octet-stream' if safari and attachment.mimetype and 'video' in attachment.mimetype else attachment.mimetype, - 'res_id': attachment.res_id, - 'res_model': attachment.res_model, - } for attachment in self] + res_list = [] + for attachment in self: + res = { + 'checksum': attachment.checksum, + 'id': attachment.id, + 'filename': attachment.name, + 'name': attachment.name, + 'mimetype': 'application/octet-stream' if safari and attachment.mimetype and 'video' in attachment.mimetype else attachment.mimetype, + } + if commands: + res['originThread'] = [('insert', { + 'id': attachment.res_id, + 'model': attachment.res_model, + })] + else: + res.update({ + 'res_id': attachment.res_id, + 'res_model': attachment.res_model, + }) + res_list.append(res) + return res_list diff --git a/addons/mail/models/mail_channel.py b/addons/mail/models/mail_channel.py index dc16f1b2def..2c6621ac356 100644 --- a/addons/mail/models/mail_channel.py +++ b/addons/mail/models/mail_channel.py @@ -613,6 +613,7 @@ class Channel(models.Model): 'payload': { 'id': message.id, 'body': message.body, + 'attachments': [('insert-and-replace', message.attachment_ids._attachment_format(commands=True))], }, }) return super()._message_update_content_after_hook(message=message) diff --git a/addons/mail/models/mail_message.py b/addons/mail/models/mail_message.py index 0556b4061c7..a93b7c9241c 100644 --- a/addons/mail/models/mail_message.py +++ b/addons/mail/models/mail_message.py @@ -674,11 +674,21 @@ class Message(models.Model): return super(Message, self).export_data(fields_to_export) - def _update_content(self, body): + def _update_content(self, body, attachment_ids): self.ensure_one() thread = self.env[self.model].browse(self.res_id) thread._check_can_update_message_content(self) self.body = body + if not attachment_ids: + self.attachment_ids.unlink() + else: + message_values = { + 'model': self.model, + 'body': body, + 'res_id': self.res_id, + } + attachement_values = thread._message_post_process_attachments([], attachment_ids, message_values) + self.update(attachement_values) thread._message_update_content_after_hook(self) # ------------------------------------------------------ diff --git a/addons/mail/static/src/components/composer/composer.js b/addons/mail/static/src/components/composer/composer.js index 49333a5a209..d788e78616e 100644 --- a/addons/mail/static/src/components/composer/composer.js +++ b/addons/mail/static/src/components/composer/composer.js @@ -118,6 +118,7 @@ export class Composer extends Component { return ( this.props.hasThreadTyping || this.composer.attachments.length > 0 || + this.composer.messageInEditing || !this.props.isCompact ); } @@ -168,6 +169,10 @@ export class Composer extends Component { } return; } + if (this.composer.messageInEditing && this.composer.messageInEditing.isEditing) { + await this.composer.updateMessage(); + return; + } await this.composer.postMessage(); // TODO: we might need to remove trigger and use the store to wait for the post rpc to be done // task-2252858 diff --git a/addons/mail/static/src/components/composer/composer.scss b/addons/mail/static/src/components/composer/composer.scss index 5070b2f2625..f336447cfc7 100644 --- a/addons/mail/static/src/components/composer/composer.scss +++ b/addons/mail/static/src/components/composer/composer.scss @@ -170,7 +170,9 @@ } .o_Composer { - background-color: gray('100'); + &:not(.o-messaging-in-editing) { + background-color: gray('100'); + } } .o_Composer_actionButton.o-last.o-has-current-partner-avatar.o-composer-is-compact { @@ -191,6 +193,10 @@ .o_Composer_buttons { border: 0; + + &.o-messaging-in-editing { + border-right: $border-width solid $border-color; + } } .o_Composer_coreMain:not(.o-composer-is-compact) { diff --git a/addons/mail/static/src/components/composer/composer.xml b/addons/mail/static/src/components/composer/composer.xml index c42e4c3eaf8..77706795b40 100644 --- a/addons/mail/static/src/components/composer/composer.xml +++ b/addons/mail/static/src/components/composer/composer.xml @@ -9,6 +9,7 @@ 'o-has-footer': hasFooter, 'o-has-header': hasHeader, 'o-is-compact': props.isCompact, + 'o-messaging-in-editing': composer and composer.messageInEditing, }" t-on-keydown="_onKeydown" > @@ -24,7 +25,7 @@ attachmentLocalIds="composer.attachments.map(attachment => attachment.localId)" isPending="true" newAttachmentExtraData="newAttachmentExtraData" - threadLocalId="composer.thread.localId" + threadLocalId="composer.messageInEditing ? composer.messageInEditing.originThread.localId : composer.thread.localId" t-ref="fileUploader" /> @@ -34,6 +35,11 @@ on: + + + on: + + @@ -43,14 +49,17 @@ "" + + "" + this document - + + + @@ -87,7 +96,7 @@ t-key="composer.localId" t-ref="textInput" /> -
+
- + @@ -140,6 +149,9 @@ + + escape to cancel, enter to save +
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 e9f4620b2c9..ec77a3a6dd1 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 @@ -144,6 +144,10 @@ export class ComposerTextInput extends Component { if (!this.composer) { return; } + if (this.composer.doFocus) { + this.focus(); + this.composer.update({ doFocus: false }); + } if (this.composer.isLastStateChangeProgrammatic) { this._textareaRef.el.value = this.composer.textInputContent; if (this.composer.hasFocus) { @@ -306,6 +310,10 @@ export class ComposerTextInput extends Component { break; case 'ArrowUp': case 'PageUp': + if (ev.key === 'ArrowUp' && !this.composer.hasSuggestions && !this.composer.textInputContent && this.composer.thread) { + this.composer.thread.startEditingLastMessageFromCurrentUser(); + break; + } if (this.composer.hasSuggestions) { this.composer.setPreviousSuggestionActive(); this.composer.update({ hasToScrollToActiveSuggestion: true }); @@ -313,6 +321,10 @@ export class ComposerTextInput extends Component { break; case 'ArrowDown': case 'PageDown': + if (ev.key === 'ArrowDown' && !this.composer.hasSuggestions && !this.composer.textInputContent && this.composer.thread) { + this.composer.thread.startEditingLastMessageFromCurrentUser(); + break; + } if (this.composer.hasSuggestions) { this.composer.setNextSuggestionActive(); this.composer.update({ hasToScrollToActiveSuggestion: true }); diff --git a/addons/mail/static/src/components/message/message.js b/addons/mail/static/src/components/message/message.js index 655e067866c..aa6c83577f7 100644 --- a/addons/mail/static/src/components/message/message.js +++ b/addons/mail/static/src/components/message/message.js @@ -422,6 +422,9 @@ export class Message extends Component { this._prettyBodyRef.el.innerHTML = this.message.prettyBody; this._lastPrettyBody = this.message.prettyBody; } + if (!this._prettyBodyRef.el) { + this._lastPrettyBody = undefined; + } // Remove all readmore before if any before reinsert them with _insertReadMoreLess. // This is needed because _insertReadMoreLess is working with direct DOM mutations // which are not sync with Owl. diff --git a/addons/mail/static/src/components/message/message.xml b/addons/mail/static/src/components/message/message.xml index 2a4be3bb5bf..a5d69f53a72 100644 --- a/addons/mail/static/src/components/message/message.xml +++ b/addons/mail/static/src/components/message/message.xml @@ -7,6 +7,7 @@ 'o_Message_active': isActive, 'o-clicked': state.isClicked, 'o-discussion': message and (message.is_discussion or message.is_notification), + 'o-editing': message.isEditing, 'o-has-message-selection': threadView and threadView.threadViewer and threadView.threadViewer.selectedMessage, 'o-highlighted': message and message.isHighlighted, 'o-mobile': messaging and messaging.device.isMobile, @@ -117,7 +118,22 @@

Subject:

-
+ +
+ + + +

diff --git a/addons/mail/static/src/components/message_action_list/message_action_list.xml b/addons/mail/static/src/components/message_action_list/message_action_list.xml index 6180a98bb1e..8b6eb648c05 100644 --- a/addons/mail/static/src/components/message_action_list/message_action_list.xml +++ b/addons/mail/static/src/components/message_action_list/message_action_list.xml @@ -14,6 +14,7 @@ }" title="Mark as Todo" t-on-click="actionList.onClickToggleStar"/> +

diff --git a/addons/mail/static/src/models/attachment/attachment.js b/addons/mail/static/src/models/attachment/attachment.js index f1261870197..e952b43af07 100644 --- a/addons/mail/static/src/models/attachment/attachment.js +++ b/addons/mail/static/src/models/attachment/attachment.js @@ -63,7 +63,6 @@ function factory(dependencies) { if ('name' in data) { data2.name = data.name; } - // relation if ('res_id' in data && 'res_model' in data) { data2.originThread = insert({ @@ -71,7 +70,9 @@ function factory(dependencies) { model: data.res_model, }); } - + if ('originThread' in data) { + data2.originThread = data.originThread; + } return data2; } @@ -378,7 +379,7 @@ function factory(dependencies) { attachmentViewer: many2many('mail.attachment_viewer', { inverse: 'attachments', }), - checkSum: attr(), + checksum: attr(), composers: many2many('mail.composer', { inverse: 'attachments', }), @@ -408,6 +409,7 @@ function factory(dependencies) { isImage: attr({ compute: '_computeIsImage', }), + is_main: attr(), isLinkedToComposer: attr({ compute: '_computeIsLinkedToComposer', }), diff --git a/addons/mail/static/src/models/composer/composer.js b/addons/mail/static/src/models/composer/composer.js index 2b90537d4f1..8df3949513f 100644 --- a/addons/mail/static/src/models/composer/composer.js +++ b/addons/mail/static/src/models/composer/composer.js @@ -33,6 +33,14 @@ function factory(dependencies) { return res; } + /** + * @override + */ + _created() { + this.onClickCancelLink = this.onClickCancelLink.bind(this); + this.onClickSaveLink = this.onClickSaveLink.bind(this); + } + /** * @override */ @@ -59,6 +67,12 @@ function factory(dependencies) { * currently used as a Discuss Inbox reply composer. */ discard() { + if (this.messageInEditing) { + this.messageInEditing.update({ isEditing: false }); + if (this.messageInEditing.originThread && this.messageInEditing.originThread.composer) { + this.messageInEditing.originThread.composer.update({ doFocus: true }); + } + } if (this.discussAsReplying) { this.discussAsReplying.clearReplyingToMessage(); } @@ -153,8 +167,8 @@ function factory(dependencies) { */ _computeRecipients() { const recipients = [...this.mentionedPartners]; - if (this.thread && !this.isLog) { - for (const recipient of this.thread.suggestedRecipientInfoList) { + if (this.activeThread && !this.isLog) { + for (const recipient of this.activeThread.suggestedRecipientInfoList) { if (recipient.partner && recipient.isSelected) { recipients.push(recipient.partner); } @@ -163,6 +177,39 @@ function factory(dependencies) { return replace(recipients); } + /** + * Handles click on the cancel link. + * + * @param {MouseEvent} ev + */ + onClickCancelLink(ev) { + ev.preventDefault(); + this.discard(); + } + + /** + * Handles click on the save link. + * + * @param {MouseEvent} ev + */ + onClickSaveLink(ev) { + ev.preventDefault(); + if (!this.canPostMessage) { + if (this.hasUploadingAttachment) { + this.env.services['notification'].notify({ + message: this.env._t("Please wait while the file is uploading."), + type: 'warning', + }); + } + return; + } + if (this.messageInEditing && this.messageInEditing.isEditing) { + this.updateMessage(); + return; + } + this.postMessage(); + } + /** * Open the full composer modal. */ @@ -173,9 +220,9 @@ function factory(dependencies) { default_attachment_ids: attachmentIds, default_body: escapeAndCompactTextContent(this.textInputContent), default_is_log: this.isLog, - default_model: this.thread.model, + default_model: this.activeThread.model, default_partner_ids: this.recipients.map(partner => partner.id), - default_res_id: this.thread.id, + default_res_id: this.activeThread.id, mail_post_autofollow: true, }; @@ -193,7 +240,7 @@ function factory(dependencies) { return; } this._reset(); - this.thread.loadNewMessages(); + this.activeThread.loadNewMessages(); }, }; await this.env.bus.trigger('do-action', { action, options }); @@ -203,10 +250,10 @@ function factory(dependencies) { * Post a message in provided composer's thread based on current composer fields values. */ async postMessage() { - if (this.thread.model === 'mail.channel') { + if (this.activeThread.model === 'mail.channel') { const command = this._getCommandFromText(this.textInputContent); if (command) { - await command.execute({ channel: this.thread, body: this.textInputContent }); + await command.execute({ channel: this.activeThread, body: this.textInputContent }); if (this.exists()) { this._reset(); } @@ -214,7 +261,7 @@ function factory(dependencies) { } } if (this.messaging.currentPartner) { - this.thread.unregisterCurrentPartnerIsTyping({ immediateNotify: true }); + this.activeThread.unregisterCurrentPartnerIsTyping({ immediateNotify: true }); } const escapedAndCompactContent = escapeAndCompactTextContent(this.textInputContent); let body = escapedAndCompactContent.replace(/ /g, ' ').trim(); @@ -235,12 +282,12 @@ function factory(dependencies) { }; const params = { 'post_data': postData, - 'thread_id': this.thread.id, - 'thread_model': this.thread.model, + 'thread_id': this.activeThread.id, + 'thread_model': this.activeThread.model, }; try { this.update({ isPostingMessage: true }); - if (this.thread.model === 'mail.channel') { + if (this.activeThread.model === 'mail.channel') { Object.assign(postData, { subtype_xmlid: 'mail.mt_comment', }); @@ -275,13 +322,47 @@ function factory(dependencies) { } } + /** + * Update a posted message when the message is ready. + */ + async updateMessage() { + if (this.messageInEditing.originThread) { + if (this.messaging.currentPartner) { + this.messageInEditing.originThread.unregisterCurrentPartnerIsTyping({ immediateNotify: true }); + } + } + const escapedAndCompactContent = escapeAndCompactTextContent(this.textInputContent); + let body = escapedAndCompactContent.replace(/ /g, ' ').trim(); + body = this._generateMentionsLinks(body); + body = parseAndTransform(body, addLink); + body = this._generateEmojisOnHtml(body); + let data = { + body: body, + attachment_ids: this.messageInEditing.attachments.concat(this.attachments).map(attachment => attachment.id), + }; + try { + this.update({ isPostingMessage: true }); + await this.messageInEditing.updateContent(data); + if (this.messageInEditing.originThread.model !== 'mail.channel') { + this.messageInEditing.originThread.refreshFollowers(); + this.messageInEditing.originThread.fetchAndUpdateSuggestedRecipients(); + } + this._reset(); + } finally { + this.update({ isPostingMessage: false }); + this.messageInEditing.update({ isEditing: false }); + if (this.messageInEditing.originThread && this.messageInEditing.originThread.composer) { + this.messageInEditing.originThread.composer.update({ doFocus: true }); + } + } + } /** * Called when current partner is inserting some input in composer. * Useful to notify current partner is currently typing something in the * composer of this thread to all other members. */ handleCurrentPartnerIsTyping() { - if (!this.thread) { + if (!this.activeThread) { return; } if (!this.messaging.currentPartner) { @@ -293,10 +374,10 @@ function factory(dependencies) { ) { return; } - if (this.thread.typingMembers.includes(this.messaging.currentPartner)) { - this.thread.refreshCurrentPartnerIsTyping(); + if (this.activeThread.typingMembers.includes(this.messaging.currentPartner)) { + this.activeThread.refreshCurrentPartnerIsTyping(); } else { - this.thread.registerCurrentPartnerIsTyping(); + this.activeThread.registerCurrentPartnerIsTyping(); } } @@ -385,6 +466,20 @@ function factory(dependencies) { return link(firstRecord); } + /** + * @private + * @returns {mail.thread} + */ + _computeActiveThread() { + if (this.messageInEditing && this.messageInEditing.originThread) { + return replace(this.messageInEditing.originThread); + } + if (this.thread) { + return replace(this.thread); + } + return clear(); + } + /** * @private * @returns {boolean} @@ -648,7 +743,7 @@ function factory(dependencies) { return false; } if (command.channel_types) { - return command.channel_types.includes(this.thread.channel_type); + return command.channel_types.includes(this.activeThread.channel_type); } return true; }); @@ -723,7 +818,7 @@ function factory(dependencies) { } const Model = this.messaging.models[this.suggestionModelName]; const searchTerm = this.suggestionSearchTerm; - await this.async(() => Model.fetchSuggestions(searchTerm, { thread: this.thread })); + await this.async(() => Model.fetchSuggestions(searchTerm, { thread: this.activeThread })); this._updateSuggestionList(); if ( this.suggestionSearchTerm && @@ -774,8 +869,8 @@ function factory(dependencies) { const [ mainSuggestedRecords, extraSuggestedRecords = [], - ] = Model.searchSuggestions(this.suggestionSearchTerm, { thread: this.thread }); - const sortFunction = Model.getSuggestionSortFunction(this.suggestionSearchTerm, { thread: this.thread }); + ] = Model.searchSuggestions(this.suggestionSearchTerm, { thread: this.activeThread }); + const sortFunction = Model.getSuggestionSortFunction(this.suggestionSearchTerm, { thread: this.activeThread }); mainSuggestedRecords.sort(sortFunction); extraSuggestedRecords.sort(sortFunction); // arbitrary limit to avoid displaying too many elements at once @@ -801,6 +896,9 @@ function factory(dependencies) { activeSuggestedRecord: many2one('mail.model', { compute: '_computeActiveSuggestedRecord', }), + activeThread: many2one('mail.thread', { + compute: '_computeActiveThread', + }), attachments: many2many('mail.attachment', { inverse: 'composers', }), @@ -826,6 +924,10 @@ function factory(dependencies) { discussAsReplying: one2one('mail.discuss', { inverse: 'replyingToMessageOriginThreadComposer', }), + /** + * Determines whether this composer should be focused at next render. + */ + doFocus: attr(), /** * Determines the extra records that are currently suggested. * Allows to have different model types of mentions through a dynamic @@ -898,7 +1000,7 @@ function factory(dependencies) { /** * Determines the extra `mail.partner` (on top of existing followers) * that will receive the message being composed by `this`, and that will - * also be added as follower of `this.thread`. + * also be added as follower of `this.activeThread`. */ recipients: many2many('mail.partner', { compute: '_computeRecipients', @@ -947,7 +1049,9 @@ function factory(dependencies) { }), thread: one2one('mail.thread', { inverse: 'composer', - required: true, + }), + messageInEditing: one2one('mail.message', { + inverse: 'composerInEditing', }), }; Composer.onChanges = [ diff --git a/addons/mail/static/src/models/message/message.js b/addons/mail/static/src/models/message/message.js index bbed43fbb25..34aeaa89b9a 100644 --- a/addons/mail/static/src/models/message/message.js +++ b/addons/mail/static/src/models/message/message.js @@ -2,7 +2,7 @@ import { registerNewModel } from '@mail/model/model_core'; import { attr, many2many, many2one, one2many, one2one } from '@mail/model/model_field'; -import { clear, create, insert, insertAndReplace, replace, unlinkAll } from '@mail/model/model_field_command'; +import { clear, create, insert, insertAndReplace, link, replace, unlinkAll } from '@mail/model/model_field_command'; import emojis from '@mail/js/emojis'; import { addLink, htmlToTextContentInline, parseAndTransform, timeFromNow } from '@mail/js/utils'; @@ -276,6 +276,28 @@ function factory(dependencies) { this.messaging.discuss.replyToMessage(this); } + startEditing() { + const parser = new DOMParser(); + const htmlDoc = parser.parseFromString(this.body.replaceAll('
', '\n').replaceAll('
', '\n'), "text/html"); + const textInputContent = htmlDoc.body.textContent; + const composerData = { + doFocus: true, + isLastStateChangeProgrammatic: true, + textInputCursorStart: textInputContent.length, + textInputCursorEnd: textInputContent.length, + textInputSelectionDirection: 'none', + textInputContent, + }; + if (this.composerInEditing) { + this.composerInEditing.update(composerData); + } else { + this.messaging.models['mail.composer'].create({ + messageInEditing: link(this), + ...composerData, + }); + } + this.update({ isEditing: true }); + } /** * Toggle the starred status of the provided message. */ @@ -293,11 +315,12 @@ function factory(dependencies) { * @param {Object} param0 * @param {string} param0.body the new body of the message */ - async updateContent({ body }) { + async updateContent({ body, attachment_ids }) { const messageData = await this.env.services.rpc({ route: '/mail/message/update_content', params: { body, + attachment_ids, message_id: this.id, }, }); @@ -569,6 +592,13 @@ function factory(dependencies) { canStarBeToggled: attr({ compute: '_computeCanStarBeToggled', }), + /** + * The composer in editing mode of a message. + */ + composerInEditing: one2one('mail.composer', { + inverse: 'messageInEditing', + isCausal: true, + }), /** * Determines the date of the message as a moment object. */ @@ -617,6 +647,12 @@ function factory(dependencies) { compute: '_computeIsBodyEqualSubtypeDescription', default: false, }), + /* + * Determines whether the current user is currently editing this message. + */ + isEditing: attr({ + default: false, + }), /** * Determine whether the message has to be considered empty or not. * diff --git a/addons/mail/static/src/models/message_action_list/message_action_list.js b/addons/mail/static/src/models/message_action_list/message_action_list.js index dea2d07feb7..72056f26ab6 100644 --- a/addons/mail/static/src/models/message_action_list/message_action_list.js +++ b/addons/mail/static/src/models/message_action_list/message_action_list.js @@ -16,6 +16,7 @@ function factory(dependencies) { this.onClick = this.onClick.bind(this); this.onClickConfirmDelete = this.onClickConfirmDelete.bind(this); this.onClickDelete = this.onClickDelete.bind(this); + this.onClickEdit = this.onClickEdit.bind(this); this.onClickMarkAsRead = this.onClickMarkAsRead.bind(this); this.onReactionPopoverOpened = this.onReactionPopoverOpened.bind(this); this.onReactionPopoverClosed = this.onReactionPopoverClosed.bind(this); @@ -38,7 +39,10 @@ function factory(dependencies) { * @param {MouseEvent} ev */ onClickConfirmDelete(ev) { - this.message.updateContent({ body: '' }); + this.message.updateContent({ + body: '', + attachment_ids: [], + }); } /** @@ -49,6 +53,14 @@ function factory(dependencies) { this.update({ showDeleteConfirm: true }); } + /** + * @private + * @param {MouseEvent} ev + */ + onClickEdit(ev) { + this.message.startEditing(); + } + /** * @private * @param {MouseEvent} ev diff --git a/addons/mail/static/src/models/thread/thread.js b/addons/mail/static/src/models/thread/thread.js index f2e63ddac85..f58e2c94b94 100644 --- a/addons/mail/static/src/models/thread/thread.js +++ b/addons/mail/static/src/models/thread/thread.js @@ -702,9 +702,7 @@ function factory(dependencies) { orderBy: [{ name: 'id', asc: false }], }, { shadow: true })); this.update({ - originThreadAttachments: insertAndReplace(attachmentsData.map(data => - this.messaging.models['mail.attachment'].convertData(data) - )), + originThreadAttachments: insertAndReplace(attachmentsData), }); this.update({ areAttachmentsLoaded: true }); } @@ -1189,6 +1187,20 @@ function factory(dependencies) { }); } + /** + * Starts editing the last message of this thread from the current user. + */ + startEditingLastMessageFromCurrentUser() { + for (const threadView of this.threadViews) { + const messages = threadView.threadCache.orderedMessages; + messages.reverse(); + const message = messages.find(message => message.isCurrentUserOrGuestAuthor && message.canBeDeleted); + if (message) { + message.startEditing(); + } + } + } + /** * Unfollow current partner from this thread. */