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 c49c6235e1a..c27e7b91ddb 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -321,6 +321,56 @@ var SnippetEditor = Widget.extend({
// Private
//--------------------------------------------------------------------------
+ /**
+ * Transforms option UI description into actual DOM.
+ *
+ * @private
+ * @param {jQuery} $el
+ */
+ _createOptionUI: function ($el) {
+ const $optionSection = $(core.qweb.render('web_editor.customize_block_option'));
+ $optionSection.append($el);
+ const uiEl = $optionSection[0];
+
+ // Build group first as their internal components will be built after
+ uiEl.querySelectorAll('we-row').forEach(el => {
+ const infos = this._extraInfoFromDescriptionElement(el);
+ const groupEl = options.Class.prototype.buildRowElement(infos.title, infos.options);
+ el.parentNode.insertBefore(groupEl, el);
+ el.parentNode.removeChild(el);
+ });
+
+ // Build standard components
+ uiEl.querySelectorAll('we-select').forEach(el => {
+ const infos = this._extraInfoFromDescriptionElement(el);
+ const selectEl = options.Class.prototype.buildSelectElement(infos.title, infos.options);
+ el.parentNode.insertBefore(selectEl, el);
+ el.parentNode.removeChild(el);
+ });
+ uiEl.querySelectorAll('we-checkbox').forEach(el => {
+ const infos = this._extraInfoFromDescriptionElement(el);
+ const checkboxEl = options.Class.prototype.buildCheckboxElement(infos.title, infos.options);
+ el.parentNode.insertBefore(checkboxEl, el);
+ el.parentNode.removeChild(el);
+ });
+
+ return $optionSection;
+ },
+ /**
+ * @private
+ * @param {HTMLElement} el
+ * @returns {Object}
+ */
+ _extraInfoFromDescriptionElement: function (el) {
+ return {
+ title: el.getAttribute('string'),
+ options: {
+ classes: el.classList,
+ dataAttributes: el.dataset,
+ childNodes: [...el.childNodes],
+ },
+ };
+ },
/**
* DOMElements have a default name which appears in the overlay when they
* are being edited. This method retrieves this name; it can be defined
@@ -391,7 +441,8 @@ var SnippetEditor = Widget.extend({
}
var optionName = val.option;
- var $el = val.$el.children().clone(true).addClass('snippet-option-' + optionName);
+
+ var $ui = val.$el.children().clone(true);
var option = new (options.registry[optionName] || options.Class)(
this,
val.base_target ? this.$target.find(val.base_target).eq(0) : this.$target,
@@ -408,9 +459,8 @@ var SnippetEditor = Widget.extend({
}
this.styles[key] = option;
option.__order = i++;
- var $optionSection = $(core.qweb.render('web_editor.customize_block_option'));
- $optionSection.append($el);
- return option.attachTo($optionSection);
+ const $option = this._createOptionUI($ui).addClass('snippet-option-' + optionName);
+ return option.attachTo($option);
});
this.isTargetMovable = (this.selectorSiblings.length > 0 || this.selectorChildren.length > 0);
@@ -664,7 +714,7 @@ var SnippetsMenu = Widget.extend({
id: 'oe_snippets',
cacheSnippetTemplate: {},
events: {
- 'click we-collapse-area > we-toggler': '_onCollapseTogglerClick',
+ 'click we-select, we-collapse-area > we-toggler': '_onOptionTogglerClick',
'click .o_install_btn': '_onInstallBtnClick',
},
custom_events: {
@@ -1277,7 +1327,7 @@ var SnippetsMenu = Widget.extend({
'$el': $style,
'drop-near': $style.data('drop-near') && self._computeSelectorFunctions($style.data('drop-near'), '', false, noCheck, true),
'drop-in': $style.data('drop-in') && self._computeSelectorFunctions($style.data('drop-in'), '', false, noCheck),
- 'data': $style.data(),
+ 'data': _.extend({string: $style.attr('string')}, $style.data()),
};
self.templateOptions.push(option);
selectors.push(option.selector);
@@ -1664,11 +1714,17 @@ var SnippetsMenu = Widget.extend({
* @private
* @param {*} ev
*/
- _onCollapseTogglerClick: function (ev) {
- var $hierarchyTogglers = $(ev.currentTarget).parents('we-collapse-area').children('we-toggler');
- this.$('we-collapse-area > we-toggler').not($hierarchyTogglers).removeClass('active');
- $hierarchyTogglers.not(ev.currentTarget).addClass('active');
- ev.currentTarget.classList.toggle('active');
+ _onOptionTogglerClick: function (ev) {
+ var togglerEl = ev.currentTarget;
+ if (togglerEl.tagName !== 'WE-TOGGLER') {
+ togglerEl = togglerEl.querySelector('we-toggler');
+ }
+
+ var togglerParentSelector = 'we-select, we-collapse-area';
+ var $hierarchyTogglers = $(togglerEl).parents(togglerParentSelector).children('we-toggler');
+ this.$(togglerParentSelector).children('we-toggler').not($hierarchyTogglers).removeClass('active');
+ $hierarchyTogglers.not(togglerEl).addClass('active');
+ togglerEl.classList.toggle('active');
},
/**
* Called when the overlay dimensions/positions should be recomputed.
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 16b7355ffc7..2ccc66dc6b6 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -35,11 +35,11 @@ var SnippetOption = Widget.extend({
*/
init: function (parent, $target, $overlay, data, options) {
this._super.apply(this, arguments);
- this.options = options;
this.$target = $target;
- this.ownerDocument = this.$target[0].ownerDocument;
this.$overlay = $overlay;
this.data = data;
+ this.options = options;
+ this.ownerDocument = this.$target[0].ownerDocument;
this.__methodNames = [];
},
/**
@@ -49,7 +49,7 @@ var SnippetOption = Widget.extend({
* @override
*/
start: function () {
- this._setActive();
+ this._updateUI();
return this._super.apply(this, arguments);
},
/**
@@ -133,7 +133,7 @@ var SnippetOption = Widget.extend({
* @param {jQuery} $opt - the related DOMElement option
*/
selectClass: function (previewMode, value, $opt) {
- var $group = $opt && $opt.parents('we-collapse-area').last();
+ var $group = $opt && $opt.parents('we-collapse-area, we-select').last();
if (!$group || !$group.length) {
$group = this.$el;
}
@@ -206,7 +206,7 @@ var SnippetOption = Widget.extend({
*/
setTarget: function ($target) {
this.$target = $target;
- this._setActive();
+ this._updateUI();
this.$target.trigger('snippet-option-change', [this]);
},
@@ -242,7 +242,7 @@ var SnippetOption = Widget.extend({
*/
_select: function (previewMode, $opt) {
// Options can say they respond to strong choice
- if (previewMode && ($opt.data('noPreview') || $opt.parent().data('noPreview'))) {
+ if (previewMode && ($opt.data('noPreview') || $opt.closest('[data-no-preview="true"]').length)) {
return;
}
// If it is not preview mode, the user selected the option for good
@@ -278,7 +278,7 @@ var SnippetOption = Widget.extend({
this.__methodNames = _.uniq(this.__methodNames);
if (!previewMode) {
- this._setActive();
+ this._updateUI();
}
this.$target.trigger('content_changed');
@@ -301,8 +301,8 @@ var SnippetOption = Widget.extend({
.addClass('active');
// Get submenus which are not inside submenus
- var $submenus = this.$el.find('we-collapse-area')
- .not('we-collapse-area we-collapse-area');
+ var $submenus = this.$el.find('we-collapse-area, we-select')
+ .not('we-collapse-area *, we-select *');
// Add unique active class for each submenu active item
_.each($submenus, function (submenu) {
@@ -312,7 +312,7 @@ var SnippetOption = Widget.extend({
// Add unique active class for out-of-submenu active item
var $externalElements = this.$el.find('[data-select-class]')
- .not('we-collapse-area *, we-collapse-area');
+ .not('we-collapse-area *, we-select *');
_processSelectClassElements($externalElements);
function _processSelectClassElements($elements) {
@@ -331,6 +331,20 @@ var SnippetOption = Widget.extend({
.addClass('active');
}
},
+ /**
+ * @private
+ */
+ _updateUI: function () {
+ this._setActive();
+
+ this.el.querySelectorAll('we-select').forEach(selectEl => {
+ const activeEl = selectEl.querySelector('we-button.active');
+ const valueEl = selectEl.querySelector('we-toggler');
+ if (valueEl) {
+ valueEl.textContent = activeEl ? activeEl.textContent : "/";
+ }
+ });
+ },
//--------------------------------------------------------------------------
// Handlers
@@ -385,6 +399,112 @@ var SnippetOption = Widget.extend({
}
this._reset();
},
+
+ //--------------------------------------------------------------------------
+ // Static
+ //--------------------------------------------------------------------------
+
+ /**
+ * Build the correct DOM for an element according to the given options.
+ *
+ * @static
+ * @param {string} tagName
+ * @param {string} [title]
+ * @param {Object} [options]
+ * @param {string[]} [options.classes]
+ * @param {Object} [options.dataAttributes]
+ * @returns {HTMLElement}
+ */
+ buildElement: function (tagName, title, options) {
+ const el = document.createElement(tagName);
+
+ if (title) {
+ const titleEl = SnippetOption.prototype.buildTitleElement(title);
+ el.appendChild(titleEl);
+ }
+
+ if (options && options.classes) {
+ el.classList.add(...options.classes);
+ }
+ if (options && options.dataAttributes) {
+ for (const key in options.dataAttributes) {
+ el.dataset[key] = options.dataAttributes[key];
+ }
+ }
+
+ return el;
+ },
+ /**
+ * Build the correct DOM for a we-checkbox element.
+ *
+ * @static
+ * @param {string} [title]
+ * @param {Object} [options] - @see this.buildElement
+ * @returns {HTMLElement}
+ */
+ buildCheckboxElement: function (title, options) {
+ const buttonEl = SnippetOption.prototype.buildElement('we-button', title, options);
+ buttonEl.classList.add('o_we_checkbox_wrapper');
+
+ const checkboxEl = document.createElement('we-checkbox');
+ buttonEl.appendChild(checkboxEl);
+
+ return buttonEl;
+ },
+ /**
+ * Build the correct DOM for a we-row element.
+ *
+ * @static
+ * @param {string} [title]
+ * @param {Object} [options] - @see this.buildElement
+ * @param {HTMLElement[]} [options.childNodes]
+ * @returns {HTMLElement}
+ */
+ buildRowElement: function (title, options) {
+ const groupEl = SnippetOption.prototype.buildElement('we-row', title, options);
+
+ const rowEl = document.createElement('div');
+ groupEl.appendChild(rowEl);
+
+ if (options && options.childNodes) {
+ options.childNodes.forEach(node => rowEl.appendChild(node));
+ }
+
+ return groupEl;
+ },
+ /**
+ * Build the correct DOM for a we-select element.
+ *
+ * @static
+ * @param {string} [title]
+ * @param {Object} [options] - @see this.buildElement
+ * @param {HTMLElement[]} [options.childNodes]
+ * @returns {HTMLElement}
+ */
+ buildSelectElement: function (title, options) {
+ const selectEl = SnippetOption.prototype.buildElement('we-select', title, options);
+
+ const menuTogglerEl = document.createElement('we-toggler');
+ selectEl.appendChild(menuTogglerEl);
+
+ var menuEl = document.createElement('we-select-menu');
+ if (options && options.childNodes) {
+ options.childNodes.forEach(node => menuEl.appendChild(node));
+ }
+ selectEl.appendChild(menuEl);
+
+ return selectEl;
+ },
+ /**
+ * @static
+ * @param {string} title
+ * @returns {HTMLElement}
+ */
+ buildTitleElement: function (title) {
+ const titleEl = document.createElement('we-title');
+ titleEl.textContent = title;
+ return titleEl;
+ },
});
//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
@@ -972,7 +1092,7 @@ registry.background = SnippetOption.extend({
this.__customImageSrc = value;
this.background(false, this.__customImageSrc);
this.$target.toggleClass('oe_custom_bg', !!value);
- this._setActive();
+ this._updateUI();
this.$target.trigger('snippet-option-change', [this]);
},
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 f8fd1d7ce04..11b7d7b84ff 100644
--- a/addons/web_editor/static/src/scss/web_editor.variables.scss
+++ b/addons/web_editor/static/src/scss/web_editor.variables.scss
@@ -5,6 +5,7 @@
$o-we-font-family: Roboto, 'Montserrat', 'Segoe UI', 'Helvetica Neue', Helvetica, Arial, sans-serif !default;
$o-we-font-size: 13px;
+$o-we-line-height: 1.5;
$o-we-bg-color: #F7F7F7 !default;
$o-we-bg-color-dark: #a7a7a7 !default;
$o-we-color: #212629 !default;
diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
index 0f28857762e..b9d1af61f43 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -51,6 +51,8 @@ body.editor_enable.editor_has_snippets {
color: var(--o-we-snippets-color);
box-shadow: 0px 10px 10px -10px black inset;
font-family: Roboto, $font-family-sans-serif;
+ font-size: $o-we-font-size;
+ line-height: $o-we-line-height;
transition: left 400ms $o-we-md-ease 0s;
@@ -173,17 +175,38 @@ body.editor_enable.editor_has_snippets {
}
> .o_we_customize_panel {
+ padding: 0 0.5rem;
we-button, we-toggler {
cursor: pointer;
- @include o-text-overflow(inline-block);
- padding: 0.25rem 0.5rem;
+ @include o-text-overflow(block);
+ padding: 0.1em 0.4em;
margin: 0;
+ border: 1px solid var(--o-we-bg-color-dark);
+ border-radius: 0;
background-color: var(--o-we-bg-color-dark);
color: var(--o-we-color);
line-height: 1.5;
- font-weight: 500;
white-space: nowrap;
+ text-align: left;
+
+ &.fa {
+ display: inline-block;
+ margin: 0 0 0 0.3em;
+ padding: 0;
+ border: none;
+ background-color: transparent;
+ color: var(--o-we-snippets-color);
+ text-align: center;
+
+ &.active {
+ padding: 0;
+
+ &::after {
+ display: none;
+ }
+ }
+ }
}
%we-icon-button {
@@ -230,22 +253,6 @@ body.editor_enable.editor_has_snippets {
> we-customizeblock-option {
display: inline-block; // Ideally -> display: contents;
}
- we-button {
- display: inline-block;
- margin: 0 0 0 0.3em;
- padding: 0;
- border: none;
- font-size: 0.9em;
- text-align: center;
-
- &.active {
- padding: 0;
-
- &::after {
- display: none;
- }
- }
- }
}
}
}
@@ -254,21 +261,47 @@ body.editor_enable.editor_has_snippets {
position: relative;
display: block;
- > * {
+ > *, > div > * {
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);
}
}
+ .o_we_checkbox_wrapper.active {
+ padding-right: 0;
+
+ &::after {
+ right: 0;
+ }
+ }
+ we-toggler {
+ @include we-icon-button('\f107');
+ }
+
+ we-row {
+ display: block;
+
+ > div {
+ display: flex;
+ align-items: center;
+
+ > * {
+ flex: 1 1 auto;
+
+ + * {
+ margin-left: 0.5em;
+ }
+ }
+ > .fa {
+ flex: 0 0 auto;
+ }
+ }
+ }
+
we-collapse {
position: relative;
display: block;
@@ -276,16 +309,64 @@ body.editor_enable.editor_has_snippets {
overflow: hidden;
transition: max-height 200ms ease 0s;
}
- we-toggler {
- @include we-icon-button('\f104');
- &.active {
- @include we-icon-button('\f107');
+ we-select {
+ position: relative;
+ display: flex;
+ flex-flow: row wrap;
+ align-items: center;
- + we-collapse {
- max-height: 500px;
+ we-toggler {
+ flex: 0 0 100%;
+
+ &.active {
+ background-color: transparent;
+ border-color: var(--o-we-color);
}
}
+ we-select-menu {
+ @include o-position-absolute(100%, 0, auto, 0);
+ display: none;
+ z-index: $zindex-dropdown;
+ border: 1px solid var(--o-we-color);
+ border-top-style: none;
+ background-color: var(--o-we-bg-color-dark);
+ }
+ }
+
+ we-toggler.active {
+
+ + we-collapse {
+ max-height: 500px;
+ }
+ + we-select-menu {
+ display: block;
+ }
+ }
+
+ .o_we_checkbox_wrapper {
+ display: flex;
+ align-items: center;
+ padding: 0;
+ border: none;
+ background-color: transparent;
+ color: inherit;
+
+ > we-title {
+ flex: 1 1 auto;
+ min-width: 0;
+ @include o-text-overflow(block);
+ }
+ > we-checkbox {
+ flex: 0 0 auto;
+ display: block;
+ width: 1em;
+ height: 1em;
+ margin-left: auto;
+ border: 1px solid var(--o-we-bg-color-dark);
+ border-radius: 0;
+ background-color: var(--o-we-bg-color-dark);
+ }
}
.dropdown-menu {
diff --git a/addons/web_editor/static/tests/test_utils.js b/addons/web_editor/static/tests/test_utils.js
index fcc2b895a3a..c757c4dfd93 100644
--- a/addons/web_editor/static/tests/test_utils.js
+++ b/addons/web_editor/static/tests/test_utils.js
@@ -88,15 +88,12 @@ MockServer.include({
'