summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src/js/widgets/widgets.js
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2017-03-28 11:38:26 +0200
committerqsm-odoo <qsm@odoo.com>2017-08-17 12:41:15 +0200
commit6c9a56a65fda9066dee9efde49b7ade6aff1ca46 (patch)
treec0a216ba4b7e3ccfdc08c891fb3ee76d6c69e09f /addons/web_editor/static/src/js/widgets/widgets.js
parent56972f8b0549804513dc7acaceacccbdc9bce1aa (diff)
[REF] web_editor: improve "Video" dialog design and behavior
Original design by @stefanorigano.
Diffstat (limited to 'addons/web_editor/static/src/js/widgets/widgets.js')
-rw-r--r--addons/web_editor/static/src/js/widgets/widgets.js393
1 files changed, 254 insertions, 139 deletions
diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js
index 20b67828fad..5340efb821e 100644
--- a/addons/web_editor/static/src/js/widgets/widgets.js
+++ b/addons/web_editor/static/src/js/widgets/widgets.js
@@ -621,164 +621,80 @@ var fontIconsDialog = Widget.extend({
},
});
-
-function createVideoNode(url, options) {
- options = options || {};
-
- // video url patterns(youtube, instagram, vimeo, dailymotion, youku)
- var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((\w|-){11})(?:\S+)?$/;
- var ytMatch = url.match(ytRegExp);
-
- var igRegExp = /\/\/instagram.com\/p\/(.[a-zA-Z0-9]*)/;
- var igMatch = url.match(igRegExp);
-
- var vRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/;
- var vMatch = url.match(vRegExp);
-
- var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/;
- var vimMatch = url.match(vimRegExp);
-
- var dmRegExp = /.+dailymotion.com\/(video|hub)\/([^_]+)[^#]*(#video=([^_&]+))?/;
- var dmMatch = url.match(dmRegExp);
-
- var youkuRegExp = /\/\/v\.youku\.com\/v_show\/id_(\w+)\.html/;
- var youkuMatch = url.match(youkuRegExp);
-
- var $video = $('<iframe>');
- if (ytMatch && ytMatch[1].length === 11) {
- var youtubeId = ytMatch[1];
- $video = $('<iframe>', {
- src: '//www.youtube.com/embed/' + youtubeId,
- width: '640',
- height: '360'
- });
- } else if (igMatch && igMatch[0].length) {
- $video = $('<iframe>', {
- src: igMatch[0] + '/embed/',
- width: '612',
- height: '710',
- scrolling: 'no',
- allowtransparency: 'true'
- });
- } else if (vMatch && vMatch[0].length) {
- $video = $('<iframe>', {
- src: vMatch[0] + '/embed/simple',
- width: '600',
- height: '600',
- class: 'vine-embed'
- });
- } else if (vimMatch && vimMatch[3].length) {
- $video = $('<iframe>', {
- src: '//player.vimeo.com/video/' + vimMatch[3],
- width: '640',
- height: '360'
- });
- } else if (dmMatch && dmMatch[2].length) {
- $video = $('<iframe>', {
- src: '//www.dailymotion.com/embed/video/' + dmMatch[2],
- width: '640',
- height: '360'
- });
- } else if (youkuMatch && youkuMatch[1].length) {
- $video = $('<iframe>', {
- height: '498',
- width: '510',
- src: '//player.youku.com/embed/' + youkuMatch[1]
- });
- } else {
- // this is not a known video link. Now what, Cat? Now what?
- $video = $('<iframe>', {
- width: '640',
- height: '360',
- src: url
- });
- }
-
- if (options.autoplay) {
- $video.attr("src", $video.attr("src") + "?autoplay=1");
- }
-
- $video.attr('frameborder', 0);
-
- return $video;
-}
-
/**
- * VideoDialog widget. Lets users change a video, support all summernote
- * video, and embled iframe
+ * VideoDialog widget. Let users change a video, support all summernote
+ * video, and embed iframe.
*/
var VideoDialog = Widget.extend({
template: 'web_editor.dialog.video',
xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
- events : _.extend({}, Dialog.prototype.events, {
- 'click input#urlvideo ~ button': 'get_video',
- 'click input#embedvideo ~ button': 'get_embed_video',
- 'change input#autoplay': 'get_video',
- 'change input#urlvideo': 'change_input',
- 'keyup input#urlvideo': 'change_input',
- 'change input#embedvideo': 'change_input',
- 'keyup input#embedvideo': 'change_input',
- 'keydown.dismiss.bs.modal': function () {},
- }),
+ events : {
+ 'change .o_video_dialog_options input': '_onUpdateVideoOption',
+ 'input textarea#o_video_text': '_onVideoCodeInput',
+ 'change textarea#o_video_text': '_onVideoCodeChange',
+ },
+
+ /**
+ * @constructor
+ */
init: function (parent, media) {
this._super.apply(this, arguments);
this.parent = parent;
this.media = media;
+ this._onVideoCodeInput = _.debounce(this._onVideoCodeInput, 1000);
},
+ /**
+ * @override
+ */
start: function () {
this.$preview = this.$('.preview-container').detach();
- this.$iframe = this.$("iframe");
+ this.$content = this.$('.o_video_dialog_iframe');
+
var $media = $(this.media);
- if ($media.hasClass("media_iframe_video")) {
- var src = $media.data('src');
- this.$("input#urlvideo").val(src);
- this.$("input#autoplay").prop("checked", (src || "").indexOf("autoplay") >= 0);
- this.get_video();
+ if ($media.hasClass('media_iframe_video')) {
+ var src = $media.data('src') || '';
+ this.$('textarea#o_video_text').val(src);
+
+ this.$('input#o_video_autoplay').prop('checked', src.indexOf('autoplay=1') >= 0);
+ this.$('input#o_video_hide_controls').prop('checked', src.indexOf('controls=0') >= 0);
+ this.$('input#o_video_loop').prop('checked', src.indexOf('loop=1') >= 0);
+ this.$('input#o_video_hide_fullscreen').prop('checked', src.indexOf('fs=0') >= 0);
+ this.$('input#o_video_hide_yt_logo').prop('checked', src.indexOf('modestbranding=1') >= 0);
+ this.$('input#o_video_hide_dm_logo').prop('checked', src.indexOf('ui-logo=0') >= 0);
+ this.$('input#o_video_hide_dm_share').prop('checked', src.indexOf('sharing-enable=0') >= 0);
+
+ this._updateVideo();
}
+
return this._super.apply(this, arguments);
},
- change_input: function (e) {
- var $input = $(e.target);
- var $button = $input.parent().find("button");
- if ($input.val() === "") {
- $button.addClass("btn-default").removeClass("btn-primary");
- } else {
- $button.removeClass("btn-default").addClass("btn-primary");
- }
- },
- get_embed_video: function (event) {
- event.preventDefault();
- var embedvideo = this.$("input#embedvideo").val().match(/src=["']?([^"']+)["' ]?/);
- if (embedvideo) {
- this.$("input#urlvideo").val(embedvideo[1]);
- this.get_video(event);
- }
- return false;
- },
- get_video: function (event) {
- if (event) event.preventDefault();
- var $video = createVideoNode(this.$("input#urlvideo").val(), {autoplay: this.$("input#autoplay").is(":checked")});
- this.$iframe.replaceWith($video);
- this.$iframe = $video;
- return false;
- },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
save: function () {
- var video_id = this.$("#video_id").val();
- if (!video_id) {
- this.$("button.btn-primary").click();
- video_id = this.$("#video_id").val();
+ this._updateVideo();
+
+ if (this.$('.o_video_dialog_iframe').is('iframe')) {
+ var $content = $(
+ '<div class="media_iframe_video" data-src="' + this.$content.attr('src') + '">'+
+ '<div class="css_editable_mode_display">&nbsp;</div>'+
+ '<div class="media_iframe_video_size" contenteditable="false">&nbsp;</div>'+
+ '<iframe src="' + this.$content.attr('src') + '" frameborder="0" contenteditable="false"></iframe>'+
+ '</div>'
+ );
+ $(this.media).replaceWith($content);
+ this.media = $content[0];
+ return this.media;
}
- var $iframe = $(
- '<div class="media_iframe_video" data-src="'+this.$iframe.attr("src")+'">'+
- '<div class="css_editable_mode_display">&nbsp;</div>'+
- '<div class="media_iframe_video_size" contentEditable="false">&nbsp;</div>'+
- '<iframe src="'+this.$iframe.attr("src")+'" frameborder="0" allowfullscreen="allowfullscreen" contentEditable="false"></iframe>'+
- '</div>');
- $(this.media).replaceWith($iframe);
- this.media = $iframe[0];
-
- return this.media;
},
+ /**
+ * @override
+ */
clear: function () {
if (this.media.dataset.src) {
try {
@@ -789,6 +705,205 @@ var VideoDialog = Widget.extend({
}
this.media.className = this.media.className.replace(/(^|\s)media_iframe_video(\s|$)/g, ' ');
},
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Creates a video node according to the given URL and options. If not
+ * possible, returns an error code.
+ *
+ * @private
+ * @param {string} url
+ * @param {Object} options
+ * @returns {Object}
+ * $video -> the created video jQuery node
+ * type -> the type of the created video
+ * errorCode -> if defined, either '0' for invalid URL or '1' for
+ * unsupported video provider
+ */
+ _createVideoNode: function (url, options) {
+ options = options || {};
+
+ // Video url patterns(youtube, instagram, vimeo, dailymotion, youku, ...)
+ var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube\.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)){
+ return {errorCode: 0};
+ }
+
+ var autoplay = options.autoplay ? '?autoplay=1' : '?autoplay=0';
+
+ if (ytMatch && ytMatch[1].length === 11) {
+ $video.attr('src', '//www.youtube.com/embed/' + ytMatch[1] + 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);
+ videoType = 'vim';
+ } else if (dmMatch && dmMatch[2].length) {
+ var just_id = dmMatch[2].replace('video/','');
+ $video.attr('src', '//www.dailymotion.com/embed/video/' + just_id + autoplay);
+ videoType = 'dm';
+ } else if (ykuMatch && ykuMatch[3].length) {
+ var yku_id = ykuMatch[3].indexOf('.html?') >= 0 ? ykuMatch[3].substring(0, ykuMatch[3].indexOf('.html?')) : ykuMatch[3];
+ $video.attr('src', '//player.youku.com/embed/' + yku_id);
+ videoType = 'yku';
+ } else {
+ return {errorCode: 1};
+ }
+
+ if (options.loop && (ytMatch || vimMatch)) {
+ $video.attr('src', $video.attr('src') + '&loop=1');
+ }
+ 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};
+ },
+ /**
+ * Updates the video preview according to video code and enabled options.
+ *
+ * @private
+ */
+ _updateVideo: function () {
+ // Reset the feedback
+ this.$content.empty();
+ this.$('#o_video_form_group').removeClass('has-error has-success');
+ this.$('.o_video_dialog_options li').addClass('hidden');
+
+ // Check video code
+ var $textarea = this.$('textarea#o_video_text');
+ var code = $textarea.val().trim();
+ if (!code) {
+ return;
+ }
+
+ // Detect if we have an embed code rather than an URL
+ var embedMatch = code.match(/(src|href)=["']?([^"']+)?/);
+ if (embedMatch && embedMatch[2].length > 0 && embedMatch[2].indexOf('instagram')){
+ embedMatch[1] = embedMatch[2]; // Instagram embed code is different
+ }
+ var url = embedMatch ? embedMatch[1] : code;
+
+ var query = this._createVideoNode(url, {
+ autoplay: this.$('input#o_video_autoplay').is(':checked'),
+ hide_controls: this.$('input#o_video_hide_controls').is(':checked'),
+ loop: this.$('input#o_video_loop').is(':checked'),
+ hide_fullscreen: this.$('input#o_video_hide_fullscreen').is(':checked'),
+ hide_yt_logo: this.$('input#o_video_hide_yt_logo').is(':checked'),
+ hide_dm_logo: this.$('input#o_video_hide_dm_logo').is(':checked'),
+ hide_dm_share: this.$('input#o_video_hide_dm_share').is(':checked'),
+ });
+
+ var $opt_box = this.$('.o_video_dialog_options');
+
+ // Show / Hide preview elements
+ this.$el.find('.o_video_dialog_preview_text, .media_iframe_video_size').add($opt_box).toggleClass('hidden', !query.$video);
+ // Toggle validation classes
+ this.$el.find('#o_video_form_group').toggleClass('has-error', !query.$video).toggleClass('has-success', !!query.$video);
+
+ // Individually show / hide options base on the video provider
+ $opt_box.find('li.o_' + query.type + '_option').removeClass('hidden');
+
+ // Hide the entire options box if no options are available
+ $opt_box.toggleClass('hidden', $opt_box.find('li:not(.hidden)').length === 0);
+
+ if (query.type === 'yt') {
+ // Youtube only: If 'hide controls' is checked, hide 'fullscreen'
+ // and 'youtube logo' options too
+ this.$('input#o_video_hide_fullscreen, input#o_video_hide_yt_logo').closest('li').toggleClass('hidden', this.$('input#o_video_hide_controls').is(':checked'));
+ }
+
+ var $content = query.$video;
+ if (!$content) {
+ switch (query.errorCode) {
+ case 0:
+ $content = $('<div/>', {
+ class: 'alert alert-danger o_video_dialog_iframe',
+ text: _t("The provided url is not valid"),
+ });
+ break;
+ case 1:
+ $content = $('<div/>', {
+ class: 'alert alert-warning o_video_dialog_iframe',
+ text: _t("The provided url does not reference any supported video"),
+ });
+ break;
+ }
+ }
+ this.$content.replaceWith($content);
+ this.$content = $content;
+ },
+
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when a video option changes -> Updates the video preview.
+ *
+ * @private
+ */
+ _onUpdateVideoOption: function () {
+ this._updateVideo();
+ },
+ /**
+ * Called when the video code (URL / Iframe) change is confirmed -> Updates
+ * the video preview immediately.
+ *
+ * @private
+ */
+ _onVideoCodeChange: function () {
+ this._updateVideo();
+ },
+ /**
+ * Called when the video code (URL / Iframe) changes -> Updates the video
+ * preview (note: this function is automatically debounced).
+ *
+ * @private
+ */
+ _onVideoCodeInput: function () {
+ this._updateVideo();
+ },
});
/**