From 53ef25284fa12bb98e76a390a24137a0a9729a6f Mon Sep 17 00:00:00 2001 From: Nicolas Lempereur Date: Thu, 25 Jul 2019 15:47:34 +0000 Subject: [FIX] web_editor: enter at A edge no dup link In the editor, when you write a letter at the end or beginning of a link it is written outside the link. But if you press "Enter", we get on a newline with an empty link duplicated on the new line. This is caused by the custom code handling "Enter" keypress. With this changeset, in this situation we place the range just before the beginning or after the end of a link. opw-1724606 opw-2042097 closes #35188 Signed-off-by: Nicolas Lempereur (nle) --- addons/web_editor/static/src/js/editor/summernote.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'addons/web_editor/static/src') diff --git a/addons/web_editor/static/src/js/editor/summernote.js b/addons/web_editor/static/src/js/editor/summernote.js index bc7f5e7ff9f..e0968ecdb58 100644 --- a/addons/web_editor/static/src/js/editor/summernote.js +++ b/addons/web_editor/static/src/js/editor/summernote.js @@ -1181,6 +1181,20 @@ $.summernote.pluginEvents.enter = function (event, editor, layoutInfo) { var br = $("
")[0]; + // set selection outside of A if range is at beginning or end + var elem = dom.isBR(elem) ? elem.parentNode : dom.node(r.sc); + if (elem.tagName === "A") { + if (r.so === 0 && dom.firstChild(elem) === r.sc) { + r.ec = r.sc = dom.hasContentBefore(elem) || $(dom.createText('')).insertBefore(elem)[0]; + r.eo = r.so = dom.nodeLength(r.sc); + r.select(); + } else if (dom.nodeLength(r.sc) === r.so && dom.lastChild(elem) === r.sc) { + r.ec = r.sc = dom.hasContentAfter(elem) || dom.insertAfter(dom.createText(''), elem); + r.eo = r.so = 0; + r.select(); + } + } + var node; var $node; var $clone; -- cgit v1.2.3 From 7acb22a5e5c9beceb18ed6fea691e14502b93ad7 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Tue, 27 Aug 2019 14:45:59 +0000 Subject: [FIX] web_editor: restore moving snippets to nearest dropzone Hard to say when this was broken since the current work and revert of the editor: when dropping a snippet outside of a dropzone it was not dropped in the nearest one anymore because the dropzones were somehow destroyed first in the current code. Part of https://github.com/odoo/odoo/pull/36068 Discovered while working on task-1942370 --- addons/web_editor/static/src/js/editor/snippets.editor.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'addons/web_editor/static/src') 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 4fba95f2490..d79e0a9a2cc 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -446,8 +446,6 @@ var SnippetEditor = Widget.extend({ * @param {Object} ui */ _onDragAndDropStop: function (ev, ui) { - this.$editable.find('.oe_drop_zone').droppable('destroy').remove(); - // TODO lot of this is duplicated code of the d&d feature of snippets if (!this.dropped) { var $el = $.nearest({x: ui.position.left, y: ui.position.top}, '.oe_drop_zone', {container: document.body}).first(); @@ -457,6 +455,8 @@ var SnippetEditor = Widget.extend({ } } + this.$editable.find('.oe_drop_zone').droppable('destroy').remove(); + var prev = this.$target.first()[0].previousSibling; var next = this.$target.last()[0].nextSibling; var $parent = this.$target.parent(); -- cgit v1.2.3 From b47f097feb7f99be1296cde1f0cd1e4f950d83fd Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Fri, 23 Aug 2019 10:57:54 +0000 Subject: [FIX] web_editor, *: review assets organisation * mass_mailing, point_of_sale, website - Lib files were included twice - Variables files were split but added multiple times in the same assets bundle - Useless assets templates were defined or split - ... Part of https://github.com/odoo/odoo/pull/36068 Related to task-1942370 --- .../static/src/js/wysiwyg/wysiwyg_iframe.js | 5 +-- .../static/src/scss/web_editor.variables.scss | 43 ++++++++++++++++++++++ .../static/src/scss/wysiwyg_variables.scss | 42 --------------------- 3 files changed, 44 insertions(+), 46 deletions(-) delete mode 100644 addons/web_editor/static/src/scss/wysiwyg_variables.scss (limited to 'addons/web_editor/static/src') diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js index 64b7c6a8f8c..c0709a2c90e 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js @@ -23,9 +23,6 @@ Wysiwyg.include({ init: function (parent, options) { this._super.apply(this, arguments); if (this.options.inIframe) { - if (!this.options.iframeCssAssets) { - this.options.iframeCssAssets = 'web_editor.wysiwyg_iframe_css_assets'; - } this._onUpdateIframeId = 'onLoad_' + this.id; } }, @@ -133,4 +130,4 @@ Wysiwyg.include({ }, }); -}); \ No newline at end of file +}); 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 e317fe7616b..fd821c547c2 100644 --- a/addons/web_editor/static/src/scss/web_editor.variables.scss +++ b/addons/web_editor/static/src/scss/web_editor.variables.scss @@ -48,6 +48,29 @@ $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; + + // ============ 'Preview' component Mixins =============== // Create a "preview" work-area to edit/customize specific elements @@ -66,6 +89,26 @@ $o-checklist-before-size: 13px; box-shadow: 0 0 15px 2px #000; } +// Mixins for buttons with alpha bg-color +@mixin o-alpha-button-preview { + position: relative; + z-index: 1; + + // Place an overlay that inherit the button's bg-color. + &::before { + content: ""; + @include o-position-absolute(0, 0, 0, 0); + background-color: inherit; + } + + &::after { + content: ""; + @include o-position-absolute(0, 0, 0, 0); + z-index: -1; + background-image: url('/web_editor/static/src/img/transparent.png'); + background-size: 10px auto; + } +} // ============ Preservation mixins =============== // Used to preserve web_editor and website UI from themes customizations diff --git a/addons/web_editor/static/src/scss/wysiwyg_variables.scss b/addons/web_editor/static/src/scss/wysiwyg_variables.scss deleted file mode 100644 index 2beafe71681..00000000000 --- a/addons/web_editor/static/src/scss/wysiwyg_variables.scss +++ /dev/null @@ -1,42 +0,0 @@ -// Mixins for buttons with alpha bg-color -@mixin o-alpha-button-preview { - position: relative; - z-index: 1; - - // Place an overlay that inherit the button's bg-color. - &::before { - content: ""; - @include o-position-absolute(0, 0, 0, 0); - background-color: inherit; - } - - &::after { - content: ""; - @include o-position-absolute(0, 0, 0, 0); - z-index: -1; - background-image: url('/web_editor/static/src/img/transparent.png'); - background-size: 10px auto; - } -} - -$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; -- cgit v1.2.3 From 05f9b1f4e680ef05fccf2b9b324ec24282daa65d Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Tue, 27 Aug 2019 14:44:06 +0000 Subject: [REF] web_editor: do some linting and ES6 adaptation before UI refactor This commit aims at linting and converting to ES6 some parts of web_editor JS to avoid as much noise as possible in UI refactor in next commit. Part of https://github.com/odoo/odoo/pull/36068 task-1942370 --- .../static/src/js/editor/snippets.editor.js | 124 ++++++++++--------- .../static/src/js/editor/snippets.options.js | 136 +++++++++++---------- 2 files changed, 135 insertions(+), 125 deletions(-) (limited to 'addons/web_editor/static/src') 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 d79e0a9a2cc..1f0bda9a1fc 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -10,9 +10,9 @@ var Wysiwyg = require('web_editor.wysiwyg'); var _t = core._t; var globalSelector = { - closest: function () { return $(); }, - all: function () { return $(); }, - is: function () { return false; }, + closest: () => $(), + all: () => $(), + is: () => false, }; /** @@ -29,14 +29,14 @@ var SnippetEditor = Widget.extend({ }, custom_events: { cover_update: '_onCoverUpdate', - option_update: '_onOptionUpdate', + 'option_update': '_onOptionUpdate', }, /** * @constructor * @param {Widget} parent * @param {Element} target - * @param templateOptions + * @param {Object} templateOptions * @param {jQuery} $editable * @param {Object} options */ @@ -404,14 +404,20 @@ var SnippetEditor = Widget.extend({ self.$el.addClass('d-none'); var $selectorSiblings; - for (var i = 0 ; i < self.selectorSiblings.length ; i++) { - if (!$selectorSiblings) $selectorSiblings = self.selectorSiblings[i].all(); - else $selectorSiblings = $selectorSiblings.add(self.selectorSiblings[i].all()); + for (var i = 0; i < self.selectorSiblings.length; i++) { + if (!$selectorSiblings) { + $selectorSiblings = self.selectorSiblings[i].all(); + } else { + $selectorSiblings = $selectorSiblings.add(self.selectorSiblings[i].all()); + } } var $selectorChildren; - for (i = 0 ; i < self.selectorChildren.length ; i++) { - if (!$selectorChildren) $selectorChildren = self.selectorChildren[i].all(); - else $selectorChildren = $selectorChildren.add(self.selectorChildren[i].all()); + for (i = 0; i < self.selectorChildren.length; i++) { + if (!$selectorChildren) { + $selectorChildren = self.selectorChildren[i].all(); + } else { + $selectorChildren = $selectorChildren.add(self.selectorChildren[i].all()); + } } this.trigger_up('go_to_parent', {$snippet: this.$target}); @@ -572,14 +578,14 @@ var SnippetsMenu = Widget.extend({ cacheSnippetTemplate: {}, activeSnippets: [], custom_events: { - activate_insertion_zones: '_onActivateInsertionZones', - call_for_each_child_snippet: '_onCallForEachChildSnippet', - deactivate_snippet: '_onDeactivateSnippet', - drag_and_drop_stop: '_onDragAndDropStop', - go_to_parent: '_onGoToParent', - remove_snippet: '_onRemoveSnippet', - snippet_removed: '_onSnippetRemoved', - reload_snippet_dropzones: '_disableUndroppableSnippets', + 'activate_insertion_zones': '_onActivateInsertionZones', + 'call_for_each_child_snippet': '_onCallForEachChildSnippet', + 'deactivate_snippet': '_onDeactivateSnippet', + 'drag_and_drop_stop': '_onDragAndDropStop', + 'go_to_parent': '_onGoToParent', + 'remove_snippet': '_onRemoveSnippet', + 'snippet_removed': '_onSnippetRemoved', + 'reload_snippet_dropzones': '_disableUndroppableSnippets', }, /** @@ -635,22 +641,22 @@ var SnippetsMenu = Widget.extend({ }).insertAfter(this.$el); // Active snippet editor on click in the page - var lastClickedElement; - this.$document.on('click.snippets_menu', '*', function (ev) { + var lastElement; + this.$document.on('click.snippets_menu', '*', ev => { var srcElement = ev.target || (ev.originalEvent && (ev.originalEvent.target || ev.originalEvent.originalTarget)) || ev.srcElement; - if (lastClickedElement === srcElement || !srcElement) { + if (!srcElement || lastElement === srcElement) { return; } - lastClickedElement = srcElement; + lastElement = srcElement; _.defer(function () { - lastClickedElement = false; + lastElement = false; }); var $target = $(srcElement); if ($target.closest('.oe_overlay, .note-popover').length) { return; } - self._activateSnippet($target); + this._activateSnippet($target); }); core.bus.on('deactivate_snippet', this, this._onDeactivateSnippet); @@ -804,7 +810,7 @@ var SnippetsMenu = Widget.extend({ */ _activateInsertionZones: function ($selectorSiblings, $selectorChildren) { var self = this; - var zone_template = $('
', { + var zoneTemplate = $('
', { class: 'oe_drop_zone oe_insert', }); @@ -820,11 +826,11 @@ var SnippetsMenu = Widget.extend({ var float = css.float || css.cssFloat; var parentDisplay = parentCss.display; var parentFlex = parentCss.flexDirection; - var $drop = zone_template.clone(); + var $drop = zoneTemplate.clone(); $zone.append($drop); var node = $drop[0].previousSibling; - var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === 'BR')); + var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === 'BR')); if (test) { $drop.addClass('oe_vertical').css({ height: parseInt(self.window.getComputedStyle($zone[0]).lineHeight), @@ -842,7 +848,7 @@ var SnippetsMenu = Widget.extend({ $zone.prepend($drop); node = $drop[0].nextSibling; - test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === 'BR')); + test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === 'BR')); if (test) { $drop.addClass('oe_vertical').css({ height: parseInt(self.window.getComputedStyle($zone[0]).lineHeight), @@ -875,7 +881,7 @@ var SnippetsMenu = Widget.extend({ var parentFlex = parentCss.flexDirection; if ($zone.prev('.oe_drop_zone:visible').length === 0) { - $drop = zone_template.clone(); + $drop = zoneTemplate.clone(); if (float === 'left' || float === 'right' || (parentDisplay === 'flex' && parentFlex === 'row')) { $drop.css('float', float); if (!isFullWidth($zone)) { @@ -885,7 +891,7 @@ var SnippetsMenu = Widget.extend({ $zone.before($drop); } if ($zone.next('.oe_drop_zone:visible').length === 0) { - $drop = zone_template.clone(); + $drop = zoneTemplate.clone(); if (float === 'left' || float === 'right' || (parentDisplay === 'flex' && parentFlex === 'row')) { $drop.css('float', float); if (!isFullWidth($zone)) { @@ -918,15 +924,15 @@ var SnippetsMenu = Widget.extend({ zone.remove(); return; } - var float_prev = prev.css('float') || 'none'; - var float_next = next.css('float') || 'none'; - var disp_prev = prev.css('display') || null; - var disp_next = next.css('display') || null; - if ((float_prev === 'left' || float_prev === 'right') - && (float_next === 'left' || float_next === 'right')) { + var floatPrev = prev.css('float') || 'none'; + var floatNext = next.css('float') || 'none'; + var dispPrev = prev.css('display') || null; + var dispNext = next.css('display') || null; + if ((floatPrev === 'left' || floatPrev === 'right') + && (floatNext === 'left' || floatNext === 'right')) { zone.remove(); - } else if (disp_prev !== null && disp_next !== null - && disp_prev.indexOf('inline') >= 0 && disp_next.indexOf('inline') >= 0) { + } else if (dispPrev !== null && dispNext !== null + && dispPrev.indexOf('inline') >= 0 && dispNext.indexOf('inline') >= 0) { zone.remove(); } }); @@ -1040,7 +1046,7 @@ var SnippetsMenu = Widget.extend({ * considered (@see noCheck), this is true if the DOM elements' * parent must also be in an editable environment to be considered. */ - _computeSelectorFunctions : function (include, exclude, target, noCheck, isChildren) { + _computeSelectorFunctions: function (include, exclude, target, noCheck, isChildren) { var self = this; // Convert the selector for elements to include into a list @@ -1064,7 +1070,7 @@ var SnippetsMenu = Widget.extend({ } // (Re)join the subselectors - var selector =_.map(selectorList, function (s) { + var selector = _.map(selectorList, function (s) { return s + selectorConditions; }).join(', '); @@ -1087,7 +1093,7 @@ var SnippetsMenu = Widget.extend({ return $from.closest(selector, parentNode).filter(function () { var node = this; while (node.parentNode) { - if (parents.indexOf(node)!==-1) { + if (parents.indexOf(node) !== -1) { return true; } node = node.parentNode; @@ -1131,9 +1137,9 @@ var SnippetsMenu = Widget.extend({ var exclude = $style.data('exclude') || ''; var target = $style.data('target'); var noCheck = $style.data('no-check'); - var option_id = $style.data('js'); + var optionID = $style.data('js'); var option = { - 'option': option_id, + 'option': optionID, 'base_selector': selector, 'base_exclude': exclude, 'base_target': target, @@ -1151,7 +1157,7 @@ var SnippetsMenu = Widget.extend({ globalSelector.closest = function ($from) { var $temp; var $target; - for (var i = 0, len = selectors.length ; i < len ; i++) { + for (var i = 0, len = selectors.length; i < len; i++) { $temp = selectors[i].closest($from, $target && $target[0]); if ($temp.length) { $target = $temp; @@ -1161,13 +1167,13 @@ var SnippetsMenu = Widget.extend({ }; globalSelector.all = function ($from) { var $target = $(); - for (var i = 0, len = selectors.length ; i < len ; i++) { + for (var i = 0, len = selectors.length; i < len; i++) { $target = $target.add(selectors[i].all($from)); } return $target; }; globalSelector.is = function ($from) { - for (var i = 0, len = selectors.length ; i < len ; i++) { + for (var i = 0, len = selectors.length; i < len; i++) { if (selectors[i].is($from)) { return true; } @@ -1197,9 +1203,9 @@ var SnippetsMenu = Widget.extend({ return; // Compatibility with elements which do not use 't-snippet' } var $thumbnail = $(_.str.sprintf( - '
'+ - '
'+ - '%s'+ + '
' + + '
' + + '%s' + '
', $snippet.find('[data-oe-thumbnail]').data('oeThumbnail'), name @@ -1231,7 +1237,7 @@ var SnippetsMenu = Widget.extend({ // Remove branding from template _.each($html.find('[data-oe-model], [data-oe-type]'), function (el) { - for (var k = 0 ; k < el.attributes.length ; k++) { + for (var k = 0; k < el.attributes.length; k++) { if (el.attributes[k].name.indexOf('data-oe-') === 0) { $(el).removeAttr(el.attributes[k].name); k--; @@ -1296,11 +1302,13 @@ var SnippetsMenu = Widget.extend({ var cache = {}; this.$snippets.each(function () { var $snippet = $(this); - var $snippet_body = $snippet.find('.oe_snippet_body'); + var $snippetBody = $snippet.find('.oe_snippet_body'); var check = false; _.each(self.templateOptions, function (option, k) { - if (check || !($snippet_body.is(option.base_selector) && !$snippet_body.is(option.base_exclude))) return; + if (check || !($snippetBody.is(option.base_selector) && !$snippetBody.is(option.base_exclude))) { + return; + } cache[k] = cache[k] || { 'drop-near': option['drop-near'] ? option['drop-near'].all().length : 0, @@ -1321,8 +1329,8 @@ var SnippetsMenu = Widget.extend({ _makeSnippetDraggable: function ($snippets) { var self = this; var $tumb = $snippets.find('.oe_snippet_thumbnail_img:first'); - var left = $tumb.outerWidth()/2; - var top = $tumb.outerHeight()/2; + var left = $tumb.outerWidth() / 2; + var top = $tumb.outerHeight() / 2; var $toInsert, dropped, $snippet; $snippets.draggable({ @@ -1339,12 +1347,12 @@ var SnippetsMenu = Widget.extend({ start: function () { dropped = false; $snippet = $(this); - var $base_body = $snippet.find('.oe_snippet_body'); + var $baseBody = $snippet.find('.oe_snippet_body'); var $selectorSiblings = $(); var $selectorChildren = $(); var temp = self.templateOptions; for (var k in temp) { - if ($base_body.is(temp[k].base_selector) && !$base_body.is(temp[k].base_exclude)) { + if ($baseBody.is(temp[k].base_selector) && !$baseBody.is(temp[k].base_exclude)) { if (temp[k]['drop-near']) { $selectorSiblings = $selectorSiblings.add(temp[k]['drop-near'].all()); } @@ -1354,7 +1362,7 @@ var SnippetsMenu = Widget.extend({ } } - $toInsert = $base_body.clone().data('name', $base_body.data('name')); + $toInsert = $baseBody.clone().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"); 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 1278d70afda..893fd8adebb 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -65,7 +65,7 @@ var SnippetOption = Widget.extend({ * * @abstract */ - onFocus : function () {}, + onFocus: function () {}, /** * Called when the parent edition overlay is covering the associated snippet * for the first time, when it is a new snippet dropped from the d&d snippet @@ -80,7 +80,7 @@ var SnippetOption = Widget.extend({ * * @abstract */ - onBlur : function () {}, + onBlur: function () {}, /** * Called when the associated snippet is the result of the cloning of * another snippet (so `this.$target` is a cloned element). @@ -257,16 +257,16 @@ var SnippetOption = Widget.extend({ } while (this.$el.parent().has(el).length); // Call the found method in the right order (parents -> child) - _.each(methods.reverse(), function (data) { + methods.reverse().forEach(data => { var $el = $(data[0]); var methods = data[1]; - _.each(methods, function (value, methodName) { - if (self[methodName]) { + Object.keys(methods).forEach(methodName => { + if (this[methodName]) { if (previewMode === true) { - self.__methodNames.push(methodName); + this.__methodNames.push(methodName); } - self[methodName](previewMode, value, $el); + this[methodName](previewMode, methods[methodName], $el); } }); }); @@ -463,22 +463,22 @@ registry.sizing = SnippetOption.extend({ $body.addClass(cursor); var xy = ev['page' + XY]; - var body_mousemove = function (ev) { + var bodyMouseMove = function (ev) { ev.preventDefault(); var dd = ev['page' + XY] - xy + resize[1][begin]; - var next = current + (current+1 === resize[1].length ? 0 : 1); - var prev = current ? (current-1) : 0; + var next = current + (current + 1 === resize[1].length ? 0 : 1); + var prev = current ? (current - 1) : 0; var change = false; - if (dd > (2*resize[1][next] + resize[1][current])/3) { - self.$target.attr('class', (self.$target.attr('class')||'').replace(regClass, '')); + if (dd > (2 * resize[1][next] + resize[1][current]) / 3) { + self.$target.attr('class', (self.$target.attr('class') || '').replace(regClass, '')); self.$target.addClass(resize[0][next]); current = next; change = true; } - if (prev !== current && dd < (2*resize[1][prev] + resize[1][current])/3) { - self.$target.attr('class', (self.$target.attr('class')||'').replace(regClass, '')); + if (prev !== current && dd < (2 * resize[1][prev] + resize[1][current]) / 3) { + self.$target.attr('class', (self.$target.attr('class') || '').replace(regClass, '')); self.$target.addClass(resize[0][prev]); current = prev; change = true; @@ -490,9 +490,9 @@ registry.sizing = SnippetOption.extend({ $handle.addClass('o_active'); } }; - var body_mouseup = function () { - $body.off('mousemove', body_mousemove); - $body.off('mouseup', body_mouseup); + var bodyMouseUp = function () { + $body.off('mousemove', bodyMouseMove); + $body.off('mouseup', bodyMouseUp); $body.removeClass(cursor); $handle.removeClass('o_active'); @@ -512,8 +512,8 @@ registry.sizing = SnippetOption.extend({ }); }, 0); }; - $body.on('mousemove', body_mousemove); - $body.on('mouseup', body_mouseup); + $body.on('mousemove', bodyMouseMove); + $body.on('mouseup', bodyMouseUp); }); return def; @@ -607,7 +607,7 @@ registry.sizing = SnippetOption.extend({ /** * Handles the edition of padding-top and padding-bottom. */ -registry.sizing_y = registry.sizing.extend({ +registry['sizing_y'] = registry.sizing.extend({ //-------------------------------------------------------------------------- // Private @@ -629,13 +629,13 @@ registry.sizing_y = registry.sizing.extend({ } var grid = []; - for (var i = 0 ; i <= 256/8 ; i++) { + for (var i = 0; i <= (256 / 8); i++) { grid.push(i * 8); } grid.splice(1, 0, 4); this.grid = { - n: [_.map(grid, function (v) { return nClass + v; }), grid, nProp], - s: [_.map(grid, function (v) { return sClass + v; }), grid, sProp], + n: [grid.map(v => nClass + v), grid, nProp], + s: [grid.map(v => sClass + v), grid, sProp], }; return this.grid; }, @@ -851,10 +851,10 @@ registry.background = SnippetOption.extend({ 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'), + 'onlyImages': true, + 'firstFilters': ['background'], + 'res_model': $editable.data('oe-model'), + 'res_id': $editable.data('oe-id'), }, $image[0]).open(); _editor.on('save', this, function () { @@ -881,7 +881,9 @@ registry.background = SnippetOption.extend({ this.$target.off('.background-option') .on('background-color-event.background-option', (function (e, previewMode) { e.stopPropagation(); - if (e.currentTarget !== e.target) return; + if (e.currentTarget !== e.target) { + return; + } if (previewMode === false) { this.__customImageSrc = undefined; } @@ -952,7 +954,7 @@ registry.background = SnippetOption.extend({ /** * Handles the edition of snippet's background image position. */ -registry.background_position = SnippetOption.extend({ +registry['background_position'] = SnippetOption.extend({ xmlDependencies: ['/web_editor/static/src/xml/editor.xml'], /** @@ -984,10 +986,10 @@ registry.background_position = SnippetOption.extend({ backgroundPosition: function (previewMode, value, $opt) { var self = this; - this.previous_state = [this.$target.attr('class'), this.$target.css('background-size'), this.$target.css('background-position')]; + this.previousState = [this.$target.attr('class'), this.$target.css('background-size'), this.$target.css('background-position')]; - this.bg_pos = self.$target.css('background-position').split(' '); - this.bg_siz = self.$target.css('background-size').split(' '); + this.bgPos = self.$target.css('background-position').split(' '); + this.bgSize = self.$target.css('background-size').split(' '); this.modal = new Dialog(null, { title: _t("Background Image Sizing"), @@ -1000,7 +1002,7 @@ registry.background_position = SnippetOption.extend({ this.modal.opened().then(function () { // Fetch data form $target - var value = ((self.$target.hasClass('o_bg_img_opt_contain'))? 'contain' : ((self.$target.hasClass('o_bg_img_opt_custom'))? 'custom' : 'cover')); + var value = ((self.$target.hasClass('o_bg_img_opt_contain')) ? 'contain' : ((self.$target.hasClass('o_bg_img_opt_custom')) ? 'custom' : 'cover')); self.modal.$('> label > input[value=' + value + ']').prop('checked', true); if (self.$target.hasClass('o_bg_img_opt_repeat')) { @@ -1012,26 +1014,26 @@ registry.background_position = SnippetOption.extend({ self.modal.$('#o_bg_img_opt_custom_repeat').val('o_bg_img_opt_repeat_y'); } - if (self.bg_pos.length > 1) { - self.bg_pos = { - x: self.bg_pos[0], - y: self.bg_pos[1], + if (self.bgPos.length > 1) { + self.bgPos = { + x: self.bgPos[0], + y: self.bgPos[1], }; - self.modal.$('#o_bg_img_opt_custom_pos_x').val(self.bg_pos.x.replace('%', '')); - self.modal.$('#o_bg_img_opt_custom_pos_y').val(self.bg_pos.y.replace('%', '')); + self.modal.$('#o_bg_img_opt_custom_pos_x').val(self.bgPos.x.replace('%', '')); + self.modal.$('#o_bg_img_opt_custom_pos_y').val(self.bgPos.y.replace('%', '')); } - if (self.bg_siz.length > 1) { - self.modal.$('#o_bg_img_opt_custom_size_x').val(self.bg_siz[0].replace('%', '')); - self.modal.$('#o_bg_img_opt_custom_size_y').val(self.bg_siz[1].replace('%', '')); + if (self.bgSize.length > 1) { + self.modal.$('#o_bg_img_opt_custom_size_x').val(self.bgSize[0].replace('%', '')); + self.modal.$('#o_bg_img_opt_custom_size_y').val(self.bgSize[1].replace('%', '')); } // Focus Point - self.$focus = self.modal.$('.o_focus_point'); + self.$focus = self.modal.$('.o_focus_point'); self._updatePosInformation(); - var img_url = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image')); - img_url = (img_url && img_url[1]) || ''; - var $img = $('', {class: 'img img-fluid', src: img_url}); + var imgURL = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image')); + imgURL = (imgURL && imgURL[1]) || ''; + var $img = $('', {class: 'img img-fluid', src: imgURL}); $img.on('load', function () { self._bindImageEvents($img); }); @@ -1085,9 +1087,9 @@ registry.background_position = SnippetOption.extend({ function _update(e) { var posX = e.pageX - $(e.target).offset().left; var posY = e.pageY - $(e.target).offset().top; - self.bg_pos = { - x: clipValue(posX/$img.width()*100).toFixed(2) + '%', - y: clipValue(posY/$img.height()*100).toFixed(2) + '%', + self.bgPos = { + x: clipValue(posX / $img.width() * 100).toFixed(2) + '%', + y: clipValue(posY / $img.height() * 100).toFixed(2) + '%', }; self._updatePosInformation(); self._saveChanges(); @@ -1116,10 +1118,10 @@ registry.background_position = SnippetOption.extend({ */ _discardChanges: function () { this._clean(); - if (this.previous_state) { - this.$target.addClass(this.previous_state[0]).css({ - 'background-size': this.previous_state[1], - 'background-position': this.previous_state[2], + if (this.previousState) { + this.$target.addClass(this.previousState[0]).css({ + 'background-size': this.previousState[1], + 'background-position': this.previousState[2], }); } }, @@ -1129,11 +1131,11 @@ registry.background_position = SnippetOption.extend({ * @private */ _updatePosInformation: function () { - this.modal.$('.o_bg_img_opt_ui_info .o_x').text(this.bg_pos.x); - this.modal.$('.o_bg_img_opt_ui_info .o_y').text(this.bg_pos.y); + this.modal.$('.o_bg_img_opt_ui_info .o_x').text(this.bgPos.x); + this.modal.$('.o_bg_img_opt_ui_info .o_y').text(this.bgPos.y); this.$focus.css({ - left: this.bg_pos.x, - top: this.bg_pos.y, + left: this.bgPos.x, + top: this.bgPos.y, }); }, /** @@ -1144,10 +1146,10 @@ registry.background_position = SnippetOption.extend({ _saveChanges: function () { this._clean(); - var bg_img_size = this.modal.$('> :not(label):not(.o_hidden)').data('value') || 'cover'; - switch (bg_img_size) { + var bgImgSize = this.modal.$('> :not(label):not(.o_hidden)').data('value') || 'cover'; + switch (bgImgSize) { case 'cover': - this.$target.css('background-position', this.bg_pos.x + ' ' + this.bg_pos.y); + this.$target.css('background-position', this.bgPos.x + ' ' + this.bgPos.y); break; case 'contain': this.$target.addClass('o_bg_img_opt_contain'); @@ -1161,8 +1163,8 @@ registry.background_position = SnippetOption.extend({ var posY = this.modal.$('#o_bg_img_opt_custom_pos_y').val(); this.$target.addClass(this.modal.$('#o_bg_img_opt_custom_repeat').val()) .css({ - 'background-size': ((sizeX)? sizeX + '%' : 'auto') + ' ' + ((sizeY)? sizeY + '%' : 'auto'), - 'background-position': ((posX)? posX + '%' : 'auto') + ' ' + ((posY)? posY + '%' : 'auto'), + 'background-size': (sizeX ? sizeX + '%' : 'auto') + ' ' + (sizeY ? sizeY + '%' : 'auto'), + 'background-position': (posX ? posX + '%' : 'auto') + ' ' + (posY ? posY + '%' : 'auto'), }); break; } @@ -1288,7 +1290,7 @@ registry.many2one = SnippetOption.extend({ }, }).then(function (result) { self.$search.siblings().remove(); - self.$search.after(qweb.render('web_editor.many2one.search',{contacts:result})); + self.$search.after(qweb.render('web_editor.many2one.search', {contacts: result})); }); }, /** @@ -1308,10 +1310,10 @@ registry.many2one = SnippetOption.extend({ if (self.$target.data('oe-type') === 'contact') { $('[data-oe-contact-options]') - .filter('[data-oe-model="'+self.$target.data('oe-model')+'"]') - .filter('[data-oe-id="'+self.$target.data('oe-id')+'"]') - .filter('[data-oe-field="'+self.$target.data('oe-field')+'"]') - .filter('[data-oe-contact-options!="'+self.$target.data('oe-contact-options')+'"]') + .filter('[data-oe-model="' + self.$target.data('oe-model') + '"]') + .filter('[data-oe-id="' + self.$target.data('oe-id') + '"]') + .filter('[data-oe-field="' + self.$target.data('oe-field') + '"]') + .filter('[data-oe-contact-options!="' + self.$target.data('oe-contact-options') + '"]') .add(self.$target) .attr('data-oe-many2one-id', self.ID).data('oe-many2one-id', self.ID) .each(function () { -- cgit v1.2.3 From 4f27e52cabb77b8b1a9637a11185ddf882adc9af Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Tue, 27 Aug 2019 14:47:47 +0000 Subject: [IMP] web_editor, *: introduce and restore new web_editor UI * mass_mailing, note, website, website_blog, website_form, website_mass_mailing, website_sale This commit, unfortunately, mixes three things: - Restoring as much as possible the scss organisation to allow styling the web_editor UI properly. - Fixing some bugs like a border around the page once the editor is loaded, no ability to scroll the snippets, etc - Introducing a whole new UI for snippet options: a left panel instead of the old dropdown & button overlay. Note: this commit also do some linting and ES6 convertion even though some of it has been done in the parent commit. Note 2: some elements that were removed are still styled in the POS apps but this is because part of a feature was removed while leaving dead code behind, this is handled in another PR which is to be merged (https://github.com/odoo/odoo/pull/36136). Part of https://github.com/odoo/odoo/pull/36068 task-1942370 closes odoo/odoo#36068 Signed-off-by: Quentin Smetz (qsm) --- addons/web_editor/static/src/js/editor/editor.js | 4 +- .../static/src/js/editor/snippets.editor.js | 511 ++++++++----- .../static/src/js/editor/snippets.options.js | 121 ++- .../static/src/scss/web_editor.backend.scss | 7 +- .../static/src/scss/web_editor.variables.scss | 72 +- addons/web_editor/static/src/scss/wysiwyg.scss | 439 ++++------- .../web_editor/static/src/scss/wysiwyg_iframe.scss | 13 - .../static/src/scss/wysiwyg_snippets.scss | 832 +++++++++------------ addons/web_editor/static/src/xml/snippets.xml | 38 +- 9 files changed, 925 insertions(+), 1112 deletions(-) (limited to 'addons/web_editor/static/src') 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(); @@ -160,6 +158,24 @@ var SnippetEditor = Widget.extend({ this.$('.o_handles').css('height', this.$target.outerHeight()); 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($('
', {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($('
', {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($('
', {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}); @@ -377,15 +422,6 @@ var SnippetEditor = Widget.extend({ this.trigger_up('snippet_cloned', {$target: $clone, $origin: this.$target}); $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 @@ -503,6 +537,47 @@ var SnippetEditor = Widget.extend({ $snippet: this.$target, }); }, + /** + * @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) { @@ -534,28 +608,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. * @@ -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} * (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 { @@ -1480,6 +1559,15 @@ var SnippetsMenu = Widget.extend({ _onActivateInsertionZones: function (ev) { 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. @@ -1499,6 +1587,24 @@ var SnippetsMenu = Widget.extend({ _onCleanForSaveDemand: function (ev) { 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("
" + elem.dataset.display + "
"); + $(elem).prepend("
" + elem.dataset.display + "
"); }); // 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 @@
+
-
-
-
- - + + +
+
+
+
+ + + + + + + + + + + + + +
-
Background Color