diff options
| author | Christophe Simonis <chs@odoo.com> | 2019-09-20 17:33:22 +0200 |
|---|---|---|
| committer | Christophe Simonis <chs@odoo.com> | 2019-09-20 17:34:45 +0200 |
| commit | 64e43808b79badb5869a8d73cc1cc3f3b3aa8e12 (patch) | |
| tree | 9a22d5f6c03073a4d10faedea8df23a5211e8b8f /addons/web_editor/static/src | |
| parent | 944891ced081cda3b8b3db5faf3b7ac23b6b4ce8 (diff) | |
| parent | 58a83d12228fbbd4e9dde542e133d264204ee0f3 (diff) | |
[MERGE] forward port branch saas-12.5 up to 58a83d12228
Diffstat (limited to 'addons/web_editor/static/src')
13 files changed, 191 insertions, 61 deletions
diff --git a/addons/web_editor/static/src/js/backend/field_html.js b/addons/web_editor/static/src/js/backend/field_html.js index 0be20743132..cc06bc53c96 100644 --- a/addons/web_editor/static/src/js/backend/field_html.js +++ b/addons/web_editor/static/src/js/backend/field_html.js @@ -432,6 +432,8 @@ var FieldHtml = basic_fields.DebouncedField.extend(TranslatableFieldMixin, { if (ev.offsetX > 0) { return; } + ev.stopPropagation(); + ev.preventDefault(); var checked = $(ev.target).hasClass('o_checked'); var checklistId = parseInt(($(ev.target).attr('id') || '0').replace(/^checklist-id-/, '')); diff --git a/addons/web_editor/static/src/js/editor/rte.js b/addons/web_editor/static/src/js/editor/rte.js index bfd7994b161..529bf68887e 100644 --- a/addons/web_editor/static/src/js/editor/rte.js +++ b/addons/web_editor/static/src/js/editor/rte.js @@ -323,11 +323,20 @@ var RTEWidget = Widget.extend({ }); // start element observation - $(document).on('content_changed', '.o_editable', function (ev) { + $(document).on('content_changed', function (ev) { self.trigger_up('rte_change', {target: ev.target}); + + // Add the dirty flag to the element that changed by either adding + // it on the highest editable ancestor or, if there is no editable + // ancestor, on the element itself (that element may not be editable + // but if it received a content_changed event, it should be marked + // as dirty to allow for custom savings). if (!ev.__isDirtyHandled) { - $(this).addClass('o_dirty'); ev.__isDirtyHandled = true; + + var el = ev.target; + var dirty = el.closest('.o_editable') || el; + dirty.classList.add('o_dirty'); } }); @@ -586,11 +595,16 @@ var RTEWidget = Widget.extend({ * page element) */ _saveElement: function ($el, context, withLang) { + var viewID = $el.data('oe-id'); + if (!viewID) { + return Promise.resolve(); + } + return this._rpc({ model: 'ir.ui.view', method: 'save', args: [ - $el.data('oe-id'), + viewID, this._getEscapedElement($el).prop('outerHTML'), $el.data('oe-xpath') || null, ], @@ -720,7 +734,7 @@ var RTEWidget = Widget.extend({ // selected too) // // The triple click behavior is reimplemented for all browsers here - if (ev.originalEvent.detail === 3) { + if (ev.originalEvent && ev.originalEvent.detail === 3) { // Select the whole content inside the deepest DOM element that was // triple-clicked range.create(ev.target, 0, ev.target, ev.target.childNodes.length).select(); diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js index b5b74f64466..95ce86572ae 100644 --- a/addons/web_editor/static/src/js/editor/rte.summernote.js +++ b/addons/web_editor/static/src/js/editor/rte.summernote.js @@ -452,6 +452,9 @@ eventHandler.modules.linkDialog.showLinkDialog = function ($editable, $dialog, l $editable.data('range').select(); $editable.data('NoteHistory').recordUndo(); + var link = linkInfo.range.commonAncestor().closest('a'); + linkInfo.className = link && link.className; + var def = new $.Deferred(); core.bus.trigger('link_dialog_demand', { $editable: $editable, diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index c9571f59c51..b94c7108122 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -77,7 +77,16 @@ var SnippetEditor = Widget.extend({ return $clone; }, start: this._onDragAndDropStart.bind(this), - stop: this._onDragAndDropStop.bind(this), + stop: function () { + // Delay our stop handler so that some summernote handlers + // which occur on mouseup (and are themself delayed) are + // executed first (this prevents the library to crash + // because our stop handler may change the DOM). + var args = arguments; + setTimeout(function () { + self._onDragAndDropStop.apply(self, args); + }, 0); + }, }); } else { this.$('.o_move_handle').addClass('d-none'); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index f124d1841f0..eec85058ebc 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -700,6 +700,17 @@ registry.colorpicker = SnippetOption.extend({ this.$el.find('we-collapse').append($pt); } + + // TODO refactor in master + // The primary and secondary are hardcoded here (but marked as hidden) + // so they can be removed from snippets when selecting another color. + // Normally, the chosable colors do not contain them, which prevents + // them to be removed. Indeed, normally, the 'alpha' and 'beta' colors + // (which are the same) are displayed instead... but not for all themes. + var $colorpicker = this.$el.find('.colorpicker'); + $colorpicker.append($('<button/>', {'class': 'd-none', 'data-color': 'primary'})); + $colorpicker.append($('<button/>', {'class': 'd-none', 'data-color': 'secondary'})); + var classes = []; this.$el.find('.colorpicker button').each(function () { var $color = $(this); @@ -838,23 +849,18 @@ registry.background = SnippetOption.extend({ * @see this.selectClass for parameters */ chooseImage: function (previewMode, value, $opt) { - // Put fake image in the DOM, edit it and use it as background-image - var $image = $('<img/>', {class: 'd-none', src: value === 'true' ? '' : value}).appendTo(this.$target); + var options = this._getMediaDialogOptions(); + var media = this._getEditableMedia(); - var $editable = this.$target.closest('.o_editable'); - var _editor = new weWidgets.MediaDialog(this, { - 'onlyImages': true, - 'firstFilters': ['background'], - 'res_model': $editable.data('oe-model'), - 'res_id': $editable.data('oe-id'), - }, $image[0]).open(); - - _editor.on('save', this, function () { - this._setCustomBackground($image.attr('src')); + var _editor = new weWidgets.MediaDialog(this, options, media).open(); + _editor.on('save', this, data => { + this._onSaveMediaDialog(data); this.$target.trigger('content_changed'); }); - _editor.on('closed', this, function () { - $image.remove(); + _editor.on('closed', this, () => { + if (media.classList.contains('o_we_fake_image')) { + media.parentNode.removeChild(media); + } }); }, @@ -871,16 +877,7 @@ registry.background = SnippetOption.extend({ return; } this.$target.off('.background-option') - .on('background-color-event.background-option', (function (e, previewMode) { - e.stopPropagation(); - if (e.currentTarget !== e.target) { - return; - } - if (previewMode === false) { - this.__customImageSrc = undefined; - } - this.background(previewMode); - }).bind(this)); + .on('background-color-event.background-option', this._onBackgroundColorUpdate.bind(this)); }, /** * @override @@ -897,6 +894,37 @@ registry.background = SnippetOption.extend({ //-------------------------------------------------------------------------- /** + * Returns a media element the media dialog will be able to edit to use + * the result as the snippet's background somehow. + * + * @private + * @returns {HTMLElement} + */ + _getEditableMedia: function () { + var $image = $('<img/>', { + class: 'd-none o_we_fake_image', + }).appendTo(this.$target); + return $image[0]; + }, + /** + * Returns the options to be given to the MediaDialog instance when choosing + * a snippet's background. + * + * @private + * @returns {Object} + */ + _getMediaDialogOptions: function () { + var $editable = this.$target.closest('.o_editable'); + return { + noDocuments: true, + noIcons: true, + noVideos: true, + firstFilters: ['background'], + res_model: $editable.data('oe-model'), + res_id: $editable.data('oe-id'), + }; + }, + /** * Returns the src value from a css value related to a background image * (e.g. "url('blabla')" => "blabla" / "none" => ""). * @@ -941,6 +969,42 @@ registry.background = SnippetOption.extend({ this._setActive(); this.$target.trigger('snippet-option-change', [this]); }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Called on background-color update (useful to remove the background to be + * able to see the chosen color). + * + * @private + * @param {Event} ev + * @param {boolean|string} previewMode + * @returns {boolean} true if the color has been applied (removing the + * background) + */ + _onBackgroundColorUpdate: function (ev, previewMode) { + ev.stopPropagation(); + if (ev.currentTarget !== ev.target) { + return false; + } + if (previewMode === false) { + this.__customImageSrc = undefined; + } + this.background(previewMode); + return true; + }, + /** + * Called on media dialog save (when choosing a snippet's background) -> + * sets the resulting media as the snippet's background somehow. + * + * @private + * @param {Object} data + */ + _onSaveMediaDialog: function (data) { + this._setCustomBackground(data.src); + }, }); /** diff --git a/addons/web_editor/static/src/js/editor/summernote.js b/addons/web_editor/static/src/js/editor/summernote.js index 6c62758142d..c2cf0d68d6c 100644 --- a/addons/web_editor/static/src/js/editor/summernote.js +++ b/addons/web_editor/static/src/js/editor/summernote.js @@ -1790,6 +1790,7 @@ $.summernote.pluginEvents.insertUnorderedList = function (event, editor, layoutI } r.clean().select(); event.preventDefault(); + return false; }; $.summernote.pluginEvents.insertOrderedList = function (event, editor, layoutInfo) { @@ -1797,6 +1798,7 @@ $.summernote.pluginEvents.insertOrderedList = function (event, editor, layoutInf }; $.summernote.pluginEvents.insertCheckList = function (event, editor, layoutInfo) { $.summernote.pluginEvents.insertUnorderedList(event, editor, layoutInfo, "checklist"); + $(range.create().sc.parentNode).trigger('input'); // to update checklist-id }; $.summernote.pluginEvents.indent = function (event, editor, layoutInfo, outdent) { var $editable = layoutInfo.editable(); @@ -2469,6 +2471,7 @@ function mouseDownChecklist (e) { if (!dom.isLi(e.target) || !$(e.target).parent('ul.o_checklist').length || e.offsetX > 0) { return; } + e.stopPropagation(); e.preventDefault(); var checked = $(e.target).hasClass('o_checked'); $(e.target).toggleClass('o_checked', !checked); @@ -2478,12 +2481,14 @@ function mouseDownChecklist (e) { }); if (checked) { $sublevel.removeClass('o_checked'); - $parents.prev('ul.o_checklist li').removeClass('o_checked'); + do { + $parents = $parents.prev('ul.o_checklist li').removeClass('o_checked'); + } while ($parents.length); } else { $sublevel.addClass('o_checked'); var $lis; do { - $lis = $parents.not(':has(li:not(.o_checked))').prev('ul.o_checklist li:not(.o_checked)'); + $lis = $parents.not(':has(li[id^="checklist-id"]:not(.o_checked))').prev('ul.o_checklist li:not(.o_checked)'); $lis.addClass('o_checked'); } while ($lis.length); } diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/crop_dialog.js b/addons/web_editor/static/src/js/wysiwyg/widgets/crop_dialog.js index 19623d05f5e..9f827410221 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/crop_dialog.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/crop_dialog.js @@ -14,12 +14,6 @@ var CropImageDialog = Dialog.extend({ xmlDependencies: Dialog.prototype.xmlDependencies.concat( ['/web_editor/static/src/xml/wysiwyg.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', }), @@ -116,8 +110,9 @@ var CropImageDialog = Dialog.extend({ } } this.$cropperImage.cropper({ - viewMode: 1, - autoCropArea: 1, + viewMode: 2, + dragMode: 'move', + autoCropArea: 1.0, aspectRatio: ratio, data: _.pick(data, 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY') }); diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js index cbb39ea6173..c291a00a0c7 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js @@ -1009,8 +1009,9 @@ var VideoWidget = MediaWidget.extend({ /** * @constructor */ - init: function (parent, media) { + init: function (parent, media, options) { this._super.apply(this, arguments); + this.isForBgVideo = !!options.isForBgVideo; this._onVideoCodeInput = _.debounce(this._onVideoCodeInput, 1000); }, /** @@ -1021,7 +1022,7 @@ var VideoWidget = MediaWidget.extend({ if (this.media) { var $media = $(this.media); - var src = $media.data('oe-expression') || $media.data('src') || ''; + var src = $media.data('oe-expression') || $media.data('src') || ($media.is('iframe') ? $media.attr('src') : '') || ''; this.$('textarea#o_video_text').val(src); this.$('input#o_video_autoplay').prop('checked', src.indexOf('autoplay=1') >= 0); @@ -1047,6 +1048,9 @@ var VideoWidget = MediaWidget.extend({ */ save: function () { this._updateVideo(); + if (this.isForBgVideo) { + return Promise.resolve({bgVideoSrc: this.$content.attr('src')}); + } if (this.$('.o_video_dialog_iframe').is('iframe')) { this.$media = $( '<div class="media_iframe_video" data-oe-expression="' + this.$content.attr('src') + '">' + @@ -1135,7 +1139,7 @@ var VideoWidget = MediaWidget.extend({ $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); + $video.attr('src', '//player.vimeo.com/video/' + vimMatch[3] + autoplay.replace('mute', 'muted')); videoType = 'vim'; } else if (dmMatch && dmMatch[2].length) { var justId = dmMatch[2].replace('video/', ''); @@ -1200,13 +1204,13 @@ var VideoWidget = MediaWidget.extend({ 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'), + 'autoplay': this.isForBgVideo || this.$('input#o_video_autoplay').is(':checked'), + 'hide_controls': this.isForBgVideo || this.$('input#o_video_hide_controls').is(':checked'), + 'loop': this.isForBgVideo || this.$('input#o_video_loop').is(':checked'), + 'hide_fullscreen': this.isForBgVideo || this.$('input#o_video_hide_fullscreen').is(':checked'), + 'hide_yt_logo': this.isForBgVideo || this.$('input#o_video_hide_yt_logo').is(':checked'), + 'hide_dm_logo': this.isForBgVideo || this.$('input#o_video_hide_dm_logo').is(':checked'), + 'hide_dm_share': this.isForBgVideo || this.$('input#o_video_hide_dm_share').is(':checked'), }); var $optBox = this.$('.o_video_dialog_options'); @@ -1222,8 +1226,9 @@ var VideoWidget = MediaWidget.extend({ // 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); + // Hide the entire options box if no options are available or if the + // dialog is opened for a background-video + $optBox.toggleClass('d-none', this.isForBgVideo || $optBox.find('div:not(.d-none)').length === 0); if (query.type === 'yt') { // Youtube only: If 'hide controls' is checked, hide 'fullscreen' diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js index 84dd8e7f650..6d1f2b642c7 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js @@ -76,7 +76,7 @@ var MediaDialog = Dialog.extend({ this.activeWidget = this.imageWidget; } else if (this.documentWidget && $media.is('a.o_image')) { this.activeWidget = this.documentWidget; - } else if (this.videoWidget && $media.hasClass('media_iframe_video')) { + } else if (this.videoWidget && $media.is('.media_iframe_video, .o_bg_video_iframe')) { this.activeWidget = this.videoWidget; } else if (this.iconWidget && $media.is('span, i')) { this.activeWidget = this.iconWidget; diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index d33f5455da4..361355c4cab 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -78,9 +78,7 @@ var Wysiwyg = Widget.extend({ this.$editor.data('oe-id', options.recordInfo.res_id); $(document).on('mousedown', this._blur); this._value = this.$target.html() || this.$target.val(); - return this._super.apply(this, arguments).then(() => { - this.$editor.trigger('mouseup'); - }); + return this._super.apply(this, arguments); }, /** * @override diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_snippets.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_snippets.js index 4b095029432..df6d243ffc6 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_snippets.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_snippets.js @@ -20,8 +20,6 @@ Wysiwyg.include({ await this.editor.prependTo(this.$editor[0].ownerDocument.body); this.options.toolbarHandler.append(this.editor.$el); this.$el.on('content_changed', function (e) { - var $editable = $(e.target).closest('[data-oe-model], .o_editable'); - $editable.addClass('o_dirty'); self.trigger_up('wysiwyg_change'); }); } else { diff --git a/addons/web_editor/static/src/scss/wysiwyg.scss b/addons/web_editor/static/src/scss/wysiwyg.scss index 175463da8f1..bfef4658081 100644 --- a/addons/web_editor/static/src/scss/wysiwyg.scss +++ b/addons/web_editor/static/src/scss/wysiwyg.scss @@ -19,9 +19,25 @@ $o-we-zindex: $o-we-overlay-zindex + 1 !default; } // EDITOR TOP BAR AND POPOVER -body.editor_enable { +.note-popover .popover { + max-width: 350px; + left: 50% !important; + transform: translate(-50%, 0); + .popover-body { + white-space: normal; + } +} + +.editor_enable { padding-top: var(--o-we-toolbar-height) !important; + .note-popover .popover .popover-body { + height: var(--o-we-toolbar-height); + } + .note-popover .popover { + transform: none; + } } + #web_editor-top-edit { @include o-position-absolute(0, 0, auto, 0); position: fixed; @@ -34,6 +50,7 @@ body.editor_enable { left: 0 !important; right: 0 !important; border: none !important; + max-width: none; } } @@ -43,14 +60,16 @@ body.editor_enable { margin: 0; padding: 0; } +// avoid popover bar over its opened modal +.note-popover .popover { + z-index: $o-we-overlay-zindex; +} .note-popover .popover .popover-body, .panel-heading.note-toolbar { - height: var(--o-we-toolbar-height); padding-bottom: 0; border-bottom: $o-we-border-width solid $o-we-border-color; background-color: var(--o-we-bg-color); font-family: $o-we-font-family; - white-space: nowrap; // Main layout of buttons .btn-group, .btn { @@ -141,6 +160,24 @@ body.editor_enable { .note-color-palette div .note-color-btn { border-color: var(--o-we-dropdown-bg-color); } + .note-custom-color-palette .note-color-row { + height: auto!important; + .note-color-btn { + float: left; + height: 20px; + width: 20px; + padding: 0; + margin: 0; + border: 1px solid var(--o-we-dropdown-bg-color); + } + } +} +.note-color ul.show { + min-width: 172px !important; +} + +.panel-heading.note-toolbar { + white-space: nowrap; } // ANIMATIONS diff --git a/addons/web_editor/static/src/xml/wysiwyg.xml b/addons/web_editor/static/src/xml/wysiwyg.xml index b9b29a8984e..bcf3a480a93 100644 --- a/addons/web_editor/static/src/xml/wysiwyg.xml +++ b/addons/web_editor/static/src/xml/wysiwyg.xml @@ -383,7 +383,7 @@ <textarea class="form-control" id="o_video_text" placeholder="Copy-paste your URL or embed code here"/> </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> + <small class="text-muted">Accepts <b><i>Youtube</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 mt-4"> <div class="o_yt_option o_vim_option o_dm_option"> |
