diff options
Diffstat (limited to 'addons/web_editor/static/src')
9 files changed, 925 insertions, 1112 deletions
diff --git a/addons/web_editor/static/src/js/editor/editor.js b/addons/web_editor/static/src/js/editor/editor.js index 07947db56e4..31c31e5ce6a 100644 --- a/addons/web_editor/static/src/js/editor/editor.js +++ b/addons/web_editor/static/src/js/editor/editor.js @@ -106,7 +106,9 @@ var EditorMenuBar = Widget.extend({ // Snippets menu if (self.snippetsMenu) { - defs.push(this.snippetsMenu.insertAfter(this.$el)); + // TODO improve this, the editor can be relocated so here, + // 'insertAfter(this.$el)' won't work. + defs.push(this.snippetsMenu.appendTo(this.$el.parent())); } this.rte.editable().find('*').off('mousedown mouseup click'); 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 1f0bda9a1fc..fdb79ebd04a 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -1,6 +1,7 @@ odoo.define('web_editor.snippet.editor', function (require) { 'use strict'; +var concurrency = require('web.concurrency'); var core = require('web.core'); var dom = require('web.dom'); var Widget = require('web.Widget'); @@ -22,13 +23,9 @@ var SnippetEditor = Widget.extend({ template: 'web_editor.snippet_overlay', xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'], events: { - 'click .oe_snippet_parent': '_onParentButtonClick', - 'click .oe_snippet_clone': '_onCloneClick', - 'click .oe_snippet_remove': '_onRemoveClick', - 'mousedown .oe_options [data-toggle="dropdown"]:first': '_onOpenCusomize', + 'mousedown': '_onUIMouseDown', }, custom_events: { - cover_update: '_onCoverUpdate', 'option_update': '_onOptionUpdate', }, @@ -49,57 +46,57 @@ var SnippetEditor = Widget.extend({ this.$target = $(target); this.$target.data('snippet-editor', this); this.templateOptions = templateOptions; + this.isTargetParentEditable = false; + this.isTargetMovable = false; }, /** * @override */ start: function () { - var self = this; var defs = [this._super.apply(this, arguments)]; - // Hide parent button if no parent - var $parent = globalSelector.closest(this.$target.parent()); - if (!$parent.length) { - this.$('.oe_snippet_parent').remove(); - } - // Initialize the associated options (see snippets.options.js) defs.push(this._initializeOptions()); + var $customize = this._customize$Elements[this._customize$Elements.length - 1]; + + this.isTargetParentEditable = this.$target.parent().is(':o_editable'); + this.isTargetMovable = this.isTargetParentEditable && this.isTargetMovable; // Initialize move/clone/remove buttons - if (!this.$target.is(':o_editable')) { - this.$el.find('.oe_snippet_move, .oe_snippet_clone, .oe_snippet_remove').remove(); - } else { + if (this.isTargetMovable) { this.dropped = false; this.$el.draggable({ greedy: true, appendTo: this.$body, cursor: 'move', - handle: '.oe_snippet_move', + handle: '.o_move_handle', cursorAt: { left: 18, top: 14 }, - helper: function () { - var $clone = $(this).clone().css({width: '24px', height: '24px', border: 0}); - $clone.find('.oe_overlay_options >:not(:contains(.oe_snippet_move)), .o_handle').remove(); - $clone.find(':not(.glyphicon)').css({position: 'absolute', top: 0, left: 0}); - $clone.appendTo(self.$body).removeClass('d-none'); + helper: () => { + var $clone = this.$el.clone().css({width: '24px', height: '24px', border: 0}); + $clone.appendTo(this.$body).removeClass('d-none'); return $clone; }, - start: _.bind(self._onDragAndDropStart, self), - stop: _.bind(self._onDragAndDropStop, self) + start: this._onDragAndDropStart.bind(this), + stop: this._onDragAndDropStop.bind(this), }); + } else { + this.$('.o_move_handle').addClass('d-none'); + $customize.find('.oe_snippet_clone').addClass('d-none'); } - this.$target.on('transitionstart.snippet_editor, animationstart.snippet_editor', function () { - self._targetIsAnimated = true; + if (!this.isTargetParentEditable) { + $customize.find('.oe_snippet_remove').addClass('d-none'); + } + + this.$target.on('transitionstart.snippet_editor, animationstart.snippet_editor', () => { + this._targetIsAnimated = true; }); - this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', function () { - self._targetIsAnimated = false; - if (self.$el.is('.oe_active')) { - self.cover(); - } + this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', () => { + this._targetIsAnimated = false; + this.cover(); }); return Promise.all(defs); @@ -143,9 +140,10 @@ var SnippetEditor = Widget.extend({ * Makes the editor overlay cover the associated snippet. */ cover: function () { - if (this._targetIsAnimated) { + if (this._targetIsAnimated || !this.isShown() || !this.$target.length) { // Do not cover a target being animated, it will be covered once the - // animation is completed. + // animation is completed. Also ignore if the overlay is not shown + // yet or that the $target element has been removed. return; } var offset = this.$target.offset(); @@ -161,6 +159,24 @@ var SnippetEditor = Widget.extend({ this.$el.toggleClass('o_top_cover', offset.top < this.$editable.offset().top); }, /** + * @returns {boolean} + */ + isOpened: function () { + return this.$el && this.$el.hasClass('o_we_options_opened'); + }, + /** + * @return {boolean} + */ + isShown: function () { + return this.$el && this.$el.parent().length && this.$el.hasClass('oe_active'); + }, + /** + * @return {boolean} + */ + isSticky: function () { + return this.$el && this.$el.hasClass('o_we_sticky'); + }, + /** * Removes the associated snippet from the DOM and destroys the associated * editor (itself). */ @@ -176,6 +192,7 @@ var SnippetEditor = Widget.extend({ }, }); + this.trigger_up('go_to_parent', {$snippet: this.$target}); var $parent = this.$target.parent(); this.$target.find('*').addBack().tooltip('dispose'); this.$target.remove(); @@ -217,51 +234,25 @@ var SnippetEditor = Widget.extend({ * snippet options are moved to the editor overlay. * * @param {boolean} focus - true to display, false to hide + * @param {boolean} [previewMode=false] + * @param {boolean} [onlyOverlay=false] */ - toggleFocus: function (focus) { + toggleFocus: function (focus, previewMode, onlyOverlay) { if (!this.$el) { return; } - var do_action = (focus ? _do_action_focus : _do_action_blur); - - // Attach own and parent options on the current overlay - var $style_button = this.$el.find('.oe_options'); - var $ul = $style_button.find('.dropdown-menu:first'); - var $headers = $ul.find('.dropdown-header:data(editor)'); - _.each($headers, (function (el) { - var $el = $(el); - var styles = _.values($el.data('editor').styles); - if ($el.data('editor') !== this) { - styles = _.filter(styles, function (option) { return !option.preventChildPropagation; }); - } - var count = 0; - _.each(_.sortBy(styles, '__order').reverse(), function (style) { - if (do_action(style, $el)) { - count++; - } - }); - $el.toggleClass('d-none', count === 0); - }).bind(this)); - - // Activate the overlay - $style_button.toggleClass('d-none', $ul.children(':not(.o_main_header):not(.dropdown-divider):not(.d-none)').length === 0); - this.cover(); - this.$el.toggleClass('oe_active', !!focus); - - if (focus) { - this.trigger_up('snippet_focused'); + if (!previewMode && !onlyOverlay) { + this._toggleOptions(focus ? (this._customize$Elements.length - 1) : false); } - - function _do_action_focus(style, $dest) { - style.$el.insertAfter($dest); - style.onFocus(); - return (style.$el.length > 0); - } - function _do_action_blur(style, $dest) { - style.$el.detach(); - style.onBlur(); - return false; + this._toggleOverlay(!!focus, previewMode ? undefined : !!focus); + }, + /** + * @param {boolean} [isTextEdition=false] + */ + toggleTextEdition: function (isTextEdition) { + if (this.$el) { + this.$el.toggleClass('o_keypress', !!isTextEdition && this.isShown()); } }, @@ -291,58 +282,112 @@ var SnippetEditor = Widget.extend({ * @private */ _initializeOptions: function () { - var self = this; - var $styles = this.$el.find('.oe_options'); - var $ul = $styles.find('.dropdown-menu:first'); + this._customize$Elements = []; this.styles = {}; this.selectorSiblings = []; this.selectorChildren = []; + var $element = this.$target.parent(); + while ($element.length) { + var parentEditor = $element.data('snippet-editor'); + if (parentEditor) { + this._customize$Elements = this._customize$Elements + .concat(parentEditor._customize$Elements); + break; + } + $element = $element.parent(); + } + + var $optionsSection = $(core.qweb.render('web_editor.customize_block_options_section', { + name: this._getName(), + })).data('editor', this); + $optionsSection.on('mouseover', this._onOptionsSectionMouseOver.bind(this)); + $optionsSection.on('mouseleave', this._onOptionsSectionMouseLeave.bind(this)); + $optionsSection.on('click', this._onOptionsSectionClick.bind(this)); + $optionsSection.on('click', '.oe_snippet_clone', this._onCloneClick.bind(this)); + $optionsSection.on('click', '.oe_snippet_remove', this._onRemoveClick.bind(this)); + this._customize$Elements.push($optionsSection); + var i = 0; - $ul.append($('<div/>', {class: 'dropdown-header o_main_header', text: this._getName()}).data('editor', this)); - var defs = _.map(this.templateOptions, function (val, option_id) { - if (!val.selector.is(self.$target)) { + var defs = _.map(this.templateOptions, val => { + if (!val.selector.is(this.$target)) { return; } - if (val['drop-near']) self.selectorSiblings.push(val['drop-near']); - if (val['drop-in']) self.selectorChildren.push(val['drop-in']); + if (val['drop-near']) { + this.selectorSiblings.push(val['drop-near']); + } + if (val['drop-in']) { + this.selectorChildren.push(val['drop-in']); + } var optionName = val.option; var $el = val.$el.children().clone(true).addClass('snippet-option-' + optionName); var option = new (options.registry[optionName] || options.Class)( - self, - val.base_target ? self.$target.find(val.base_target).eq(0) : self.$target, - self.$el, + this, + val.base_target ? this.$target.find(val.base_target).eq(0) : this.$target, + this.$el, val.data, - self.options + this.options ); - self.styles[optionName || _.uniqueId('option')] = option; + this.styles[optionName || _.uniqueId('option')] = option; option.__order = i++; - return option.attachTo($el); + var $optionSection = $(core.qweb.render('web_editor.customize_block_option')); + $optionSection.append($el); + return option.attachTo($optionSection); }); - $ul.append($('<div/>', {class: 'dropdown-divider mt-2'})); - var $parents = this.$target.parents(); - _.each($parents, function (parent) { - var parentEditor = $(parent).data('snippet-editor'); - if (parentEditor) { - for (var styleName in parentEditor.styles) { - if (!parentEditor.styles[styleName].preventChildPropagation) { - $ul.append($('<div/>', {class: 'dropdown-header o_parent_editor_header', text: parentEditor._getName()}).data('editor', parentEditor)); - break; - } - } - } - }); - - if (!this.selectorSiblings.length && !this.selectorChildren.length) { - this.$el.find('.oe_snippet_move, .oe_snippet_clone').addClass('d-none'); - } + this.isTargetMovable = (this.selectorSiblings.length > 0 || this.selectorChildren.length > 0); this.$el.find('[data-toggle="dropdown"]').dropdown(); return Promise.all(defs); }, + /** + * @private + * @param {integer|false} index + */ + _toggleOptions: function (index) { + if (index === false || !this._customize$Elements[index].parent().length) { + this.trigger_up('update_customize_elements', { + customize$Elements: index !== false ? this._customize$Elements : [], + }); + } + this._customize$Elements.forEach(($el, i) => { + var open = (i === index); + + $el.toggleClass('o_we_closed', !open); + + var editor = $el.data('editor'); + editor.$el.toggleClass('o_we_options_opened', open); + + var styles = _.values(editor.styles); + $el.toggleClass('d-none', styles.length === 0); + _.each(_.sortBy(styles, '__order').reverse(), function (style) { + if (open) { + style.$el.appendTo($el); + style.onFocus(); + } else { + style.$el.detach(); + style.onBlur(); + } + }); + }); + }, + /** + * @private + * @param {boolean} show - whether the overlay should be shown or not + * @param {boolean} [sticky] + * whether the overlay should be sticky or not (if the value is + * undefined, the stickyness is left unchanged) + */ + _toggleOverlay: function (show, sticky) { + this.$el.toggleClass('oe_active', show); + this.cover(); + + if (sticky !== undefined) { + this.$el.toggleClass('o_we_sticky', sticky); + } + }, //-------------------------------------------------------------------------- // Handlers @@ -356,7 +401,7 @@ var SnippetEditor = Widget.extend({ */ _onCloneClick: function (ev) { ev.preventDefault(); - this.trigger_up('cover_will_change'); + ev.stopPropagation(); this.trigger_up('snippet_will_be_cloned', {$target: this.$target}); @@ -378,15 +423,6 @@ var SnippetEditor = Widget.extend({ $clone.trigger('content_changed'); }, /** - * Called when the overlay dimensions/positions should be recomputed. - * - * @private - */ - _onCoverUpdate: function () { - this.trigger_up('cover_will_change'); - this.cover(); - }, - /** * Called when the snippet is starting to be dragged thanks to the 'move' * button. * @@ -440,8 +476,6 @@ var SnippetEditor = Widget.extend({ self.dropped = false; }, }); - - this.trigger_up('cover_will_change'); }, /** * Called when the snippet is dropped after being dragged thanks to the @@ -504,6 +538,47 @@ var SnippetEditor = Widget.extend({ }); }, /** + * @private + */ + _onOptionsSectionMouseOver: function (ev) { + var $optionsSection = $(ev.currentTarget); + if (!$optionsSection.hasClass('o_we_closed')) { + return; + } + + this.trigger_up('activate_snippet', { + $snippet: this.$target, + previewMode: true, + }); + }, + /** + * @private + */ + _onOptionsSectionMouseLeave: function (ev) { + this.trigger_up('activate_snippet', { + $snippet: false, + previewMode: true, + }); + }, + /** + * @private + */ + _onOptionsSectionClick: function (ev) { + var $optionsSection = $(ev.currentTarget); + if (!$optionsSection.hasClass('o_we_closed')) { + return; + } + + this.trigger_up('activate_snippet', { + $snippet: this.$target, + previewMode: false, + onlyOverlay: true, + }); + var $neighbors = $optionsSection.nextAll('we-customizeblock-options'); + var editor = $neighbors.length ? $neighbors.last().data('editor') : this; + editor._toggleOptions($optionsSection.index()); + }, + /** * Called when a child editor/option asks for another option to perform a * specific action/react to a specific event. * @@ -511,7 +586,6 @@ var SnippetEditor = Widget.extend({ * @param {OdooEvent} ev */ _onOptionUpdate: function (ev) { - this.trigger_up('cover_will_change'); // If multiple option names are given, we suppose it should not be // propagated to parent editor if (ev.data.optionNames) { @@ -535,28 +609,6 @@ var SnippetEditor = Widget.extend({ } }, /** - * Called when the user opens the customize menu. - * - * @private - * @param {OdooEvent} ev - */ - _onOpenCusomize: function (ev) { - this.trigger_up('request_history_undo_record', {$target: this.$target}); - }, - - /** - * Called when the 'parent' button is clicked. - * - * @private - * @param {Event} ev - */ - _onParentButtonClick: function (ev) { - ev.preventDefault(); - this.trigger_up('go_to_parent', { - $snippet: this.$target, - }); - }, - /** * Called when the 'remove' button is clicked. * * @private @@ -564,10 +616,24 @@ var SnippetEditor = Widget.extend({ */ _onRemoveClick: function (ev) { ev.preventDefault(); - this.trigger_up('cover_will_change'); + ev.stopPropagation(); this.trigger_up('request_history_undo_record', {$target: this.$target}); this.removeSnippet(); }, + /** + * The UI is displayed on hover and become sticky once the related target is + * clicked. If we click the UI while it is not sticky, it should become + * sticky. + * + * @private + */ + _onUIMouseDown: function (ev) { + if (!this.isOpened()) { + this.trigger_up('activate_snippet', { + $snippet: this.$target, + }); + } + } }); /** @@ -576,16 +642,21 @@ var SnippetEditor = Widget.extend({ var SnippetsMenu = Widget.extend({ id: 'oe_snippets', cacheSnippetTemplate: {}, - activeSnippets: [], + events: { + 'click we-collapse-area > we-toggler': '_onCollapseTogglerClick', + }, custom_events: { 'activate_insertion_zones': '_onActivateInsertionZones', + 'activate_snippet': '_onActivateSnippet', 'call_for_each_child_snippet': '_onCallForEachChildSnippet', + 'cover_update': '_onOverlaysCoverUpdate', 'deactivate_snippet': '_onDeactivateSnippet', 'drag_and_drop_stop': '_onDragAndDropStop', 'go_to_parent': '_onGoToParent', 'remove_snippet': '_onRemoveSnippet', 'snippet_removed': '_onSnippetRemoved', 'reload_snippet_dropzones': '_disableUndroppableSnippets', + 'update_customize_elements': '_onUpdateCustomizeElements', }, /** @@ -613,8 +684,8 @@ var SnippetsMenu = Widget.extend({ if (!this.options.snippets) { this.options.snippets = 'web_editor.snippets'; } - this.$activeSnippet = false; this.snippetEditors = []; + this._activateSnippetMutex = new concurrency.Mutex(); this.setSelectorEditableArea(options.$el, options.selectorEditableArea); }, @@ -622,7 +693,6 @@ var SnippetsMenu = Widget.extend({ * @override */ start: function () { - var self = this; var defs = [this._super.apply(this, arguments)]; this.ownerDocument = this.$el[0].ownerDocument; this.$document = $(this.ownerDocument); @@ -631,8 +701,12 @@ var SnippetsMenu = Widget.extend({ // Fetch snippet templates and compute it - defs.push(this.loadSnippets().then(function (html) { - return self._computeSnippetTemplates(html); + defs.push(this.loadSnippets().then(html => { + return this._computeSnippetTemplates(html); + }).then(() => { + this.customizePanel = document.createElement('div'); + this.customizePanel.classList.add('o_we_customize_panel', 'd-none'); + this.$el.append(this.customizePanel); })); // Prepare snippets editor environment @@ -653,7 +727,14 @@ var SnippetsMenu = Widget.extend({ }); var $target = $(srcElement); - if ($target.closest('.oe_overlay, .note-popover').length) { + if ($target.closest('#snippets_menu').length) { + this._activateSnippet(false); + return; + } + if (!$target.closest('body > *').length) { + return; + } + if ($target.closest('#web_editor-top-edit, #oe_snippets, #oe_manipulators, .o_technical_modal, .oe_drop_zone').length) { return; } this._activateSnippet($target); @@ -663,23 +744,23 @@ var SnippetsMenu = Widget.extend({ core.bus.on('snippet_editor_clean_for_save', this, this._onCleanForSaveDemand); // Adapt overlay covering when the window is resized / content changes - var debouncedCoverUpdate = _.debounce(function () { - self.updateCurrentSnippetEditorOverlay(); - }, 200); + var debouncedCoverUpdate = _.throttle(() => { + this.updateCurrentSnippetEditorOverlay(); + }, 50); this.$window.on('resize.snippets_menu', debouncedCoverUpdate); this.$window.on('content_changed.snippets_menu', debouncedCoverUpdate); // On keydown add a class on the active overlay to hide it and show it // again when the mouse moves - this.$document.on('keydown.snippets_menu', function () { - if (self.$activeSnippet && self.$activeSnippet.data('snippet-editor')) { - self.$activeSnippet.data('snippet-editor').$el.addClass('o_keypress'); - } + this.$document.on('keydown.snippets_menu', () => { + this.snippetEditors.forEach(editor => { + editor.toggleTextEdition(true); + }); }); - this.$document.on('mousemove.snippets_menu, mousedown.snippets_menu', function () { - if (self.$activeSnippet && self.$activeSnippet.data('snippet-editor')) { - self.$activeSnippet.data('snippet-editor').$el.removeClass('o_keypress'); - } + this.$document.on('mousemove.snippets_menu, mousedown.snippets_menu', () => { + this.snippetEditors.forEach(editor => { + editor.toggleTextEdition(false); + }); }); // Auto-selects text elements with a specific class and remove this @@ -694,12 +775,19 @@ var SnippetsMenu = Widget.extend({ $(range && range.sc).closest('.o_default_snippet_text').removeClass('o_default_snippet_text'); }); - return Promise.all(defs).then(function () { + return Promise.all(defs).then(() => { + this.$('[data-title]').tooltip({ + delay: 0, + title: function () { + return this.classList.contains('active') ? false : this.dataset.title; + }, + }); + // Trigger a resize event once entering edit mode as the snippets // menu will take part of the screen width (delayed because of // animation). (TODO wait for real animation end) - setTimeout(function () { - self.$window.trigger('resize'); + setTimeout(() => { + this.$window.trigger('resize'); }, 1000); }); }, @@ -782,11 +870,9 @@ var SnippetsMenu = Widget.extend({ * Updates the cover dimensions of the current snippet editor. */ updateCurrentSnippetEditorOverlay: function () { - if (this.$activeSnippet && this.$activeSnippet.data('snippet-editor')) { - this.$activeSnippet.data('snippet-editor').cover(); - } this.snippetEditors = _.filter(this.snippetEditors, function (snippetEditor) { if (snippetEditor.$target.closest('body').length) { + snippetEditor.cover(); return true; } snippetEditor.destroy(); @@ -946,38 +1032,36 @@ var SnippetsMenu = Widget.extend({ * first parent which is one is used instead. * * @param {jQuery|false} $snippet - * The DOM element whose editor need to be enabled. Only disable the - * current one if false is given. + * The DOM element whose editor (and its parent ones) need to be + * enabled. Only disable the current one if false is given. + * @param {boolean} [previewMode=false] + * @param {boolean} [onlyOverlay=false] * @returns {Promise<SnippetEditor>} * (might be async when an editor must be created) */ - _activateSnippet: function ($snippet) { - if ($snippet) { - if (!globalSelector.is($snippet)) { + _activateSnippet: function ($snippet, previewMode, onlyOverlay) { + return this._activateSnippetMutex.exec(() => { + // First disable all shown editors and also sticky ones if not in + // preview mode (so new sticky ones have to be shown) + this.snippetEditors.forEach(editor => { + if (!previewMode || editor.isShown() && !editor.isSticky()) { + editor.toggleFocus(false, previewMode, onlyOverlay); + } + }); + // Take the first parent of the provided DOM (or itself) which + // should have an associated snippet editor and create + enable it. + if ($snippet && $snippet.length) { $snippet = globalSelector.closest($snippet); + if (!$snippet.length) { + return Promise.resolve(null); + } + return this._createSnippetEditor($snippet).then(editor => { + editor.toggleFocus(true, previewMode, onlyOverlay); + return editor; + }); } - if (this.$activeSnippet && this.$activeSnippet[0] === $snippet[0]) { - return Promise.resolve($snippet.data('snippet-editor')); - } - } - var editor = null; - if (this.$activeSnippet) { - editor = this.$activeSnippet.data('snippet-editor'); - if (editor) { - editor.toggleFocus(false); - } - this.$activeSnippet = false; - } - if ($snippet && $snippet.length) { - var self = this; - this.trigger_up('activate_snippet', $snippet); - return this._createSnippetEditor($snippet).then(function (editor) { - self.$activeSnippet = $snippet; - editor.toggleFocus(true); - return editor; - }); - } - return Promise.resolve(editor); + return Promise.resolve(null); + }); }, /** * @private @@ -1362,7 +1446,7 @@ var SnippetsMenu = Widget.extend({ } } - $toInsert = $baseBody.clone().data('name', $baseBody.data('name')); + $toInsert = $baseBody.clone().attr('data-name', $baseBody.data('name')); if (!$selectorSiblings.length && !$selectorChildren.length) { console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in"); @@ -1390,8 +1474,6 @@ var SnippetsMenu = Widget.extend({ } }, }); - - self.trigger_up('cover_will_change'); }, stop: function (ev, ui) { $toInsert.removeClass('oe_snippet_body'); @@ -1435,9 +1517,6 @@ var SnippetsMenu = Widget.extend({ editor.buildSnippet(); }).then(function () { $target.trigger('content_changed'); - if ($target.closest('body').length) { // can be destroyed (eg in test) - self._activateSnippet($target); - } }); }); } else { @@ -1481,6 +1560,15 @@ var SnippetsMenu = Widget.extend({ this._activateInsertionZones(ev.data.$selectorSiblings, ev.data.$selectorChildren); }, /** + * Called when a child editor asks to deactivate the current snippet + * overlay. + * + * @private + */ + _onActivateSnippet: function (ev) { + this._activateSnippet(ev.data.$snippet, ev.data.previewMode, ev.data.onlyOverlay); + }, + /** * Called when a child editor asks to operate some operation on all child * snippet of a DOM element. * @@ -1500,6 +1588,24 @@ var SnippetsMenu = Widget.extend({ this.cleanForSave(); }, /** + * @private + * @param {*} ev + */ + _onCollapseTogglerClick: function (ev) { + this.$('we-collapse-area > we-toggler').not(ev.currentTarget).removeClass('active'); + ev.currentTarget.classList.toggle('active'); + }, + /** + * Called when the overlay dimensions/positions should be recomputed. + * + * @private + */ + _onOverlaysCoverUpdate: function () { + this.snippetEditors.forEach(editor => { + editor.cover(); + }); + }, + /** * Called when a child editor asks to deactivate the current snippet * overlay. * @@ -1548,6 +1654,23 @@ var SnippetsMenu = Widget.extend({ _onSnippetRemoved: function () { this._disableUndroppableSnippets(); }, + /** + * @private + * @param {OdooEvent} ev + */ + _onUpdateCustomizeElements: function (ev) { + while (this.customizePanel.firstChild) { + this.customizePanel.removeChild(this.customizePanel.firstChild); + } + ev.data.customize$Elements.forEach($el => { + this.customizePanel.appendChild($el[0]); + }); + var customize = !!ev.data.customize$Elements.length; + this.$('#o_scroll').toggleClass('d-none', customize); + this.$('.o_we_add_snippet_btn').toggleClass('active', !customize); + this.customizePanel.classList.toggle('d-none', !customize); + this.$('.o_we_customize_snippet_btn').toggleClass('active', customize); + }, }); return { 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 893fd8adebb..63f0c38c7c3 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -18,23 +18,16 @@ var _t = core._t; var SnippetOption = Widget.extend({ events: { 'mouseenter': '_onLinkEnter', - 'mouseenter a': '_onLinkEnter', + 'mouseenter we-button': '_onLinkEnter', 'click': '_onLinkClick', - 'click a': '_onLinkClick', + 'click we-button': '_onLinkClick', 'mouseleave': '_onMouseleave', - 'mouseleave a': '_onMouseleave', - 'mouseleave .dropdown-menu': '_onMouseleave', + 'mouseleave we-button': '_onMouseleave', }, - /** - * When editing a snippet, its options are shown alongside the ones of its - * parent snippets. The parent options are only shown if the following flag - * is set to false (default). - */ - preventChildPropagation: false, /** - * The option `$el` is supposed to be the associated DOM element in the - * options dropdown. The option controls another DOM element: the snippet it + * The option `$el` is supposed to be the associated DOM UI element. + * The option controls another DOM element: the snippet it * customizes, which can be found at `$target`. Access to the whole edition * overlay is possible with `$overlay` (this is not recommended though). * @@ -120,7 +113,7 @@ var SnippetOption = Widget.extend({ /** * Default option method which allows to select one and only one class in * the option classes set and set it on the associated snippet. The common - * case is having a subdropdown with each item having a `data-select-class` + * case is having a sub-collapse with each item having a `data-select-class` * value allowing to choose the associated class. * * @param {boolean|string} previewMode @@ -131,12 +124,14 @@ var SnippetOption = Widget.extend({ * @param {jQuery} $opt - the related DOMElement option */ selectClass: function (previewMode, value, $opt) { - var $group = $opt && $opt.parents('.dropdown-submenu').last(); + var $group = $opt && $opt.parents('we-collapse-area').last(); if (!$group || !$group.length) { $group = this.$el; } - var $lis = $group.find('[data-select-class]').addBack('[data-select-class]'); - var classes = $lis.map(function () {return $(this).data('selectClass');}).get().join(' '); + var $lis = $group.find('[data-select-class]'); + var classes = $lis.map(function () { + return $(this).data('selectClass'); + }).get().join(' '); this.$target.removeClass(classes); if (value) { @@ -146,15 +141,19 @@ var SnippetOption = Widget.extend({ /** * Default option method which allows to select one or multiple classes in * the option classes set and set it on the associated snippet. The common - * case is having a subdropdown with each item having a `data-toggle-class` + * case is having a sub-collapse with each item having a `data-toggle-class` * value allowing to toggle the associated class. * * @see this.selectClass */ toggleClass: function (previewMode, value, $opt) { - var $lis = this.$el.find('[data-toggle-class]').addBack('[data-toggle-class]'); - var classes = $lis.map(function () {return $(this).data('toggleClass');}).get().join(' '); - var activeClasses = $lis.filter('.active, :has(.active)').map(function () {return $(this).data('toggleClass');}).get().join(' '); + var $lis = this.$el.find('[data-toggle-class]'); + var classes = $lis.map(function () { + return $(this).data('toggleClass'); + }).get().join(' '); + var activeClasses = $lis.filter('.active, :has(.active)').map(function () { + return $(this).data('toggleClass'); + }).get().join(' '); this.$target.removeClass(classes).addClass(activeClasses); if (value && previewMode !== 'reset') { @@ -168,7 +167,7 @@ var SnippetOption = Widget.extend({ /** * Override the helper method to search inside the $target element instead - * of the dropdown item element. + * of the UI item element. * * @override */ @@ -211,7 +210,7 @@ var SnippetOption = Widget.extend({ */ _reset: function () { var self = this; - var $actives = this.$el.find('.active').addBack('.active'); + var $actives = this.$el.find('we-button.active'); _.each($actives, function (activeElement) { var $activeElement = $(activeElement); self.__methodNames = _.without.apply(_, [self.__methodNames].concat(_.keys($activeElement.data()))); @@ -233,8 +232,6 @@ var SnippetOption = Widget.extend({ * @param {jQuery} $opt - the related DOMElement option */ _select: function (previewMode, $opt) { - var self = this; - // Options can say they respond to strong choice if (previewMode && ($opt.data('noPreview') || $opt.parent().data('noPreview'))) { return; @@ -244,7 +241,6 @@ var SnippetOption = Widget.extend({ if (!previewMode) { this._reset(); this.trigger_up('request_history_undo_record', {$target: this.$target}); - this.$target.trigger('content_changed'); } // Search for methods (data-...) (i.e. data-toggle-class) on the @@ -275,6 +271,8 @@ var SnippetOption = Widget.extend({ if (!previewMode) { this._setActive(); } + + this.$target.trigger('content_changed'); }, /** * Tweaks the option DOM elements to show the selected value according to @@ -286,7 +284,6 @@ var SnippetOption = Widget.extend({ _setActive: function () { var self = this; this.$el.find('[data-toggle-class]') - .addBack('[data-toggle-class]') .removeClass('active') .filter(function () { var className = $(this).data('toggleClass'); @@ -295,25 +292,20 @@ var SnippetOption = Widget.extend({ .addClass('active'); // Get submenus which are not inside submenus - var $submenus = this.$el.find('.dropdown-submenu') - .addBack('.dropdown-submenu') - .not('.dropdown-submenu .dropdown-submenu'); + var $submenus = this.$el.find('we-collapse-area') + .not('we-collapse-area we-collapse-area'); // Add unique active class for each submenu active item _.each($submenus, function (submenu) { - var $elements = _getSelectClassElements($(submenu)); + var $elements = $(submenu).find('[data-select-class]'); _processSelectClassElements($elements); }); // Add unique active class for out-of-submenu active item - var $externalElements = _getSelectClassElements(this.$el) - .not('.dropdown-submenu *, .dropdown-submenu'); + var $externalElements = this.$el.find('[data-select-class]') + .not('we-collapse-area *, we-collapse-area'); _processSelectClassElements($externalElements); - function _getSelectClassElements($el) { - return $el.find('[data-select-class]') - .addBack('[data-select-class]'); - } function _processSelectClassElements($elements) { var maxNbClasses = -1; $elements.removeClass('active') @@ -343,20 +335,11 @@ var SnippetOption = Widget.extend({ * @param {Event} ev */ _onLinkEnter: function (ev) { - var $opt = $(ev.target).closest('.dropdown-item'); + var $opt = $(ev.target).closest('we-button'); if (!$opt.length) { return; } - var $dsmenu = $opt.parent('.dropdown-submenu'); - if ($dsmenu.length) { - var $menu = $dsmenu.children('.dropdown-menu'); // FIXME - if ($menu.length) { - var menuRightPosition = $dsmenu.offset().left + $dsmenu.outerWidth() + $menu.outerWidth(); - $menu.toggleClass('o_open_to_left', menuRightPosition > $(window).outerWidth()); - } - } - if (!$opt.is(':hasData')) { return; } @@ -371,7 +354,7 @@ var SnippetOption = Widget.extend({ * @param {Event} ev */ _onLinkClick: function (ev) { - var $opt = $(ev.target).closest('.dropdown-item'); + var $opt = $(ev.target).closest('we-button'); if (ev.isDefaultPrevented() || !$opt.length || !$opt.is(':hasData')) { return; } @@ -403,8 +386,6 @@ var SnippetOption = Widget.extend({ var registry = {}; registry.sizing = SnippetOption.extend({ - preventChildPropagation: true, - /** * @override */ @@ -413,11 +394,8 @@ registry.sizing = SnippetOption.extend({ var def = this._super.apply(this, arguments); this.$handles = this.$overlay.find('.o_handle'); - var resizeValues = this._getSize(); - _.each(resizeValues, function (value, key) { - self.$handles.filter('.' + key).toggleClass('readonly', !value); - }); + var resizeValues = this._getSize(); this.$handles.on('mousedown', function (ev) { ev.preventDefault(); @@ -522,8 +500,19 @@ registry.sizing = SnippetOption.extend({ * @override */ onFocus: function () { + var resizeValues = this._getSize(); + _.each(resizeValues, (value, key) => { + this.$handles.filter('.' + key).toggleClass('readonly', !value); + }); + this._onResize(); }, + /** + * @override + */ + onBlur: function () { + this.$handles.addClass('readonly'); + }, //-------------------------------------------------------------------------- // Public @@ -596,6 +585,9 @@ registry.sizing = SnippetOption.extend({ width: ml, left: '-' + ml, }); + this.$overlay.find('.o_handle.e').css({ + width: 0, + }); _.each(this.$overlay.find(".o_handle.n, .o_handle.s"), function (handle) { var $handle = $(handle); var direction = $handle.hasClass('n') ? 'top': 'bottom'; @@ -671,7 +663,7 @@ registry.colorpicker = SnippetOption.extend({ var $clpicker = $(qweb.render('web_editor.colorpicker')); _.each($clpicker.find('.o_colorpicker_section'), function (elem) { - $(elem).prepend("<div class='text-muted mt8'>" + elem.dataset.display + "</div>"); + $(elem).prepend("<div>" + elem.dataset.display + "</div>"); }); // Retrieve excluded palettes list @@ -705,7 +697,7 @@ registry.colorpicker = SnippetOption.extend({ } $pt.find('.o_colorpicker_section_tabs').append($clpicker); - this.$el.find('.dropdown-menu').append($pt); + this.$el.find('we-collapse').append($pt); } var classes = []; @@ -945,7 +937,7 @@ registry.background = SnippetOption.extend({ _setCustomBackground: function (value) { this.__customImageSrc = value; this.background(false, this.__customImageSrc); - this.$target.addClass('oe_custom_bg'); + this.$target.toggleClass('oe_custom_bg', !!value); this._setActive(); this.$target.trigger('snippet-option-change', [this]); }, @@ -1193,7 +1185,7 @@ registry.many2one = SnippetOption.extend({ // create search button and bind search bar this.$btn = $(qweb.render('web_editor.many2one.button')) - .insertAfter(this.$overlay.find('.oe_options')); + .prependTo(this.$el); this.$ul = this.$btn.find('ul'); this.$search = this.$ul.find('li:first'); @@ -1203,17 +1195,10 @@ registry.many2one = SnippetOption.extend({ // move menu item setTimeout(function () { - if (self.$overlay.find('.oe_options').hasClass('d-none')) { - self.$btn.css('height', '0').find('> a').addClass('d-none'); - self.$ul.show().css({ - 'top': '-24px', 'margin': '0', 'padding': '2px 0', 'position': 'relative' - }); - } else { - self.$btn.find('a').on('click', function (e) { - self._clear(); - }); - } - },0); + self.$btn.find('a').on('click', function (e) { + self._clear(); + }); + }, 0); // bind search input this.$search.find('input') diff --git a/addons/web_editor/static/src/scss/web_editor.backend.scss b/addons/web_editor/static/src/scss/web_editor.backend.scss index ecbe42721f3..385bb370fd7 100644 --- a/addons/web_editor/static/src/scss/web_editor.backend.scss +++ b/addons/web_editor/static/src/scss/web_editor.backend.scss @@ -3,7 +3,7 @@ word-wrap: break-word; .note-editable { - min-height: 180px; + min-height: 330px; font: inherit !important; font-family: inherit !important; line-height: inherit !important; @@ -23,13 +23,16 @@ width: 100%; margin: 0; padding: 0; - border: 0; ul > li > p { margin: 0px; } min-height: 300px; min-height: -webkit-calc(100vh - 170px); min-height: calc(100vh - 170px); + + &.o_readonly { + border: none; + } } .rounded { border-radius: .25rem !important; diff --git a/addons/web_editor/static/src/scss/web_editor.variables.scss b/addons/web_editor/static/src/scss/web_editor.variables.scss index fd821c547c2..b03277b2b95 100644 --- a/addons/web_editor/static/src/scss/web_editor.variables.scss +++ b/addons/web_editor/static/src/scss/web_editor.variables.scss @@ -2,14 +2,22 @@ /// This files regroups the variables and mixins which are specific to the editor. /// -$o-we-font-family: Roboto, 'Montserrat', "Segoe UI","Helvetica Neue",Helvetica,Arial,sans-serif !default; +$o-we-font-family: Roboto, 'Montserrat', 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif !default; + +$o-we-font-size: 13px; +$o-we-bg-color: #F7F7F7 !default; +$o-we-bg-color-dark: #a7a7a7 !default; +$o-we-color: #212629 !default; +$o-we-border-width: 1px !default; +$o-we-border-color: rgba(0, 0, 0, 0.125) !default; +$o-we-dropdown-bg-color: #FFFFFF; +$o-we-dropdown-color: inherit; $o-we-color-dark: #2c2c36 !default; $o-we-color-darker: darken($o-we-color-dark, 2%) !default; $o-we-color-divider: #33363e !default; $o-we-color-light: #4e525b !default; -$o-we-color-normal: #50545d !default; -$o-we-color-paper: #fcfcfc !default; +$o-we-color-normal: #3D4047 !default; $o-we-color-text-normal: #999999 !default; $o-we-color-text-light: #d4d5d7 !default; $o-we-color-text-lighter: #ebecee !default; @@ -18,58 +26,38 @@ $o-we-color-warning: #f0ad4e !default; $o-we-color-success: #40ad67 !default; $o-we-color-info: #6999a8 !default; -$o-we-sidebar-width: 210px !default; +$o-we-sidebar-width: 230px !default; -$o-we-tab-active-bg: #3D4048 !default; +$o-we-handles-offset-to-hide: 10000px !default; +$o-we-handles-btn-size: 18px !default; +$o-we-handles-accent-color: #00e2ff !default; +$o-we-handles-accent-color-preview: $o-enterprise-color !default; +$o-we-handle-edge-size: 8px !default; +$o-we-handle-border-width: 2px !default; +$o-we-handle-border-width-preview: 6px !default; // Transitions $o-we-md-ease: cubic-bezier(0.19, 1, 0.22, 1) !default; // Editor -$o-we-dropzone-size: 40px !default; +$o-we-dropzone-size: 30px !default; // $grid-gutter-width (todo: allow to use the variable) $o-we-dropzone-border-width: 2px !default; $o-we-dropzone-border: $o-we-dropzone-border-width dashed $o-brand-odoo !default; -$o-we-handle-edge-size: 1px !default; -$o-we-handle-border-width: 1px !default; -$o-we-handles-offset-to-hide: 10000px !default; -$o-we-handles-accent-color: #00e2ff !default; -$o-we-handles-btn-size: 18px !default; - -$o-we-overlay-option-size: 25px !default; - $o-we-ace-color: #2F3129 !default; $o-we-switch-size: 2ex !default; -$o-we-switch-inactive-color: $o-we-color-paper !default; +$o-we-switch-inactive-color: $o-we-bg-color !default; // Checklist $o-checklist-margin-left: 20px; $o-checklist-checkmark-width: 2px; $o-checklist-before-size: 13px; -$o-wysiwyg-zindex: 1000; - -$o-wysiwyg-md-ease: cubic-bezier(0.19, 1, 0.22, 1) !default; - -$o-wysiwyg-font-family: Roboto, -"Montserrat", -"Segoe UI", -"Helvetica Neue", -Helvetica, -Arial, -sans-serif; - -$o-wysiwyg-toolbar-height: 32px; - -$o-wysiwyg-color: #212629 !default; -$o-wysiwyg-color-light: #616669 !default; -$o-wysiwyg-color-danger: #912629 !default; -$o-wysiwyg-color-success: #219629 !default; -$o-wysiwyg-bg-color: #f7f7f7 !default; -$o-wysiwyg-bg-color-dark: #a7a7a7 !default; -$o-wysiwyg-border-color: #a9a9a9 !default; +$o-we-zindex: 1000; +$o-we-overlay-zindex: $o-we-zindex - 10; +$o-we-toolbar-height: 32px; // ============ 'Preview' component Mixins =============== // Create a "preview" work-area to edit/customize specific elements @@ -191,7 +179,7 @@ $o-wysiwyg-border-color: #a9a9a9 !default; .modal-content { border-radius: 0; - background-color: $o-we-color-paper; + background-color: $o-we-bg-color; .modal-header { border-bottom-color: #e9ecef; @@ -222,7 +210,7 @@ $o-wysiwyg-border-color: #a9a9a9 !default; &.active { &, &:hover, &:focus { color: $o-we-color-normal; - background-color: $o-we-color-paper; + background-color: $o-we-bg-color; border-color: #dee2e6 #dee2e6 #FFFFFF; } } @@ -242,11 +230,11 @@ $o-wysiwyg-border-color: #a9a9a9 !default; @include button-variant($o-brand-primary, $o-brand-primary); } &.btn-secondary { - @include button-variant($o-we-color-paper, $o-we-color-paper); + @include button-variant($o-we-bg-color, $o-we-bg-color); color: $o-brand-primary; } &.btn-link { - @include button-variant($o-we-color-paper, $o-we-color-paper); + @include button-variant($o-we-bg-color, $o-we-bg-color); color: $o-brand-primary; } &.btn-success { @@ -268,8 +256,8 @@ $o-wysiwyg-border-color: #a9a9a9 !default; .card { padding: 19px; margin-bottom: 20px; - background-color: $o-we-color-paper; - border: 1px solid darken($o-we-color-paper, 5%); + background-color: $o-we-bg-color; + border: 1px solid darken($o-we-bg-color, 5%); border-radius: 0; box-shadow: none; } diff --git a/addons/web_editor/static/src/scss/wysiwyg.scss b/addons/web_editor/static/src/scss/wysiwyg.scss index cd8fa7bcf44..90180133f12 100644 --- a/addons/web_editor/static/src/scss/wysiwyg.scss +++ b/addons/web_editor/static/src/scss/wysiwyg.scss @@ -1,322 +1,168 @@ -// NOTE EDITOR +// Use css variables to control the default style of the editor so that an +// external assets bundle can influence it without duplicating the css. +:root { + --o-we-toolbar-height: #{$o-we-toolbar-height}; -body.editor_enable { - padding-top: $o-wysiwyg-toolbar-height !important; -} + --o-we-bg-color: #{$o-we-bg-color}; + --o-we-bg-color-dark: #{$o-we-bg-color-dark}; + --o-we-color: #{$o-we-color}; + --o-we-color-accent: #{$o-we-color}; -#wrapwrap { - height: calc(100vh - #{$o-wysiwyg-toolbar-height}); - border: 1px solid $o-wysiwyg-border-color; - border-left: 0; - border-top: 0; + --o-we-dropdown-bg-color: #{$o-we-dropdown-bg-color}; + --o-we-dropdown-color: #{$o-we-dropdown-color}; - .btn { - -webkit-user-select: none; - } + --o-we-snippets-bg-color: #{$o-we-bg-color}; + --o-we-snippets-color: #{$o-we-color}; } -// TOP BAR (EDIT) +// EDITOR TOP BAR AND POPOVER +body.editor_enable { + padding-top: var(--o-we-toolbar-height) !important; +} #web_editor-top-edit { - height: $o-wysiwyg-toolbar-height; - background-color: $o-wysiwyg-bg-color; - z-index: $o-wysiwyg-zindex - 2; + @include o-position-absolute(0, 0, auto, 0); position: fixed; - top: 0px; - right: 0px; - left: 0px; - white-space: nowrap; - border: 1px solid $o-wysiwyg-border-color; - border-bottom: 1px solid rgba(0, 0, 0, 0.125); - - transition: background-color 400ms $o-we-md-ease 0s; - font-family: $o-we-font-family; -} - -.note-editor.note-frame { - background-color: inherit; - - .note-editing-area .note-editable { - background-color: transparent !important; - color: inherit; - padding-left: 0; - padding-right: 0; - } - - &.fullscreen { - background-color: #fff; + z-index: $o-we-zindex; + height: var(--o-we-toolbar-height); + background-color: var(--o-we-bg-color); + + .note-popover .popover { + top: 0 !important; + left: 0 !important; + right: 0 !important; + border: none !important; } } -.note-editor:not(.note-frame) .note-editing-area { - overflow: visible; -} - -#web_editor-top-edit .note-popover .popover { - height: $o-wysiwyg-toolbar-height - 2; - top: 0 !important; - left: 0 !important; - padding: 0; +.wysiwyg_iframe, +.note-editor { + border: $o-we-border-width solid $o-we-border-color; margin: 0; - border: 0; - width: auto; - text-align: center; - box-shadow: none; - background-color: $o-wysiwyg-bg-color; - z-index: $o-wysiwyg-zindex; - - .popover-body { - height: $o-wysiwyg-toolbar-height - 2; - - > .btn-group { - height: $o-wysiwyg-toolbar-height - 2; - margin-top: 0; - } - - .btn { - height: $o-wysiwyg-toolbar-height - 2; - border-top: 0; - } - } + padding: 0; } +.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; -#web_editor-toolbars .popover, -.note-popover.popover .popover-content, -.note-editor .note-toolbar { - font-family: $o-wysiwyg-font-family; - - h1, - h2, - h3, - h4, - h5, - h6, - .h1, - .h2, - .h3, - .h4, - .h5, - .h6 { - font-family: $o-wysiwyg-font-family; - } - - .btn { + // Main layout of buttons + .btn-group, .btn { + width: auto !important; + height: 100% !important; + margin-top: 0; + margin-bottom: 0; + background: transparent; border: none; border-radius: 0; - background-color: inherit; + } + .btn-secondary { color: inherit; - transition: all 0.3s ease 0s; + } + + // Active buttons and opened dropdowns + .btn { + padding: 0.5em 0.75em !important; + border-left: 1px solid transparent; + border-right: 1px solid transparent; + color: var(--o-we-color); + font-size: $o-we-font-size !important; - >.fa { - color: inherit; + &.active { + background-color: var(--o-we-bg-color-dark); + color: var(--o-we-color-accent); } &:hover { - border-top: 2px solid inherit; - color: inherit; - - >.fa { - color: inherit; - } + color: var(--o-we-color-accent); } - .o_image_alt { - @include o-text-overflow(inline-block); - max-width: 100px; + &:focus, &:active, &:focus:active { + @extend %we-active-button; } - - &.active { - background-color: inherit; - color: inherit; - box-shadow: inset 0 3px 10px rgba(0, 0, 0, 0.5); - - >.fa { - color: inherit; - } + } + .btn-group.show { + > .btn { + @extend %we-active-button; } - - &.o_disabled { - opacity: 0.5; - pointer-events: none; + &::after { + content: ''; + @include o-position-absolute(100%, $o-we-border-width, auto, $o-we-border-width); + height: $o-we-border-width; + background-color: var(--o-we-dropdown-bg-color); } } - - .note-btn-group { - margin: 0; - } - - >.btn-group { - margin-top: 0; - - &.show, - .btn-group.show { - .dropdown-toggle { - border-top: 2px solid white; - background-color: $o-wysiwyg-bg-color-dark; - box-shadow: none; - - >.fa, - >span { - color: inherit; - } - } - } + %we-active-button { + border-left-color: $o-we-border-color; + border-right-color: $o-we-border-color; + background: var(--o-we-dropdown-bg-color); + box-shadow: none !important; + outline: none !important; } - .dropdown-menu { - margin: 0; + margin-top: 0; + border: $o-we-border-width solid $o-we-border-color; + border-top-color: transparent; border-radius: 0; - background-color: $o-wysiwyg-bg-color; - color: inherit; - white-space: normal; - - .dropdown-item { - width: 100%; - color: inherit; - - >.fa { - color: inherit; - } - - &:hover { - background-color: $o-wysiwyg-bg-color-dark; - color: inherit; - - >.fa { - color: inherit; - } - } - } + background-color: var(--o-we-dropdown-bg-color); + color: var(--o-we-dropdown-color); + box-shadow: none; } - - .note-para .dropdown-menu { + .dropdown-menu.show { // To overcome .note-XXX .dropdown-menu rules min-width: 0; - white-space: nowrap; } + .dropdown-item { // To overcome summernote rules breaking this in iframes + display: block; + max-width: none; + overflow: visible; + color: var(--o-we-color); - .note-color { - - .note-back-color-preview, - .note-fore-color-preview { - .dropdown-toggle { - padding: 0px 7px; - height: 35px; - width: auto; - border-top: none; - border-bottom: 3px solid; - - &:after { - display: none; - } - } - } - - .dropdown-menu { - min-width: 180px; - - .note-custom-color { - cursor: pointer; - border-radius: 5px; - - &:hover { - background-color: gray('400'); - color: black - } - } - } - - .dropdown-toggle:after { - display: none; - } - - .note-palette:first-child { - width: 100%; - - .note-color-reset, - .note-custom-color { - width: 162px; - } - - .note-color-reset { - padding: 0 3px !important; - height: 32px; - } - - .note-holder, - .note-color-palette .note-color-row, - .note-custom-color-palette .note-color-row { - height: auto !important; - - &::after { - content: ""; - display: table; - clear: both; - } - - .note-color-btn { - float: left; - height: 20px; - width: 20px; - margin: 1px 1px 0 0; - border: 1px solid black; - cursor: pointer; - @include o-alpha-button-preview; - - &.o_small { - width: 16px; - height: 16px; - margin: 3px 3px 2px 2px; - border-radius: 50%; - } - - &.o_clear { - clear: both; - } + &:hover { + background-color: var(--o-we-bg-color) !important; + color: var(--o-we-color-accent) !important; - &.o_btn_transparent { - display: none; - } - } + > * { + color: inherit !important; } } } -} - -.note-popover.popover.note-hint-popover .popover-content { - height: auto; -} -.note-popover, -.note-editor { - .modal .modal-footer p { - display: none; + // Specific elements + .o_image_alt { + @include o-text-overflow(); + max-width: 150px; } -} - -.note-handle { - pointer-events: none; - - .note-control-selection { - z-index: 1; - overflow: hidden; - - .note-control-se { - border-top: 0; - border-left: 0; - background: transparent; - } + .note-color-palette div .note-color-btn { + border-color: var(--o-we-dropdown-bg-color); } } -.o_fake_editable:not([data-oe-model]):focus { - outline: none; +// ANIMATIONS +@keyframes fadeInDownSmall { + 0% { + opacity: 0; + transform: translate(0, -5px); + } + 100% { + opacity: 1; + transform: translate(0, 0); + } } -.note-editor.dragover .note-dropzone { - z-index: $o-wysiwyg-zindex + 1; +@keyframes inputHighlighter { + from { + background: $o-brand-primary; + } + to { + width: 0; + background: transparent; + } } // MODALS - body .modal { // SELECT MEDIA @@ -362,7 +208,7 @@ body .modal { @include o-position-absolute($top: 0, $right: 0); border-radius: 0 0 0 2px; &:hover { - color: $o-wysiwyg-color-danger; + color: $o-we-color-danger; } } @@ -390,7 +236,7 @@ body .modal { } .font-icons-icons { - >span { + > span { text-align: center; font-size: 22px; margin: 5px; @@ -401,8 +247,8 @@ body .modal { &.o_selected { cursor: auto; - background-color: $o-wysiwyg-bg-color-dark; - box-shadow: 0px 0px 0px 1px $o-wysiwyg-color-success; + background-color: $o-we-bg-color-dark; + box-shadow: 0px 0px 0px 1px $o-we-color-success; } } } @@ -421,7 +267,7 @@ body .modal { position: relative; width: 100%; - >textarea { + > textarea { width: 100%; min-height: 95px; padding-bottom: 25px; @@ -460,7 +306,7 @@ body .modal { .cropper-point { background-color: white; - transition: all 400ms $o-wysiwyg-md-ease 0s; + transition: all 400ms $o-we-md-ease 0s; transform: scale(1.5, 1.5); border-radius: 10px; box-shadow: 0 0 1px 1px rgba(23, 23, 23, 0.5); @@ -482,7 +328,7 @@ body .modal { // LINK EDITOR DIALOG COLOR SELECTOR .o_link_dialog { - .o_link_dialog_color>.o_link_dialog_color_item { + .o_link_dialog_color > .o_link_dialog_color_item { position: relative; width: 30px; height: 30px; @@ -490,7 +336,7 @@ body .modal { border: 2px solid rgba(0, 0, 0, 0.25); border-radius: 50%; - input:checked+i::before { + input:checked + i::before { content: "\f00c"; @include o-position-absolute($top: 6px, $left: 5px); } @@ -498,7 +344,7 @@ body .modal { &.btn-link { padding: 3px 9px; - >span { + > span { text-decoration: underline; } } @@ -511,7 +357,7 @@ body .modal { .o_we_image_optimize_dialog { .o_we_title_label { - font-size: 1rem; + font-size: $o-we-font-size; } .o_we_preview_area { max-height: 400px; @@ -539,32 +385,3 @@ img.o_we_selected_image { img.o_we_custom_image.mx-auto { display: block; } - -// ENTER IN EDIT MODE - -.note-editor .css_editable_mode_display { - border: 1px dashed rgba(153, 153, 153, 0.5); - display: block !important; -} - -.note-editable { - - li, - p, - h1, - h2, - h3, - h4, - h5, - h6 { - &:empty:after { - content: "\00a0" - } - } -} - -.wysiwyg_iframe { - margin: 0; - padding: 0; - border: 0; -} diff --git a/addons/web_editor/static/src/scss/wysiwyg_iframe.scss b/addons/web_editor/static/src/scss/wysiwyg_iframe.scss index e1af34e41fa..e7554317994 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_iframe.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_iframe.scss @@ -16,21 +16,8 @@ iframe.wysiwyg_iframe.o_fullscreen { body.o_in_iframe { background-color: $o-view-background-color; - overflow: auto; - - .note-editable { - height: calc(100vh - #{$o-wysiwyg-toolbar-height} - 2px); - } .note-statusbar { display: none; } - - #web_editor-toolbars .popover .btn { - font-size: 1rem; - } - - .o_mail_theme_selector .btn { - font-size: 1rem; - } } diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index 6b576acecba..70665b4861f 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -2,86 +2,74 @@ /// This file contains all variables and mixins that are specific to the editor. /// -$o-wysiwyg-handles-accent-color: #00e2ff !default; -$o-wysiwyg-handle-edge-size: 1px !default; -$o-wysiwyg-dropzone-size: 40px !default; -$o-wysiwyg-sidebar-width: 210px !default; -$o-wysiwyg-sidebar-height: 36px !default; -$o-wysiwyg-overlay-option-size: 25px !default; -$o-wysiwyg-handles-btn-size: 18px !default; -$o-wysiwyg-handle-border-width: 1px !default; - -$o-wysiwyg-handles-offset-to-hide: 10000px !default; - // OVERRIDES FOR EDITOR WITH SNIPPETS body.editor_enable.editor_has_snippets { padding-left: $o-we-sidebar-width !important; #web_editor-top-edit .note-popover .popover { - margin: 0 0 0 $o-we-sidebar-width; - } -} - -// NOTE EDITOR -.note-editor.o_snippets_loaded { - transition: padding-left 400ms $o-wysiwyg-md-ease 0s; - padding-left: 210px !important; -} - -.note-editor.note-frame .note-editing-area .o_wrap_editable_snippets { - overflow: auto; - - .note-editable { - overflow: visible; + left: $o-we-sidebar-width !important; } } -.o_in_iframe .note-editor.o_snippets_loaded .note-editable { - padding-top: 30px; -} - // SNIPPET PANEL #oe_snippets { @include o-w-preserve-btn; + @include o-position-absolute(0, auto, 0, -$o-we-sidebar-width); + position: fixed; + z-index: $o-we-zindex; display: flex; flex-flow: column nowrap; - position: fixed; - top: 0; - width: $o-wysiwyg-sidebar-width; - z-index: $o-wysiwyg-zindex - 1; + width: $o-we-sidebar-width; - font-family: Roboto, $font-family-sans-serif; - border-right: 1px solid #aaa; - transition: left 400ms $o-wysiwyg-md-ease 0s; - background-image: linear-gradient(45deg, $o-wysiwyg-bg-color, darken($o-wysiwyg-bg-color, 10%)); + border-right: $o-we-border-width solid $o-we-border-color; + background-color: var(--o-we-snippets-bg-color); + color: var(--o-we-snippets-color); box-shadow: 0px 10px 10px -10px black inset; - border: 1px solid $o-wysiwyg-border-color; + font-family: Roboto, $font-family-sans-serif; + + transition: left 400ms $o-we-md-ease 0s; + + &.o_loaded { + left: 0 !important; + } #snippets_menu { flex: 0 0 auto; - height: $o-wysiwyg-toolbar-height; - line-height: $o-wysiwyg-sidebar-height; - margin: 0; - padding: 0; - background: $o-wysiwyg-bg-color; - text-align: center; - font-weight: normal; - font-size: 17px; - color: $o-wysiwyg-color; - font-family: $o-wysiwyg-font-family; + display: flex; + height: var(--o-we-toolbar-height); + background: var(--o-we-bg-color); + color: var(--o-we-color); + font-family: $o-we-font-family; + + > button { + flex: 1 1 auto; + border: none; + background: transparent; + color: inherit; + @include o-text-overflow(); + + &.active { + background: var(--o-we-snippets-bg-color); + color: var(--o-we-snippets-color); + } + } } - #o_scroll { + > #o_scroll, > .o_we_customize_panel { + flex: 1 1 auto; height: 100%; overflow: auto; + } + + > #o_scroll { .o_panel_header { display: flex; align-items: center; justify-content: center; padding: 3%; - color: $o-wysiwyg-color; + color: inherit; margin-bottom: 3%; i { @@ -149,24 +137,156 @@ body.editor_enable.editor_has_snippets { } } - &:nth-child(2n+1) { + &:nth-child(2n + 1) { clear: left; } } } } - &.o_loaded { - left: 0 !important; + > .o_we_customize_panel { + + we-button, we-toggler { + cursor: pointer; + @include o-text-overflow(inline-block); + padding: 0.25rem 0.5rem; + margin: 0; + background-color: var(--o-we-bg-color-dark); + color: var(--o-we-color); + line-height: 1.5; + font-weight: 500; + white-space: nowrap; + } + + %we-icon-button { + position: relative; + + &::after { + position: absolute; + width: 1em; + right: 0.5em; + text-align: center; + font-family: FontAwesome; + } + } + + @mixin we-icon-button($icon, $color: null) { + @extend %we-icon-button; + padding-right: 0.5em + 1em + 0.5em; + + &::after { + content: $icon; + color: $color; + } + } + + we-customizeblock-options { + display: block; + border: $o-we-border-width solid var(--o-we-snippets-color); + color: var(--o-we-snippets-color); + margin-top: 0.25rem; + + > we-title { + display: flex; + align-items: center; + margin: 0.25rem 0; + padding: 0 0.5rem; + font-weight: bold; + color: inherit !important; + + @include we-icon-button('\f107'); + + > we-button-group { + flex: 0 0 auto; + display: block; + margin-left: auto; + text-align: center; + + > we-button { + margin-left: 0.25rem; + width: 1.5rem; + padding: 0.125rem; + } + } + } + } + + we-customizeblock-option { + position: relative; + display: block; + overflow: hidden; + max-height: 500px; + transition: max-height 200ms ease 0s; + + > * { + display: block; + margin-top: 0.25rem; + } + + we-button, we-toggler { + display: block; + text-align: left; + border-radius: 0; + } + we-button { + &.we-button-hover-icon:hover { + @include we-icon-button('\f040'); + } + + &.active { + @include we-icon-button('\f00c', $o-we-color-success); + } + } + we-collapse { + position: relative; + display: block; + max-height: 0; + overflow: hidden; + transition: max-height 200ms ease 0s; + } + we-toggler { + @include we-icon-button('\f104'); + + &.active { + @include we-icon-button('\f107'); + + + we-collapse { + max-height: 500px; + } + } + } + + .dropdown-menu { + // FIXME temporary fix for m2o option for example + position: static !important; + } + } + + we-customizeblock-options.o_we_closed { + cursor: pointer; + + > we-title { + @include we-icon-button('\f104'); + } + > we-customizeblock-option { + max-height: 0; + transition-duration: 25ms; + + + we-customizeblock-option { + margin-top: 0; + } + } + } } } .oe_snippet { // No root because can be drag and drop (and the helper is in the body) position: relative; + z-index: $o-we-zindex; width: 100px; - background-color: $o-wysiwyg-bg-color; - z-index: $o-wysiwyg-zindex; + background-color: var(--o-we-snippets-bg-color); + color: var(--o-we-snippets-color); > :not(.oe_snippet_thumbnail) { display: none !important; @@ -178,7 +298,7 @@ body.editor_enable.editor_has_snippets { .oe_snippet_thumbnail_img { width: 100%; height: 0; - border: 1px solid $o-wysiwyg-border-color; + border: $o-we-border-width solid $o-we-border-color; padding-bottom: 75%; background-size: cover; background-position: center center; @@ -193,16 +313,14 @@ body.editor_enable.editor_has_snippets { .oe_snippet_thumbnail_title { display: block; - border: 1px solid $o-wysiwyg-bg-color; padding: 5px; font-size: 12px; font-weight: 300; text-shadow: none; - color: $o-wysiwyg-color; } &:hover .oe_snippet_thumbnail_title { - color: $o-wysiwyg-color-light; + color: var(--o-we-color-accent); } } } @@ -212,66 +330,59 @@ body.editor_enable.editor_has_snippets { .o_colorpicker_sections { font-size: 14px; - .o_colorpicker_section_tabs { - width: 225px; - height: 100%; - } - button { float: left; - height: 25px; - width: 25px; - margin: 1px 1px 0 2px; - border: 1px solid black; + width: percentage(1 / 8); + padding-top: percentage(1 / 8); + margin: 0; + border: none; + box-shadow: inset 0 0 0 1px var(--o-we-snippets-bg-color); } - div[data-group="transparent_grayscale"] button { - @include o-alpha-button-preview; - - &.o_small { - width: 23px; - height: 23px; - margin: 2px; - border-radius: 50%; - } + .o_colorpicker_section { - &:hover, - &.selected { - box-shadow: 0px 0px 2px 2px $o-wysiwyg-color; + > :first-child { + margin: 8px 0 4px; } + button { + position: relative; - &.selected:before { - content: "\f00c"; - font-family: "FontAwesome"; - padding-top: 1px; - color: theme-color('success'); - } + &.o_small { + border-radius: 50%; + } - &[data-event="foreColor"] { - background-color: $o-wysiwyg-bg-color; + &:hover, + &.selected { + box-shadow: inset 0 0 1px 1px var(--o-we-snippets-bg-color); + } - &:before { - background-color: rgba(white, 0.3); + &.selected::before { + @include o-position-absolute(0, 0, 0, 0); + content: "\f00c" !important; + display: flex; + justify-content: center; + align-items: center; + font-family: FontAwesome !important; + color: $o-we-color-success; } - } - &.o_btn_transparent::before { - background-color: transparent; + &.o_btn_transparent::before { + background-color: transparent; + } } - } - .note-color-palette>div { - clear: both; - } + &[data-name="transparent_grayscale"] button { + @include o-alpha-button-preview; - .note-color-row { - height: 28px; + &::before, &::after { + box-shadow: inherit; + } + } } } .note-palette-title { - padding: 10px 0px 10px 10px; - color: $o-wysiwyg-color-light; + padding: 10px 0 10px 10px; font-weight: bold; } @@ -282,14 +393,14 @@ body.editor_enable.editor_has_snippets { .note-color-reset { font-size: 20px !important; - color: desaturate(rgba($o-wysiwyg-color-danger, 0.6), 40%); + color: desaturate(rgba($o-we-color-danger, 0.6), 40%); margin: 1px 0 0; padding: 0; cursor: pointer; &:hover { background: transparent !important; - color: $o-wysiwyg-color-danger; + color: $o-we-color-danger; } } } @@ -298,50 +409,26 @@ body.editor_enable.editor_has_snippets { // DROPZONES @keyframes dropZoneInsert { to { - background-color: rgba($o-brand-odoo, 0.2); + background-color: rgba($o-brand-odoo, 0.3); } } -.note-editable .oe_drop_zone { - background-color: rgba($o-brand-odoo, 0.05); +.oe_drop_zone { + background-color: rgba($o-brand-odoo, 0.15); animation: dropZoneInsert 1s linear 0s infinite alternate; &.oe_insert { - z-index: $o-wysiwyg-zindex - 1; - - border: 2px dashed $o-wysiwyg-border-color; - border-top: none; - border-bottom: none; - position: relative; + z-index: $o-we-overlay-zindex; width: 100%; - height: $o-wysiwyg-dropzone-size; - margin: (-$o-wysiwyg-dropzone-size/2) 0; - - &:after { - content: ""; - display: block; - width: 100%; - height: 50%; - border-bottom: 2px dashed $o-wysiwyg-border-color; - box-sizing: content-box; - } + height: $o-we-dropzone-size; + margin: (-$o-we-dropzone-size/2) 0; + border: 2px dashed $o-we-border-color; &.oe_vertical { - border: 2px dashed $o-brand-odoo; - border-left: none; - border-right: none; - - width: $o-wysiwyg-dropzone-size; + width: $o-we-dropzone-size; float: left; - margin: 0 (-$o-wysiwyg-dropzone-size/2); - - &:after { - width: 50%; - height: 100%; - border-bottom: none; - border-right: 2px dashed $o-wysiwyg-border-color; - } + margin: 0 (-$o-we-dropzone-size/2); } } } @@ -349,13 +436,14 @@ body.editor_enable.editor_has_snippets { // MANIPULATORS #oe_manipulators { position: relative; - z-index: 1; + z-index: $o-we-overlay-zindex; // SNIPPET MANIPULATORS .oe_overlay { @include o-position-absolute; display: none; height: 0; + border-color: $o-we-handles-accent-color-preview; background: transparent; text-align: center; transition: opacity 400ms linear 0s; @@ -363,381 +451,82 @@ body.editor_enable.editor_has_snippets { &.o_keypress { opacity: 0; } - &.oe_active { display: block; } - - // OVERLAY OPTIONS - >.oe_overlay_options { - font-family: $o-wysiwyg-font-family; - @include o-position-absolute($bottom: 0, $left: 0); - width: 250px; // the parent oe_overlay has a width equal to the snippet width, the 250px is there to force the options to stay on one line without overriding bootstrap .btn-group - text-align: left; - z-index: $o-wysiwyg-zindex + 1; - - >.btn-group { - white-space: nowrap; - border: 0; - - .btn { - display: inline-block; - width: $o-wysiwyg-overlay-option-size; - height: $o-wysiwyg-overlay-option-size; - padding: 0; - border: 1px solid $o-wysiwyg-bg-color; - line-height: $o-wysiwyg-overlay-option-size; - font-size: 11px; - border-radius: 0; - font-weight: normal; - - transition: all 400ms ease 0s; - color: $o-wysiwyg-color; - background-color: $o-wysiwyg-bg-color; - - &:hover { - color: $o-wysiwyg-color-light; - background-color: lighten($o-wysiwyg-bg-color, 10%); - } - - &.oe_snippet_remove { - color: $o-wysiwyg-color; - background-color: $o-wysiwyg-color-danger; - border-color: darken($o-wysiwyg-color-danger, 20%); - - >.fa { - color: $o-wysiwyg-color; - } - - &:hover { - background-color: darken($o-wysiwyg-color-danger, 20%); - } - } - - &.oe_snippet_parent>i { - transform: scaleX(-1); - font-size: 12px; - } - - >.fa { - color: $o-wysiwyg-color; - line-height: inherit; - vertical-align: top; - } - } - - // CUSTOMIZE MENU BUTTON - >.oe_options { - .btn { - width: auto; - padding: 0 25px 0 5px; - background-color: $o-wysiwyg-bg-color; - text-transform: uppercase; - - &:hover, - &:active, - &:focus { - background-color: lighten($o-wysiwyg-bg-color, 5%); - border-color: lighten($o-wysiwyg-bg-color, 10%); - } - - &:before, - &:after { - content: ""; - width: 7px; - height: 2px; - background-color: $o-wysiwyg-color-light; - @include o-position-absolute(46%); - transition: all 0.3s ease 0s; - } - - &:before { - right: 9px; - transform: rotate(45deg); - } - - &:after { - right: 5px; - transform: rotate(-45deg); - } - } - - // Open menu - &.show .btn { - - &:active, - &:focus, - &:active:focus { - box-shadow: none; - } - - &:before, - &:after { - width: 9px; - } - - &:before { - right: 10px; - transform: translateX(5px) rotate(-45deg); - } - - &:after { - transform: rotate(45deg); - } - } - - // CUSTOMIZE MENU - .dropdown-menu { - margin: -1px 0 0 0; - padding: 0; - border: 1px solid $o-wysiwyg-bg-color; - border-radius: 0; - background-color: $o-wysiwyg-bg-color; - - .dropdown-item { - position: relative; - color: $o-wysiwyg-color-light; - font-weight: normal; - font-size: 12px; - padding: 5px 40px 5px 10px; - - &:hover { - background: transparent; - cursor: pointer; - } - - &.active { - &:before { - @include o-position-absolute($top: 5px, $right: 5px); - content: "\f00c"; - font-family: "FontAwesome"; - color: $o-brand-primary; - } - } - - >i { - width: 15px; - margin-right: 10px; - text-align: center; - } - } - - .dropdown-submenu { - position: relative; - - &::before { - @include o-position-absolute($top: 12px, $right: 10px); - @include o-caret-right(4px); - border-left-color: $o-wysiwyg-color-light; - } - - &:hover>.dropdown-menu { - display: block; - left: 100%; - top: 0; - - &.o_open_to_left { - left: auto; - right: 100%; - } - } - } - - .dropdown-divider { - display: block; - height: 1px; - border-top: 1px solid #666; - margin: 0; - } - - .dropdown-header { - color: $o-wysiwyg-color-light; - font-size: 11px; - margin-top: 2px; - text-transform: uppercase; - - &:hover { - background-color: initial; - } - - &.o_main_header { - padding-left: 10px; - } - - &.o_parent_editor_header { - position: relative; - color: $o-wysiwyg-color-light; - font-weight: 400; - font-size: 10px; - margin: 10px 0 0 10px; - text-transform: uppercase; - padding-bottom: .25em; - - &::before { - content: "\f148"; - @include o-position-absolute($top: 5px, $left: 4px); - font-family: FontAwesome; - transform: scaleX(-1); - } - - ~.dropdown-submenu .dropdown-item, - ~.dropdown-item { - font-size: 11px; - opacity: 0.7; - - &:hover { - opacity: 1; - } - } - - ~.dropdown-submenu::before { - @include o-position-absolute($top: 10px, $right: 10px); - } - } - } - } - } - } - } - - &.o_top_cover>.oe_overlay_options { - bottom: auto; - top: 1px; - right: 1px; + &.o_we_options_opened { + z-index: 1; } // HANDLES - >.o_handles { - @include o-position-absolute(-$o-wysiwyg-handles-offset-to-hide, 0, auto, 0); - z-index: $o-wysiwyg-zindex; - - &:hover>.o_handle { - background-color: rgba($o-wysiwyg-handles-accent-color, 0.05); - } - - >.o_handle { + > .o_handles { + @include o-position-absolute(-$o-we-handles-offset-to-hide, 0, auto, 0); + border-color: inherit; + > .o_handle, > .o_move_handle { position: relative; - border: 0 solid $o-wysiwyg-handles-accent-color; - transition: background 300ms ease 0s; - - &:hover, - &.o_active { - background-color: rgba($o-wysiwyg-handles-accent-color, 0.2); - - &:before { - content: ''; - @include o-position-absolute(0, $left: 50%); - width: 1px; - height: 100%; - margin-left: -1px; - background-color: rgba($o-wysiwyg-handles-accent-color, 0.5); - } - - &.w:before { - @include o-position-absolute(50%, $left: 0); - width: 100%; - height: 1px; - } - - &:after { - border-color: darken($o-wysiwyg-handles-accent-color, 10%); - background-color: darken($o-wysiwyg-handles-accent-color, 20%); - } - } - &:after { - display: block; - width: $o-wysiwyg-handles-btn-size; - height: $o-wysiwyg-handles-btn-size; - border: solid 1px darken($o-wysiwyg-handles-accent-color, 10%); - line-height: $o-wysiwyg-handles-btn-size - 2; - font-size: 14px; - font-family: FontAwesome; - background-color: darken($o-wysiwyg-handles-accent-color, 20%); - color: white; + &.w { + @include o-position-absolute($o-we-handles-offset-to-hide, auto, -$o-we-handles-offset-to-hide, 0); + width: $o-we-handle-edge-size; } - &.w:after, - &.e:after { - content: "\f07e"; + &.e { + @include o-position-absolute($o-we-handles-offset-to-hide, 0, -$o-we-handles-offset-to-hide, auto); + width: $o-we-handle-edge-size; } - &.s:after, - &.n:after { - content: "\f07d"; + &.n { + @include o-position-absolute($o-we-handles-offset-to-hide, 0, auto, 0); + height: $o-we-handle-edge-size; } - &.o_handle_start { - - &.w:after, - &.e:after { - content: '\f061'; - } - - &.n:after, - &.s:after { - content: '\f063'; - } + &.s { + @include o-position-absolute(auto, 0, -$o-we-handles-offset-to-hide, 0); + height: $o-we-handle-edge-size; } + } - &.o_handle_end { - - &.w:after, - &.e:after { - content: '\f060'; - } - - &.n:after, - &.s:after { - content: '\f062'; - } - } + > .o_handle { + border: 0 dashed transparent; + border-color: inherit; + transition: background 300ms ease 0s; &.w { - @include o-position-absolute($o-wysiwyg-handles-offset-to-hide, auto, -$o-wysiwyg-handles-offset-to-hide, 0); - width: $o-wysiwyg-handle-edge-size; - border-width: $o-wysiwyg-handle-border-width; + border-width: $o-we-handle-border-width-preview; border-right-width: 0; cursor: e-resize; &:after { @include o-position-absolute($top: 50%, $left: 40%); - margin-top: -$o-wysiwyg-handles-btn-size/2; + margin-top: -$o-we-handles-btn-size/2; } } - &.e { - @include o-position-absolute($o-wysiwyg-handles-offset-to-hide, 0, -$o-wysiwyg-handles-offset-to-hide, auto); - width: $o-wysiwyg-handle-edge-size; - border-right-width: $o-wysiwyg-handle-border-width; + border-right-width: $o-we-handle-border-width-preview; cursor: w-resize; &:after { @include o-position-absolute($top: 50%, $right: 40%); - margin-top: -$o-wysiwyg-handles-btn-size/2; + margin-top: -$o-we-handles-btn-size/2; } } - &.n { - @include o-position-absolute($o-wysiwyg-handles-offset-to-hide, 0, auto, 0); - border-top-width: $o-wysiwyg-handle-border-width; + border-top-width: $o-we-handle-border-width-preview; cursor: ns-resize; &:after { @include o-position-absolute($left: 50%, $top: 40%); - margin-left: -$o-wysiwyg-handles-btn-size/2; + margin-left: -$o-we-handles-btn-size/2; } } - &.s { - @include o-position-absolute(auto, 0, -$o-wysiwyg-handles-offset-to-hide, 0); - border-bottom-width: $o-wysiwyg-handle-border-width; + border-bottom-width: $o-we-handle-border-width-preview; cursor: ns-resize; &:after { @include o-position-absolute($left: 50%, $bottom: 40%); - margin-left: -$o-wysiwyg-handles-btn-size/2; + margin-left: -$o-we-handles-btn-size/2; } } @@ -753,6 +542,113 @@ body.editor_enable.editor_has_snippets { } } } + + > .o_move_handle { + cursor: pointer; + cursor: move; + } + } + + &.o_we_sticky { + border-color: $o-we-handles-accent-color; + + > .o_handles { + &:hover > .o_handle { + background-color: rgba($o-we-handles-accent-color, 0.05); + } + > .o_handle { + &:hover, + &.o_active { + background-color: rgba($o-we-handles-accent-color, 0.2); + + &:before { + content: ''; + @include o-position-absolute(0, $left: 50%); + width: 1px; + height: 100%; + margin-left: -1px; + background-color: rgba($o-we-handles-accent-color, 0.5); + } + + &.w:before { + @include o-position-absolute(50%, $left: 0); + width: 100%; + height: 1px; + } + + &:after { + border-color: darken($o-we-handles-accent-color, 10%); + background-color: darken($o-we-handles-accent-color, 20%); + } + } + + &:after { + z-index: 1; + display: block; + width: $o-we-handles-btn-size; + height: $o-we-handles-btn-size; + border: solid 1px darken($o-we-handles-accent-color, 10%); + line-height: $o-we-handles-btn-size - 2; + font-size: 14px; + font-family: FontAwesome; + background-color: darken($o-we-handles-accent-color, 20%); + color: white; + } + + &.w:after, + &.e:after { + content: "\f07e"; + } + + &.s:after, + &.n:after { + content: "\f07d"; + } + + &.o_handle_start { + + &.w:after, + &.e:after { + content: '\f061'; + } + + &.n:after, + &.s:after { + content: '\f063'; + } + } + + &.o_handle_end { + + &.w:after, + &.e:after { + content: '\f060'; + } + + &.n:after, + &.s:after { + content: '\f062'; + } + } + + &.w { + border-width: $o-we-handle-border-width; + border-right-width: 0; + } + &.e { + border-right-width: $o-we-handle-border-width; + } + &.n { + border-top-width: $o-we-handle-border-width; + } + &.s { + border-bottom-width: $o-we-handle-border-width; + } + } + } + } + &.o_we_options_opened > .o_handles > .o_handle { + border-style: solid; } } diff --git a/addons/web_editor/static/src/xml/snippets.xml b/addons/web_editor/static/src/xml/snippets.xml index 6e2ae656f2d..89819b44396 100644 --- a/addons/web_editor/static/src/xml/snippets.xml +++ b/addons/web_editor/static/src/xml/snippets.xml @@ -3,29 +3,41 @@ <t t-name="web_editor.snippet_overlay"> <div class="oe_overlay"> <div class="o_handles"> + <!-- Visible overlay borders + allow to resize when not readonly --> <div class="o_handle n readonly"/> <div class="o_handle e readonly"/> <div class="o_handle w readonly"/> <div class="o_handle s readonly"/> - </div> - <div class="oe_overlay_options" contentEditable="false"> - <div class="btn-group"> - <a role="button" href="#" class="btn btn-secondary oe_snippet_parent" title="Select Parent Container" aria-label="Select Parent Container"><i class="fa fa-lg fa-level-up"/></a> - <div class="dropdown oe_options d-none btn-group"> - <a role="button" href="#" data-toggle="dropdown" class="btn btn-primary" title="Customize">Customize</a> - <div class="dropdown-menu" role="menu"/> - </div> - <a role="button" href="#" class="btn btn-secondary oe_snippet_move" title="Drag to Move" aria-label="Drag to Move"><i class="fa fa-arrows ml4 mr4"></i></a> - <a role="button" href="#" class="btn btn-secondary oe_snippet_clone" title="Duplicate Container" aria-label="Duplicate Container"><i class="fa fa-files-o ml4 mr4"></i></a> - <a role="button" href="#" class="btn btn-secondary oe_snippet_remove" title="Remove Block" aria-label="Remove Block"><i class="fa fa-trash-o ml4 mr4"></i></a> - </div> + + <!-- Invisible overlay borders which allow to drag and drop --> + <div class="o_move_handle n"/> + <div class="o_move_handle e"/> + <div class="o_move_handle w"/> + <div class="o_move_handle s"/> </div> </div> </t> + <t t-name="web_editor.customize_block_options_section"> + <we-customizeblock-options> + <we-title> + <span t-esc="name"/> + <we-button-group> + <we-button class="fa fa-files-o oe_snippet_clone" + title="Duplicate Container" + aria-label="Duplicate Container"/> + <we-button class="fa fa-trash-o oe_snippet_remove" + title="Remove Block" + aria-label="Remove Block"/> + </we-button-group> + </we-title> + </we-customizeblock-options> + </t> + <t t-name="web_editor.customize_block_option"> + <we-customizeblock-option/> + </t> <!-- options --> <div t-name="web_editor.snippet.option.colorpicker" class="colorpicker"> - <div class="note-palette-title">Background Color</div> <div class="btn-group palette-reset"> <div class="note-color-reset" data-event="foreColor" data-value="inherit" title="None" role="img" aria-label="No color"> <i class="fa fa-ban"/> |
