summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
authorSamuel Degueldre <sad@odoo.com>2019-12-09 06:51:17 +0000
committerSamuel Degueldre <sad@odoo.com>2019-12-31 07:58:10 +0000
commitf8632ec6f3485912aed850a4f59934061baa1cc7 (patch)
tree4e96fff2c286bac54ba05a0ff0157868049e7c54 /addons/web_editor/static/src
parent38b5c759f3ade54aaaa620f2b5ab23c11e9cc71e (diff)
[IMP] web_editor: add an image-picker UserValueWidget
This commit introduces a new web_editor UserValueWidget on the we-imagepicker tag, this widget allows the user to pick an image or a video using the MediaDialog, and calls the containing snippet option's method (specified via data-attribute) with the url of the chosen picture or video as widgetValue. Videos are disabled by default and can be accessed through the use of the data-allow-videos="selector" data attribute, any $target that matches the selector will have videos available in the media dialog. Part of odoo/odoo#41561 task-2155808
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js99
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/media.js136
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_snippets.scss11
3 files changed, 173 insertions, 73 deletions
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 9e46a728c51..39e6e0c2f11 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -1069,6 +1069,104 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({
},
});
+const ImagepickerUserValueWidget = UserValueWidget.extend({
+ tagName: 'we-imagepicker',
+ events: {
+ 'click .o_we_edit_image': '_onEditImage',
+ 'click .o_we_remove_image': '_onRemoveImage',
+ },
+
+ /**
+ * @override
+ */
+ start: async function () {
+ await this._super(...arguments);
+ const allowedSelector = this.el.dataset.allowVideos;
+ this.firstFilters = (this.el.dataset.firstFilters || '').split(',').filter(s => s !== '');
+ this.allowVideos = allowedSelector ? this.$target.is(allowedSelector) : false;
+
+ this.editImageButton = document.createElement('we-button');
+ this.editImageButton.classList.add('o_we_edit_image', 'fa', 'fa-fw', 'fa-edit');
+
+ this.removeImageButton = document.createElement('we-button');
+ this.removeImageButton.classList.add('o_we_remove_image', 'fa', 'fa-fw', 'fa-times');
+ this.removeImageButton.title = _t("Remove");
+
+ this.containerEl.appendChild(this.editImageButton);
+ this.containerEl.appendChild(this.removeImageButton);
+ },
+ /**
+ * @override
+ */
+ getMethodsParams: function (methodName) {
+ return _.extend({isVideo: this.isVideo}, this._super(...arguments));
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _updateUI: async function () {
+ await this._super(...arguments);
+ this.removeImageButton.classList.toggle('d-none', !this.isActive());
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when the edit background button is clicked.
+ *
+ * @private
+ */
+ _onEditImage: function (ev) {
+ // Need a dummy element for the media dialog to modify.
+ const dummyEl = document.createElement(this.isVideo ? 'iframe' : 'img');
+ dummyEl.src = this._value;
+ if (this.isVideo) {
+ // Allows the mediaDialog to select the video tab immediately.
+ dummyEl.classList.add('media_iframe_video');
+ }
+ const $editable = this.$target.closest('.o_editable');
+ const mediaDialog = new weWidgets.MediaDialog(this, {
+ noIcons: true,
+ noDocuments: true,
+ noVideos: !this.allowVideos,
+ isForBgVideo: true,
+ res_model: $editable.data('oe-model'),
+ res_id: $editable.data('oe-id'),
+ firstFilters: this.firstFilters,
+ }, dummyEl).open();
+ mediaDialog.on('save', this, data => {
+ if (data.bgVideoSrc) {
+ this._value = data.bgVideoSrc;
+ this.isVideo = true;
+ } else {
+ // 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.isVideo = false;
+ }
+ this._notifyValueChange(false);
+ });
+ },
+ /**
+ * Called when the remove background button is clicked.
+ *
+ * @private
+ */
+ _onRemoveImage: function (ev) {
+ this._value = '';
+ this.isVideo = false;
+ this._notifyValueChange(false);
+ },
+});
+
const DatetimePickerUserValueWidget = InputUserValueWidget.extend({
events: { // Explicitely not consider all InputUserValueWidget events
'blur input': '_onInputBlur',
@@ -1200,6 +1298,7 @@ const userValueWidgetsRegistry = {
'we-multi': MultiUserValueWidget,
'we-colorpicker': ColorpickerUserValueWidget,
'we-datetimepicker': DatetimePickerUserValueWidget,
+ 'we-imagepicker': ImagepickerUserValueWidget,
};
/**
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 5b1f7338683..011989f16e0 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
@@ -1102,82 +1102,19 @@ var VideoWidget = MediaWidget.extend({
*/
_createVideoNode: function (url, options) {
options = options || {};
-
- // Video url patterns(youtube, instagram, vimeo, dailymotion, youku, ...)
- var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((?:\w|-){11})(?:\S+)?$/;
- var ytMatch = url.match(ytRegExp);
-
- var insRegExp = /(.*)instagram.com\/p\/(.[a-zA-Z0-9]*)/;
- var insMatch = url.match(insRegExp);
-
- var vinRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
- var vinMatch = url.match(vinRegExp);
-
- var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
- var vimMatch = url.match(vimRegExp);
-
- var dmRegExp = /.+dailymotion.com\/(video|hub|embed)\/([^_]+)[^#]*(#video=([^_&]+))?/;
- var dmMatch = url.match(dmRegExp);
-
- var ykuRegExp = /(.*).youku\.com\/(v_show\/id_|embed\/)(.+)/;
- var ykuMatch = url.match(ykuRegExp);
-
- var $video = $('<iframe>').width(1280).height(720).attr('frameborder', 0).addClass('o_video_dialog_iframe');
- var videoType = 'yt';
-
- if (!/^(http:\/\/|https:\/\/|\/\/)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(url)){
+ const videoData = this._getVideoURLData(url, options);
+ if (videoData.error) {
return {errorCode: 0};
}
-
- var autoplay = options.autoplay ? '?autoplay=1&mute=1' : '?autoplay=0';
-
- if (ytMatch && ytMatch[2].length === 11) {
- $video.attr('src', '//www.youtube' + (ytMatch[1] || '') + '.com/embed/' + ytMatch[2] + autoplay);
- } else if (insMatch && insMatch[2].length) {
- $video.attr('src', '//www.instagram.com/p/' + insMatch[2] + '/embed/');
- videoType = 'ins';
- } else if (vinMatch && vinMatch[0].length) {
- $video.attr('src', vinMatch[0] + '/embed/simple');
- videoType = 'vin';
- } else if (vimMatch && vimMatch[3].length) {
- $video.attr('src', '//player.vimeo.com/video/' + vimMatch[3] + autoplay.replace('mute', 'muted'));
- videoType = 'vim';
- } else if (dmMatch && dmMatch[2].length) {
- var justId = dmMatch[2].replace('video/', '');
- $video.attr('src', '//www.dailymotion.com/embed/video/' + justId + autoplay);
- videoType = 'dm';
- } else if (ykuMatch && ykuMatch[3].length) {
- var ykuId = ykuMatch[3].indexOf('.html?') >= 0 ? ykuMatch[3].substring(0, ykuMatch[3].indexOf('.html?')) : ykuMatch[3];
- $video.attr('src', '//player.youku.com/embed/' + ykuId);
- videoType = 'yku';
- } else {
+ if (!videoData.type) {
return {errorCode: 1};
}
+ const $video = $('<iframe>').width(1280).height(720)
+ .attr('frameborder', 0)
+ .attr('src', videoData.embedURL)
+ .addClass('o_video_dialog_iframe');
- if (ytMatch) {
- $video.attr('src', $video.attr('src') + '&rel=0');
- }
- if (options.loop && (ytMatch || vimMatch)) {
- var videoSrc = _.str.sprintf('%s&loop=1', $video.attr('src'));
- $video.attr('src', ytMatch ? _.str.sprintf('%s&playlist=%s', videoSrc, ytMatch[2]) : videoSrc);
- }
- if (options.hide_controls && (ytMatch || dmMatch)) {
- $video.attr('src', $video.attr('src') + '&controls=0');
- }
- if (options.hide_fullscreen && ytMatch) {
- $video.attr('src', $video.attr('src') + '&fs=0');
- }
- if (options.hide_yt_logo && ytMatch) {
- $video.attr('src', $video.attr('src') + '&modestbranding=1');
- }
- if (options.hide_dm_logo && dmMatch) {
- $video.attr('src', $video.attr('src') + '&ui-logo=0');
- }
- if (options.hide_dm_share && dmMatch) {
- $video.attr('src', $video.attr('src') + '&sharing-enable=0');
- }
-
- return {$video: $video, type: videoType};
+ return {$video: $video, type: videoData.type};
},
/**
* Updates the video preview according to video code and enabled options.
@@ -1288,6 +1225,63 @@ var VideoWidget = MediaWidget.extend({
_onVideoCodeInput: function () {
this._updateVideo();
},
+ /**
+ * Parses a URL and returns the provider type and an emebedable URL.
+ *
+ * @private
+ */
+ _getVideoURLData: function (url, options) {
+ if (!url.match(/^(http:\/\/|https:\/\/|\/\/)[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i)) {
+ return {
+ error: true,
+ message: 'The provided url is invalid',
+ };
+ }
+ const regexes = {
+ youtube: /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((?:\w|-){11})(?:\S+)?$/,
+ instagram: /(.*)instagram.com\/p\/(.[a-zA-Z0-9]*)/,
+ vine: /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/,
+ vimeo: /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/,
+ dailymotion: /.+dailymotion.com\/(video|hub|embed)\/([^_]+)[^#]*(#video=([^_&]+))?/,
+ youku: /(.*).youku\.com\/(v_show\/id_|embed\/)(.+)/,
+ };
+ const matches = _.mapObject(regexes, regex => url.match(regex));
+ const autoplay = options.autoplay ? '?autoplay=1&mute=1' : '?autoplay=0';
+ const controls = options.hide_controls ? '&controls=0' : '';
+ const loop = options.loop ? '&loop=1' : '';
+
+ let embedURL;
+ let type;
+ if (matches.youtube && matches.youtube[2].length === 11) {
+ const fullscreen = options.hide_fullscreen ? '&fs=0' : '';
+ const ytLoop = loop ? loop + `&playlist=${matches.youtube[2]}` : '';
+ const logo = options.hide_yt_logo ? '&modestbranding=1' : '';
+ embedURL = `//www.youtube${matches.youtube[1] || ''}.com/embed/${matches.youtube[2]}${autoplay}&rel=0${ytLoop}${controls}${fullscreen}${logo}`;
+ type = 'youtube';
+ } else if (matches.instagram && matches.instagram[2].length) {
+ embedURL = `//www.instagram.com/p/${matches.instagram[2]}/embed/`;
+ type = 'instagram';
+ } else if (matches.vine && matches.vine[0].length) {
+ embedURL = `${matches.vine[0]}/embed/simple`;
+ type = 'vine';
+ } else if (matches.vimeo && matches.vimeo[3].length) {
+ const vimeoAutoplay = autoplay.replace('mute', 'muted');
+ embedURL = `//player.vimeo.com/video/${matches.vimeo[3]}${vimeoAutoplay}${loop}`;
+ type = 'vimeo';
+ } else if (matches.dailymotion && matches.dailymotion[2].length) {
+ const videoId = matches.dailymotion[2].replace('video/', '');
+ const logo = options.hide_dm_logo ? '&ui-logo=0' : '';
+ const share = options.hide_dm_share ? '&sharing-enable=0' : '';
+ embedURL = `//www.dailymotion.com/embed/video/${videoId}${autoplay}${controls}${logo}${share}`;
+ type = 'dailymotion';
+ } else if (matches.youku && matches.youku[3].length) {
+ const videoId = matches.youku[3].indexOf('.html?') >= 0 ? matches.youku[3].substring(0, matches.youku[3].indexOf('.html?')) : matches.youku[3];
+ embedURL = `//player.youku.com/embed/${videoId}`;
+ type = 'youku';
+ }
+
+ return {type: type, embedURL: embedURL};
+ },
});
return {
diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
index a5c1ed6195d..275166aac22 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -301,12 +301,19 @@ body.editor_enable.editor_has_snippets {
> div {
display: flex;
align-items: baseline;
+ justify-content: flex-end;
}
}
// Buttons
- we-button.o_we_user_value_widget.active {
- @include we-icon-button('\f00c', $o-we-color-success);
+ we-button.o_we_user_value_widget {
+ &.active {
+ @include we-icon-button('\f00c', $o-we-color-success);
+ }
+
+ > div {
+ justify-content: flex-start;
+ }
}
// Checkboxes