summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src/js/editor
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/js/editor
parent48503fcf08eac6fed12e080d4f501f9bbc8d257d (diff)
parent197474e5b9ad43ab2d587a98136a1a809ce9a2e7 (diff)
[MERGE] forward port branch saas-12.5 up to 197474e5b9a
Diffstat (limited to 'addons/web_editor/static/src/js/editor')
-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
3 files changed, 538 insertions, 430 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 () {