summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src/js
diff options
context:
space:
mode:
authorSamuel Degueldre <sad@odoo.com>2020-03-31 07:10:34 +0000
committerqsm-odoo <qsm@odoo.com>2020-06-05 18:11:42 +0000
commitc2de755bc690987f59cda27bd83a7429278955fc (patch)
tree8da996cffc724c2b2e5564a760d99b484ca42e1c /addons/web_editor/static/src/js
parent59cd23a4dfde62a9986373e232b14f22a34d90c2 (diff)
[IMP] web_editor, *: change image cropping from modal to inline widget
*: base Previously, cropping an image was done inside of a modal window, turning it into an inline widget makes the editor feel more integrated and allows you to better see the result of cropping directly in the page, it's also less intrusive to the user's edition workflow. At the same time, the way cropped images are saved was refactored to use the new original_id field on ir_attachment, as the old system was creating problems. Part of https://github.com/odoo/odoo/pull/51517 task-2192755
Diffstat (limited to 'addons/web_editor/static/src/js')
-rw-r--r--addons/web_editor/static/src/js/editor/rte.summernote.js76
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.editor.js1
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js374
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/media.js19
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js4
5 files changed, 263 insertions, 211 deletions
diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js
index 514dfdfe85c..92d8cefe0bc 100644
--- a/addons/web_editor/static/src/js/editor/rte.summernote.js
+++ b/addons/web_editor/static/src/js/editor/rte.summernote.js
@@ -460,7 +460,7 @@ $.summernote.pluginEvents.alt = function (event, editor, layoutInfo, sorted) {
$.summernote.pluginEvents.cropImage = function (event, editor, layoutInfo, sorted) {
var $editable = layoutInfo.editable();
var $selection = layoutInfo.handle().find('.note-control-selection');
- topBus.trigger('crop_image_dialog_demand', {
+ topBus.trigger('crop_image_demand', {
$editable: $editable,
media: $selection.data('target'),
});
@@ -1075,7 +1075,7 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, ServicesMixin,
this.setParent(parent);
topBus.on('alt_dialog_demand', this, this._onAltDialogDemand);
- topBus.on('crop_image_dialog_demand', this, this._onCropImageDialogDemand);
+ topBus.on('crop_image_demand', this, this._onCropImageDemand);
topBus.on('link_dialog_demand', this, this._onLinkDialogDemand);
topBus.on('media_dialog_demand', this, this._onMediaDialogDemand);
},
@@ -1086,7 +1086,7 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, ServicesMixin,
mixins.EventDispatcherMixin.destroy.call(this);
topBus.off('alt_dialog_demand', this, this._onAltDialogDemand);
- topBus.off('crop_image_dialog_demand', this, this._onCropImageDialogDemand);
+ topBus.off('crop_image_demand', this, this._onCropImageDemand);
topBus.off('link_dialog_demand', this, this._onLinkDialogDemand);
topBus.off('media_dialog_demand', this, this._onMediaDialogDemand);
},
@@ -1099,44 +1099,25 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, ServicesMixin,
*/
saveCroppedImages: function ($editable) {
var defs = _.map($editable.find('.o_cropped_img_to_save'), async croppedImg => {
- var $croppedImg = $(croppedImg);
- $croppedImg.removeClass('o_cropped_img_to_save');
-
- var resModel = $croppedImg.data('crop:resModel');
- var resID = $croppedImg.data('crop:resID');
- var cropID = $croppedImg.data('crop:id');
- var mimetype = $croppedImg.data('crop:mimetype');
- var originalSrc = $croppedImg.data('crop:originalSrc');
-
- var datas = $croppedImg.attr('src').split(',')[1];
- let attachmentID = cropID;
- if (!cropID) {
- var name = originalSrc + '.crop';
- attachmentID = await this._rpc({
- model: 'ir.attachment',
- method: 'create',
- args: [{
- res_model: resModel,
- res_id: resID,
- name: name,
- datas: datas,
- mimetype: mimetype,
- url: originalSrc, // To save the original image that was cropped
- }],
- });
- } else {
- await this._rpc({
- model: 'ir.attachment',
- method: 'write',
- args: [[cropID], {datas: datas}],
- });
- }
- const access_token = await this._rpc({
- model: 'ir.attachment',
- method: 'generate_access_token',
- args: [[attachmentID]],
+ croppedImg.classList.remove('o_cropped_img_to_save');
+ // Cropping an image always creates a copy of the original, even if
+ // it was cropped previously, as the other cropped image may be used
+ // elsewhere if the snippet was duplicated or was saved as a custom one.
+ const croppedAttachmentSrc = await this._rpc({
+ route: '/web_editor/crop_attachment',
+ params: {
+ res_model: croppedImg.dataset.resModel,
+ res_id: parseInt(croppedImg.dataset.resId),
+ name: croppedImg.dataset.originalName + '.crop',
+ data: croppedImg.getAttribute('src').split(',')[1],
+ mimetype: croppedImg.dataset.mimetype,
+ original_id: parseInt(croppedImg.dataset.originalId),
+ },
+ });
+ croppedImg.setAttribute('src', croppedAttachmentSrc);
+ weWidgets.ImageCropWidget.prototype.removeOnSaveAttributes.forEach(attr => {
+ delete croppedImg.dataset[attr];
});
- $croppedImg.attr('src', '/web/image/' + attachmentID + '?access_token=' + access_token[0]);
});
return Promise.all(defs);
},
@@ -1169,30 +1150,23 @@ var SummernoteManager = Class.extend(mixins.EventDispatcherMixin, ServicesMixin,
altDialog.open();
},
/**
- * Called when a demand to open a crop dialog is received on the bus.
+ * Called when a demand to crop an image is received on the bus.
*
* @private
* @param {Object} data
*/
- _onCropImageDialogDemand: function (data) {
+ _onCropImageDemand: function (data) {
if (data.__alreadyDone) {
return;
}
data.__alreadyDone = true;
- var cropImageDialog = new weWidgets.CropImageDialog(this,
+ new weWidgets.ImageCropWidget(this,
_.extend({
res_model: data.$editable.data('oe-model'),
res_id: data.$editable.data('oe-id'),
}, data.options || {}),
data.media
- );
- if (data.onSave) {
- cropImageDialog.on('save', this, data.onSave);
- }
- if (data.onCancel) {
- cropImageDialog.on('cancel', this, data.onCancel);
- }
- cropImageDialog.open();
+ ).appendTo(data.$editable);
},
/**
* Called when a demand to open a link dialog is received on the bus.
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index 762bade5e07..06263b23182 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -874,6 +874,7 @@ var SnippetsMenu = Widget.extend({
'.o_we_no_overlay',
'.ui-autocomplete',
'.modal .close',
+ '.o_we_crop_widget',
].join(', ');
},
/**
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js b/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js
index 9f827410221..91f76bba5d4 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/image_crop_widget.js
@@ -1,174 +1,226 @@
-odoo.define('wysiwyg.widgets.CropImageDialog', function (require) {
+odoo.define('wysiwyg.widgets.ImageCropWidget', function (require) {
'use strict';
-var core = require('web.core');
-var Dialog = require('wysiwyg.widgets.Dialog');
+const core = require('web.core');
+const qweb = core.qweb;
+const Widget = require('web.Widget');
-var _t = core._t;
+const _t = core._t;
-/**
- * CropImageDialog widget. Let users crop an image.
- */
-var CropImageDialog = Dialog.extend({
- template: 'wysiwyg.widgets.crop_image',
- xmlDependencies: Dialog.prototype.xmlDependencies.concat(
- ['/web_editor/static/src/xml/wysiwyg.xml']
- ),
- events: _.extend({}, Dialog.prototype.events, {
- 'click .o_crop_options [data-event]': '_onCropOptionClick',
- }),
+// Fields returned by cropper lib 'getData' method
+const cropperDataFields = ['x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY'];
+const ImageCropWidget = Widget.extend({
+ template: ['wysiwyg.widgets.crop'],
+ xmlDependencies: ['/web_editor/static/src/xml/wysiwyg.xml'],
+ events: {
+ 'click.crop_options [data-action]': '_onCropOptionClick',
+ // zoom event is triggered by the cropperjs library when the user zooms.
+ 'zoom': '_onCropZoom',
+ },
+ // Crop attributes that are saved with the DOM. Should only be removed when the image is changed.
+ persistentAttributes: [
+ ...cropperDataFields,
+ 'aspectRatio',
+ ],
+ // Attributes that are used to keep data from one crop to the next in the same session
+ // If present, should be used by the cropper instead of querying db
+ sessionAttributes: [
+ 'attachmentId',
+ 'originalSrc',
+ 'originalId',
+ 'originalName',
+ 'mimetype',
+ ],
+ // Attributes that are used by saveCroppedImages to create or update attachments
+ saveAttributes: [
+ 'resModel',
+ 'resId',
+ 'attachmentId',
+ 'originalId',
+ 'originalName',
+ 'mimetype',
+ ],
/**
* @constructor
*/
- init: function (parent, options, media) {
- var self = this;
+ init(parent, options, media) {
+ this._super(...arguments);
this.media = media;
- this.$media = $(this.media);
- var src = this.$media.attr('src').split('?')[0];
- this.aspectRatioList = [
- [_t("Free"), '0/0', 0],
- ["16:9", '16/9', 16 / 9],
- ["4:3", '4/3', 4 / 3],
- ["1:1", '1/1', 1],
- ["2:3", '2/3', 2 / 3],
- ];
- this.imageData = {
- imageSrc: src,
- originalSrc: this.$media.data('crop:originalSrc') || src, // the original src for cropped DB images will be fetched later
- mimetype: this.$media.data('crop:mimetype') || (_.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg'), // the mimetype for DB images will be fetched later
- aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1],
- isExternalImage: src.substr(0, 5) !== 'data:' && src[0] !== '/' && src.indexOf(window.location.host) < 0,
+ this.$media = $(media);
+ // Needed for editors in iframes.
+ this.document = media.ownerDocument;
+ // Used for res_model and res_id
+ this.options = options;
+ // key: ratio identifier, label: displayed to user, value: used by cropper lib
+ this.aspectRatios = {
+ "0/0": {label: _t("Free"), value: 0},
+ "16/9": {label: "16:9", value: 16 / 9},
+ "4/3": {label: "4:3", value: 4 / 3},
+ "1/1": {label: "1:1", value: 1},
+ "2/3": {label: "2:3", value: 2 / 3},
};
- this.options = _.extend({
- title: _t("Crop Image"),
- buttons: this.imageData.isExternalImage ? [{
- text: _t("Close"),
- close: true,
- }] : [{
- text: _t("Save"),
- classes: 'btn-primary',
- click: this.save,
- }, {
- text: _t("Discard"),
- close: true,
- }],
- }, options || {});
- this._super(parent, this.options);
- this.trigger_up('getRecordInfo', _.extend(this.options, {
- callback: function (recordInfo) {
- _.defaults(self.options, recordInfo);
- },
- }));
+ const src = this.media.getAttribute('src');
+ const data = Object.assign({}, media.dataset);
+ this.initialSrc = src;
+ this.aspectRatio = data.aspectRatio || "0/0";
+ this.mimetype = data.mimetype || src.endsWith('.png') ? 'image/png' : 'image/jpeg';
+ this.isCroppable = src.startsWith('data:') || new URL(src, window.location.origin).origin === window.location.origin;
},
/**
* @override
*/
- willStart: function () {
- var self = this;
- var def = this._super.apply(this, arguments);
- if (this.imageData.isExternalImage) {
- return def;
+ async willStart() {
+ await this._super.apply(this, arguments);
+ if (!this.isCroppable) {
+ return;
}
-
- var defs = [def];
- var params = {};
- var isDBImage = false;
- var matchImageID = this.imageData.imageSrc.match(/^\/web\/image\/(\d+)/);
- if (matchImageID) {
- params.image_id = parseInt(matchImageID[1]);
- isDBImage = true;
- } else {
- var matchXmlID = this.imageData.imageSrc.match(/^\/web\/image\/([^/?]+)/);
- if (matchXmlID) {
- params.xml_id = matchXmlID[1];
- isDBImage = true;
- }
+ // If there is a marked originalSrc, a previous crop has already happened,
+ // we won't find the original from the data-url. Reuse the data from the previous crop.
+ if (this.media.dataset.originalSrc) {
+ this.sessionAttributes.forEach(attr => {
+ this[attr] = this.media.dataset[attr];
+ });
+ return;
}
- if (isDBImage) {
- defs.push(this._rpc({
- route: '/web_editor/get_image_info',
- params: params,
- }).then(function (res) {
- _.extend(self.imageData, res);
- }));
+
+ // Get id, mimetype and originalSrc.
+ const {attachment, original} = await this._rpc({
+ route: '/web_editor/get_image_info',
+ params: {src: this.initialSrc.split(/[?#]/)[0]},
+ });
+ if (!attachment) {
+ // Local image that doesn't have an attachment, don't allow crop?
+ // In practice, this can happen if an image is directly linked with its
+ // static url and there is no corresponding attachment, (eg, logo in mass_mailing)
+ this.isCroppable = false;
+ return;
}
- return Promise.all(defs);
+ this.originalId = original.id;
+ this.originalSrc = original.image_src;
+ this.originalName = original.name;
+ this.mimetype = original.mimetype;
+ this.attachmentId = attachment.id;
},
/**
* @override
*/
- start: function () {
- this.$cropperImage = this.$('.o_cropper_image');
- if (this.$cropperImage.length) {
- var data = this.$media.data();
- var ratio = 0;
- for (var i = 0; i < this.aspectRatioList.length; i++) {
- if (this.aspectRatioList[i][1] === data.aspectRatio) {
- ratio = this.aspectRatioList[i][2];
- break;
- }
- }
- this.$cropperImage.cropper({
- viewMode: 2,
- dragMode: 'move',
- autoCropArea: 1.0,
- aspectRatio: ratio,
- data: _.pick(data, 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY')
+ async start() {
+ if (!this.isCroppable) {
+ this.displayNotification({
+ type: 'warning',
+ title: _t("This image is an external image"),
+ message: _t("This type of image is not supported for cropping.<br/>If you want to crop it, please first download it from the original source and upload it in Odoo."),
});
+ return this.destroy();
}
- return this._super.apply(this, arguments);
+ const _super = this._super.bind(this);
+ const $cropperWrapper = this.$('.o_we_cropper_wrapper');
+
+ // Replacing the src with the original's so that the layout is correct.
+ this.media.setAttribute('src', this.originalSrc);
+ await new Promise(resolve => this.media.addEventListener('load', resolve, {once: true}));
+ this.$cropperImage = this.$('.o_we_cropper_img');
+ const cropperImage = this.$cropperImage[0];
+ [cropperImage.style.width, cropperImage.style.height] = [this.$media.width() + 'px', this.$media.height() + 'px'];
+
+ // Overlaying the cropper image over the real image
+ const offset = this.$media.offset();
+ offset.left += parseInt(this.$media.css('padding-left'));
+ offset.top += parseInt(this.$media.css('padding-right'));
+ $cropperWrapper.offset(offset);
+
+ cropperImage.setAttribute('src', this.originalSrc);
+ await new Promise(resolve => cropperImage.addEventListener('load', resolve, {once: true}));
+ this.$cropperImage.cropper({
+ viewMode: 2,
+ dragMode: 'move',
+ autoCropArea: 1.0,
+ aspectRatio: this.aspectRatios[this.aspectRatio].value,
+ data: _.mapObject(_.pick(this.media.dataset, ...cropperDataFields), value => parseFloat(value)),
+ // Can't use 0 because it's falsy and the lib will then use its defaults (200x100)
+ minContainerWidth: 1,
+ minContainerHeight: 1,
+ });
+ core.bus.trigger('deactivate_snippet');
+
+ this._onDocumentMousedown = this._onDocumentMousedown.bind(this);
+ // We use capture so that the handler is called before other editor handlers
+ // like save, such that we can restore the src before a save.
+ this.document.addEventListener('mousedown', this._onDocumentMousedown, {capture: true});
+ return _super(...arguments);
},
/**
* @override
*/
- destroy: function () {
- if (this.$cropperImage.length) {
+ destroy() {
+ if (this.$cropperImage) {
this.$cropperImage.cropper('destroy');
+ this.document.removeEventListener('mousedown', this._onDocumentMousedown, {capture: true});
}
- this._super.apply(this, arguments);
+ this.media.setAttribute('src', this.initialSrc);
+ return this._super(...arguments);
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
/**
* Updates the DOM image with cropped data and associates required
* information for a potential future save (where required cropped data
* attachments will be created).
*
- * @override
+ * @private
*/
- save: function () {
- var self = this;
- var cropperData = this.$cropperImage.cropper('getData');
-
- // Mark the media for later creation of required cropped attachments...
- this.$media.addClass('o_cropped_img_to_save');
-
- // ... and attach required data
- this.$media.data('crop:resModel', this.options.res_model);
- this.$media.data('crop:resID', this.options.res_id);
- this.$media.data('crop:id', this.imageData.id);
- this.$media.data('crop:mimetype', this.imageData.mimetype);
- this.$media.data('crop:originalSrc', this.imageData.originalSrc);
-
- // Mark the media with the cropping information which is required for
- // a future crop edition
- this.$media
- .attr('data-aspect-ratio', this.imageData.aspectRatio)
- .data('aspectRatio', this.imageData.aspectRatio);
- _.each(cropperData, function (value, key) {
- key = _.str.dasherize(key);
- self.$media.attr('data-' + key, value);
- self.$media.data(key, value);
+ _save() {
+ // Mark the media for later creation/update of cropped attachment
+ this.media.classList.add('o_cropped_img_to_save');
+
+ this.allAttributes.forEach(attr => {
+ delete this.media.dataset[attr];
+ const value = this._getAttributeValue(attr);
+ if (value) {
+ this.media.dataset[attr] = value;
+ }
});
// Update the media with base64 source for preview before saving
- var canvas = this.$cropperImage.cropper('getCroppedCanvas', {
+ const cropperData = this.$cropperImage.cropper('getData');
+ const canvas = this.$cropperImage.cropper('getCroppedCanvas', {
width: cropperData.width,
height: cropperData.height,
});
- this.$media.attr('src', canvas.toDataURL(this.imageData.mimetype));
-
- this.final_data = this.media;
- return this._super.apply(this, arguments);
+ // 1 is the quality if the image is jpeg (in the range O-1), defaults to .92
+ this.initialSrc = canvas.toDataURL(this.mimetype, 1);
+ // src will be set to this.initialSrc in the destroy method
+ this.destroy();
+ },
+ /**
+ * Returns an attribute's value for saving.
+ *
+ * @private
+ */
+ _getAttributeValue(attr) {
+ switch (attr) {
+ case 'resModel':
+ return this.options.res_model;
+ case 'resId':
+ return this.options.res_id;
+ }
+ if (cropperDataFields.includes(attr)) {
+ return this.$cropperImage.cropper('getData')[attr];
+ }
+ return this[attr];
+ },
+ /**
+ * Resets the crop box to prevent it going outside the image.
+ *
+ * @private
+ */
+ _resetCropBox() {
+ this.$cropperImage.cropper('clear');
+ this.$cropperImage.cropper('crop');
},
//--------------------------------------------------------------------------
@@ -181,31 +233,65 @@ var CropImageDialog = Dialog.extend({
* @private
* @param {MouseEvent} ev
*/
- _onCropOptionClick: function (ev) {
- var $option = $(ev.currentTarget);
- var opt = $option.data('event');
- var value = $option.data('value');
- switch (opt) {
+ _onCropOptionClick(ev) {
+ const {action, value, scaleDirection} = ev.currentTarget.dataset;
+ switch (action) {
case 'ratio':
this.$cropperImage.cropper('reset');
- this.imageData.aspectRatio = $option.data('label');
- this.$cropperImage.cropper('setAspectRatio', value);
+ this.aspectRatio = value;
+ this.$cropperImage.cropper('setAspectRatio', this.aspectRatios[this.aspectRatio].value);
break;
case 'zoom':
- case 'rotate':
case 'reset':
- this.$cropperImage.cropper(opt, value);
+ this.$cropperImage.cropper(action, value);
break;
- case 'flip':
- var direction = value === 'horizontal' ? 'x' : 'y';
- var scaleAngle = -$option.data(direction);
- $option.data(direction, scaleAngle);
- this.$cropperImage.cropper('scale' + direction.toUpperCase(), scaleAngle);
+ case 'rotate':
+ this.$cropperImage.cropper(action, value);
+ this._resetCropBox();
break;
+ case 'flip': {
+ const amount = this.$cropperImage.cropper('getData')[scaleDirection] * -1;
+ return this.$cropperImage.cropper(scaleDirection, amount);
+ }
+ case 'apply':
+ return this._save();
+ case 'discard':
+ return this.destroy();
}
},
+ /**
+ * Discards crop if the user clicks outside of the widget.
+ *
+ * @private
+ * @param {MouseEvent} ev
+ */
+ _onDocumentMousedown(ev) {
+ if (document.body.contains(ev.target) && this.$(ev.target).length === 0) {
+ return this.destroy();
+ }
+ },
+ /**
+ * Resets the cropbox on zoom to prevent crop box overflowing.
+ *
+ * @private
+ */
+ async _onCropZoom() {
+ // Wait for the zoom event to be fully processed before reseting.
+ await new Promise(res => setTimeout(res, 0));
+ this._resetCropBox();
+ },
});
+const proto = ImageCropWidget.prototype;
+proto.allAttributes = [...new Set([
+ ...proto.persistentAttributes,
+ ...proto.sessionAttributes,
+ ...proto.saveAttributes,
+])];
+proto.removeOnSaveAttributes = [...new Set([
+ ...proto.sessionAttributes,
+ ...proto.saveAttributes,
+])];
-return CropImageDialog;
+return ImageCropWidget;
});
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
index c717417f428..645b49c7ead 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
@@ -2,7 +2,6 @@ odoo.define('wysiwyg.widgets.media', function (require) {
'use strict';
var concurrency = require('web.concurrency');
-var config = require('web.config');
var core = require('web.core');
var Dialog = require('web.Dialog');
var dom = require('web.dom');
@@ -11,6 +10,7 @@ var ImageOptimizeDialog = require('wysiwyg.widgets.image_optimize_dialog').Image
var utils = require('web.utils');
var Widget = require('web.Widget');
var session = require('web.session');
+const ImageCropWidget = require('wysiwyg.widgets.ImageCropWidget');
var QWeb = core.qweb;
var _t = core._t;
@@ -497,19 +497,10 @@ var FileWidget = SearchableMediaWidget.extend({
}
// Remove crop related attributes
- if (this.$media.attr('data-aspect-ratio')) {
- var attrs = ['aspect-ratio', 'x', 'y', 'width', 'height', 'rotate', 'scale-x', 'scale-y'];
- Object.keys(self.$media.data()).forEach(function (key) {
- if (_.str.startsWith(key, 'crop:')) {
- attrs.push(key);
- }
- });
- this.$media.removeClass('o_cropped_img_to_save');
- attrs.forEach(attr => {
- this.$media.removeData(attr);
- this.$media.removeAttr('data-' + attr);
- });
- }
+ ImageCropWidget.prototype.allAttributes.forEach(attr => {
+ delete this.media.dataset[attr];
+ });
+ this.media.classList.remove('o_cropped_img_to_save');
return this.media;
},
/**
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js b/addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js
index 93d1068b949..64a9dc062f3 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js
@@ -5,7 +5,7 @@ var Dialog = require('wysiwyg.widgets.Dialog');
var AltDialog = require('wysiwyg.widgets.AltDialog');
var MediaDialog = require('wysiwyg.widgets.MediaDialog');
var LinkDialog = require('wysiwyg.widgets.LinkDialog');
-var CropImageDialog = require('wysiwyg.widgets.CropImageDialog');
+var ImageCropWidget = require('wysiwyg.widgets.ImageCropWidget');
const {ColorpickerDialog} = require('web.Colorpicker');
var media = require('wysiwyg.widgets.media');
@@ -15,7 +15,7 @@ return {
AltDialog: AltDialog,
MediaDialog: MediaDialog,
LinkDialog: LinkDialog,
- CropImageDialog: CropImageDialog,
+ ImageCropWidget: ImageCropWidget,
ColorpickerDialog: ColorpickerDialog,
MediaWidget: media.MediaWidget,