[IMP] web_editor, *: improve media dialog
* web_unsplash, website_sale - Visual improvements and cleaning in each tab: video, documents, pictogram, images. - Only one search input for both the local library and unsplash. - Replace the idea of multiple pages by adding a 'Load More' button. task-1896705
This commit is contained in:
@@ -4,7 +4,7 @@ odoo.define('web_editor.widget', function (require) {
|
||||
var ajax = require('web.ajax');
|
||||
var base = require('web_editor.base');
|
||||
var core = require('web.core');
|
||||
var Dialog = require('web.Dialog');
|
||||
var DialogBase = require('web.Dialog');
|
||||
var Widget = require('web.Widget');
|
||||
var weContext = require("web_editor.context");
|
||||
|
||||
@@ -26,7 +26,7 @@ function simulateMouseEvent(el, type) {
|
||||
/**
|
||||
* Extend Dialog class to handle save/cancel of edition components.
|
||||
*/
|
||||
Dialog = Dialog.extend({
|
||||
var Dialog = DialogBase.extend({
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
@@ -109,6 +109,9 @@ var AltDialog = Dialog.extend({
|
||||
|
||||
var MediaWidget = Widget.extend({
|
||||
xmlDependencies: ['/web_editor/static/src/xml/editor.xml'],
|
||||
events: {
|
||||
'input input#icon-search': '_onSearchInput',
|
||||
},
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
@@ -117,7 +120,7 @@ var MediaWidget = Widget.extend({
|
||||
this._super.apply(this, arguments);
|
||||
this.media = media;
|
||||
this.$media = $(media);
|
||||
this.page = 0;
|
||||
this._onSearchInput = _.debounce(this._onSearchInput, 250);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -144,12 +147,6 @@ var MediaWidget = Widget.extend({
|
||||
pagerRightEnabled: false,
|
||||
};
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goToPage: function (page) {
|
||||
this.page = page;
|
||||
},
|
||||
/**
|
||||
* @abstract
|
||||
* @param {string} needle
|
||||
@@ -178,6 +175,17 @@ var MediaWidget = Widget.extend({
|
||||
this.$media = $media;
|
||||
this.media = $media[0];
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSearchInput: function (ev) {
|
||||
this.search($(ev.currentTarget).val() || '');
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -185,19 +193,20 @@ var MediaWidget = Widget.extend({
|
||||
*/
|
||||
var ImageWidget = MediaWidget.extend({
|
||||
template: 'web_editor.dialog.image',
|
||||
events: {
|
||||
'click .o_upload_image_button': '_onUploadButtonClick',
|
||||
'click .o_upload_image_button_no_optimization': '_onUploadButtonNoOptimizationClick',
|
||||
events: _.extend({}, MediaWidget.prototype.events || {}, {
|
||||
'click .o_upload_media_button': '_onUploadButtonClick',
|
||||
'click .o_upload_media_button_no_optimization': '_onUploadButtonNoOptimizationClick',
|
||||
'change input[type=file]': '_onImageSelection',
|
||||
'click .o_upload_image_url_button': '_onUploadURLButtonClick',
|
||||
'input input.url': "_onSearchInput",
|
||||
'click .o_upload_media_url_button': '_onUploadURLButtonClick',
|
||||
'input input.url': '_onChangeInputURL',
|
||||
'click .existing-attachments [data-src]': '_onImageClick',
|
||||
'dblclick .existing-attachments [data-src]': '_onImageDblClick',
|
||||
'click .o_existing_attachment_remove': '_onRemoveClick',
|
||||
},
|
||||
'click .o_load_more': '_onLoadMore',
|
||||
}),
|
||||
|
||||
IMAGES_PER_ROW: 6,
|
||||
IMAGES_ROWS: 2,
|
||||
IMAGES_ROWS: 5,
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
@@ -205,7 +214,7 @@ var ImageWidget = MediaWidget.extend({
|
||||
init: function (parent, media, options) {
|
||||
this._super.apply(this, arguments);
|
||||
|
||||
this.IMAGES_PER_PAGE = this.IMAGES_PER_ROW * this.IMAGES_ROWS;
|
||||
this.IMAGES_DISPLAYED_TOTAL = this.IMAGES_PER_ROW * this.IMAGES_ROWS;
|
||||
|
||||
this.options = options;
|
||||
this.accept = options.accept || (options.document ? '*/*' : 'image/*');
|
||||
@@ -265,22 +274,6 @@ var ImageWidget = MediaWidget.extend({
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
getControlPanelConfig: function () {
|
||||
return _.extend(this._super.apply(this, arguments), {
|
||||
pagerLeftEnabled: this.page > 0,
|
||||
pagerRightEnabled: (this.page + 1) * this.IMAGES_PER_PAGE < this.records.length,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
goToPage: function (page) {
|
||||
this._super.apply(this, arguments);
|
||||
this._renderImages();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -444,16 +437,28 @@ var ImageWidget = MediaWidget.extend({
|
||||
*/
|
||||
_renderImages: function () {
|
||||
var self = this;
|
||||
var startIndex = this.page * this.IMAGES_PER_PAGE;
|
||||
var rows = _(this.records).chain()
|
||||
.slice(startIndex, startIndex + this.IMAGES_PER_PAGE)
|
||||
.slice(0, this.IMAGES_DISPLAYED_TOTAL)
|
||||
.groupBy(function (a, index) { return Math.floor(index / self.IMAGES_PER_ROW); })
|
||||
.values()
|
||||
.value();
|
||||
var noMoreImgToLoad = this.IMAGES_DISPLAYED_TOTAL >= this.records.length;
|
||||
|
||||
this.$('.o_load_more').toggleClass('d-none', noMoreImgToLoad);
|
||||
this.$('.o_load_done_msg').toggleClass('d-none', !noMoreImgToLoad);
|
||||
this.$('.form-text').empty();
|
||||
|
||||
this.$('.existing-attachments').replaceWith(QWeb.render('web_editor.dialog.image.existing.content', {rows: rows}));
|
||||
//Rendering menu & content for 'Image' or 'Document' tab
|
||||
var templateMenu = 'web_editor.dialog.files.submenu';
|
||||
var templateContent = 'web_editor.dialog.files.existing.content';
|
||||
this.$('.o_submenu').replaceWith(QWeb.render(templateMenu, {
|
||||
accept: this.accept,
|
||||
isDocument: this.options.document,
|
||||
}));
|
||||
this.$('.existing-attachments').replaceWith(QWeb.render(templateContent, {
|
||||
rows: rows,
|
||||
isDocument: this.options.document,
|
||||
}));
|
||||
|
||||
var $divs = this.$('.o_image');
|
||||
var imageDefs = _.map($divs, function (el) {
|
||||
@@ -479,12 +484,12 @@ var ImageWidget = MediaWidget.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_toggleImage: function (attachment, clearSearch, force_select) {
|
||||
_toggleImage: function (attachment, clearSearch, forceSelect) {
|
||||
if (this.multiImages) {
|
||||
var img = _.select(this.images, function (v) { return v.id === attachment.id; });
|
||||
if (img.length) {
|
||||
if (!force_select) {
|
||||
this.images.splice(this.images.indexOf(img[0]),1);
|
||||
if (!forceSelect) {
|
||||
this.images.splice(this.images.indexOf(img[0]), 1);
|
||||
}
|
||||
} else {
|
||||
this.images.push(attachment);
|
||||
@@ -501,14 +506,14 @@ var ImageWidget = MediaWidget.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_uploadImage: function () {
|
||||
_uploadFile: function () {
|
||||
var self = this;
|
||||
|
||||
/**
|
||||
* @todo file upload cannot be handled with _rpc smoothly. This uses the
|
||||
* form posting in iframe trick to handle the upload.
|
||||
*/
|
||||
var $iframe = this.$('iframe');
|
||||
var $iframe = this.$('.o_file_upload_iframe');
|
||||
$iframe.on('load', function () {
|
||||
var iWindow = $iframe[0].contentWindow;
|
||||
var attachments = iWindow.attachments;
|
||||
@@ -524,7 +529,7 @@ var ImageWidget = MediaWidget.extend({
|
||||
_processFile(null, error || !attachments.length);
|
||||
}
|
||||
self.images = attachments;
|
||||
for (var i = 0 ; i < attachments.length ; i++) {
|
||||
for (var i = 0; i < attachments.length; i++) {
|
||||
_processFile(attachments[i], error);
|
||||
}
|
||||
|
||||
@@ -533,22 +538,25 @@ var ImageWidget = MediaWidget.extend({
|
||||
}
|
||||
|
||||
function _processFile(attachment, error) {
|
||||
var $button = self.$('.o_upload_image_button');
|
||||
if (!error) {
|
||||
$button.addClass('btn-success');
|
||||
self._toggleImage(attachment, true);
|
||||
} else {
|
||||
$button.addClass('btn-danger');
|
||||
self.$el.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid');
|
||||
self.$el.find('.form-text').text(error);
|
||||
}
|
||||
|
||||
if (!self.multiImages) {
|
||||
self.trigger_up('save_request');
|
||||
}
|
||||
}
|
||||
});
|
||||
this.$el.submit();
|
||||
// Emptying file inputs
|
||||
this.$('.o_file_input').val('');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_loadMoreImages: function () {
|
||||
this.IMAGES_ROWS += 2;
|
||||
this.IMAGES_DISPLAYED_TOTAL = this.IMAGES_ROWS * this.IMAGES_PER_ROW;
|
||||
this._renderImages();
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -558,12 +566,12 @@ var ImageWidget = MediaWidget.extend({
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onImageClick: function (ev, force_select) {
|
||||
_onImageClick: function (ev, forceSelect) {
|
||||
var $img = $(ev.currentTarget);
|
||||
var attachment = _.find(this.records, function (record) {
|
||||
return record.id === $img.data('id');
|
||||
});
|
||||
this._toggleImage(attachment, false, force_select);
|
||||
this._toggleImage(attachment, false, forceSelect);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -580,44 +588,70 @@ var ImageWidget = MediaWidget.extend({
|
||||
this.$el.addClass('nosave');
|
||||
$form.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
|
||||
$form.find('.form-text').empty();
|
||||
this.$('.o_upload_image_button').removeClass('btn-danger btn-success');
|
||||
this._uploadImage();
|
||||
this.$('.o_upload_media_button').removeClass('btn-danger btn-success');
|
||||
this._uploadFile();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onRemoveClick: function (ev) {
|
||||
var self = this;
|
||||
var $helpBlock = this.$('.form-text').empty();
|
||||
var $a = $(ev.currentTarget);
|
||||
var id = parseInt($a.data('id'), 10);
|
||||
var attachment = _.findWhere(this.records, {id: id});
|
||||
var message = _t("Are you sure you want to delete this file ?");
|
||||
DialogBase.confirm(this, message, {
|
||||
confirm_callback: function () {
|
||||
var $helpBlock = this.$('.form-text').empty();
|
||||
var $a = $(ev.currentTarget);
|
||||
var id = parseInt($a.data('id'), 10);
|
||||
var attachment = _.findWhere(this.records, {id: id});
|
||||
|
||||
return this._rpc({
|
||||
route: '/web_editor/attachment/remove',
|
||||
params: {
|
||||
ids: [id],
|
||||
},
|
||||
}).then(function (prevented) {
|
||||
if (_.isEmpty(prevented)) {
|
||||
self.records = _.without(self.records, attachment);
|
||||
self._renderImages();
|
||||
return;
|
||||
return self._rpc({
|
||||
route: '/web_editor/attachment/remove',
|
||||
params: {
|
||||
ids: [id],
|
||||
},
|
||||
}).then(function (prevented) {
|
||||
if (_.isEmpty(prevented)) {
|
||||
self.records = _.without(self.records, attachment);
|
||||
self.search('');
|
||||
return;
|
||||
}
|
||||
$helpBlock.replaceWith(QWeb.render('web_editor.dialog.image.existing.error', {
|
||||
views: prevented[id]
|
||||
}));
|
||||
});
|
||||
}
|
||||
$helpBlock.replaceWith(QWeb.render('web_editor.dialog.image.existing.error', {
|
||||
views: prevented[id]
|
||||
}));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSearchInput: function (ev) {
|
||||
_onChangeInputURL: function (ev) {
|
||||
var $input = $(ev.currentTarget);
|
||||
var $button = $input.next('.input-group-append').children();
|
||||
var emptyValue = ($input.val() === '');
|
||||
var inputValue = $input.val();
|
||||
var emptyValue = (inputValue === '');
|
||||
var $warning = this.$('.warning-url');
|
||||
var $success = this.$('.success-url');
|
||||
var $error = this.$('.error-url');
|
||||
var $btn = this.$('.o_upload_media_url_button');
|
||||
var regex = /^(?:https?:\/\/)?[\w.-]+(?:\.[\w\.-]+)+[\w\-\._~:/?#[\]@!\$&'\(\)\*\+,;=.]+$/;
|
||||
var isURL = regex.test(inputValue);
|
||||
var showWarningOrError = (inputValue.length !== 0 ? isURL : !isURL);
|
||||
|
||||
$button.toggleClass('btn-secondary', emptyValue).toggleClass('btn-primary', !emptyValue)
|
||||
.prop('disabled', emptyValue);
|
||||
|
||||
if (!this.options.document) {
|
||||
_.each(['.gif', '.jpe', '.jpg', '.png'], function (format) {
|
||||
var endsWith = inputValue.endsWith(format);
|
||||
$button.text(endsWith ? _t("Add image") : _t("Add as document"));
|
||||
});
|
||||
} else {
|
||||
$btn.prop('disabled', !isURL);
|
||||
}
|
||||
$success.toggleClass('d-none', !isURL);
|
||||
$warning.toggleClass('d-none', showWarningOrError);
|
||||
$error.toggleClass('d-none', showWarningOrError);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
@@ -630,13 +664,19 @@ var ImageWidget = MediaWidget.extend({
|
||||
*/
|
||||
_onUploadButtonNoOptimizationClick: function () {
|
||||
this.$('input[name="disable_optimization"]').val('1');
|
||||
this.$('.o_upload_image_button').click();
|
||||
this.$('.o_upload_media_button').click();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onUploadURLButtonClick: function () {
|
||||
this._uploadImage();
|
||||
this._uploadFile();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onLoadMore: function () {
|
||||
this._loadMoreImages();
|
||||
},
|
||||
});
|
||||
|
||||
@@ -646,10 +686,10 @@ var ImageWidget = MediaWidget.extend({
|
||||
*/
|
||||
var IconWidget = MediaWidget.extend({
|
||||
template: 'web_editor.dialog.font-icons',
|
||||
events : {
|
||||
events: _.extend({}, MediaWidget.prototype.events || {}, {
|
||||
'click .font-icons-icon': '_onIconClick',
|
||||
'dblclick .font-icons-icon': '_onIconDblClick',
|
||||
},
|
||||
}),
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
@@ -669,7 +709,7 @@ var IconWidget = MediaWidget.extend({
|
||||
start: function () {
|
||||
this.$icons = this.$('.font-icons-icon');
|
||||
var classes = (this.media && this.media.className || '').split(/\s+/);
|
||||
for (var i = 0 ; i < classes.length ; i++) {
|
||||
for (var i = 0; i < classes.length; i++) {
|
||||
var cls = classes[i];
|
||||
if (_.contains(this.alias, cls)) {
|
||||
this.selectedIcon = cls;
|
||||
@@ -753,12 +793,12 @@ var IconWidget = MediaWidget.extend({
|
||||
*/
|
||||
_getFont: function (classNames) {
|
||||
if (!(classNames instanceof Array)) {
|
||||
classNames = (classNames||"").split(/\s+/);
|
||||
classNames = (classNames || "").split(/\s+/);
|
||||
}
|
||||
var fontIcon, cssData;
|
||||
for (var k=0; k<this.iconsParser.length; k++) {
|
||||
for (var k = 0; k < this.iconsParser.length; k++) {
|
||||
fontIcon = this.iconsParser[k];
|
||||
for (var s=0; s<fontIcon.cssData.length; s++) {
|
||||
for (var s = 0; s < fontIcon.cssData.length; s++) {
|
||||
cssData = fontIcon.cssData[s];
|
||||
if (_.intersection(classNames, cssData.names).length) {
|
||||
return {
|
||||
@@ -809,7 +849,7 @@ var IconWidget = MediaWidget.extend({
|
||||
*/
|
||||
var VideoWidget = MediaWidget.extend({
|
||||
template: 'web_editor.dialog.video',
|
||||
events : {
|
||||
events: {
|
||||
'change .o_video_dialog_options input': '_onUpdateVideoOption',
|
||||
'input textarea#o_video_text': '_onVideoCodeInput',
|
||||
'change textarea#o_video_text': '_onVideoCodeChange',
|
||||
@@ -867,10 +907,10 @@ var VideoWidget = MediaWidget.extend({
|
||||
this._updateVideo();
|
||||
if (this.$('.o_video_dialog_iframe').is('iframe')) {
|
||||
this._replaceMedia($(
|
||||
'<div class="media_iframe_video" data-oe-expression="' + this.$content.attr('src') + '">'+
|
||||
'<div class="css_editable_mode_display"> </div>'+
|
||||
'<div class="media_iframe_video_size" contenteditable="false"> </div>'+
|
||||
'<iframe src="' + this.$content.attr('src') + '" frameborder="0" contenteditable="false"></iframe>'+
|
||||
'<div class="media_iframe_video" data-oe-expression="' + this.$content.attr('src') + '">' +
|
||||
'<div class="css_editable_mode_display"> </div>' +
|
||||
'<div class="media_iframe_video_size" contenteditable="false"> </div>' +
|
||||
'<iframe src="' + this.$content.attr('src') + '" frameborder="0" contenteditable="false"></iframe>' +
|
||||
'</div>'
|
||||
));
|
||||
}
|
||||
@@ -950,12 +990,12 @@ var VideoWidget = MediaWidget.extend({
|
||||
$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);
|
||||
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 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);
|
||||
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 {
|
||||
return {errorCode: 1};
|
||||
@@ -994,7 +1034,7 @@ var VideoWidget = MediaWidget.extend({
|
||||
// Reset the feedback
|
||||
this.$content.empty();
|
||||
this.$('#o_video_form_group').removeClass('o_has_error o_has_success').find('.form-control, .custom-select').removeClass('is-invalid is-valid');
|
||||
this.$('.o_video_dialog_options li').addClass('d-none');
|
||||
this.$('.o_video_dialog_options div').addClass('d-none');
|
||||
|
||||
// Check video code
|
||||
var $textarea = this.$('textarea#o_video_text');
|
||||
@@ -1005,7 +1045,7 @@ var VideoWidget = MediaWidget.extend({
|
||||
|
||||
// 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')){
|
||||
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;
|
||||
@@ -1020,10 +1060,10 @@ var VideoWidget = MediaWidget.extend({
|
||||
hide_dm_share: this.$('input#o_video_hide_dm_share').is(':checked'),
|
||||
});
|
||||
|
||||
var $opt_box = this.$('.o_video_dialog_options');
|
||||
var $optBox = 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('d-none', !query.$video);
|
||||
this.$el.find('.o_video_dialog_preview_text, .media_iframe_video_size').add($optBox).toggleClass('d-none', !query.$video);
|
||||
// Toggle validation classes
|
||||
this.$el.find('#o_video_form_group')
|
||||
.toggleClass('o_has_error', !query.$video).find('.form-control, .custom-select').toggleClass('is-invalid', !query.$video)
|
||||
@@ -1031,15 +1071,16 @@ var VideoWidget = MediaWidget.extend({
|
||||
.toggleClass('o_has_success', !!query.$video).find('.form-control, .custom-select').toggleClass('is-valid', !!query.$video);
|
||||
|
||||
// Individually show / hide options base on the video provider
|
||||
$opt_box.find('li.o_' + query.type + '_option').removeClass('d-none');
|
||||
$optBox.find('div.o_' + query.type + '_option').removeClass('d-none');
|
||||
|
||||
// Hide the entire options box if no options are available
|
||||
$opt_box.toggleClass('d-none', $opt_box.find('li:not(.d-none)').length === 0);
|
||||
$optBox.toggleClass('d-none', $optBox.find('div:not(.d-none)').length === 0);
|
||||
$('.option_title').toggleClass('d-none');
|
||||
|
||||
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('d-none', this.$('input#o_video_hide_controls').is(':checked'));
|
||||
this.$('input#o_video_hide_fullscreen, input#o_video_hide_yt_logo').closest('div').toggleClass('d-none', this.$('input#o_video_hide_controls').is(':checked'));
|
||||
}
|
||||
|
||||
var $content = query.$video;
|
||||
@@ -1047,13 +1088,13 @@ var VideoWidget = MediaWidget.extend({
|
||||
switch (query.errorCode) {
|
||||
case 0:
|
||||
$content = $('<div/>', {
|
||||
class: 'alert alert-danger o_video_dialog_iframe',
|
||||
class: 'alert alert-danger o_video_dialog_iframe mb-2 mt-2',
|
||||
text: _t("The provided url is not valid"),
|
||||
});
|
||||
break;
|
||||
case 1:
|
||||
$content = $('<div/>', {
|
||||
class: 'alert alert-warning o_video_dialog_iframe',
|
||||
class: 'alert alert-warning o_video_dialog_iframe mb-2 mt-2',
|
||||
text: _t("The provided url does not reference any supported video"),
|
||||
});
|
||||
break;
|
||||
@@ -1105,14 +1146,11 @@ var MediaDialog = Dialog.extend({
|
||||
xmlDependencies: Dialog.prototype.xmlDependencies.concat(
|
||||
['/web_editor/static/src/xml/editor.xml']
|
||||
),
|
||||
events : _.extend({}, Dialog.prototype.events, {
|
||||
'input input#icon-search': '_onSearchInput',
|
||||
events: _.extend({}, Dialog.prototype.events, {
|
||||
'shown.bs.tab a[data-toggle="tab"]': '_onTabChange',
|
||||
'click .previous:not(.disabled), .next:not(.disabled)': '_onPagerClick',
|
||||
}),
|
||||
custom_events: _.extend({}, Dialog.prototype.custom_events || {}, {
|
||||
save_request: '_onSaveRequest',
|
||||
update_control_panel: '_updateControlPanel',
|
||||
}),
|
||||
|
||||
/**
|
||||
@@ -1122,6 +1160,7 @@ var MediaDialog = Dialog.extend({
|
||||
var self = this;
|
||||
this._super(parent, _.extend({}, {
|
||||
title: _t("Select a Media"),
|
||||
save_text: _t("Add"),
|
||||
}, options));
|
||||
|
||||
if ($editable) {
|
||||
@@ -1168,8 +1207,6 @@ var MediaDialog = Dialog.extend({
|
||||
}
|
||||
self.$('[href="#editor-media-' + tabToShow + '"]').tab('show');
|
||||
});
|
||||
|
||||
this._onSearchInput = _.debounce(this._onSearchInput, 250);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -1266,31 +1303,12 @@ var MediaDialog = Dialog.extend({
|
||||
*/
|
||||
_setActive: function (widget) {
|
||||
this.active = widget;
|
||||
this.active.goToPage(0);
|
||||
this._updateControlPanel();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_updateControlPanel: function () {
|
||||
var cpConfig = this.active.getControlPanelConfig();
|
||||
this.$('li.search').toggleClass('d-none', !cpConfig.searchEnabled);
|
||||
this.$('.previous, .next').toggleClass('d-none', !cpConfig.pagerEnabled);
|
||||
this.$('.previous').toggleClass("disabled", !cpConfig.pagerLeftEnabled);
|
||||
this.$('.next').toggleClass("disabled", !cpConfig.pagerRightEnabled);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onPagerClick: function (ev) {
|
||||
this.active.goToPage(this.active.page + ($(ev.currentTarget).hasClass('previous') ? -1 : 1));
|
||||
this._updateControlPanel();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -1298,16 +1316,6 @@ var MediaDialog = Dialog.extend({
|
||||
ev.stopPropagation();
|
||||
this.save();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSearchInput: function (ev) {
|
||||
var self = this;
|
||||
this.active.goToPage(0);
|
||||
this.active.search($(ev.currentTarget).val() || '').then(function () {
|
||||
self._updateControlPanel();
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -1359,7 +1367,7 @@ var LinkDialog = Dialog.extend({
|
||||
this.data.iniClassName = $(this.data.range.sc).filter("a").attr("class") || "";
|
||||
this.data.className = this.data.iniClassName.replace(/(^|\s+)btn(-[a-z0-9_-]*)?/gi, ' ');
|
||||
|
||||
var is_link = this.data.range.isOnAnchor();
|
||||
var isLink = this.data.range.isOnAnchor();
|
||||
|
||||
var sc = r.sc;
|
||||
var so = r.so;
|
||||
@@ -1367,7 +1375,7 @@ var LinkDialog = Dialog.extend({
|
||||
var eo = r.eo;
|
||||
|
||||
var nodes;
|
||||
if (!is_link) {
|
||||
if (!isLink) {
|
||||
if (sc.tagName) {
|
||||
sc = dom.firstChild(so ? sc.childNodes[so] : sc);
|
||||
so = 0;
|
||||
@@ -1381,7 +1389,7 @@ var LinkDialog = Dialog.extend({
|
||||
so = 0;
|
||||
}
|
||||
if (ec.tagName) {
|
||||
ec = dom.lastChild(eo ? ec.childNodes[eo-1] : ec);
|
||||
ec = dom.lastChild(eo ? ec.childNodes[eo - 1] : ec);
|
||||
eo = ec.textContent.length;
|
||||
} else if (eo !== ec.textContent.length) {
|
||||
ec.splitText(eo);
|
||||
@@ -1391,11 +1399,11 @@ var LinkDialog = Dialog.extend({
|
||||
|
||||
// browsers can't target a picture or void node
|
||||
if (dom.isVoid(sc) || dom.isImg(sc)) {
|
||||
so = dom.listPrev(sc).length-1;
|
||||
so = dom.listPrev(sc).length - 1;
|
||||
sc = sc.parentNode;
|
||||
}
|
||||
if (dom.isBR(ec)) {
|
||||
eo = dom.listPrev(ec).length-1;
|
||||
eo = dom.listPrev(ec).length - 1;
|
||||
ec = ec.parentNode;
|
||||
} else if (dom.isVoid(ec) || dom.isImg(sc)) {
|
||||
eo = dom.listPrev(ec).length;
|
||||
@@ -1414,15 +1422,15 @@ var LinkDialog = Dialog.extend({
|
||||
if (nodes.length > 1 || dom.ancestor(nodes[0], dom.isImg)) {
|
||||
var text = "";
|
||||
this.data.images = [];
|
||||
for (var i=0; i<nodes.length; i++) {
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
if (dom.ancestor(nodes[i], dom.isImg)) {
|
||||
this.data.images.push(dom.ancestor(nodes[i], dom.isImg));
|
||||
text += '[IMG]';
|
||||
} else if (!is_link && nodes[i].nodeType === 1) {
|
||||
} else if (!isLink && nodes[i].nodeType === 1) {
|
||||
// just use text nodes from listBetween
|
||||
} else if (!is_link && i===0) {
|
||||
} else if (!isLink && i === 0) {
|
||||
text += nodes[i].textContent.slice(so, Infinity);
|
||||
} else if (!is_link && i===nodes.length-1) {
|
||||
} else if (!isLink && i === nodes.length - 1) {
|
||||
text += nodes[i].textContent.slice(0, eo);
|
||||
} else {
|
||||
text += nodes[i].textContent;
|
||||
@@ -1534,7 +1542,7 @@ var LinkDialog = Dialog.extend({
|
||||
var label = this.$('input[name="label"]').val() || url;
|
||||
|
||||
if (label && this.data.images) {
|
||||
for (var i = 0 ; i < this.data.images.length ; i++) {
|
||||
for (var i = 0; i < this.data.images.length; i++) {
|
||||
label = label.replace(/</, "<").replace(/>/, ">").replace(/\[IMG\]/, this.data.images[i].outerHTML);
|
||||
}
|
||||
}
|
||||
@@ -1600,7 +1608,7 @@ var CropImageDialog = Dialog.extend({
|
||||
cssLibs: [
|
||||
'/web_editor/static/lib/cropper/css/cropper.css',
|
||||
],
|
||||
events : _.extend({}, Dialog.prototype.events, {
|
||||
events: _.extend({}, Dialog.prototype.events, {
|
||||
'click .o_crop_options [data-event]': '_onCropOptionClick',
|
||||
}),
|
||||
|
||||
@@ -1683,7 +1691,7 @@ var CropImageDialog = Dialog.extend({
|
||||
if (this.$cropperImage.length) {
|
||||
var data = this.$media.data();
|
||||
var ratio = 0;
|
||||
for (var i = 0 ; i < this.aspectRatioList.length ; i++) {
|
||||
for (var i = 0; i < this.aspectRatioList.length; i++) {
|
||||
if (this.aspectRatioList[i][1] === data.aspectRatio) {
|
||||
ratio = this.aspectRatioList[i][2];
|
||||
break;
|
||||
|
||||
@@ -1386,10 +1386,16 @@ body .modal {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
.o_size_fa_icon {
|
||||
font-size: 1.4rem;
|
||||
}
|
||||
.o_size_dropdown_item {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
|
||||
// VIDEO TAB
|
||||
#editor-media-video .card {
|
||||
#editor-media-video .o_video_dialog_container {
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-flow: row wrap;
|
||||
|
||||
@@ -57,18 +57,6 @@
|
||||
<li class="nav-item"><a role="tab" href="#editor-media-document" aria-controls="editor-media-document" data-toggle="tab" t-attf-class="nav-link#{widget.noDocuments ? ' d-none' : ''}">Document</a></li>
|
||||
<li class="nav-item"><a role="tab" href="#editor-media-icon" aria-controls="editor-media-icon" data-toggle="tab" t-attf-class="nav-link#{widget.noIcons ? ' d-none' : ''}">Pictogram</a></li>
|
||||
<li class="nav-item"><a role="tab" href="#editor-media-video" aria-controls="editor-media-video" data-toggle="tab" t-attf-class="nav-link#{widget.noVideos ? ' d-none' : ''}">Video</a></li>
|
||||
<li class="nav-item ml-auto search">
|
||||
<div class="form-group">
|
||||
<input class="form-control" id="icon-search" type="search"/>
|
||||
<span class="fa fa-search" title="Search" role="img" aria-label="Search"/>
|
||||
</div>
|
||||
</li>
|
||||
<li class="nav-item search">
|
||||
<div class="btn-group">
|
||||
<a role="button" class="btn btn-secondary disabled previous" href="#"><i class="fa fa-angle-left"/> Previous</a>
|
||||
<a role="button" class="btn btn-secondary disabled next" href="#">Next <i class="fa fa-angle-right"/></a>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
@@ -91,37 +79,81 @@
|
||||
<input type="hidden" name="filters" t-att-value="widget.firstFilters.join('_')"/>
|
||||
<input t-if="widget.options.res_id" type="hidden" name="res_id" t-att-value="widget.options.res_id"/>
|
||||
<input t-if="widget.options.res_model" type="hidden" name="res_model" t-att-value="widget.options.res_model"/>
|
||||
<div class="o_media_image_card card w-100">
|
||||
<div class="card-body p-0">
|
||||
<div class="form-inline">
|
||||
<input type="file" class="d-none" name="upload" t-att-accept="widget.accept" multiple="multiple"/>
|
||||
<div class="mt-4 w-100">
|
||||
<div class="mb-4">
|
||||
<div class="o_submenu"></div>
|
||||
</div>
|
||||
<div class="form-text"/>
|
||||
<div class="existing-attachments">
|
||||
<t t-call="web_editor.dialog.image.existing"/>
|
||||
</div>
|
||||
<div class="mt-4 text-center o_load_elements mx-auto">
|
||||
<button class="btn btn-primary o_load_more" type="button">Load more...</button>
|
||||
<div class="mt-4 o_load_done_msg d-none">
|
||||
<span><i>All attachments have been loaded</i></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<iframe class="o_file_upload_iframe d-none" t-att-name="iframeName"/>
|
||||
</form>
|
||||
</t>
|
||||
<t t-name="web_editor.dialog.files.submenu">
|
||||
<div class="o_submenu">
|
||||
<div class="row">
|
||||
<div class="col-sm">
|
||||
<div class="form-inline row no-gutters">
|
||||
<input type="file" class="d-none o_file_input" name="upload" t-att-accept="accept" multiple="multiple"/>
|
||||
<input type="hidden" name="disable_optimization" value=""/>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary o_upload_image_button" type="button">Upload an image</button>
|
||||
<button class="btn btn-primary dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" type="button">
|
||||
<span class="sr-only">Alternate Upload</span>
|
||||
<div class="btn-group o_btn_group_image">
|
||||
<button class="btn btn-primary o_upload_media_button" type="button">
|
||||
<t t-if="isDocument">Upload a document</t>
|
||||
<t t-else="">Upload an image</t>
|
||||
</button>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<a role="menuitem" class="dropdown-item o_upload_image_button_no_optimization">Upload image without optimization</a>
|
||||
</div>
|
||||
<t t-if="!isDocument">
|
||||
<button class="btn btn-primary dropdown-toggle dropdown-toggle-split o_media_optimization" data-toggle="dropdown" type="button">
|
||||
<span class="sr-only">Alternate Upload</span>
|
||||
</button>
|
||||
<div class="dropdown-menu" role="menu">
|
||||
<a role="menuitem" class="dropdown-item o_upload_media_button_no_optimization">Upload image without optimization</a>
|
||||
</div>
|
||||
</t>
|
||||
</div>
|
||||
<span class="text-muted">— or —</span>
|
||||
<label for="imageurl">Add an image URL</label>
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control url" id="imageurl" name="url" placeholder="https://www.odoo.com/logo.png" style="width: 250px;"/>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-secondary o_upload_image_url_button" disabled="disabled">Add</button>
|
||||
<div class="input-group col-8 ml-2">
|
||||
<input type="text" class="form-control url col-7" name="url" t-attf-placeholder="https://www.odoo.com/#{isDocument ? 'mydocument' : 'logo.png'}"/>
|
||||
<div class="input-group-append ml-2">
|
||||
<button type="button" class="btn btn-sm btn-secondary o_upload_media_url_button" disabled="disabled">
|
||||
<t t-if="isDocument">Add document</t>
|
||||
<t t-else="">Add image</t>
|
||||
</button>
|
||||
</div>
|
||||
<div class="ml-2 o_size_fa_icon">
|
||||
<span class="success-url text-success d-none fa fa-check" title="The URL seems valid"/>
|
||||
<t t-if="isDocument">
|
||||
<span class="error-url text-danger d-none fa fa-times" title="This URL does not seem to work"/>
|
||||
</t>
|
||||
<t t-else="">
|
||||
<span class="warning-url text-warning d-none fa fa-warning" title="The URL provided does not contain any image. It will be added in the document section."/>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-sm text-right">
|
||||
<ul class="list-unstyled row">
|
||||
<li class="nav-item ml-auto col-6 search">
|
||||
<div class="form-group o_search_div">
|
||||
<t t-set="searchPlaceholder">
|
||||
<t t-if="isDocument">Search a document</t>
|
||||
<t t-else="">Search an image</t>
|
||||
</t>
|
||||
<input class="form-control w-100" id="icon-search" type="search" t-att-placeholder="searchPlaceholder.trim()"/>
|
||||
<span class="fa fa-search" title="Search" role="img" aria-label="Search"/>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-text"/>
|
||||
<div class="existing-attachments">
|
||||
<t t-call="web_editor.dialog.image.existing"/>
|
||||
</div>
|
||||
<iframe class="d-none" t-att-name="iframeName"/>
|
||||
</form>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="web_editor.dialog.image.existing">
|
||||
<div role="dialog" class="modal" tabindex="-1">
|
||||
@@ -136,19 +168,22 @@
|
||||
<div class="form-text"/>
|
||||
</main>
|
||||
<footer class="modal-footer">
|
||||
<a data-dismiss="modal" href="#" aria-label="Discard">Discard</a>
|
||||
<button data-dismiss="modal" type="button" aria-label="Discard">Discard</button>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
<t t-name="web_editor.dialog.image.existing.content">
|
||||
<t t-name="web_editor.dialog.files.existing.content">
|
||||
<div class="existing-attachments">
|
||||
<div class="row mt16" t-as="row" t-foreach="rows">
|
||||
<div class="row mt16 ml-auto" t-as="row" t-foreach="rows">
|
||||
<div class="col-2 o_existing_attachment_cell" t-as="attachment" t-foreach="row">
|
||||
<i t-if="attachment.res_model === 'ir.ui.view'" class="fa fa-times o_existing_attachment_remove" title="This file is a public view attachment" role="img" aria-label="This file is a public view attachment" t-att-data-id="attachment.id"/>
|
||||
<i t-else="" class="fa fa-times o_existing_attachment_remove" title="This file is attached to the current record" role="img" aria-label="This file is attached to the current record" t-att-data-id="attachment.id"/>
|
||||
<i t-if="attachment.res_model === 'ir.ui.view'" class="fa fa-trash o_existing_attachment_remove" title="This file is a public view attachment" role="img" aria-label="This file is a public view attachment" t-att-data-id="attachment.id"/>
|
||||
<i t-else="" class="fa fa-trash o_existing_attachment_remove" title="This file is attached to the current record" role="img" aria-label="This file is attached to the current record" t-att-data-id="attachment.id"/>
|
||||
<div class="o_attachment_border" t-att-style="attachment.res_model === 'ir.ui.view' ? null : 'border: 1px solid #5cb85c;'"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" role="img" t-att-aria-label="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" class="o_image o_image_loading"/></div>
|
||||
<t t-if="isDocument">
|
||||
<small class="o_file_name"><t t-esc="attachment.name"/></small>
|
||||
</t>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,7 +204,15 @@
|
||||
|
||||
<!-- Icon choosing part of the Media Dialog -->
|
||||
<t t-name="web_editor.dialog.font-icons">
|
||||
<form action="#">
|
||||
<form action="#" class="mt-2">
|
||||
<div class="d-flex flex-row-reverse">
|
||||
<li class="nav-item search list-unstyled col-3 p-2">
|
||||
<div class="form-group">
|
||||
<input class="form-control" id="icon-search" type="search" placeholder="Search a pictogram"/>
|
||||
<span class="fa fa-search" title="Search" role="img" aria-label="Search"/>
|
||||
</div>
|
||||
</li>
|
||||
</div>
|
||||
<div class="font-icons-icons">
|
||||
<t t-call="web_editor.dialog.font-icons.icons">
|
||||
<t t-set="iconsParser" t-value="widget.iconsParser"/>
|
||||
@@ -189,42 +232,46 @@
|
||||
</t>
|
||||
|
||||
<!-- Video choosing part of the Media Dialog -->
|
||||
<div t-name="web_editor.dialog.video" class="card">
|
||||
<form action="#" class="form-inline card-body">
|
||||
<div t-name="web_editor.dialog.video" class="o_video_dialog_container mt-4">
|
||||
<form action="#" class="form-inline w-100">
|
||||
<div class="o_video_dialog_form">
|
||||
<div class="form-group" id="o_video_form_group">
|
||||
<label class="mt8" for="o_video_text">Video code <small class="text-muted">(URL or Embed)</small></label>
|
||||
<div class="form-group mb-2" id="o_video_form_group">
|
||||
<label>Video code </label>
|
||||
<small class="ml-1 form-text text-muted"> (URL or Embed)</small>
|
||||
<label class="ml-2 col-form-label o_validate_feedback position-static" for="o_video_text">
|
||||
<i class="fa fa-check text-success" role="img" aria-label="Checked" title="Checked"/>
|
||||
<i class="fa fa-exclamation-triangle text-danger" role="img" aria-label="Attention" title="Attention"/>
|
||||
</label>
|
||||
<textarea class="form-control url" id="o_video_text" placeholder="Copy-paste your URL or embed code here"/>
|
||||
<label class="col-form-label o_validate_feedback" for="o_video_text"><i class="fa fa-check" role="img" aria-label="Checked" title="Checked"/><i class="fa fa-exclamation-triangle" role="img" aria-label="Attention" title="Attention"/></label>
|
||||
</div>
|
||||
<div class="text-right">
|
||||
<small class="text-muted">Accepts <b><i>Youtube</i></b>, <b><i>Instagram</i></b>, <b><i>Vine.co</i></b>, <b><i>Vimeo</i></b>, <b><i>Dailymotion</i></b> and <b><i>Youku</i></b> videos</small>
|
||||
</div>
|
||||
<div class="o_video_dialog_options d-none mt48">
|
||||
<small class="text-muted">Options</small>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item o_yt_option o_vim_option o_dm_option">
|
||||
<label class="o_switch mb0"><input id="o_video_autoplay" type="checkbox"/><span/>Autoplay</label>
|
||||
</li>
|
||||
<li class="list-group-item o_yt_option o_vim_option">
|
||||
<label class="o_switch mb0"><input id="o_video_loop" type="checkbox"/><span/>Loop</label>
|
||||
</li>
|
||||
<li class="list-group-item o_yt_option o_dm_option">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_controls" type="checkbox"/><span/>Hide player controls</label>
|
||||
</li>
|
||||
<li class="list-group-item o_yt_option">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_fullscreen" type="checkbox"/><span/>Hide fullscreen button</label>
|
||||
</li>
|
||||
<li class="list-group-item o_yt_option">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_yt_logo" type="checkbox"/><span/>Hide Youtube logo</label>
|
||||
</li>
|
||||
<li class="list-group-item o_dm_option">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_dm_logo" type="checkbox"/><span/>Hide Dailymotion logo</label>
|
||||
</li>
|
||||
<li class="list-group-item o_dm_option">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_dm_share" type="checkbox"/><span/>Hide sharing button</label>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="o_video_dialog_options d-none mt-4">
|
||||
<div class="option_title mb-2 d-flex">
|
||||
<label>Options</label>
|
||||
</div>
|
||||
<div class="o_yt_option o_vim_option o_dm_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_autoplay" type="checkbox"/><span/>Autoplay</label>
|
||||
</div>
|
||||
<div class="o_yt_option o_vim_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_loop" type="checkbox"/><span/>Loop</label>
|
||||
</div>
|
||||
<div class="o_yt_option o_dm_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_controls" type="checkbox"/><span/>Hide player controls</label>
|
||||
</div>
|
||||
<div class="o_yt_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_fullscreen" type="checkbox"/><span/>Hide fullscreen button</label>
|
||||
</div>
|
||||
<div class="o_yt_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_yt_logo" type="checkbox"/><span/>Hide Youtube logo</label>
|
||||
</div>
|
||||
<div class="o_dm_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_dm_logo" type="checkbox"/><span/>Hide Dailymotion logo</label>
|
||||
</div>
|
||||
<div class="o_dm_option d-flex">
|
||||
<label class="o_switch mb0"><input id="o_video_hide_dm_share" type="checkbox"/><span/>Hide sharing button</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="video-preview">
|
||||
@@ -307,7 +354,7 @@
|
||||
<div class="form-group text-center">
|
||||
<label>Preview</label>
|
||||
<div style="overflow: auto; max-width: 100%; max-height: 200px;">
|
||||
<a href="#" id="link-preview" aria-label="Preview" title="Preview"/>
|
||||
<a href="#" type="button" id="link-preview" aria-label="Preview" title="Preview"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -3,22 +3,26 @@ odoo.define('web_unsplash.image_widgets', function (require) {
|
||||
|
||||
var core = require('web.core');
|
||||
var UnsplashAPI = require('unsplash.api');
|
||||
|
||||
var ImageWidget = require('web_editor.widget').ImageWidget;
|
||||
var weWidgets = require('web_editor.widget');
|
||||
var QWeb = core.qweb;
|
||||
|
||||
var unsplashAPI = null;
|
||||
|
||||
ImageWidget.include({
|
||||
xmlDependencies: ImageWidget.prototype.xmlDependencies.concat(
|
||||
weWidgets.ImageWidget.include({
|
||||
xmlDependencies: weWidgets.ImageWidget.prototype.xmlDependencies.concat(
|
||||
['/web_unsplash/static/src/xml/unsplash_image_widget.xml']
|
||||
),
|
||||
events: _.extend({}, ImageWidget.prototype.events, {
|
||||
'input input.unsplash_search': '_onChangeUnsplashSearch',
|
||||
events: _.extend({}, weWidgets.ImageWidget.prototype.events, {
|
||||
'dblclick .unsplash_img_container [data-imgid]': '_onUnsplashImgDblClick',
|
||||
'click .unsplash_img_container [data-imgid]': '_onUnsplashImgClick',
|
||||
'click button.save_unsplash': '_onSaveUnsplash',
|
||||
'click button.access_key': '_onSetAccessKey',
|
||||
'click .o_load_more': '_onLoadMore',
|
||||
'click .o_search_from_unsplash': '_onSearchFromUnsplash',
|
||||
'click .o_search_an_image': '_onSearchImage',
|
||||
'keyup input#icon-search, .o_dropdown_search': '_onPressKeySearch',
|
||||
}),
|
||||
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
@@ -28,6 +32,7 @@ ImageWidget.include({
|
||||
isMaxed: false,
|
||||
query: false,
|
||||
};
|
||||
|
||||
// TODO This is a `hack` to prevent the UnsplashAPI to be destroyed every time
|
||||
// the media dialog is closed.
|
||||
// Indeed, UnsplashAPI has a cache system to recude unsplash call, it is
|
||||
@@ -84,7 +89,7 @@ ImageWidget.include({
|
||||
route: '/web_unsplash/attachment/add',
|
||||
params: {
|
||||
unsplashurls: self._unsplash.selectedImages,
|
||||
res_model : self.options.res_model,
|
||||
res_model: self.options.res_model,
|
||||
res_id: self.options.res_id,
|
||||
query: self._unsplash.query,
|
||||
}
|
||||
@@ -134,7 +139,7 @@ ImageWidget.include({
|
||||
.values()
|
||||
.value();
|
||||
|
||||
self.$('.unsplash_img_container').html(QWeb.render('web_unsplash.dialog.image.content', { rows: rows }));
|
||||
self.$('.unsplash_img_container').html(QWeb.render('web_unsplash.dialog.image.content', {rows: rows}));
|
||||
self._highlightSelectedImages();
|
||||
}).fail(function (err) {
|
||||
self.$('.unsplash_img_container').html(QWeb.render('web_unsplash.dialog.error.content', { status: err }));
|
||||
@@ -146,10 +151,26 @@ ImageWidget.include({
|
||||
* @private
|
||||
*/
|
||||
_toggleAttachmentContaines: function (hideUnsplash) {
|
||||
this.$('.existing-attachments').toggleClass('o_hidden', !hideUnsplash);
|
||||
this.$('.unsplash_img_container').toggleClass('o_hidden', hideUnsplash);
|
||||
this.$('.existing-attachments').toggleClass('d-none', !hideUnsplash);
|
||||
this.$('.unsplash_img_container').toggleClass('d-none', hideUnsplash);
|
||||
this.$('.o_load_more').toggleClass('d-none', hideUnsplash);
|
||||
this.$('.o_load_done_msg').toggleClass('d-none', !hideUnsplash);
|
||||
this.trigger_up('update_control_panel');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_toggleLoadMoreBtnUnsplash: function (display) {
|
||||
this.$('.o_load_done_msg').toggleClass('d-none', display);
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_toggleSearchDropdown: function (show) {
|
||||
var inputValue = this.$('#icon-search').val();
|
||||
this.$('.o_search_value_input').text(inputValue);
|
||||
this.$('.o_dropdown_search').toggleClass('show', show);
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
@@ -189,15 +210,16 @@ ImageWidget.include({
|
||||
_onChangeUnsplashSearch: _.debounce(function () {
|
||||
// oldPage saves the original image widget pager.
|
||||
// Emptying the unsplash search will set the pager to its previous state
|
||||
this._unsplash.query = this.$('.unsplash_search').val().trim();
|
||||
this._unsplash.query = this.$('#icon-search').val().trim();
|
||||
if (this._unsplash.query) {
|
||||
this.oldPage = this.page;
|
||||
this.page = 1;
|
||||
this._renderImages();
|
||||
} else {
|
||||
this.page = this.oldPage || 0;
|
||||
this.page = this.oldPage || 1;
|
||||
this._toggleAttachmentContaines(true);
|
||||
}
|
||||
this._toggleLoadMoreBtnUnsplash(true);
|
||||
}, 1000),
|
||||
/**
|
||||
* @private
|
||||
@@ -205,14 +227,14 @@ ImageWidget.include({
|
||||
_onUnsplashImgClick: function (ev) {
|
||||
var imgid = $(ev.currentTarget).data('imgid');
|
||||
var url = $(ev.currentTarget).data('url');
|
||||
var download_url = $(ev.currentTarget).data('download-url');
|
||||
var downloadUrl = $(ev.currentTarget).data('download-url');
|
||||
if (!this.multiImages) {
|
||||
this._unsplash.selectedImages = {};
|
||||
}
|
||||
if (imgid in this._unsplash.selectedImages) {
|
||||
delete this._unsplash.selectedImages[imgid];
|
||||
} else {
|
||||
this._unsplash.selectedImages[imgid] = {url: url, download_url: download_url};
|
||||
this._unsplash.selectedImages[imgid] = {url: url, downloadUrl: downloadUrl};
|
||||
}
|
||||
this._highlightSelectedImages();
|
||||
},
|
||||
@@ -222,5 +244,61 @@ ImageWidget.include({
|
||||
_onUnsplashImgDblClick: function (ev) {
|
||||
this.trigger_up('save_request');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onLoadMore: function () {
|
||||
this._unsplash.query = this.$('#icon-search').val().trim();
|
||||
this._loadMoreImages();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSearchFromUnsplash: function () {
|
||||
this.$('.o_dropdown_search').removeClass('show');
|
||||
this._toggleAttachmentContaines(false);
|
||||
this._onChangeUnsplashSearch();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onSearchImage: function () {
|
||||
var searchText = this.$('#icon-search').val().trim();
|
||||
this._unsplash.query = false;
|
||||
this.search(searchText);
|
||||
this.$('.o_dropdown_search').removeClass('show');
|
||||
this._toggleAttachmentContaines(true);
|
||||
this._renderImages();
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onPressKeySearch: function (ev) {
|
||||
var $searchAnImage = this.$('button.dropdown-item.o_search_an_image');
|
||||
switch (ev.which) {
|
||||
case $.ui.keyCode.ENTER:
|
||||
$searchAnImage.focus();
|
||||
break;
|
||||
case $.ui.keyCode.UP:
|
||||
$searchAnImage.focus();
|
||||
break;
|
||||
case $.ui.keyCode.DOWN:
|
||||
$searchAnImage.next().focus();
|
||||
break;
|
||||
default: return;
|
||||
}
|
||||
ev.preventDefault();
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onSearchInput: function (ev) {
|
||||
if (!this.options.document) {
|
||||
this._toggleSearchDropdown(true);
|
||||
} else {
|
||||
this.search($(ev.currentTarget).val() || '');
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -2,14 +2,23 @@
|
||||
<templates id="template" xml:space="preserve">
|
||||
|
||||
<t t-extend="web_editor.dialog.image">
|
||||
<t t-jquery=".o_media_image_card .form-inline" t-operation="append">
|
||||
<div class="ml-auto border-left pl-4">
|
||||
<span class="d-block font-weight-bold">Photos by <a href="https://unsplash.com" target="_blank">Unsplash</a></span>
|
||||
<input type="text" class="form-control unsplash_search mr8" placeholder="Search free high-resolution photos" style="width: 250px;"/>
|
||||
</div>
|
||||
</t>
|
||||
<t t-jquery=".existing-attachments" t-operation="after">
|
||||
<div class="unsplash_img_container container-fluid"></div>
|
||||
<div class="unsplash_img_container"></div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
<t t-extend="web_editor.dialog.files.submenu">
|
||||
<t t-jquery=".o_search_div" t-operation="append">
|
||||
<div class="dropdown-menu o_dropdown_search w-100" style="background-color:#efefef;">
|
||||
<button class="dropdown-item o_search_an_image o_size_dropdown_item" type="button">
|
||||
Search among my images for:
|
||||
<div class="font-weight-bold o_search_value_input"></div>
|
||||
</button>
|
||||
<button class="dropdown-item o_search_from_unsplash o_size_dropdown_item" type="button">
|
||||
Search from Unsplash for:
|
||||
<div class="font-weight-bold o_search_value_input"></div>
|
||||
</button>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -17,7 +26,7 @@
|
||||
<div class="row mt16" t-as="row" t-foreach="rows">
|
||||
<div class="col-sm-2 o_unsplash_img_cell text-center" t-as="img" t-foreach="row">
|
||||
<div class="o_image o_webimage" t-att-data-imgid="img.id" t-att-data-url="img.urls.regular" t-att-data-download-url="img.links.download_location">
|
||||
<img class="img-fluid" t-att-src="img.urls.thumb" />
|
||||
<img class="img-fluid" t-att-src="img.urls.thumb"/>
|
||||
</div>
|
||||
<div class="author_name"><a t-att-href="img.user.links.html" target="_blank"><t t-esc="img.user.name"/></a></div>
|
||||
</div>
|
||||
|
||||
@@ -41,8 +41,8 @@ odoo.define("website_sale.tour_shop", function (require) {
|
||||
actions.dblclick();
|
||||
},
|
||||
}, {
|
||||
trigger: ".o_select_media_dialog .o_upload_image_button",
|
||||
content: _t("Upload an image from your local library."),
|
||||
trigger: ".o_select_media_dialog .o_upload_media_button",
|
||||
content: _t("Upload a file from your local library."),
|
||||
position: "bottom",
|
||||
run: function (actions) {
|
||||
actions.auto(".modal-footer .btn-secondary");
|
||||
|
||||
Reference in New Issue
Block a user