[IMP] website, web_editor, *: save dropped images as attachments
*: mass_mailing When images are dropped from outside the browser into a website page text paragraph, they are stored as an inline base64-encoded source. This commit marks those images to be saved as attachments when it happens in website. The save happens upon page save similarly to what is done when images are transformed. This commit also applies this behavior for pasted images. task-2928495 closes odoo/odoo#98801 Signed-off-by: Romain Derie (rde) <rde@odoo.com>
This commit is contained in:
committed by
Romain Derie
parent
7442fb7161
commit
3bbce756c6
@@ -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();
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user