summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
committerChristophe Simonis <chs@odoo.com>2019-09-13 18:07:59 +0200
commit6c948c36bbf72dd896e0bff03aa7fbddadcbb0cd (patch)
treef8620905c011735d8a3c340c46d1417a54a75972 /addons/web_editor/static/src
parent48503fcf08eac6fed12e080d4f501f9bbc8d257d (diff)
parent197474e5b9ad43ab2d587a98136a1a809ce9a2e7 (diff)
[MERGE] forward port branch saas-12.5 up to 197474e5b9a
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/js/editor/editor.js4
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.editor.js705
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js259
-rw-r--r--addons/web_editor/static/src/js/frontend/loader.js28
-rw-r--r--addons/web_editor/static/src/js/frontend/loader_loading.js30
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/wysiwyg_iframe.js5
-rw-r--r--addons/web_editor/static/src/scss/web_editor.backend.scss7
-rw-r--r--addons/web_editor/static/src/scss/web_editor.frontend.scss11
-rw-r--r--addons/web_editor/static/src/scss/web_editor.variables.scss70
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg.scss447
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_iframe.scss13
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_snippets.scss795
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_variables.scss42
-rw-r--r--addons/web_editor/static/src/xml/snippets.xml35
14 files changed, 1165 insertions, 1286 deletions
diff --git a/addons/web_editor/static/src/js/editor/editor.js b/addons/web_editor/static/src/js/editor/editor.js
index 07947db56e4..31c31e5ce6a 100644
--- a/addons/web_editor/static/src/js/editor/editor.js
+++ b/addons/web_editor/static/src/js/editor/editor.js
@@ -106,7 +106,9 @@ var EditorMenuBar = Widget.extend({
// Snippets menu
if (self.snippetsMenu) {
- defs.push(this.snippetsMenu.insertAfter(this.$el));
+ // TODO improve this, the editor can be relocated so here,
+ // 'insertAfter(this.$el)' won't work.
+ defs.push(this.snippetsMenu.appendTo(this.$el.parent()));
}
this.rte.editable().find('*').off('mousedown mouseup click');
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index 4fba95f2490..c9571f59c51 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');
@@ -10,9 +11,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,
};
/**
@@ -21,22 +22,15 @@ var globalSelector = {
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',
- },
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
*/
@@ -49,58 +43,75 @@ 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;
- });
- this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', function () {
- self._targetIsAnimated = false;
- if (self.$el.is('.oe_active')) {
- self.cover();
- }
+ if (!this.isTargetParentEditable) {
+ $customize.find('.oe_snippet_remove').addClass('d-none');
+ }
+
+ var _animationsCount = 0;
+ var postAnimationCover = _.throttle(() => this.cover(), 100);
+ this.$target.on('transitionstart.snippet_editor, animationstart.snippet_editor', () => {
+ // We cannot rely on the fact each transition/animation start will
+ // trigger a transition/animation end as the element may be removed
+ // from the DOM before or it could simply be an infinite animation.
+ //
+ // By simplicity, for each start, we add a delayed operation that
+ // will decrease the animation counter after a fixed duration and
+ // do the post animation cover if none is registered anymore.
+ _animationsCount++;
+ setTimeout(() => {
+ if (!--_animationsCount) {
+ postAnimationCover();
+ }
+ }, 500); // This delay have to be huge enough to take care of long
+ // animations which will not trigger an animation end event
+ // but if it is too small for some, this is the job of the
+ // animation creator to manually ask for a re-cover
});
+ // On top of what is explained above, do the post animation cover for
+ // each detected transition/animation end so that the user does not see
+ // a flickering when not needed.
+ this.$target.on('transitionend.snippet_editor, animationend.snippet_editor', postAnimationCover);
return Promise.all(defs);
},
@@ -143,9 +154,7 @@ var SnippetEditor = Widget.extend({
* Makes the editor overlay cover the associated snippet.
*/
cover: function () {
- if (this._targetIsAnimated) {
- // Do not cover a target being animated, it will be covered once the
- // animation is completed.
+ if (!this.isShown() || !this.$target.length) {
return;
}
var offset = this.$target.offset();
@@ -161,6 +170,12 @@ var SnippetEditor = Widget.extend({
this.$el.toggleClass('o_top_cover', offset.top < this.$editable.offset().top);
},
/**
+ * @return {boolean}
+ */
+ isShown: function () {
+ return this.$el && this.$el.parent().length && this.$el.hasClass('oe_active');
+ },
+ /**
* Removes the associated snippet from the DOM and destroys the associated
* editor (itself).
*/
@@ -176,6 +191,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();
@@ -189,17 +205,17 @@ var SnippetEditor = Widget.extend({
}
if ($parent.closest(':data("snippet-editor")').length) {
- while (!$parent.data('snippet-editor')) {
+ var editor = $parent.data('snippet-editor');
+ while (!editor) {
var $nextParent = $parent.parent();
- if ($parent.children().length === 0 && $parent.text().trim() === '' && !$parent.hasClass('oe_structure')) {
+ if (isEmptyAndRemovable($parent)) {
$parent.remove();
}
$parent = $nextParent;
+ editor = $parent.data('snippet-editor');
}
- if ($parent.children().length === 0 && $parent.text().trim() === '' && !$parent.hasClass('oe_structure')) {
- _.defer(function () {
- $parent.data('snippet-editor').removeSnippet();
- });
+ if (isEmptyAndRemovable($parent, editor)) {
+ setTimeout(() => editor.removeSnippet());
}
}
@@ -210,6 +226,12 @@ var SnippetEditor = Widget.extend({
this.trigger_up('snippet_removed');
this.destroy();
$parent.trigger('content_changed');
+
+ function isEmptyAndRemovable($el, editor) {
+ editor = editor || $el.data('snippet-editor');
+ return $el.children().length === 0 && $el.text().trim() === ''
+ && !$el.hasClass('oe_structure') && (!editor || editor.isTargetParentEditable);
+ }
},
/**
* Displays/Hides the editor overlay and notifies the associated snippet
@@ -217,51 +239,63 @@ 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]
*/
- toggleFocus: function (focus) {
+ toggleFocus: function (focus, previewMode) {
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));
+ if (previewMode) {
+ // In preview mode, the sticky classes are left untouched, we only
+ // add/remove the preview class when toggling/untoggling
+ this.$el.toggleClass('o_we_overlay_preview', focus);
+ } else {
+ // In non preview mode, the preview class is always removed, and the
+ // sticky class is added/removed when toggling/untoggling
+ this.$el.removeClass('o_we_overlay_preview');
+ this.$el.toggleClass('o_we_overlay_sticky', focus);
+ }
- // Activate the overlay
- $style_button.toggleClass('d-none', $ul.children(':not(.o_main_header):not(.dropdown-divider):not(.d-none)').length === 0);
+ focus = this.$el.hasClass('o_we_overlay_sticky') ? true : focus;
+
+ // Show/hide overlay in preview mode or not
+ this.$el.toggleClass('oe_active', focus);
this.cover();
- this.$el.toggleClass('oe_active', !!focus);
- if (focus) {
- this.trigger_up('snippet_focused');
+ // In non-preview mode, update the options panel if necessary
+ if (previewMode) {
+ return;
}
-
- function _do_action_focus(style, $dest) {
- style.$el.insertAfter($dest);
- style.onFocus();
- return (style.$el.length > 0);
+ var lastIndex = this._customize$Elements.length - 1;
+ var optionsAlreadyShown = !!this._customize$Elements[lastIndex].parent().length;
+ if (optionsAlreadyShown === focus) {
+ return;
}
- function _do_action_blur(style, $dest) {
- style.$el.detach();
- style.onBlur();
- return false;
+ this.trigger_up('update_customize_elements', {
+ customize$Elements: focus ? this._customize$Elements : [],
+ });
+ this._customize$Elements.forEach(($el, i) => {
+ var editor = $el.data('editor');
+ var styles = _.values(editor.styles);
+ $el.toggleClass('d-none', styles.length === 0);
+ _.sortBy(styles, '__order').reverse().forEach(style => {
+ if (focus) {
+ style.$el.appendTo($el);
+ style.onFocus();
+ } else {
+ style.$el.detach();
+ style.onBlur();
+ }
+ });
+ });
+ },
+ /**
+ * @param {boolean} [isTextEdition=false]
+ */
+ toggleTextEdition: function (isTextEdition) {
+ if (this.$el) {
+ this.$el.toggleClass('o_keypress', !!isTextEdition && this.isShown());
}
},
@@ -291,53 +325,61 @@ var SnippetEditor = Widget.extend({
* @private
*/
_initializeOptions: function () {
- var self = this;
- var $styles = this.$el.find('.oe_options');
- var $ul = $styles.find('.dropdown-menu:first');
+ this._customize$Elements = [];
this.styles = {};
this.selectorSiblings = [];
this.selectorChildren = [];
+ var $element = this.$target.parent();
+ while ($element.length) {
+ var parentEditor = $element.data('snippet-editor');
+ if (parentEditor) {
+ this._customize$Elements = this._customize$Elements
+ .concat(parentEditor._customize$Elements);
+ break;
+ }
+ $element = $element.parent();
+ }
+
+ var $optionsSection = $(core.qweb.render('web_editor.customize_block_options_section', {
+ name: this._getName(),
+ })).data('editor', this);
+ $optionsSection.on('mouseover', this._onOptionsSectionMouseOver.bind(this));
+ $optionsSection.on('mouseleave', this._onOptionsSectionMouseLeave.bind(this));
+ $optionsSection.on('click', this._onOptionsSectionClick.bind(this));
+ $optionsSection.on('click', '.oe_snippet_clone', this._onCloneClick.bind(this));
+ $optionsSection.on('click', '.oe_snippet_remove', this._onRemoveClick.bind(this));
+ this._customize$Elements.push($optionsSection);
+
var i = 0;
- $ul.append($('<div/>', {class: 'dropdown-header o_main_header', text: this._getName()}).data('editor', this));
- var defs = _.map(this.templateOptions, function (val, option_id) {
- if (!val.selector.is(self.$target)) {
+ var defs = _.map(this.templateOptions, val => {
+ if (!val.selector.is(this.$target)) {
return;
}
- if (val['drop-near']) self.selectorSiblings.push(val['drop-near']);
- if (val['drop-in']) self.selectorChildren.push(val['drop-in']);
+ if (val['drop-near']) {
+ this.selectorSiblings.push(val['drop-near']);
+ }
+ if (val['drop-in']) {
+ this.selectorChildren.push(val['drop-in']);
+ }
var optionName = val.option;
var $el = val.$el.children().clone(true).addClass('snippet-option-' + optionName);
var option = new (options.registry[optionName] || options.Class)(
- self,
- val.base_target ? self.$target.find(val.base_target).eq(0) : self.$target,
- self.$el,
+ this,
+ val.base_target ? this.$target.find(val.base_target).eq(0) : this.$target,
+ this.$el,
val.data,
- self.options
+ this.options
);
- self.styles[optionName || _.uniqueId('option')] = option;
+ this.styles[optionName || _.uniqueId('option')] = option;
option.__order = i++;
- return option.attachTo($el);
+ var $optionSection = $(core.qweb.render('web_editor.customize_block_option'));
+ $optionSection.append($el);
+ return option.attachTo($optionSection);
});
- $ul.append($('<div/>', {class: 'dropdown-divider mt-2'}));
- var $parents = this.$target.parents();
- _.each($parents, function (parent) {
- var parentEditor = $(parent).data('snippet-editor');
- if (parentEditor) {
- for (var styleName in parentEditor.styles) {
- if (!parentEditor.styles[styleName].preventChildPropagation) {
- $ul.append($('<div/>', {class: 'dropdown-header o_parent_editor_header', text: parentEditor._getName()}).data('editor', parentEditor));
- break;
- }
- }
- }
- });
-
- if (!this.selectorSiblings.length && !this.selectorChildren.length) {
- this.$el.find('.oe_snippet_move, .oe_snippet_clone').addClass('d-none');
- }
+ this.isTargetMovable = (this.selectorSiblings.length > 0 || this.selectorChildren.length > 0);
this.$el.find('[data-toggle="dropdown"]').dropdown();
@@ -356,7 +398,7 @@ var SnippetEditor = Widget.extend({
*/
_onCloneClick: function (ev) {
ev.preventDefault();
- this.trigger_up('cover_will_change');
+ ev.stopPropagation();
this.trigger_up('snippet_will_be_cloned', {$target: this.$target});
@@ -378,15 +420,6 @@ var SnippetEditor = Widget.extend({
$clone.trigger('content_changed');
},
/**
- * Called when the overlay dimensions/positions should be recomputed.
- *
- * @private
- */
- _onCoverUpdate: function () {
- this.trigger_up('cover_will_change');
- this.cover();
- },
- /**
* Called when the snippet is starting to be dragged thanks to the 'move'
* button.
*
@@ -404,14 +437,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});
@@ -434,8 +473,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
@@ -446,8 +483,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 +492,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();
@@ -498,6 +535,33 @@ var SnippetEditor = Widget.extend({
});
},
/**
+ * @private
+ */
+ _onOptionsSectionMouseOver: function (ev) {
+ 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) {
+ this.trigger_up('activate_snippet', {
+ $snippet: this.$target,
+ previewMode: false,
+ });
+ },
+ /**
* Called when a child editor/option asks for another option to perform a
* specific action/react to a specific event.
*
@@ -505,7 +569,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) {
@@ -529,28 +592,6 @@ var SnippetEditor = Widget.extend({
}
},
/**
- * Called when the user opens the customize menu.
- *
- * @private
- * @param {OdooEvent} ev
- */
- _onOpenCusomize: function (ev) {
- this.trigger_up('request_history_undo_record', {$target: this.$target});
- },
-
- /**
- * Called when the 'parent' button is clicked.
- *
- * @private
- * @param {Event} ev
- */
- _onParentButtonClick: function (ev) {
- ev.preventDefault();
- this.trigger_up('go_to_parent', {
- $snippet: this.$target,
- });
- },
- /**
* Called when the 'remove' button is clicked.
*
* @private
@@ -558,7 +599,7 @@ 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();
},
@@ -570,16 +611,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',
- 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',
+ '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',
},
/**
@@ -607,8 +653,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);
},
@@ -616,7 +662,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);
@@ -625,8 +670,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
@@ -635,45 +684,52 @@ 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) {
+ if ($target.closest('#snippets_menu').length) {
+ this._activateSnippet(false);
return;
}
- self._activateSnippet($target);
+ 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);
});
core.bus.on('deactivate_snippet', this, this._onDeactivateSnippet);
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
@@ -688,12 +744,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);
});
},
@@ -776,11 +839,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();
@@ -804,9 +865,6 @@ var SnippetsMenu = Widget.extend({
*/
_activateInsertionZones: function ($selectorSiblings, $selectorChildren) {
var self = this;
- var zone_template = $('<div/>', {
- class: 'oe_drop_zone oe_insert',
- });
function isFullWidth($elem) {
return $elem.parent().width() === $elem.outerWidth(true);
@@ -815,49 +873,50 @@ var SnippetsMenu = Widget.extend({
if ($selectorChildren) {
$selectorChildren.each(function () {
var $zone = $(this);
+ var style;
+ var vertical;
+ var node;
var css = self.window.getComputedStyle(this);
var parentCss = self.window.getComputedStyle($zone.parent()[0]);
var float = css.float || css.cssFloat;
var parentDisplay = parentCss.display;
var parentFlex = parentCss.flexDirection;
- var $drop = zone_template.clone();
- $zone.append($drop);
- var node = $drop[0].previousSibling;
- var test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === 'BR'));
+ style = {};
+ vertical = false;
+ node = $zone[0].lastChild;
+ 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),
- float: 'none',
- display: 'inline-block',
- });
+ vertical = true;
+ style['float'] = 'none';
+ style['height'] = parseInt(self.window.getComputedStyle($zone[0]).lineHeight) + 'px';
+ style['display'] = 'inline-block';
} else if (float === 'left' || float === 'right' || (parentDisplay === 'flex' && parentFlex === 'row')) {
- $drop.css('float', float);
+ style['float'] = float;
if (!isFullWidth($zone)) {
- $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().last().outerHeight()), 30));
+ vertical = true;
+ style['height'] = Math.max($zone.outerHeight(), 30) + 'px';
}
}
+ self._insertDropzone($('<we-hook/>').appendTo($zone), vertical, style);
- $drop = $drop.clone();
-
- $zone.prepend($drop);
- node = $drop[0].nextSibling;
- test = !!(node && ((!node.tagName && node.textContent.match(/\S/)) || node.tagName === 'BR'));
+ style = {};
+ vertical = false;
+ node = $zone[0].firstChild;
+ 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),
- float: 'none',
- display: 'inline-block'
- });
+ vertical = true;
+ style['float'] = 'none';
+ style['height'] = parseInt(self.window.getComputedStyle($zone[0]).lineHeight) + 'px';
+ style['display'] = 'inline-block';
} else if (float === 'left' || float === 'right' || (parentDisplay === 'flex' && parentFlex === 'row')) {
- $drop.css('float', float);
+ style['float'] = float;
if (!isFullWidth($zone)) {
- $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.children().first().outerHeight()), 30));
+ vertical = true;
+ style['height'] = Math.max($zone.outerHeight(), 30) + 'px';
}
}
- if (test) {
- $drop.css({'float': 'none', 'display': 'inline-block'});
- }
+ self._insertDropzone($('<we-hook/>').prependTo($zone), vertical, style);
});
// add children near drop zone
@@ -867,7 +926,8 @@ var SnippetsMenu = Widget.extend({
if ($selectorSiblings) {
$selectorSiblings.filter(':not(.oe_drop_zone):not(.oe_drop_clone)').each(function () {
var $zone = $(this);
- var $drop;
+ var style;
+ var vertical;
var css = self.window.getComputedStyle(this);
var parentCss = self.window.getComputedStyle($zone.parent()[0]);
var float = css.float || css.cssFloat;
@@ -875,24 +935,28 @@ var SnippetsMenu = Widget.extend({
var parentFlex = parentCss.flexDirection;
if ($zone.prev('.oe_drop_zone:visible').length === 0) {
- $drop = zone_template.clone();
+ style = {};
+ vertical = false;
if (float === 'left' || float === 'right' || (parentDisplay === 'flex' && parentFlex === 'row')) {
- $drop.css('float', float);
+ style['float'] = float;
if (!isFullWidth($zone)) {
- $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.prev().outerHeight() || Infinity), 30));
+ vertical = true;
+ style['height'] = Math.max($zone.outerHeight(), 30) + 'px';
}
}
- $zone.before($drop);
+ self._insertDropzone($('<we-hook/>').insertBefore($zone), vertical, style);
}
if ($zone.next('.oe_drop_zone:visible').length === 0) {
- $drop = zone_template.clone();
+ style = {};
+ vertical = false;
if (float === 'left' || float === 'right' || (parentDisplay === 'flex' && parentFlex === 'row')) {
- $drop.css('float', float);
+ style['float'] = float;
if (!isFullWidth($zone)) {
- $drop.addClass('oe_vertical').css('height', Math.max(Math.min($zone.outerHeight(), $zone.next().outerHeight() || Infinity), 30));
+ vertical = true;
+ style['height'] = Math.max($zone.outerHeight(), 30) + 'px';
}
}
- $zone.after($drop);
+ self._insertDropzone($('<we-hook/>').insertAfter($zone), vertical, style);
}
});
}
@@ -918,15 +982,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();
}
});
@@ -940,38 +1004,32 @@ 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]
* @returns {Promise<SnippetEditor>}
* (might be async when an editor must be created)
*/
- _activateSnippet: function ($snippet) {
- if ($snippet) {
- if (!globalSelector.is($snippet)) {
+ _activateSnippet: function ($snippet, previewMode) {
+ return this._activateSnippetMutex.exec(() => {
+ // First disable all editors if necessary
+ this.snippetEditors.forEach(editor => {
+ editor.toggleFocus(false, previewMode);
+ });
+ // 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);
+ 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
@@ -1040,7 +1098,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 +1122,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 +1145,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 +1189,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 +1209,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 +1219,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;
}
@@ -1188,8 +1246,8 @@ var SnippetsMenu = Widget.extend({
if (snippetClasses && snippetClasses.length) {
snippetClasses = '.' + snippetClasses.join('.');
}
- $(snippetClasses).data('name', name);
- $sbody.data('name', name);
+ var $els = $(snippetClasses).not('[data-name]').add($sbody);
+ $els.attr('data-name', name).data('name', name);
}
// Create the thumbnail
@@ -1197,9 +1255,9 @@ var SnippetsMenu = Widget.extend({
return; // Compatibility with elements which do not use 't-snippet'
}
var $thumbnail = $(_.str.sprintf(
- '<div class="oe_snippet_thumbnail">'+
- '<div class="oe_snippet_thumbnail_img" style="background-image: url(%s);"/>'+
- '<span class="oe_snippet_thumbnail_title">%s</span>'+
+ '<div class="oe_snippet_thumbnail">' +
+ '<div class="oe_snippet_thumbnail_img" style="background-image: url(%s);"/>' +
+ '<span class="oe_snippet_thumbnail_title">%s</span>' +
'</div>',
$snippet.find('[data-oe-thumbnail]').data('oeThumbnail'),
name
@@ -1231,7 +1289,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 +1354,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,
@@ -1313,6 +1373,26 @@ var SnippetsMenu = Widget.extend({
});
},
/**
+ * Creates a dropzone element and inserts it by replacing the given jQuery
+ * location. This allows to add data on the dropzone depending on the hook
+ * environment.
+ *
+ * @private
+ * @param {jQuery} $hook
+ * @param {boolean} [vertical=false]
+ * @param {Object} [style]
+ */
+ _insertDropzone: function ($hook, vertical, style) {
+ var $dropzone = $('<div/>', {
+ 'class': 'oe_drop_zone oe_insert' + (vertical ? ' oe_vertical' : ''),
+ });
+ if (style) {
+ $dropzone.css(style);
+ }
+ $hook.replaceWith($dropzone);
+ return $dropzone;
+ },
+ /**
* Make given snippets be draggable/droppable thanks to their thumbnail.
*
* @private
@@ -1321,8 +1401,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 +1419,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 +1434,7 @@ var SnippetsMenu = Widget.extend({
}
}
- $toInsert = $base_body.clone().data('name', $base_body.data('name'));
+ $toInsert = $baseBody.clone();
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");
@@ -1382,8 +1462,6 @@ var SnippetsMenu = Widget.extend({
}
},
});
-
- self.trigger_up('cover_will_change');
},
stop: function (ev, ui) {
$toInsert.removeClass('oe_snippet_body');
@@ -1427,9 +1505,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 {
@@ -1473,6 +1548,15 @@ var SnippetsMenu = Widget.extend({
this._activateInsertionZones(ev.data.$selectorSiblings, ev.data.$selectorChildren);
},
/**
+ * Called when a child editor asks to deactivate the current snippet
+ * overlay.
+ *
+ * @private
+ */
+ _onActivateSnippet: function (ev) {
+ this._activateSnippet(ev.data.$snippet, ev.data.previewMode);
+ },
+ /**
* Called when a child editor asks to operate some operation on all child
* snippet of a DOM element.
*
@@ -1492,6 +1576,24 @@ var SnippetsMenu = Widget.extend({
this.cleanForSave();
},
/**
+ * @private
+ * @param {*} ev
+ */
+ _onCollapseTogglerClick: function (ev) {
+ this.$('we-collapse-area > we-toggler').not(ev.currentTarget).removeClass('active');
+ ev.currentTarget.classList.toggle('active');
+ },
+ /**
+ * Called when the overlay dimensions/positions should be recomputed.
+ *
+ * @private
+ */
+ _onOverlaysCoverUpdate: function () {
+ this.snippetEditors.forEach(editor => {
+ editor.cover();
+ });
+ },
+ /**
* Called when a child editor asks to deactivate the current snippet
* overlay.
*
@@ -1540,6 +1642,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 1278d70afda..f124d1841f0 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).
*
@@ -65,7 +58,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 +73,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).
@@ -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
@@ -257,16 +253,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);
}
});
});
@@ -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();
@@ -463,22 +441,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 +468,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 +490,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;
@@ -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';
@@ -607,7 +599,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 +621,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;
},
@@ -671,7 +663,7 @@ registry.colorpicker = SnippetOption.extend({
var $clpicker = $(qweb.render('web_editor.colorpicker'));
_.each($clpicker.find('.o_colorpicker_section'), function (elem) {
- $(elem).prepend("<div class='text-muted mt8'>" + elem.dataset.display + "</div>");
+ $(elem).prepend("<div>" + elem.dataset.display + "</div>");
});
// Retrieve excluded palettes list
@@ -705,7 +697,7 @@ registry.colorpicker = SnippetOption.extend({
}
$pt.find('.o_colorpicker_section_tabs').append($clpicker);
- this.$el.find('.dropdown-menu').append($pt);
+ this.$el.find('we-collapse').append($pt);
}
var classes = [];
@@ -775,7 +767,7 @@ registry.colorpicker = SnippetOption.extend({
if ($selected.length) {
if ($selected.data('color')) {
this.$target.addClass(this.colorPrefix + $selected.data('color'));
- } else {
+ } else if ($selected.hasClass('o_custom_color')) {
this.$target.css('background-color', $selected.css('background-color'));
}
}
@@ -851,10 +843,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 +873,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;
}
@@ -943,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]);
},
@@ -952,7 +946,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 +978,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 +994,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 +1006,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 = $('<img/>', {class: 'img img-fluid', src: img_url});
+ var imgURL = /\(['"]?([^'"]+)['"]?\)/g.exec(self.$target.css('background-image'));
+ imgURL = (imgURL && imgURL[1]) || '';
+ var $img = $('<img/>', {class: 'img img-fluid', src: imgURL});
$img.on('load', function () {
self._bindImageEvents($img);
});
@@ -1085,9 +1079,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 +1110,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 +1123,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 +1138,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 +1155,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;
}
@@ -1191,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');
@@ -1201,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')
@@ -1288,7 +1275,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 +1295,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 () {
diff --git a/addons/web_editor/static/src/js/frontend/loader.js b/addons/web_editor/static/src/js/frontend/loader.js
new file mode 100644
index 00000000000..9c6cf0ef984
--- /dev/null
+++ b/addons/web_editor/static/src/js/frontend/loader.js
@@ -0,0 +1,28 @@
+odoo.define('web_editor.loader', function (require) {
+'use strict';
+
+var Wysiwyg = require('web_editor.wysiwyg.root');
+
+function load(parent, textarea, options) {
+ var loading = textarea.nextElementSibling;
+ if (loading && !loading.classList.contains('o_wysiwyg_loading')) {
+ loading = null;
+ }
+
+ if (!textarea.value.match(/\S/)) {
+ textarea.value = '<p><br/></p>';
+ }
+
+ var wysiwyg = new Wysiwyg(parent, options);
+ return wysiwyg.attachTo(textarea).then(() => {
+ if (loading) {
+ loading.parentNode.removeChild(loading);
+ }
+ return wysiwyg;
+ });
+}
+
+return {
+ load: load,
+};
+});
diff --git a/addons/web_editor/static/src/js/frontend/loader_loading.js b/addons/web_editor/static/src/js/frontend/loader_loading.js
new file mode 100644
index 00000000000..b5156944470
--- /dev/null
+++ b/addons/web_editor/static/src/js/frontend/loader_loading.js
@@ -0,0 +1,30 @@
+(function () {
+'use strict';
+
+/**
+ * This file makes sure textarea elements with a specific editor class are
+ * tweaked as soon as the DOM is ready so that they appear to be loading.
+ *
+ * They must then be loaded using standard Odoo modules system. In particular,
+ * @see web_editor.loader
+ */
+
+document.addEventListener('DOMContentLoaded', () => {
+ document.querySelectorAll('textarea.o_wysiwyg_loader').forEach(textarea => {
+ var wrapper = document.createElement('div');
+ wrapper.classList.add('position-relative', 'o_wysiwyg_wrapper');
+
+ var loadingElement = document.createElement('div');
+ loadingElement.classList.add('o_wysiwyg_loading');
+ var loadingIcon = document.createElement('i');
+ loadingIcon.classList.add('text-600', 'text-center',
+ 'fa', 'fa-circle-o-notch', 'fa-spin', 'fa-2x');
+ loadingElement.appendChild(loadingIcon);
+ wrapper.appendChild(loadingElement);
+
+ textarea.parentNode.insertBefore(wrapper, textarea);
+ wrapper.insertBefore(textarea, loadingElement);
+ });
+});
+
+})();
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.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.frontend.scss b/addons/web_editor/static/src/scss/web_editor.frontend.scss
index 7df7cacccb4..58a314bb7ad 100644
--- a/addons/web_editor/static/src/scss/web_editor.frontend.scss
+++ b/addons/web_editor/static/src/scss/web_editor.frontend.scss
@@ -3,3 +3,14 @@
transform: none !important;
}
}
+
+.o_wysiwyg_loader {
+ @extend .form-control:disabled;
+ pointer-events: none;
+ min-height: 100px;
+ color: transparent;
+}
+.o_wysiwyg_loading {
+ @include o-position-absolute($top: 50%, $left: 50%);
+ transform: translate(-50%, -50%)
+}
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..0ab8dc07b87 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,36 +26,36 @@ $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-we-toolbar-height: 32px;
+
// ============ 'Preview' component Mixins ===============
// Create a "preview" work-area to edit/customize specific elements
@@ -66,6 +74,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
@@ -148,7 +176,7 @@ $o-checklist-before-size: 13px;
.modal-content {
border-radius: 0;
- background-color: $o-we-color-paper;
+ background-color: $o-we-bg-color;
.modal-header {
border-bottom-color: #e9ecef;
@@ -179,7 +207,7 @@ $o-checklist-before-size: 13px;
&.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;
}
}
@@ -199,11 +227,11 @@ $o-checklist-before-size: 13px;
@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 {
@@ -225,8 +253,8 @@ $o-checklist-before-size: 13px;
.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..175463da8f1 100644
--- a/addons/web_editor/static/src/scss/wysiwyg.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg.scss
@@ -1,322 +1,171 @@
-// NOTE EDITOR
+$o-we-overlay-zindex: ($zindex-fixed + $zindex-modal-backdrop) / 2 !default;
+$o-we-zindex: $o-we-overlay-zindex + 1 !default;
-body.editor_enable {
- padding-top: $o-wysiwyg-toolbar-height !important;
-}
+// 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};
-#wrapwrap {
- height: calc(100vh - #{$o-wysiwyg-toolbar-height});
- border: 1px solid $o-wysiwyg-border-color;
- border-left: 0;
- border-top: 0;
+ --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};
- .btn {
- -webkit-user-select: none;
- }
+ --o-we-dropdown-bg-color: #{$o-we-dropdown-bg-color};
+ --o-we-dropdown-color: #{$o-we-dropdown-color};
+
+ --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;
+ %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;
}
-
- >.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;
- }
- }
- }
- }
-
.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 +211,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;
}
}
@@ -384,13 +233,8 @@ body .modal {
}
}
- .o_we_attachment_selected {
- border-color: $o-brand-primary;
- box-shadow: 0px 0px 2px 2px $o-brand-primary;
- }
-
.font-icons-icons {
- >span {
+ > span {
text-align: center;
font-size: 22px;
margin: 5px;
@@ -401,8 +245,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 +265,7 @@ body .modal {
position: relative;
width: 100%;
- >textarea {
+ > textarea {
width: 100%;
min-height: 95px;
padding-bottom: 25px;
@@ -460,7 +304,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 +326,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 +334,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 +342,7 @@ body .modal {
&.btn-link {
padding: 3px 9px;
- >span {
+ > span {
text-decoration: underline;
}
}
@@ -511,7 +355,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 +383,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..d3f10ebd526 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -2,86 +2,85 @@
/// 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;
+ left: $o-we-sidebar-width !important;
}
-}
-
-// 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;
+ .modal:not(.o_technical_modal) {
+ // set z-index so customize options visible on dialog.
+ z-index: $o-we-overlay-zindex - 1;
+ // just for fake backdrop effect
+ background-color: rgba(66, 66, 66, 0.4);
}
}
-.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);
+ }
+
+ &:focus, &:active, &:focus:active {
+ outline: none;
+ }
+ }
}
- #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 +148,130 @@ 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;
+ color: var(--o-we-snippets-color);
+ margin-top: 0.5rem;
+
+ > we-title {
+ display: flex;
+ align-items: center;
+ margin: 0.25rem 0;
+ padding: 0 0.5rem;
+ font-weight: bold;
+ color: inherit !important;
+
+ > 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;
+
+ > * {
+ display: block;
+ margin-top: 0.25rem;
+ }
+
+ we-button, we-toggler {
+ display: block;
+ text-align: left;
+ border-radius: 0;
+ }
+ we-button {
+ &.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;
+ }
+ }
}
}
.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 +283,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 +298,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 +315,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_colorpicker_section {
- &.o_small {
- width: 23px;
- height: 23px;
- margin: 2px;
- border-radius: 50%;
- }
-
- &: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 +378,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 +394,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 +421,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;
background: transparent;
text-align: center;
transition: opacity 400ms linear 0s;
@@ -363,301 +436,94 @@ 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;
+ z-index: 1;
}
// HANDLES
- >.o_handles {
- @include o-position-absolute(-$o-wysiwyg-handles-offset-to-hide, 0, auto, 0);
- z-index: $o-wysiwyg-zindex;
+ > .o_handles {
+ @include o-position-absolute(-$o-we-handles-offset-to-hide, 0, auto, 0);
+ border-color: inherit;
- &:hover>.o_handle {
- background-color: rgba($o-wysiwyg-handles-accent-color, 0.05);
+ &:hover > .o_handle {
+ background-color: rgba($o-we-handles-accent-color, 0.05);
}
-
- >.o_handle {
-
+ > .o_handle {
position: relative;
- border: 0 solid $o-wysiwyg-handles-accent-color;
+ border: 0 solid transparent;
+ border-color: inherit;
transition: background 300ms ease 0s;
- &:hover,
- &.o_active {
- background-color: rgba($o-wysiwyg-handles-accent-color, 0.2);
+ &.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;
+ border-width: $o-we-handle-border-width;
+ border-right-width: 0;
+ cursor: e-resize;
- &: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);
+ &:after {
+ @include o-position-absolute($top: 50%, $left: 40%);
+ margin-top: -$o-we-handles-btn-size/2;
+ }
+ }
+ &.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;
+ border-right-width: $o-we-handle-border-width;
+ cursor: w-resize;
+
+ &:after {
+ @include o-position-absolute($top: 50%, $right: 40%);
+ margin-top: -$o-we-handles-btn-size/2;
}
+ }
+ &.n {
+ @include o-position-absolute($o-we-handles-offset-to-hide, 0, auto, 0);
+ height: $o-we-handle-edge-size;
+ border-top-width: $o-we-handle-border-width;
+ cursor: ns-resize;
- &.w:before {
- @include o-position-absolute(50%, $left: 0);
- width: 100%;
- height: 1px;
+ &:after {
+ @include o-position-absolute($left: 50%, $top: 40%);
+ margin-left: -$o-we-handles-btn-size/2;
}
+ }
+ &.s {
+ @include o-position-absolute(auto, 0, -$o-we-handles-offset-to-hide, 0);
+ height: $o-we-handle-edge-size;
+ border-bottom-width: $o-we-handle-border-width;
+ cursor: ns-resize;
&:after {
- border-color: darken($o-wysiwyg-handles-accent-color, 10%);
- background-color: darken($o-wysiwyg-handles-accent-color, 20%);
+ @include o-position-absolute($left: 50%, $bottom: 40%);
+ margin-left: -$o-we-handles-btn-size/2;
}
}
- &:after {
+ &::after {
+ z-index: 1;
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;
+ 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-wysiwyg-handles-accent-color, 20%);
+ background-color: darken($o-we-handles-accent-color, 20%);
color: white;
}
+ &:hover,
+ &.o_active {
+ background-color: rgba($o-we-handles-accent-color, 0.2);
+
+ &::after {
+ border-color: darken($o-we-handles-accent-color, 10%);
+ background-color: darken($o-we-handles-accent-color, 20%);
+ }
+ }
+
&.w:after,
&.e:after {
content: "\f07e";
@@ -694,64 +560,103 @@ body.editor_enable.editor_has_snippets {
}
}
- &.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-right-width: 0;
- cursor: e-resize;
+ &.readonly {
+ cursor: auto !important;
&:after {
- @include o-position-absolute($top: 50%, $left: 40%);
- margin-top: -$o-wysiwyg-handles-btn-size/2;
+ display: none !important;
+ }
+
+ &:hover {
+ opacity: 0.5;
}
}
+ }
- &.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;
- cursor: w-resize;
+ > .o_move_handle {
+ @include o-position-absolute($o-we-handles-offset-to-hide, $left: 50%);
+ transform: translate(-50%, -110%);
+ padding: 2px 4px;
+ border: 1px solid $o-we-color-text-normal;
+ border-radius: 20%;
+ color: $o-we-color-text-normal;
+ opacity: 0.5;
+ transition: opacity 400ms ease 0;
+ cursor: pointer;
+ cursor: move;
- &:after {
- @include o-position-absolute($top: 50%, $right: 40%);
- margin-top: -$o-wysiwyg-handles-btn-size/2;
- }
+ &::before, &::after {
+ content: '\f141';
+ display: block;
+ font-family: FontAwesome;
+ font-size: 0.8em;
+ line-height: 0.35em;
}
- &.n {
- @include o-position-absolute($o-wysiwyg-handles-offset-to-hide, 0, auto, 0);
- border-top-width: $o-wysiwyg-handle-border-width;
- cursor: ns-resize;
+ &:hover {
+ opacity: 1;
+ }
+ }
+ }
- &:after {
- @include o-position-absolute($left: 50%, $top: 40%);
- margin-left: -$o-wysiwyg-handles-btn-size/2;
+ &.o_we_overlay_preview {
+ border-color: $o-we-handles-accent-color-preview;
+
+ > .o_handles {
+
+ > .o_handle {
+ border-style: dashed;
+ background-color: transparent !important;
+
+ &::before {
+ content: '';
+ @include o-position-absolute(0, 0, 0, 0);
+ z-index: -1;
+ display: block;
+ border-width: inherit;
+ border-style: solid;
+ border-color: white;
+ }
+ &::after {
+ display: none;
}
- }
- &.s {
- @include o-position-absolute(auto, 0, -$o-wysiwyg-handles-offset-to-hide, 0);
- border-bottom-width: $o-wysiwyg-handle-border-width;
- cursor: ns-resize;
+ &.w {
+ border-width: $o-we-handle-border-width-preview;
+ border-right-width: 0;
- &:after {
- @include o-position-absolute($left: 50%, $bottom: 40%);
- margin-left: -$o-wysiwyg-handles-btn-size/2;
+ &::before {
+ top: -$o-we-handle-border-width-preview;
+ bottom: -$o-we-handle-border-width-preview;
+ left: -$o-we-handle-border-width-preview;
+ }
}
- }
+ &.e {
+ border-right-width: $o-we-handle-border-width-preview;
- &.readonly {
- cursor: auto !important;
+ &::before {
+ right: -$o-we-handle-border-width-preview;
+ }
+ }
+ &.n {
+ border-top-width: $o-we-handle-border-width-preview;
- &:after {
- display: none !important;
+ &::before {
+ top: -$o-we-handle-border-width-preview;
+ }
}
+ &.s {
+ border-bottom-width: $o-we-handle-border-width-preview;
- &:hover {
- opacity: 0.5;
+ &::before {
+ bottom: -$o-we-handle-border-width-preview;
+ }
}
}
+
+ > .o_move_handle {
+ display: none;
+ }
}
}
}
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;
diff --git a/addons/web_editor/static/src/xml/snippets.xml b/addons/web_editor/static/src/xml/snippets.xml
index 6e2ae656f2d..8e1fe723f0d 100644
--- a/addons/web_editor/static/src/xml/snippets.xml
+++ b/addons/web_editor/static/src/xml/snippets.xml
@@ -3,29 +3,38 @@
<t t-name="web_editor.snippet_overlay">
<div class="oe_overlay">
<div class="o_handles">
+ <!-- Visible overlay borders + allow to resize when not readonly -->
<div class="o_handle n readonly"/>
<div class="o_handle e readonly"/>
<div class="o_handle w readonly"/>
<div class="o_handle s readonly"/>
- </div>
- <div class="oe_overlay_options" contentEditable="false">
- <div class="btn-group">
- <a role="button" href="#" class="btn btn-secondary oe_snippet_parent" title="Select Parent Container" aria-label="Select Parent Container"><i class="fa fa-lg fa-level-up"/></a>
- <div class="dropdown oe_options d-none btn-group">
- <a role="button" href="#" data-toggle="dropdown" class="btn btn-primary" title="Customize">Customize</a>
- <div class="dropdown-menu" role="menu"/>
- </div>
- <a role="button" href="#" class="btn btn-secondary oe_snippet_move" title="Drag to Move" aria-label="Drag to Move"><i class="fa fa-arrows ml4 mr4"></i></a>
- <a role="button" href="#" class="btn btn-secondary oe_snippet_clone" title="Duplicate Container" aria-label="Duplicate Container"><i class="fa fa-files-o ml4 mr4"></i></a>
- <a role="button" href="#" class="btn btn-secondary oe_snippet_remove" title="Remove Block" aria-label="Remove Block"><i class="fa fa-trash-o ml4 mr4"></i></a>
- </div>
+
+ <!-- Overlay button-like handle to drag and drop -->
+ <div class="o_move_handle"/>
</div>
</div>
</t>
+ <t t-name="web_editor.customize_block_options_section">
+ <we-customizeblock-options>
+ <we-title>
+ <span t-esc="name"/>
+ <we-button-group>
+ <we-button class="fa fa-files-o oe_snippet_clone"
+ title="Duplicate Container"
+ aria-label="Duplicate Container"/>
+ <we-button class="fa fa-trash-o oe_snippet_remove"
+ title="Remove Block"
+ aria-label="Remove Block"/>
+ </we-button-group>
+ </we-title>
+ </we-customizeblock-options>
+ </t>
+ <t t-name="web_editor.customize_block_option">
+ <we-customizeblock-option/>
+ </t>
<!-- options -->
<div t-name="web_editor.snippet.option.colorpicker" class="colorpicker">
- <div class="note-palette-title">Background Color</div>
<div class="btn-group palette-reset">
<div class="note-color-reset" data-event="foreColor" data-value="inherit" title="None" role="img" aria-label="No color">
<i class="fa fa-ban"/>