diff options
| author | Christophe Matthieu <chm@odoo.com> | 2018-12-28 17:49:21 +0000 |
|---|---|---|
| committer | Christophe Matthieu <chm@odoo.com> | 2019-01-17 08:40:21 +0000 |
| commit | de444335b7da30a2ab3a4c5b92c9731a023f3f07 (patch) | |
| tree | 40ceda25aa7157ae0923cf567a448d685798e911 /addons/web_editor/static/src/js/widgets/widgets.js | |
| parent | 17c6d00531c55d0c392ddb6916bf1957bc490c1c (diff) | |
[REF] web_editor,*: Move and rename files to prepare refactoring
The refactoring of the wysiwyg editor and 'html' field allows us to
move some of the code that was in web_editor but only used in website or
in mass_mailing. Some parts are still in web_editor but will be
moved at a later time.
Diffstat (limited to 'addons/web_editor/static/src/js/widgets/widgets.js')
| -rw-r--r-- | addons/web_editor/static/src/js/widgets/widgets.js | 1795 |
1 files changed, 0 insertions, 1795 deletions
diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js deleted file mode 100644 index 9dad13a0b2b..00000000000 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ /dev/null @@ -1,1795 +0,0 @@ -odoo.define('web_editor.widget', function (require) { -'use strict'; - -var ajax = require('web.ajax'); -var base = require('web_editor.base'); -var core = require('web.core'); -var DialogBase = require('web.Dialog'); -var Widget = require('web.Widget'); -var weContext = require("web_editor.context"); - -var QWeb = core.qweb; -var range = $.summernote.core.range; -var dom = $.summernote.core.dom; - -var _t = core._t; - -/** - * @todo we should either get rid of this or move it somewhere else - */ -function simulateMouseEvent(el, type) { - var evt = document.createEvent("MouseEvents"); - evt.initMouseEvent(type, true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, el); - el.dispatchEvent(evt); -} - -/** - * Extend Dialog class to handle save/cancel of edition components. - */ -var Dialog = DialogBase.extend({ - /** - * @constructor - */ - init: function (parent, options) { - options = options || {}; - this._super(parent, _.extend({}, { - buttons: [ - {text: options.save_text || _t("Save"), classes: 'btn-primary', click: this.save}, - {text: _t("Discard"), close: true} - ] - }, options)); - - this.destroyAction = 'cancel'; - - var self = this; - this.opened().then(function () { - self.$('input:first').focus(); - }); - this.on('closed', this, function () { - this.trigger(this.destroyAction, this.final_data || null); - }); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Called when the dialog is saved. Set the destroy action type to "save" - * and should set the final_data variable correctly before closing. - */ - save: function () { - this.destroyAction = "save"; - this.close(); - }, -}); - -/** - * Let users change the alt & title of a media. - */ -var AltDialog = Dialog.extend({ - template: 'web_editor.dialog.alt', - xmlDependencies: Dialog.prototype.xmlDependencies.concat( - ['/web_editor/static/src/xml/editor.xml'] - ), - - /** - * @constructor - */ - init: function (parent, options, $editable, media) { - this._super(parent, _.extend({}, { - title: _t("Change media description and tooltip") - }, options)); - this.$editable = $editable; - this.media = media; - this.alt = ($(this.media).attr('alt') || "").replace(/"/g, '"'); - this.tag_title = ($(this.media).attr('title') || "").replace(/"/g, '"'); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - save: function () { - var self = this; - range.createFromNode(this.media).select(); - this.$editable.data('NoteHistory').recordUndo(); - var alt = this.$('#alt').val(); - var title = this.$('#title').val(); - $(this.media).attr('alt', alt ? alt.replace(/"/g, """) : null).attr('title', title ? title.replace(/"/g, """) : null); - _.defer(function () { - simulateMouseEvent(self.media, 'mouseup'); - }); - return this._super.apply(this, arguments); - }, -}); - -var MediaWidget = Widget.extend({ - xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], - events: { - 'input input.o_we_search': '_onSearchInput', - }, - - /** - * @constructor - */ - init: function (parent, media, options) { - this._super.apply(this, arguments); - this.media = media; - this.$media = $(media); - this._onSearchInput = _.debounce(this._onSearchInput, 500); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @todo comment - */ - clear: function () { - if (!this.media) { - return; - } - this._clear(); - }, - /** - * @abstract - * @param {string} needle - * @returns {Deferred} - */ - search: function (needle) {}, - /** - * @abstract - * @returns {*} - */ - save: function () {}, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @abstract - */ - _clear: function () {}, - /** - * @private - */ - _replaceMedia: function ($media) { - this.$media.replaceWith($media); - this.$media = $media; - this.media = $media[0]; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onSearchInput: function (ev) { - this.search($(ev.currentTarget).val() || ''); - this.hasSearched = true; - }, -}); - -/** - * Let users choose an image, including uploading a new image in odoo. - */ -var ImageWidget = MediaWidget.extend({ - template: 'web_editor.dialog.image', - 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_media_url_button': '_onUploadURLButtonClick', - 'input input.o_we_url_input': '_onURLInputChange', - 'click .existing-attachments [data-src]': '_onImageClick', - 'dblclick .existing-attachments [data-src]': '_onImageDblClick', - 'click .o_existing_attachment_remove': '_onRemoveClick', - 'click .o_load_more': '_onLoadMoreClick', - }), - - IMAGES_PER_ROW: 6, - IMAGES_ROWS: 5, - - /** - * @constructor - */ - init: function (parent, media, options) { - this._super.apply(this, arguments); - - this.imagesRows = this.IMAGES_ROWS; - this.IMAGES_DISPLAYED_TOTAL = this.IMAGES_PER_ROW * this.imagesRows; - - this.options = options; - this.accept = options.accept || (options.document ? '*/*' : 'image/*'); - if (options.domain) { - this.domain = typeof options.domain === 'function' ? options.domain() : options.domain; - } else if (options.res_id) { - this.domain = ['|', - '&', ['res_model', '=', options.res_model], ['res_id', '=', options.res_id], - ['res_model', '=', 'ir.ui.view']]; - } else { - this.domain = [['res_model', '=', 'ir.ui.view']]; - } - - this.multiImages = options.multiImages; - - this.firstFilters = options.firstFilters || []; - this.lastFilters = options.lastFilters || []; - - this.images = []; - }, - /** - * @override - */ - willStart: function () { - return $.when( - this._super.apply(this, arguments), - this.search('', true) - ); - }, - /** - * @override - */ - start: function () { - var def = this._super.apply(this, arguments); - var self = this; - - this._renderImages(true); - - var o = { - url: null, - alt: null, - }; - if (this.$media.is('img')) { - o.url = this.$media.attr('src'); - } else if (this.$media.is('a.o_image')) { - o.url = this.$media.attr('href').replace(/[?].*/, ''); - o.id = +o.url.match(/\/web\/content\/(\d+)/, '')[1]; - } - if (o.url) { - self._toggleImage(_.find(self.records, function (record) { return record.url === o.url;}) || o, true); - } - - return def; - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - save: function () { - var self = this; - if (this.multiImages) { - return this.images; - } - - var img = this.images[0]; - if (!img) { - return this.media; - } - - var def = $.when(); - if (!img.access_token) { - def = this._rpc({ - model: 'ir.attachment', - method: 'generate_access_token', - args: [[img.id]] - }).then(function (access_token) { - img.access_token = access_token[0]; - }); - } - - return def.then(function () { - if (!img.isDocument) { - if (img.access_token && self.options.res_model !== 'ir.ui.view') { - img.src += _.str.sprintf('?access_token=%s', img.access_token); - } - if (!self.$media.is('img')) { - // Note: by default the images receive the bootstrap opt-in - // img-fluid class. We cannot make them all responsive - // by design because of libraries and client databases img. - self._replaceMedia($('<img/>', {class: 'img-fluid o_we_custom_image'})); - } - self.$media.attr('src', img.src); - - } else { - if (!self.$media.is('a')) { - $('.note-control-selection').hide(); - self._replaceMedia($('<a/>')); - } - var href = '/web/content/' + img.id + '?'; - if (img.access_token && self.options.res_model !== 'ir.ui.view') { - href += _.str.sprintf('access_token=%s&', img.access_token); - } - href += 'unique=' + img.checksum + '&download=true'; - self.$media.attr('href', href); - self.$media.addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype); - } - - self.$media.attr('alt', img.alt); - var style = self.style; - if (style) { - self.$media.css(style); - } - - if (self.options.onUpload) { - // We consider that when selecting an image it is as if we upload it in the html content. - self.options.onUpload([img]); - } - - // Remove crop related attributes - if (self.$media.attr('data-aspect-ratio')) { - var attrs = ['aspect-ratio', 'x', 'y', 'width', 'height', 'rotate', 'scale-x', 'scale-y']; - _.each(attrs, function (attr) { - self.$media.removeData(attr); - self.$media.removeAttr('data-' + attr); - }); - } - return self.media; - }); - }, - /** - * @override - */ - search: function (needle, noRender) { - var self = this; - if (!noRender) { - this.$('input.o_we_url_input').val('').trigger('input').trigger('change'); - } - // TODO: Expand this for adding SVG - var domain = this.domain.concat(['|', ['mimetype', '=', false], ['mimetype', this.options.document ? 'not in' : 'in', ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png']]]); - if (needle && needle.length) { - domain.push('|', ['datas_fname', 'ilike', needle], ['name', 'ilike', needle]); - } - domain.push('|', ['datas_fname', '=', false], '!', ['datas_fname', '=like', '%.crop'], '!', ['name', '=like', '%.crop']); - return this._rpc({ - model: 'ir.attachment', - method: 'search_read', - args: [], - kwargs: { - domain: domain, - fields: ['name', 'datas_fname', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token'], - order: [{name: 'id', asc: false}], - context: weContext.get(), - }, - }).then(function (records) { - self.records = _.chain(records) - .filter(function (r) { - return (r.type === "binary" || r.url && r.url.length > 0); - }) - .uniq(function (r) { - return (r.url || r.id); - }) - .sortBy(function (r) { - if (_.any(self.firstFilters, function (filter) { - var regex = new RegExp(filter, 'i'); - return r.name.match(regex) || r.datas_fname && r.datas_fname.match(regex); - })) { - return -1; - } - if (_.any(self.lastFilters, function (filter) { - var regex = new RegExp(filter, 'i'); - return r.name.match(regex) || r.datas_fname && r.datas_fname.match(regex); - })) { - return 1; - } - return 0; - }) - .value(); - - _.each(self.records, function (record) { - record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes - record.isDocument = !(/gif|jpe|jpg|png/.test(record.mimetype)); - }); - if (!noRender) { - self._renderImages(); - self._adaptLoadMore(); - } - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - */ - _adaptLoadMore: function () { - 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); - }, - /** - * @override - */ - _clear: function () { - this.media.className = this.media.className.replace(/(^|\s+)((img(\s|$)|img-(?!circle|rounded|thumbnail))[^\s]*)/g, ' '); - }, - /** - * @private - */ - _highlightSelectedImages: function () { - var self = this; - this.$('.o_existing_attachment_cell.o_selected').removeClass("o_selected"); - var $select = this.$('.o_existing_attachment_cell [data-src]').filter(function () { - var $img = $(this); - return !!_.find(self.images, function (v) { - return (v.url === $img.data("src") || ($img.data("url") && v.url === $img.data("url")) || v.id === $img.data("id")); - }); - }); - $select.closest('.o_existing_attachment_cell').addClass("o_selected"); - return $select; - }, - /** - * @private - */ - _loadMoreImages: function (forceSearch) { - this.imagesRows += 2; - this.IMAGES_DISPLAYED_TOTAL = this.imagesRows * this.IMAGES_PER_ROW; - if (!forceSearch) { - this._renderImages(); - this._adaptLoadMore(); - } else { - this.search(this.$('.o_we_search').val() || ''); - } - }, - /** - * @private - */ - _renderImages: function (withEffect) { - var self = this; - var rows = _(this.records).chain() - .slice(0, this.IMAGES_DISPLAYED_TOTAL) - .groupBy(function (a, index) { return Math.floor(index / self.IMAGES_PER_ROW); }) - .values() - .value(); - - this.$('.form-text').empty(); - - // Render menu & content - this.$('.existing-attachments').replaceWith( - QWeb.render('web_editor.dialog.files.existing.content', { - rows: rows, - isDocument: this.options.document, - withEffect: withEffect, - }) - ); - - var $divs = this.$('.o_image'); - var imageDefs = _.map($divs, function (el) { - var $div = $(el); - if (/gif|jpe|jpg|png/.test($div.data('mimetype'))) { - var $img = $('<img/>', { - class: 'img-fluid', - src: $div.data('url') || $div.data('src'), - }); - var def = $.Deferred(); - $img[0].onload = def.resolve.bind(def); - $div.addClass('o_webimage').append($img); - return def; - } - }); - if (withEffect) { - $.when.apply($, imageDefs).then(function () { - _.delay(function () { - $divs.removeClass('o_image_loading'); - }, 400); - }); - } - this._highlightSelectedImages(); - }, - /** - * @private - */ - _toggleImage: function (attachment, clearSearch, forceSelect) { - if (this.multiImages) { - var img = _.select(this.images, function (v) { return v.id === attachment.id; }); - if (img.length) { - if (!forceSelect) { - this.images.splice(this.images.indexOf(img[0]), 1); - } - } else { - this.images.push(attachment); - } - } else { - this.images = [attachment]; - } - this._highlightSelectedImages(); - - if (clearSearch) { - this.search(''); - } - }, - /** - * @private - */ - _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.$('.o_file_upload_iframe'); - $iframe.on('load', function () { - var iWindow = $iframe[0].contentWindow; - var attachments = iWindow.attachments; - var error = iWindow.error; - - self.$('.well > span').remove(); - self.$('.well > div').show(); - _.each(attachments, function (record) { - record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes - record.isDocument = !(/gif|jpe|jpg|png/.test(record.mimetype)); - }); - if (error || !attachments.length) { - _processFile(null, error || !attachments.length); - } - self.images = attachments; - for (var i = 0; i < attachments.length; i++) { - _processFile(attachments[i], error); - } - - if (self.options.onUpload) { - self.options.onUpload(attachments); - } - - function _processFile(attachment, error) { - if (!error) { - self._toggleImage(attachment, true); - } else { - self.$el.addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); - self.$el.find('.form-text').text(error); - } - } - }); - this.$el.submit(); - - // Empty file input - this.$('.o_file_input').val(''); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _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, forceSelect); - }, - /** - * @private - */ - _onImageDblClick: function (ev) { - this._onImageClick(ev, true); - this.trigger_up('save_request'); - }, - /** - * @private - */ - _onImageSelection: function () { - var $form = this.$('form'); - 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_media_button').removeClass('btn-danger btn-success'); - this._uploadFile(); - }, - /** - * @private - */ - _onRemoveClick: function (ev) { - var self = this; - DialogBase.confirm(this, _t("Are you sure you want to delete this file ?"), { - 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 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], - })); - }); - } - }); - }, - /** - * @private - */ - _onURLInputChange: function (ev) { - var $input = $(ev.currentTarget); - var $button = this.$('.o_upload_media_url_button'); - var $success = this.$('.o_we_url_success'); - var $warning = this.$('.o_we_url_warning'); - var $error = this.$('.o_we_url_error'); - - var inputValue = $input.val(); - var emptyValue = (inputValue === ''); - - var isURL = /^.+\..+$/.test(inputValue); // TODO improve - var isImage = _.any(['.gif', '.jpe', '.jpg', '.png'], function (format) { - return inputValue.endsWith(format); - }); - - $button.toggleClass('btn-secondary', emptyValue).toggleClass('btn-primary', !emptyValue) - .prop('disabled', !isURL); - if (!this.options.document) { - $button.text((isURL && !isImage) ? _t("Add as document") : _t("Add image")); - } - $success.toggleClass('d-none', !isURL); - $warning.toggleClass('d-none', !isURL || this.options.document || isImage); - $error.toggleClass('d-none', emptyValue || isURL); - }, - /** - * @private - */ - _onUploadButtonClick: function () { - this.$('input[type=file]').click(); - }, - /** - * @private - */ - _onUploadButtonNoOptimizationClick: function () { - this.$('input[name="disable_optimization"]').val('1'); - this.$('.o_upload_media_button').click(); - }, - /** - * @private - */ - _onUploadURLButtonClick: function () { - this._uploadFile(); - }, - /** - * @private - */ - _onLoadMoreClick: function () { - this._loadMoreImages(); - }, - /** - * @override - */ - _onSearchInput: function () { - this.imagesRows = this.IMAGES_ROWS; - this.IMAGES_DISPLAYED_TOTAL = this.IMAGES_PER_ROW * this.imagesRows; - this._super.apply(this, arguments); - }, -}); - -/** - * Let users choose a font awesome icon, support all font awesome loaded in the - * css files. - */ -var IconWidget = MediaWidget.extend({ - template: 'web_editor.dialog.font-icons', - events: _.extend({}, MediaWidget.prototype.events || {}, { - 'click .font-icons-icon': '_onIconClick', - 'dblclick .font-icons-icon': '_onIconDblClick', - }), - - /** - * @constructor - */ - init: function (parent, media) { - this._super.apply(this, arguments); - - base.computeFonts(); - this.iconsParser = base.fontIcons; - this.alias = _.flatten(_.map(this.iconsParser, function (data) { - return data.alias; - })); - }, - /** - * @override - */ - 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++) { - var cls = classes[i]; - if (_.contains(this.alias, cls)) { - this.selectedIcon = cls; - this._highlightSelectedIcon(); - } - } - this.nonIconClasses = _.without(classes, this.selectedIcon); - - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - save: function () { - var style = this.$media.attr('style') || ''; - var iconFont = this._getFont(this.selectedIcon) || {base: 'fa', font: ''}; - var finalClasses = _.uniq(this.nonIconClasses.concat([iconFont.base, iconFont.font])); - if (!this.$media.is('span')) { - var $span = $('<span/>'); - $span.data(this.$media.data()); - this._replaceMedia($span); - style = style.replace(/\s*width:[^;]+/, ''); - } - this.$media.attr({ - class: _.compact(finalClasses).join(' '), - style: style, - }); - return this.media; - }, - /** - * @override - */ - search: function (needle) { - var iconsParser = this.iconsParser; - if (needle && needle.length) { - iconsParser = []; - _.filter(this.iconsParser, function (data) { - var cssData = _.filter(data.cssData, function (cssData) { - return _.find(cssData.names, function (alias) { - return alias.indexOf(needle) >= 0; - }); - }); - if (cssData.length) { - iconsParser.push({ - base: data.base, - cssData: cssData, - }); - } - }); - } - this.$('div.font-icons-icons').html( - QWeb.render('web_editor.dialog.font-icons.icons', {iconsParser: iconsParser}) - ); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @override - */ - _clear: function () { - this.media.className = this.media.className.replace(/(^|\s)(fa(\s|$)|fa-[^\s]*)/g, ' '); - }, - /** - * @private - */ - _getFont: function (classNames) { - if (!(classNames instanceof Array)) { - classNames = (classNames || "").split(/\s+/); - } - var fontIcon, cssData; - for (var k = 0; k < this.iconsParser.length; k++) { - fontIcon = this.iconsParser[k]; - for (var s = 0; s < fontIcon.cssData.length; s++) { - cssData = fontIcon.cssData[s]; - if (_.intersection(classNames, cssData.names).length) { - return { - base: fontIcon.base, - parser: fontIcon.parser, - font: cssData.names[0], - }; - } - } - } - return null; - }, - /** - * @private - */ - _highlightSelectedIcon: function () { - var self = this; - this.$icons.removeClass('o_selected'); - this.$icons.filter(function (i, el) { - return _.contains($(el).data('alias').split(','), self.selectedIcon); - }).addClass('o_selected'); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onIconClick: function (ev) { - ev.preventDefault(); - ev.stopPropagation(); - - this.selectedIcon = $(ev.currentTarget).data('id'); - this._highlightSelectedIcon(); - }, - /** - * @private - */ - _onIconDblClick: function () { - this.trigger_up('save_request'); - }, -}); - -/** - * Let users choose a video, support all summernote video, and embed iframe. - */ -var VideoWidget = MediaWidget.extend({ - template: 'web_editor.dialog.video', - events: _.extend({}, MediaWidget.prototype.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._onVideoCodeInput = _.debounce(this._onVideoCodeInput, 1000); - }, - /** - * @override - */ - start: function () { - this.$content = this.$('.o_video_dialog_iframe'); - - var $media = $(this.media); - if ($media.hasClass('media_iframe_video')) { - var src = $media.data('oe-expression') || $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); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - save: function () { - 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>' - )); - } - return this.media; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @override - */ - _clear: function () { - if (this.media.dataset.src) { - try { - delete this.media.dataset.src; - } catch (e) { - this.media.dataset.src = undefined; - } - } - this.media.className = this.media.className.replace(/(^|\s)media_iframe_video(\s|$)/g, ' '); - }, - /** - * 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 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 { - return {errorCode: 1}; - } - - if (ytMatch) { - $video.attr('src', $video.attr('src') + '&rel=0'); - } - 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('o_has_error o_has_success').find('.form-control, .custom-select').removeClass('is-invalid is-valid'); - this.$('.o_video_dialog_options div').addClass('d-none'); - - // 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 $optBox = this.$('.o_video_dialog_options'); - - // Show / Hide preview elements - 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) - .end() - .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 - $optBox.find('div.o_' + query.type + '_option').removeClass('d-none'); - - // Hide the entire options box if no options are available - $optBox.toggleClass('d-none', $optBox.find('div:not(.d-none)').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('div').toggleClass('d-none', 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 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 mb-2 mt-2', - 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(); - }, -}); - -/** - * MediaDialog widget. Lets users change a media, including uploading a - * new image, font awsome or video and can change a media into an other - * media. - */ -var MediaDialog = Dialog.extend({ - template: 'web_editor.dialog.media', - xmlDependencies: Dialog.prototype.xmlDependencies.concat( - ['/web_editor/static/src/xml/editor.xml'] - ), - events: _.extend({}, Dialog.prototype.events, { - 'shown.bs.tab a[data-toggle="tab"]': '_onTabChange', - }), - custom_events: _.extend({}, Dialog.prototype.custom_events || {}, { - save_request: '_onSaveRequest', - }), - - /** - * @constructor - */ - init: function (parent, options, $editable, media) { - var self = this; - this._super(parent, _.extend({}, { - title: _t("Select a Media"), - save_text: _t("Add"), - }, options)); - - if ($editable) { - this.$editable = $editable; - this.rte = this.$editable.rte || this.$editable.data('rte'); - } - - this.media = media; - this.$media = $(media); - this.range = range.create(); - - this.multiImages = options.multiImages; - var onlyImages = options.onlyImages || this.multiImages || (this.media && (this.$media.parent().data('oeField') === 'image' || this.$media.parent().data('oeType') === 'image')); - this.noImages = options.noImages; - this.noDocuments = onlyImages || options.noDocuments; - this.noIcons = onlyImages || options.noIcons; - this.noVideos = onlyImages || options.noVideos; - - if (!this.noImages) { - this.imageDialog = new ImageWidget(this, this.media, options); - } - if (!this.noDocuments) { - this.documentDialog = new ImageWidget(this, this.media, _.extend({}, options, {document: true})); - } - if (!this.noIcons) { - this.iconDialog = new IconWidget(this, this.media, options); - } - if (!this.noVideos) { - this.videoDialog = new VideoWidget(this, this.media, options); - } - - this.opened(function () { - var tabToShow = 'icon'; - if (!self.media || self.$media.is('img')) { - tabToShow = 'image'; - } else if (self.$media.is('a.o_image')) { - tabToShow = 'document'; - } else if (self.$media.attr('class').match(/(^|\s)media_iframe_video($|\s)/)) { - tabToShow = 'video'; - } else if (self.$media.parent().attr('class').match(/(^|\s)media_iframe_video($|\s)/)) { - self.$media = self.$media.parent(); - self.media = self.$media[0]; - tabToShow = 'video'; - } - self.$('[href="#editor-media-' + tabToShow + '"]').tab('show'); - }); - }, - /** - * @override - */ - start: function () { - var self = this; - var defs = [this._super.apply(this, arguments)]; - this.$modal.addClass('note-image-dialog'); - this.$modal.find('.modal-dialog').addClass('o_select_media_dialog'); - - if (this.imageDialog) { - defs.push(this.imageDialog.appendTo(this.$("#editor-media-image"))); - } - if (this.documentDialog) { - defs.push(this.documentDialog.appendTo(this.$("#editor-media-document"))); - } - if (this.iconDialog) { - defs.push(this.iconDialog.appendTo(this.$("#editor-media-icon"))); - } - if (this.videoDialog) { - defs.push(this.videoDialog.appendTo(this.$("#editor-media-video"))); - } - - return $.when.apply($, defs).then(function () { - self._setActive(self.imageDialog); - }); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - save: function () { - var self = this; - var args = arguments; - var _super = this._super; - if (this.multiImages) { - // In the case of multi images selection we suppose this was not to - // replace an old media, so we only retrieve the images and save. - return $.when(this.active.save()).then(function (data) { - self.final_data = data; - return _super.apply(self, args); - }); - } - - if (this.rte) { - this.range.select(); - this.rte.historyRecordUndo(this.media); - } - - if (this.media) { - this.$media.html(''); - _.each(['imageDialog', 'documentDialog', 'iconDialog', 'videoDialog'], function (v) { - // Note: hack since imageDialog is the same type as the documentDialog - if (self[v] && self.active._clear.toString() !== self[v]._clear.toString()) { - self[v].clear(); - } - }); - } - - return $.when(this.active.save()).then(function (media) { - if (!self.media && media) { - self.range.insertNode(media, true); - } - self.media = media; - self.$media = $(media); - - self.final_data = self.media; - $(self.final_data).trigger('input').trigger('save'); - $(document.body).trigger("media-saved", self.final_data); // TODO get rid of this - - // Update editor bar after image edition (in case the image change to icon or other) - _.defer(function () { - if (!self.media || !self.media.parentNode) { - return; - } - range.createFromNode(self.media).select(); - simulateMouseEvent(self.media, 'mousedown'); - simulateMouseEvent(self.media, 'mouseup'); - }); - return _super.apply(self, args); - }); - }, - - //-------------------------------------------------------------------------- - // - //-------------------------------------------------------------------------- - - /** - * @private - */ - _setActive: function (widget) { - this.active = widget; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onSaveRequest: function (ev) { - ev.stopPropagation(); - this.save(); - }, - /** - * @private - */ - _onTabChange: function (ev) { - var $target = $(ev.target); - if ($target.is('[href="#editor-media-image"]')) { - this._setActive(this.imageDialog); - } else if ($target.is('[href="#editor-media-document"]')) { - this._setActive(this.documentDialog); - } else if ($target.is('[href="#editor-media-icon"]')) { - this._setActive(this.active = this.iconDialog); - } else if ($target.is('[href="#editor-media-video"]')) { - this._setActive(this.active = this.videoDialog); - } - }, -}); - -/** - * Allows to customize link content and style. - */ -var LinkDialog = Dialog.extend({ - template: 'web_editor.dialog.link', - xmlDependencies: Dialog.prototype.xmlDependencies.concat( - ['/web_editor/static/src/xml/editor.xml'] - ), - events: _.extend({}, Dialog.prototype.events || {}, { - 'input': '_onAnyChange', - 'change': '_onAnyChange', - 'input input[name="url"]': '_onURLInput', - }), - - /** - * @constructor - */ - init: function (parent, options, editable, linkInfo) { - this._super(parent, _.extend({ - title: _t("Link to"), - }, options || {})); - - this.editable = editable; - this.data = linkInfo || {}; - - this.data.className = ""; - - var r = this.data.range; - this.needLabel = !r || (r.sc === r.ec && r.so === r.eo); - - if (this.data.range) { - 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 isLink = this.data.range.isOnAnchor(); - - var sc = r.sc; - var so = r.so; - var ec = r.ec; - var eo = r.eo; - - var nodes; - if (!isLink) { - if (sc.tagName) { - sc = dom.firstChild(so ? sc.childNodes[so] : sc); - so = 0; - } else if (so !== sc.textContent.length) { - if (sc === ec) { - ec = sc = sc.splitText(so); - eo -= so; - } else { - sc = sc.splitText(so); - } - so = 0; - } - if (ec.tagName) { - ec = dom.lastChild(eo ? ec.childNodes[eo - 1] : ec); - eo = ec.textContent.length; - } else if (eo !== ec.textContent.length) { - ec.splitText(eo); - } - - nodes = dom.listBetween(sc, ec); - - // browsers can't target a picture or void node - if (dom.isVoid(sc) || dom.isImg(sc)) { - so = dom.listPrev(sc).length - 1; - sc = sc.parentNode; - } - if (dom.isBR(ec)) { - eo = dom.listPrev(ec).length - 1; - ec = ec.parentNode; - } else if (dom.isVoid(ec) || dom.isImg(sc)) { - eo = dom.listPrev(ec).length; - ec = ec.parentNode; - } - - this.data.range = range.create(sc, so, ec, eo); - this.data.range.select(); - } else { - nodes = dom.ancestor(sc, dom.isAnchor).childNodes; - } - - if (dom.isImg(sc) && nodes.indexOf(sc) === -1) { - nodes.push(sc); - } - if (nodes.length > 1 || dom.ancestor(nodes[0], dom.isImg)) { - var text = ""; - this.data.images = []; - 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 (!isLink && nodes[i].nodeType === 1) { - // just use text nodes from listBetween - } else if (!isLink && i === 0) { - text += nodes[i].textContent.slice(so, Infinity); - } else if (!isLink && i === nodes.length - 1) { - text += nodes[i].textContent.slice(0, eo); - } else { - text += nodes[i].textContent; - } - } - this.data.text = text; - } - } - - this.data.text = this.data.text.replace(/[ \t\r\n]+/g, ' '); - - this._onURLInput = _.debounce(this._onURLInput, 400); - }, - /** - * @override - */ - start: function () { - var self = this; - - this.$('input.link-style').prop('checked', false).first().prop('checked', true); - if (this.data.iniClassName) { - _.each(this.$('input.link-style, select.link-style > option'), function (el) { - var $option = $(el); - if ($option.val() && self.data.iniClassName.indexOf($option.val()) >= 0) { - if ($option.is("input")) { - $option.prop("checked", true); - } else { - $option.parent().val($option.val()); - } - } - }); - } - if (this.data.url) { - var match = /mailto:(.+)/.exec(this.data.url); - this.$('input[name="url"]').val(match ? match[1] : this.data.url); - } - - // Hide the duplicate color buttons (most of the times, primary = alpha - // and secondary = beta for example but this may depend on the theme) - this.opened().then(function () { - var colors = []; - _.each(self.$('.o_btn_preview.o_link_dialog_color_item'), function (btn) { - var $btn = $(btn); - var color = $btn.css('background-color'); - if (_.contains(colors, color)) { - $btn.hide(); // Not remove to be able to edit buttons with those styles - } else { - colors.push(color); - } - }); - }); - - this._adaptPreview(); - - this.$('input:visible:first').focus(); - - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * @override - */ - save: function () { - var data = this._getData(); - if (data === null) { - var $url = this.$('input[name="url"]'); - $url.closest('.form-group').addClass('o_has_error').find('.form-control, .custom-select').addClass('is-invalid'); - $url.focus(); - return $.Deferred().reject(); - } - this.data.text = data.label; - this.data.url = data.url; - this.data.className = data.classes.replace(/\s+/gi, ' ').replace(/^\s+|\s+$/gi, ''); - if (data.classes.replace(/(^|[ ])(btn-secondary|btn-success|btn-primary|btn-info|btn-warning|btn-danger)([ ]|$)/gi, ' ')) { - this.data.style = {'background-color': '', 'color': ''}; - } - this.data.isNewWindow = data.isNewWindow; - this.final_data = this.data; - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - */ - _adaptPreview: function () { - var $preview = this.$("#link-preview"); - var data = this._getData(); - if (data === null) { - return; - } - $preview.attr({ - target: data.isNewWindow ? '_blank' : '', - href: data.url && data.url.length ? data.url : '#', - class: data.classes.replace(/float-\w+/, '') + ' o_btn_preview', - }).html((data.label && data.label.length) ? data.label : data.url); - }, - /** - * @private - */ - _getData: function () { - var $url = this.$('input[name="url"]'); - var url = $url.val(); - var label = this.$('input[name="label"]').val() || url; - - if (label && this.data.images) { - for (var i = 0; i < this.data.images.length; i++) { - label = label.replace(/</, "<").replace(/>/, ">").replace(/\[IMG\]/, this.data.images[i].outerHTML); - } - } - - if ($url.prop('required') && (!url || !$url[0].checkValidity())) { - return null; - } - - var style = this.$('input[name="link_style_color"]:checked').val() || ''; - var shape = this.$('select[name="link_style_shape"]').val() || ''; - var size = this.$('select[name="link_style_size"]').val() || ''; - var shapes = shape.split(','); - var outline = shapes[0] === 'outline'; - shape = shapes.slice(outline ? 1 : 0).join(' '); - var classes = (this.data.className || '') - + (style ? (' btn btn-' + (outline ? 'outline-' : '') + style) : '') - + (shape ? (' ' + shape) : '') - + (size ? (' btn-' + size) : ''); - var isNewWindow = this.$('input[name="is_new_window"]').prop('checked'); - - if (url.indexOf('@') >= 0 && url.indexOf('mailto:') < 0 && !url.match(/^http[s]?/i)) { - url = ('mailto:' + url); - } - return { - label: label, - url: url, - classes: classes, - isNewWindow: isNewWindow, - }; - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onAnyChange: function () { - this._adaptPreview(); - }, - /** - * @private - */ - _onURLInput: function () { - var $linkUrlInput = this.$('#o_link_dialog_url_input'); - $linkUrlInput.closest('.form-group').removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid'); - var isLink = $linkUrlInput.val().indexOf('@') < 0; - this.$('input[name="is_new_window"]').closest('.form-group').toggleClass('d-none', !isLink); - }, -}); - -/** - * CropImageDialog widget. Let users crop an image. - */ -var CropImageDialog = Dialog.extend({ - template: 'web_editor.dialog.crop_image', - xmlDependencies: Dialog.prototype.xmlDependencies.concat( - ['/web_editor/static/src/xml/editor.xml'] - ), - jsLibs: [ - '/web_editor/static/lib/cropper/js/cropper.js', - ], - cssLibs: [ - '/web_editor/static/lib/cropper/css/cropper.css', - ], - events: _.extend({}, Dialog.prototype.events, { - 'click .o_crop_options [data-event]': '_onCropOptionClick', - }), - - /** - * @constructor - */ - init: function (parent, options, $editable, media) { - this.media = media; - this.$media = $(this.media); - var src = this.$media.attr('src').split('?')[0]; - this.aspectRatioList = [ - [_t("Free"), '0/0', 0], - ["16:9", '16/9', 16 / 9], - ["4:3", '4/3', 4 / 3], - ["1:1", '1/1', 1], - ["2:3", '2/3', 2 / 3], - ]; - this.imageData = { - imageSrc: src, - originalSrc: this.$media.data('crop:originalSrc') || src, // the original src for cropped DB images will be fetched later - mimetype: this.$media.data('crop:mimetype') || (_.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg'), // the mimetype for DB images will be fetched later - aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1], - isExternalImage: src.substr(0, 5) !== 'data:' && src[0] !== '/' && src.indexOf(window.location.host) < 0, - }; - this.options = _.extend({ - title: _t("Crop Image"), - buttons: this.imageData.isExternalImage ? [{ - text: _t("Close"), - close: true, - }] : [{ - text: _t("Save"), - classes: 'btn-primary', - click: this.save, - }, { - text: _t("Discard"), - close: true, - }], - }, options || {}); - this._super(parent, this.options); - }, - /** - * @override - */ - willStart: function () { - var self = this; - var def = this._super.apply(this, arguments); - if (this.imageData.isExternalImage) { - return def; - } - - var defs = [def, ajax.loadLibs(this)]; - var params = {}; - var isDBImage = false; - var matchImageID = this.imageData.imageSrc.match(/^\/web\/image\/(\d+)/); - if (matchImageID) { - params['image_id'] = parseInt(matchImageID[1]); - isDBImage = true; - } else { - var matchXmlID = this.imageData.imageSrc.match(/^\/web\/image\/([^/?]+)/); - if (matchXmlID) { - params['xml_id'] = matchXmlID[1]; - isDBImage = true; - } - } - if (isDBImage) { - defs.push(this._rpc({ - route: '/web_editor/get_image_info', - params: params, - }).then(function (res) { - _.extend(self.imageData, res); - })); - } - return $.when.apply($, defs); - }, - /** - * @override - */ - start: function () { - this.$cropperImage = this.$('.o_cropper_image'); - if (this.$cropperImage.length) { - var data = this.$media.data(); - var ratio = 0; - for (var i = 0; i < this.aspectRatioList.length; i++) { - if (this.aspectRatioList[i][1] === data.aspectRatio) { - ratio = this.aspectRatioList[i][2]; - break; - } - } - this.$cropperImage.cropper({ - viewMode: 1, - autoCropArea: 1, - aspectRatio: ratio, - data: _.pick(data, 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY') - }); - } - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - if (this.$cropperImage.length) { - this.$cropperImage.cropper('destroy'); - } - this._super.apply(this, arguments); - }, - /** - * Updates the DOM image with cropped data and associates required - * information for a potential future save (where required cropped data - * attachments will be created). - * - * @override - */ - save: function () { - var self = this; - var cropperData = this.$cropperImage.cropper('getData'); - - // Mark the media for later creation of required cropped attachments... - this.$media.addClass('o_cropped_img_to_save'); - - // ... and attach required data - this.$media.data('crop:resModel', this.options.res_model); - this.$media.data('crop:resID', this.options.res_id); - this.$media.data('crop:id', this.imageData.id); - this.$media.data('crop:mimetype', this.imageData.mimetype); - this.$media.data('crop:originalSrc', this.imageData.originalSrc); - - // Mark the media with the cropping information which is required for - // a future crop edition - this.$media - .attr('data-aspect-ratio', this.imageData.aspectRatio) - .data('aspectRatio', this.imageData.aspectRatio); - _.each(cropperData, function (value, key) { - key = _.str.dasherize(key); - self.$media.attr('data-' + key, value); - self.$media.data(key, value); - }); - - // Update the media with base64 source for preview before saving - var canvas = this.$cropperImage.cropper('getCroppedCanvas', { - width: cropperData.width, - height: cropperData.height, - }); - this.$media.attr('src', canvas.toDataURL(this.imageData.mimetype)); - - this.$media.trigger('content_changed'); - - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when a crop option is clicked -> change the crop area accordingly. - * - * @private - * @param {MouseEvent} ev - */ - _onCropOptionClick: function (ev) { - var $option = $(ev.currentTarget); - var opt = $option.data('event'); - var value = $option.data('value'); - switch (opt) { - case 'ratio': - this.$cropperImage.cropper('reset'); - this.imageData.aspectRatio = $option.data('label'); - this.$cropperImage.cropper('setAspectRatio', value); - break; - case 'zoom': - case 'rotate': - case 'reset': - this.$cropperImage.cropper(opt, value); - break; - case 'flip': - var direction = value === 'horizontal' ? 'x' : 'y'; - var scaleAngle = -$option.data(direction); - $option.data(direction, scaleAngle); - this.$cropperImage.cropper('scale' + direction.toUpperCase(), scaleAngle); - break; - } - }, -}); - -return { - Dialog: Dialog, - AltDialog: AltDialog, - MediaDialog: MediaDialog, - LinkDialog: LinkDialog, - CropImageDialog: CropImageDialog, - ImageWidget: ImageWidget, -}; -}); |
