diff --git a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js index 16a3e1c978e..2653bda4ca1 100644 --- a/addons/mass_mailing/static/src/js/mass_mailing_html_field.js +++ b/addons/mass_mailing/static/src/js/mass_mailing_html_field.js @@ -99,7 +99,7 @@ export class MassMailingHtmlField extends HtmlField { const $editable = this.wysiwyg.getEditable(); const initialHtml = $editable.html(); await this.wysiwyg.cleanForSave(); - await this.wysiwyg.saveModifiedImages(this.$content); + await this.wysiwyg.savePendingImages(this.$content); await super.commitChanges(); diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py index 7a973bd8ca3..d4ae7da9e26 100644 --- a/addons/web_editor/controllers/main.py +++ b/addons/web_editor/controllers/main.py @@ -6,12 +6,14 @@ import logging import re import time import requests +import uuid import werkzeug.exceptions import werkzeug.urls import werkzeug.wrappers from PIL import Image, ImageFont, ImageDraw from lxml import etree from base64 import b64decode, b64encode +from datetime import datetime from odoo.http import request from odoo import http, tools, _, SUPERUSER_ID @@ -208,6 +210,12 @@ class Web_Editor(http.Controller): mimetype = guess_mimetype(data) if mimetype not in SUPPORTED_IMAGE_MIMETYPES: return {'error': format_error_msg} + if not name: + name = '%s-%s%s' % ( + datetime.now().strftime('%Y%m%d%H%M%S'), + str(uuid.uuid4())[:6], + SUPPORTED_IMAGE_MIMETYPES[mimetype], + ) except UserError: # considered as an image by the browser file input, but not # recognized as such by PIL, eg .webp diff --git a/addons/web_editor/static/src/js/backend/field_html.js b/addons/web_editor/static/src/js/backend/field_html.js index c045ec984c4..86d84938710 100644 --- a/addons/web_editor/static/src/js/backend/field_html.js +++ b/addons/web_editor/static/src/js/backend/field_html.js @@ -145,7 +145,7 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, { this._setValue(this._getValue()); this._isDirty = this.wysiwyg.isDirty(); await fullClean; - await this.wysiwyg.saveModifiedImages(this.$content); + await this.wysiwyg.savePendingImages(this.$content); // Update the value to the fully cleaned version. this._setValue(this._getValue()); _super(); diff --git a/addons/web_editor/static/src/js/backend/html_field.js b/addons/web_editor/static/src/js/backend/html_field.js index 381f07a872a..efcf9cee307 100644 --- a/addons/web_editor/static/src/js/backend/html_field.js +++ b/addons/web_editor/static/src/js/backend/html_field.js @@ -357,7 +357,7 @@ export class HtmlField extends Component { if (this.wysiwyg) { // Avoid listening to changes made during the _toInline process. this.wysiwyg.odooEditor.observerUnactive('commitChanges'); - await this.wysiwyg.saveModifiedImages(); + await this.wysiwyg.savePendingImages(); if (this.props.isInlineStyle) { await this._toInline(); } diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js index 062cdd76500..7046846dcc9 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/OdooEditor.js @@ -4220,7 +4220,15 @@ export class OdooEditor extends EventTarget { this.execCommand('insert', fragment); } } else if (files.length && targetSupportsHtmlContent) { - this.addImagesFiles(files).then(html => this.execCommand('insert', this._prepareClipboardData(html))); + this.addImagesFiles(files).then(html => { + const imageNodes = this.execCommand('insert', this._prepareClipboardData(html)); + if (imageNodes && this.options.dropImageAsAttachment) { + // Mark images as having to be saved as attachments. + for (const imageNode of imageNodes) { + imageNode.classList.add('o_b64_image_to_save'); + } + } + }); } else if (clipboardHtml && targetSupportsHtmlContent) { this.execCommand('insert', this._prepareClipboardData(clipboardHtml)); } else { @@ -4438,7 +4446,13 @@ export class OdooEditor extends EventTarget { this.execCommand('insert', this._prepareClipboardData(html)); } else if (fileTransferItems.length) { this.addImagesFiles(fileTransferItems).then(html => { - this.execCommand('insert', this._prepareClipboardData(html)); + const imageNodes = this.execCommand('insert', this._prepareClipboardData(html)); + if (imageNodes && this.options.dropImageAsAttachment) { + // Mark images as having to be saved as attachments. + for (const imageNode of imageNodes) { + imageNode.classList.add('o_b64_image_to_save'); + } + } }); } else if (htmlTransferItem) { htmlTransferItem.getAsString(pastedText => { diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 2a378e91d9d..171dc161046 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -212,6 +212,7 @@ const Wysiwyg = Widget.extend({ direction: options.direction || localization.direction || 'ltr', collaborationClientAvatarUrl: `${browser.location.origin}/web/image?model=res.users&field=avatar_128&id=${this.getSession().uid}`, renderingClasses: ['o_dirty', 'o_transform_removal', 'oe_edited_link'], + dropImageAsAttachment: options.dropImageAsAttachment, }, editorCollaborationOptions)); this.odooEditor.addEventListener('contentChanged', function () { @@ -899,7 +900,7 @@ const Wysiwyg = Widget.extend({ await this.cleanForSave(); const editables = this.options.getContentEditableAreas(); - await this.saveModifiedImages(editables.length ? $(editables) : this.$editable); + await this.savePendingImages(editables.length ? $(editables) : this.$editable); await this._saveViewBlocks(); window.removeEventListener('beforeunload', this._onBeforeUnload); @@ -935,15 +936,28 @@ const Wysiwyg = Widget.extend({ }); }, /** - * Create/Update cropped attachments. + * Create/Update attachments for unsaved images. + * (e.g. modified/cropped images, drag & dropped images, pasted images..) * * @param {jQuery} $editable * @returns {Promise} */ - saveModifiedImages: function ($editable = this.$editable) { + savePendingImages($editable = this.$editable) { const defs = _.map($editable, async editableEl => { const {oeModel: resModel, oeId: resId} = editableEl.dataset; - const proms = [...editableEl.querySelectorAll('.o_modified_image_to_save')].map(async el => { + const b64Proms = [...editableEl.querySelectorAll('.o_b64_image_to_save')].map(async el => { + const attachment = await this._rpc({ + route: '/web_editor/attachment/add_data', + params: { + name: el.dataset.fileName || '', + data: el.getAttribute('src').split(',')[1], + is_image: true, + }, + }); + el.setAttribute('src', attachment.image_src); + el.classList.remove('o_b64_image_to_save'); + }); + const modifiedProms = [...editableEl.querySelectorAll('.o_modified_image_to_save')].map(async el => { const isBackground = !el.matches('img'); el.classList.remove('o_modified_image_to_save'); // Modifying an image always creates a copy of the original, even if @@ -969,7 +983,7 @@ const Wysiwyg = Widget.extend({ el.setAttribute('src', newAttachmentSrc); } }); - return Promise.all(proms); + return Promise.all([...b64Proms, ...modifiedProms]); }); return Promise.all(defs); }, diff --git a/addons/website/static/src/js/editor/wysiwyg.js b/addons/website/static/src/js/editor/wysiwyg.js index cfc2664c133..e92b512a7ca 100644 --- a/addons/website/static/src/js/editor/wysiwyg.js +++ b/addons/website/static/src/js/editor/wysiwyg.js @@ -51,6 +51,7 @@ const WebsiteWysiwyg = Wysiwyg.extend({ this.options.toolbarHandler = $('#web_editor-top-edit'); // Do not insert a paragraph after each column added by the column commands: this.options.insertParagraphAfterColumns = false; + this.options.dropImageAsAttachment = true; const $editableWindow = this.$editable[0].ownerDocument.defaultView; // Dropdown menu initialization: handle dropdown openings by hand