diff --git a/addons/mass_mailing/static/src/snippets/s_rating/options.js b/addons/mass_mailing/static/src/snippets/s_rating/options.js
index ed6e493bccc..fc798052a5c 100644
--- a/addons/mass_mailing/static/src/snippets/s_rating/options.js
+++ b/addons/mass_mailing/static/src/snippets/s_rating/options.js
@@ -1,7 +1,8 @@
odoo.define('mass_mailing.s_rating_options', function (require) {
'use strict';
-const weWidgets = require('wysiwyg.widgets');
+const { ComponentWrapper } = require('web.OwlCompatibility');
+const { MediaDialogWrapper } = require('@web_editor/components/media_dialog/media_dialog');
const options = require('web_editor.snippets.options');
options.registry.Rating = options.Class.extend({
@@ -37,16 +38,15 @@ options.registry.Rating = options.Class.extend({
* @see this.selectClass for parameters
*/
customIcon: async function (previewMode, widgetValue, params) {
- return new Promise(resolve => {
- const dialog = new weWidgets.MediaDialog(
- this,
- {noImages: true, noDocuments: true, noVideos: true, mediaWidth: 1920},
- $('')
- );
- this._saving = false;
- dialog.on('save', this, function (attachments) {
- this._saving = true;
- const customClass = 'fa ' + attachments.className;
+ const media = document.createElement('i');
+ media.className = params.customActiveIcon === 'true' ? this.faClassActiveCustomIcons : this.faClassInactiveCustomIcons;
+ const dialog = new ComponentWrapper(this, MediaDialogWrapper, {
+ noImages: true,
+ noDocuments: true,
+ noVideos: true,
+ media,
+ save: icon => {
+ const customClass = icon.className;
const $activeIcons = this.$target.find('.s_rating_active_icons > i');
const $inactiveIcons = this.$target.find('.s_rating_inactive_icons > i');
const $icons = params.customActiveIcon === 'true' ? $activeIcons : $inactiveIcons;
@@ -57,15 +57,9 @@ options.registry.Rating = options.Class.extend({
this.$target[0].dataset.inactiveCustomIcon = this.faClassInactiveCustomIcons;
this.$target[0].dataset.icon = 'custom';
this.iconType = 'custom';
- resolve();
- });
- dialog.on('closed', this, function () {
- if (!this._saving) {
- resolve();
- }
- });
- dialog.open();
+ }
});
+ dialog.mount(document.body);
},
/**
* Sets the number of active icons.
diff --git a/addons/web_editor/static/src/components/media_dialog/media_dialog.js b/addons/web_editor/static/src/components/media_dialog/media_dialog.js
index a8093cd2250..82df7ee24d9 100644
--- a/addons/web_editor/static/src/components/media_dialog/media_dialog.js
+++ b/addons/web_editor/static/src/components/media_dialog/media_dialog.js
@@ -1,13 +1,14 @@
/** @odoo-module **/
import { useService } from '@web/core/utils/hooks';
+import { useWowlService } from '@web/legacy/utils';
import { Dialog } from '@web/core/dialog/dialog';
import { ImageSelector } from './image_selector';
import { DocumentSelector } from './document_selector';
import { IconSelector } from './icon_selector';
import { VideoSelector } from './video_selector';
-const { useState } = owl;
+const { Component, useState, onRendered, xml } = owl;
export const TABS = {
IMAGES: {
@@ -45,6 +46,8 @@ export class MediaDialog extends Dialog {
this.tabs = [];
this.selectedMedia = useState({});
+ this.initialIconClasses = [];
+
this.addTabs();
this.state = useState({
@@ -110,6 +113,7 @@ export class MediaDialog extends Dialog {
if (mediaFont) {
const selectedIcon = mediaFont.icons.find(icon => icon.names.some(name => classes.includes(name)));
if (selectedIcon) {
+ this.initialIconClasses.push(...selectedIcon.names);
this.selectMedia(selectedIcon, TABS.ICONS.id);
}
}
@@ -139,7 +143,29 @@ export class MediaDialog extends Dialog {
async save() {
const selectedMedia = this.selectedMedia[this.state.activeTab];
if (selectedMedia.length) {
- const elements = await TABS[this.state.activeTab].Component.createElements(selectedMedia);
+ const elements = await TABS[this.state.activeTab].Component.createElements(selectedMedia, { rpc: this.rpc, orm: this.orm });
+ elements.forEach(element => {
+ if (this.props.media) {
+ element.classList.add(...this.props.media.classList);
+ const style = this.props.media.getAttribute('style');
+ if (style) {
+ element.setAttribute('style', style);
+ }
+ if (this.props.media.dataset.shape) {
+ element.dataset.shape = this.props.media.dataset.shape;
+ }
+ if (this.props.media.dataset.shapeColors) {
+ element.dataset.shapeColors = this.props.media.dataset.shapeColors;
+ }
+ }
+ element.classList.add(...TABS[this.state.activeTab].Component.mediaSpecificClasses);
+ for (const otherTab of Object.keys(TABS).filter(key => key !== this.state.activeTab)) {
+ element.classList.remove(...TABS[otherTab].Component.mediaSpecificClasses);
+ }
+ element.classList.remove(...this.initialIconClasses);
+ element.classList.remove('o_modified_image_to_save');
+ element.classList.remove('oe_edited_link');
+ });
if (this.props.multiImages) {
this.props.save(elements);
} else {
@@ -151,6 +177,29 @@ export class MediaDialog extends Dialog {
}
MediaDialog.bodyTemplate = 'web_editor.MediaDialogBody';
MediaDialog.footerTemplate = 'web_editor.MediaDialogFooter';
+MediaDialog.defaultProps = {
+ useMediaLibrary: true,
+};
MediaDialog.components = {
...Object.keys(TABS).map(key => TABS[key].Component),
};
+
+export class MediaDialogWrapper extends Component {
+ setup() {
+ this.dialogs = useWowlService('dialog');
+
+ onRendered(() => {
+ this.dialogs.add(MediaDialog, {
+ ...this.props,
+ close: () => {
+ if (this.props.close) {
+ this.props.close();
+ }
+ this.destroy();
+
+ },
+ });
+ });
+ }
+}
+MediaDialogWrapper.template = xml``;
diff --git a/addons/web_editor/static/src/components/media_dialog/search_media.js b/addons/web_editor/static/src/components/media_dialog/search_media.js
index 0b99df7f1f7..ab05905bebb 100644
--- a/addons/web_editor/static/src/components/media_dialog/search_media.js
+++ b/addons/web_editor/static/src/components/media_dialog/search_media.js
@@ -1,12 +1,15 @@
/** @odoo-module **/
-import { throttle } from '@web/core/utils/timing';
+import { debounce } from '@web/core/utils/timing';
-const { Component, xml } = owl;
+const { Component, onWillUnmount, xml } = owl;
export class SearchMedia extends Component {
setup() {
- this.search = throttle((ev) => this.props.search(ev.target.value), 1000);
+ this.search = debounce((ev) => this.props.search(ev.target.value), 1000);
+ onWillUnmount(() => {
+ this.search.cancel();
+ });
}
}
SearchMedia.template = xml`
diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js
index 739a8f4a372..a7e6e6123a5 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -1,6 +1,8 @@
odoo.define('web_editor.snippets.options', function (require) {
'use strict';
+const { ComponentWrapper } = require('web.OwlCompatibility');
+const { MediaDialogWrapper } = require('@web_editor/components/media_dialog/media_dialog');
var core = require('web.core');
const {ColorpickerWidget} = require('web.Colorpicker');
const Dialog = require('web.Dialog');
@@ -1650,10 +1652,10 @@ const MediapickerUserValueWidget = UserValueWidget.extend({
* @param {boolean} [videos] whether videos should be available
* default: false
*/
- _openDialog(el, {images = false, videos = false}) {
+ _openDialog(el, {images = false, videos = false, save}) {
el.src = this._value;
const $editable = this.$target.closest('.o_editable');
- const mediaDialog = new weWidgets.MediaDialog(this, {
+ const mediaDialogWrapper = new ComponentWrapper(this, MediaDialogWrapper, {
noImages: !images,
noVideos: !videos,
noIcons: true,
@@ -1663,8 +1665,10 @@ const MediapickerUserValueWidget = UserValueWidget.extend({
'312451183', '415226028', '367762632', '340475898', '374265101', '370467553'],
'res_model': $editable.data('oe-model'),
'res_id': $editable.data('oe-id'),
- }, el).open();
- return mediaDialog;
+ save,
+ media: el,
+ });
+ return mediaDialogWrapper.mount(this.el);
},
//--------------------------------------------------------------------------
@@ -1703,13 +1707,15 @@ const ImagepickerUserValueWidget = MediapickerUserValueWidget.extend({
_onEditMedia(ev) {
// Need a dummy element for the media dialog to modify.
const dummyEl = document.createElement('img');
- const dialog = this._openDialog(dummyEl, {images: true});
- dialog.on('save', this, data => {
- // Accessing the value directly through dummyEl.src converts the url to absolute,
- // using getAttribute allows us to keep the url as it was inserted in the DOM
- // which can be useful to compare it to values stored in db.
- this._value = dummyEl.getAttribute('src');
- this._onUserValueChange();
+ this._openDialog(dummyEl, {
+ images: true,
+ save: (media) => {
+ // Accessing the value directly through dummyEl.src converts the url to absolute,
+ // using getAttribute allows us to keep the url as it was inserted in the DOM
+ // which can be useful to compare it to values stored in db.
+ this._value = media.getAttribute('src');
+ this._onUserValueChange();
+ }
});
},
});
@@ -1725,11 +1731,12 @@ const VideopickerUserValueWidget = MediapickerUserValueWidget.extend({
_onEditMedia(ev) {
// Need a dummy element for the media dialog to modify.
const dummyEl = document.createElement('iframe');
- const dialog = this._openDialog(dummyEl, {videos: true});
- dialog.on('save', this, data => {
- this._value = data.bgVideoSrc;
- this._onUserValueChange();
- });
+ this._openDialog(dummyEl, {
+ videos: true,
+ save: (media) => {
+ this._value = media.querySelector('iframe').src;
+ this._onUserValueChange();
+ }});
},
});
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 e70fe9ec9fb..4cd99e20832 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/widgets.js
@@ -3,7 +3,6 @@ odoo.define('wysiwyg.widgets', function (require) {
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 LinkTools = require('wysiwyg.widgets.LinkTools');
var ImageCropWidget = require('wysiwyg.widgets.ImageCropWidget');
@@ -15,7 +14,6 @@ var media = require('wysiwyg.widgets.media');
return {
Dialog: Dialog,
AltDialog: AltDialog,
- MediaDialog: MediaDialog,
LinkDialog: LinkDialog,
LinkTools: LinkTools,
ImageCropWidget: ImageCropWidget,
diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
index 7475886e40c..ca7e05286ce 100644
--- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
+++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js
@@ -1,6 +1,9 @@
odoo.define('web_editor.wysiwyg', function (require) {
'use strict';
+const { ComponentWrapper } = require('web.OwlCompatibility');
+const { MediaDialogWrapper } = require('@web_editor/components/media_dialog/media_dialog');
+const { saveVideos, videoSpecificClasses } = require('@web_editor/components/media_dialog/video_selector');
const dom = require('web.dom');
const core = require('web.core');
const session = require('web.session');
@@ -37,7 +40,6 @@ const setSelection = OdooEditorLib.setSelection;
const endPos = OdooEditorLib.endPos;
var id = 0;
-const faZoomClassRegex = RegExp('fa-[0-9]x');
const basicMediaSelector = 'img, .fa, .o_image, .media_iframe_video';
// (see isImageSupportedForStyle).
const mediaSelector = basicMediaSelector.split(',').map(s => `${s}:not([data-oe-xpath])`).join(',');
@@ -128,9 +130,13 @@ const Wysiwyg = Widget.extend({
}
const getYoutubeVideoElement = async (url) => {
- const videoWidget = new weWidgets.VideoWidget(this, undefined, {});
- const src = (await videoWidget.createVideoNode(url)).$video.attr('src');
- return videoWidget.getWrappedIframe(src)[0];
+ const { embed_url: src } = await this._rpc({
+ route: '/web_editor/video_url/data',
+ params: { video_url: url },
+ });
+ const [savedVideo] = saveVideos([{src}]);
+ savedVideo.classList.add(...videoSpecificClasses);
+ return savedVideo;
};
this.odooEditor = new OdooEditor(this.$editable[0], Object.assign({
@@ -256,16 +262,9 @@ const Wysiwyg = Widget.extend({
this.$editable.on('dblclick', mediaSelector, function () {
self.showTooltip = false;
const $el = $(this);
- let params = {node: $el};
+ let params = {node: this};
$el.selectElement();
- if ($el.is('.fa')) {
- // save layouting classes from icons to not break the page if you edit an icon
- params.htmlClass = [...$el[0].classList].filter((className) => {
- return !className.startsWith('fa') || faZoomClassRegex.test(className);
- }).join(' ');
- }
-
if (!$el.parent().hasClass('o_stars')) {
self.openMediaDialog(params);
}
@@ -1166,14 +1165,7 @@ const Wysiwyg = Widget.extend({
*/
openMediaDialog(params = {}, MediaDialog = weWidgets.MediaDialog) {
const sel = this.odooEditor.document.getSelection();
- const fontawesomeIcon = getInSelection(this.odooEditor.document, '.fa');
- if (fontawesomeIcon && sel.toString().trim() === "") {
- params.node = $(fontawesomeIcon);
- // save layouting classes from icons to not break the page if you edit an icon
- params.htmlClass = [...fontawesomeIcon.classList].filter((className) => {
- return !className.startsWith('fa') || faZoomClassRegex.test(className);
- }).join(' ');
- }
+
if (!sel.rangeCount) {
return;
}
@@ -1183,36 +1175,29 @@ const Wysiwyg = Widget.extend({
// selection when the modal is closed.
const restoreSelection = preserveCursor(this.odooEditor.document);
- const $node = $(params.node);
- // We need to keep track of FA icon because media.js will _clear those classes
- const wasFontAwesome = $node.hasClass('fa');
const $editable = $(OdooEditorLib.closestElement(range.startContainer, '.o_editable'));
const model = $editable.data('oe-model');
const field = $editable.data('oe-field');
const type = $editable.data('oe-type');
- const mediaParams = Object.assign({
- res_model: model,
- res_id: $editable.data('oe-id'),
+ this.mediaDialogWrapper = new ComponentWrapper(this, MediaDialogWrapper, {
+ resModel: model,
+ resId: $editable.data('oe-id'),
domain: $editable.data('oe-media-domain'),
- useMediaLibrary: field && (model === 'ir.ui.view' && field === 'arch' || type === 'html'),
- }, this.options.mediaModalParams, params);
- const mediaDialog = new MediaDialog(this, mediaParams, $node);
- mediaDialog.open();
-
- mediaDialog.on('save', this, this._onMediaDialogSave.bind(this, {
- $node: $node,
- wasFontAwesome: wasFontAwesome,
- htmlClass: params.htmlClass,
- restoreSelection: restoreSelection,
- }));
- mediaDialog.on('closed', this, function () {
- // if the mediaDialog content has been saved
- // the previous selection in not relevant anymore
- if (mediaDialog.destroyAction !== 'save') {
- restoreSelection();
- }
+ useMediaLibrary: !!(field && (model === 'ir.ui.view' && field === 'arch' || type === 'html')),
+ media: params.node,
+ save: this._onMediaDialogSave.bind(this, {
+ node: params.node,
+ restoreSelection: restoreSelection,
+ }),
+ close: () => restoreSelection(),
+ ...this.options.mediaModalParams,
+ ...params,
});
+
+ // The wysiwyg can be instanciated inside an iframe. The dialog
+ // component is mounted on the global document.
+ return this.mediaDialogWrapper.mount(document.body);
},
/**
* Sets custom CSS Variables on the snippet menu element.
@@ -1243,23 +1228,28 @@ const Wysiwyg = Widget.extend({
* @param {Element} element provided by the dialog
*/
_onMediaDialogSave: function (params, element) {
+ params.restoreSelection();
if (!element) {
return;
}
- // restore saved html classes
- if (params.htmlClass) {
- element.className += " " + params.htmlClass;
- }
- params.restoreSelection();
- if (wysiwygUtils.isImg(params.$node[0]) || params.wasFontAwesome) {
- params.$node.replaceWith(element);
+
+ if (params.node) {
+ params.node.replaceWith(element);
this.odooEditor.unbreakableStepUnactive();
this.odooEditor.historyStep();
- } else if (element) {
+ } else {
const fragment = new DocumentFragment();
fragment.append(element);
return this.odooEditor.execCommand('insertFragment', fragment);
}
+
+ if (this.snippetsMenu) {
+ this.snippetsMenu.activateSnippet($(element)).then(() => {
+ if (element.tagName === 'IMG') {
+ $(element).trigger('image_changed');
+ }
+ });
+ }
},
//--------------------------------------------------------------------------
@@ -2222,7 +2212,6 @@ odoo.define('web_editor.widget', function (require) {
'use strict';
return {
Dialog: require('wysiwyg.widgets.Dialog'),
- MediaDialog: require('wysiwyg.widgets.MediaDialog'),
LinkDialog: require('wysiwyg.widgets.LinkDialog'),
};
});
diff --git a/addons/website/static/src/snippets/s_image_gallery/options.js b/addons/website/static/src/snippets/s_image_gallery/options.js
index 8afb5c1f95a..74f68657a2c 100644
--- a/addons/website/static/src/snippets/s_image_gallery/options.js
+++ b/addons/website/static/src/snippets/s_image_gallery/options.js
@@ -1,8 +1,9 @@
odoo.define('website.s_image_gallery_options', function (require) {
'use strict';
+const { MediaDialogWrapper } = require('@web_editor/components/media_dialog/media_dialog');
+const { ComponentWrapper } = require('web.OwlCompatibility');
var core = require('web.core');
-var weWidgets = require('wysiwyg.widgets');
var options = require('web_editor.snippets.options');
var _t = core._t;
@@ -87,29 +88,36 @@ options.registry.gallery = options.Class.extend({
addImages: function (previewMode) {
const $images = this.$('img');
var $container = this.$('> .container, > .container-fluid, > .o_container_small');
- var dialog = new weWidgets.MediaDialog(this, {multiImages: true, onlyImages: true, mediaWidth: 1920});
- var lastImage = _.last(this._getImages());
- var index = lastImage ? this._getIndex(lastImage) : -1;
- return new Promise(resolve => {
- dialog.on('save', this, function (attachments) {
- for (var i = 0; i < attachments.length; i++) {
- $('
', {
+ const lastImage = _.last(this._getImages());
+ let index = lastImage ? this._getIndex(lastImage) : -1;
+ const dialog = new ComponentWrapper(this, MediaDialogWrapper, {
+ multiImages: true,
+ onlyImages: true,
+ save: images => {
+ let $newImageToSelect;
+ for (const image of images) {
+ const $img = $('
', {
class: $images.length > 0 ? $images[0].className : 'img img-fluid d-block ',
- src: attachments[i].image_src,
+ src: image.src,
'data-index': ++index,
- alt: attachments[i].description || '',
+ alt: image.alt || '',
'data-name': _t('Image'),
style: $images.length > 0 ? $images[0].style.cssText : '',
}).appendTo($container);
+ if (!$newImageToSelect) {
+ $newImageToSelect = $img;
+ }
}
- if (attachments.length > 0) {
+ if (images.length > 0) {
this.mode('reset', this.getMode());
this.trigger_up('cover_update');
+ // Triggers the re-rendering of the thumbnail
+ $newImageToSelect.trigger('image_changed');
+
}
- });
- dialog.on('closed', this, () => resolve());
- dialog.open();
+ },
});
+ dialog.mount(this.el);
},
/**
* Allows to change the number of columns when displaying images with a
diff --git a/addons/website/static/src/snippets/s_rating/options.js b/addons/website/static/src/snippets/s_rating/options.js
index b24d7daf9f6..782664233c8 100644
--- a/addons/website/static/src/snippets/s_rating/options.js
+++ b/addons/website/static/src/snippets/s_rating/options.js
@@ -1,7 +1,9 @@
odoo.define('website.s_rating_options', function (require) {
'use strict';
-const weWidgets = require('wysiwyg.widgets');
+const { ComponentWrapper } = require('web.OwlCompatibility');
+const { MediaDialogWrapper } = require('@web_editor/components/media_dialog/media_dialog');
+
const options = require('web_editor.snippets.options');
options.registry.Rating = options.Class.extend({
@@ -37,16 +39,15 @@ options.registry.Rating = options.Class.extend({
* @see this.selectClass for parameters
*/
customIcon: async function (previewMode, widgetValue, params) {
- return new Promise(resolve => {
- const dialog = new weWidgets.MediaDialog(
- this,
- {noImages: true, noDocuments: true, noVideos: true, mediaWidth: 1920},
- $('')
- );
- this._saving = false;
- dialog.on('save', this, function (attachments) {
- this._saving = true;
- const customClass = 'fa ' + attachments.className;
+ const media = document.createElement('i');
+ media.className = params.customActiveIcon === 'true' ? this.faClassActiveCustomIcons : this.faClassInactiveCustomIcons;
+ const dialog = new ComponentWrapper(this, MediaDialogWrapper, {
+ noImages: true,
+ noDocuments: true,
+ noVideos: true,
+ media,
+ save: icon => {
+ const customClass = icon.className;
const $activeIcons = this.$target.find('.s_rating_active_icons > i');
const $inactiveIcons = this.$target.find('.s_rating_inactive_icons > i');
const $icons = params.customActiveIcon === 'true' ? $activeIcons : $inactiveIcons;
@@ -57,15 +58,9 @@ options.registry.Rating = options.Class.extend({
this.$target[0].dataset.inactiveCustomIcon = this.faClassInactiveCustomIcons;
this.$target[0].dataset.icon = 'custom';
this.iconType = 'custom';
- resolve();
- });
- dialog.on('closed', this, function () {
- if (!this._saving) {
- resolve();
- }
- });
- dialog.open();
+ }
});
+ dialog.mount(this.el);
},
/**
* Sets the number of active icons.