summaryrefslogtreecommitdiff
path: root/addons/web_editor/static/src
diff options
context:
space:
mode:
authorqsm-odoo <qsm@odoo.com>2019-11-19 15:23:44 +0000
committerqsm-odoo <qsm@odoo.com>2019-12-01 15:49:57 +0000
commitfdac73e99d6edce1544c25534f8903c637a5507e (patch)
tree6fc957b40227130fc325f0abc9b244020e7f8c43 /addons/web_editor/static/src
parent216cf93b4dab381286991dcf650e859931bf1a43 (diff)
[IMP] web_editor, *: add new user value colorpicker widget
* web, mass_mailing, website Introduced a new sub-widget for snippet options: we-colorpicker. This allows to retrieve a color value (either a normalized css color or a theme color name) to be used for a snippet option method. This commit replaces the old 'colorpicker' snippet option whose behavior is now simply achieved by defining an xml option using a we-colorpicker widget in combination with the 'selectStyle' standard method. This commit also adds the possibility to control the border-color of elements alongside their border-width. Again, just a we-colorpicker widget in combination with the 'selectStyle' standard method. Note: this commit reviewed what the colorpalette palette widget notifies. This is not a cssColor and a 'isClass' flag to say if it is a css class or not. The colorpalette always returns a color string which can be the name of a color like 'alpha'. This is up to the caller to check if the color string is a css color or not with isCSSColor. In the case it is not, the related code can read the actual color by reading the related css variable. Part of https://github.com/odoo/odoo/pull/40282 In preparation of task-2122935 and other tasks
Diffstat (limited to 'addons/web_editor/static/src')
-rw-r--r--addons/web_editor/static/src/js/common/utils.js1
-rw-r--r--addons/web_editor/static/src/js/editor/rte.summernote.js11
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.options.js355
-rw-r--r--addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js40
-rw-r--r--addons/web_editor/static/src/scss/web_editor.common.scss6
-rw-r--r--addons/web_editor/static/src/scss/wysiwyg_snippets.scss71
6 files changed, 282 insertions, 202 deletions
diff --git a/addons/web_editor/static/src/js/common/utils.js b/addons/web_editor/static/src/js/common/utils.js
index 67267d670ee..c05e7a2064e 100644
--- a/addons/web_editor/static/src/js/common/utils.js
+++ b/addons/web_editor/static/src/js/common/utils.js
@@ -14,6 +14,7 @@ const ColorpickerDialog = require('web.ColorpickerDialog');
const CSS_SHORTHANDS = {
'border-width': ['border-top-width', 'border-right-width', 'border-bottom-width', 'border-left-width'],
'border-radius': ['border-top-left-radius', 'border-top-right-radius', 'border-bottom-right-radius', 'border-bottom-left-radius'],
+ 'border-color': ['border-top-color', 'border-right-color', 'border-bottom-color', 'border-left-color'],
};
/**
* Key-value mapping to list converters from an unit A to an unit B.
diff --git a/addons/web_editor/static/src/js/editor/rte.summernote.js b/addons/web_editor/static/src/js/editor/rte.summernote.js
index 233a38edb8c..befea94f328 100644
--- a/addons/web_editor/static/src/js/editor/rte.summernote.js
+++ b/addons/web_editor/static/src/js/editor/rte.summernote.js
@@ -4,6 +4,7 @@ odoo.define('web_editor.rte.summernote', function (require) {
var Class = require('web.Class');
const concurrency = require('web.concurrency');
var core = require('web.core');
+const ColorpickerDialog = require('web.ColorpickerDialog');
var ColorPaletteWidget = require('web_editor.ColorPalette').ColorPaletteWidget;
var mixins = require('web.mixins');
var fonts = require('wysiwyg.fonts');
@@ -50,13 +51,17 @@ renderer.createPalette = function ($container, options) {
const targetNode = r.sc;
const targetElement = targetNode.nodeType === Node.ELEMENT_NODE ? targetNode : targetNode.parentNode;
colorpicker = new ColorPaletteWidget(parent, {
- colorPrefix: eventName === "foreColor" ? 'text-' : 'bg-',
excluded: ['transparent_grayscale'],
$editable: rte.Class.prototype.editable(), // Our parent is the root widget, we can't retrieve the editable section from it...
selectedColor: $(targetElement).css(eventName === "foreColor" ? 'color' : 'backgroundColor'),
- targetClasses: [...targetElement.classList],
});
- colorpicker.on('color_picked', null, ev => applyColor(ev.data.target, eventName, ev.data.cssColor));
+ colorpicker.on('color_picked', null, ev => {
+ let color = ev.data.color;
+ if (!ColorpickerDialog.isCSSColor(color)) {
+ color = (eventName === "foreColor" ? 'text-' : 'bg-') + color;
+ }
+ applyColor(ev.data.target, eventName, color);
+ });
colorpicker.on('color_reset', null, ev => applyColor(ev.data.target, eventName, 'inherit'));
return colorpicker.replace(hookEl).then(() => {
if (oldColorpicker) {
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 d4fd5c11e1b..6da88c2247a 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -567,6 +567,7 @@ const SelectUserValueWidget = UserValueWidget.extend({
return;
}
}
+ this._super(...arguments);
},
//--------------------------------------------------------------------------
@@ -827,16 +828,169 @@ const MultiUserValueWidget = UserValueWidget.extend({
},
});
+const ColorpickerUserValueWidget = SelectUserValueWidget.extend({ // FIXME should be reloaded on focus
+ custom_events: {
+ 'color_picked': '_onColorPicked',
+ 'color_hover': '_onColorHovered',
+ 'color_leave': '_onColorLeft',
+ 'color_reset': '_onColorReset',
+ },
+
+ /**
+ * @override
+ */
+ init: function () {
+ this._super(...arguments);
+ if (!this.title) {
+ this.title = ' ';
+ }
+ },
+ /**
+ * @override
+ */
+ start: async function () {
+ const _super = this._super.bind(this);
+ const args = arguments;
+
+ this.el.classList.add('o_we_so_color_palette');
+
+ // Pre-instanciate the color palette widget
+ await this._renderColorPalette();
+
+ // Build the select element with a custom span to hold the color preview
+ this.colorPreviewEl = document.createElement('span');
+ this.options.childNodes = [this.colorPalette.el];
+ this.options.valueEl = this.colorPreviewEl;
+
+ return _super(...args);
+ },
+
+ //--------------------------------------------------------------------------
+ // Public
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ getMethodsParams: function () {
+ return _.extend(this._super(...arguments), {
+ colorNames: this.colorPalette.getColorNames(),
+ });
+ },
+ /**
+ * @override
+ */
+ getValue: function (methodName) {
+ return this._previewColor || this._super(...arguments);
+ },
+ /**
+ * @override
+ */
+ isContainer: function () {
+ return false;
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @private
+ * @returns {Promise}
+ */
+ _renderColorPalette: function () {
+ const options = {};
+ options.selectedColor = this._value;
+ if (this.options.dataAttributes.excluded) {
+ options.excluded = this.options.dataAttributes.excluded.replace(/ /g, '').split(',');
+ }
+ const oldColorPalette = this.colorPalette;
+ this.colorPalette = new ColorPaletteWidget(this, options);
+ if (oldColorPalette) {
+ return this.colorPalette.insertAfter(oldColorPalette.el).then(() => {
+ oldColorPalette.destroy();
+ });
+ }
+ return this.colorPalette.appendTo(document.createDocumentFragment());
+ },
+ /**
+ * Updates the color preview + re-render the whole color palette widget.
+ *
+ * @override
+ */
+ _updateUI: async function (color) {
+ await this._super(...arguments);
+
+ this.colorPreviewEl.classList.remove(...this.colorPalette.getColorNames().map(c => 'bg-' + c));
+ this.colorPreviewEl.style.removeProperty('background-color');
+
+ if (this._value) {
+ if (ColorpickerDialog.isCSSColor(this._value)) {
+ this.colorPreviewEl.style.backgroundColor = this._value;
+ } else {
+ this.colorPreviewEl.classList.add('bg-' + this._value);
+ }
+ }
+
+ await this._renderColorPalette();
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called when a color button is clicked -> confirms the preview.
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onColorPicked: function (ev) {
+ this._previewColor = false;
+ this._value = ev.data.color;
+ this._notifyValueChange(false);
+ },
+ /**
+ * Called when a color button is entered -> previews the background color.
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onColorHovered: function (ev) {
+ this._previewColor = ev.data.color;
+ this._notifyValueChange(true);
+ },
+ /**
+ * Called when a color button is left -> cancels the preview.
+ *
+ * @private
+ * @param {Event} ev
+ */
+ _onColorLeft: function (ev) {
+ this._previewColor = false;
+ this._notifyValueChange('reset');
+ },
+ /**
+ * Called when the color reset button is clicked -> removes all color
+ * classes and color styles.
+ *
+ * @private
+ */
+ _onColorReset: function () {
+ this._value = '';
+ this._notifyValueChange(false);
+ },
+});
+
const userValueWidgetsRegistry = {
'we-button': ButtonUserValueWidget,
'we-checkbox': CheckboxUserValueWidget,
'we-select': SelectUserValueWidget,
'we-input': InputUserValueWidget,
'we-multi': MultiUserValueWidget,
+ 'we-colorpicker': ColorpickerUserValueWidget,
};
-//::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
-
/**
* Handles a set of options for one snippet. The registry returned by this
* module contains the names of the specialized SnippetOptionWidget which can be
@@ -1024,7 +1178,54 @@ const SnippetOptionWidget = Widget.extend({
* @param {Object} params
*/
selectStyle: function (previewMode, widgetValue, params) {
+ if (params.cssProperty === 'background-color') {
+ this.$target.trigger('background-color-event', previewMode);
+ }
+
const cssProps = weUtils.CSS_SHORTHANDS[params.cssProperty] || [params.cssProperty];
+ for (const cssProp of cssProps) {
+ // Always reset the inline style first to not put inline style on an
+ // element which already have this style through css stylesheets.
+ this.$target[0].style.setProperty(cssProp, '');
+ }
+ if (params.extraClass) {
+ this.$target.removeClass(params.extraClass);
+ }
+
+ // Only allow to use a color name as a className if we know about the
+ // other potential color names (to remove) and if we know about a prefix
+ // (otherwise we suppose that we should use the actual related color).
+ if (params.colorNames && params.colorPrefix) {
+ const classes = params.colorNames.map(c => params.colorPrefix + c);
+ this.$target[0].classList.remove(...classes);
+
+ if (params.colorNames.includes(widgetValue)) {
+ const originalCSSValue = window.getComputedStyle(this.$target[0])[cssProps[0]];
+ const className = params.colorPrefix + widgetValue;
+ this.$target[0].classList.add(className);
+ if (originalCSSValue !== window.getComputedStyle(this.$target[0])[cssProps[0]]) {
+ // If applying the class did indeed changed the css
+ // property we are editing, nothing more has to be done.
+ // (except adding the extra class)
+ this.$target.addClass(params.extraClass);
+ return;
+ }
+ // Otherwise, it means that class probably does not exist,
+ // we remove it and continue. Especially useful for some
+ // prefixes which only work with some color names but not all.
+ this.$target[0].classList.remove(className);
+ }
+ }
+
+ // At this point, the widget value is either a property/color name or
+ // an actual css property value. If it is a property/color name, we will
+ // apply a css variable as style value.
+ const htmlStyle = window.getComputedStyle(document.documentElement);
+ const htmlPropValue = htmlStyle.getPropertyValue('--' + widgetValue);
+ if (htmlPropValue) {
+ widgetValue = `var(--${widgetValue})`;
+ }
+
const values = widgetValue.split(/\s+/g);
while (values.length < cssProps.length) {
switch (values.length) {
@@ -1043,15 +1244,6 @@ const SnippetOptionWidget = Widget.extend({
}
}
- for (const cssProp of cssProps) {
- // Always reset the inline style first to not put inline style on an
- // element which already have this style through css stylesheets.
- this.$target[0].style.setProperty(cssProp, '');
- }
- if (params.extraClass) {
- this.$target.removeClass(params.extraClass);
- }
-
const styles = window.getComputedStyle(this.$target[0]);
let hasUserValue = false;
for (let i = cssProps.length - 1; i > 0; i--) {
@@ -1160,6 +1352,14 @@ const SnippetOptionWidget = Widget.extend({
return dataValue || params.attributeDefaultValue || '';
}
case 'selectStyle': {
+ if (params.colorPrefix && params.colorNames) {
+ for (const c of params.colorNames) {
+ if (this.$target[0].classList.contains(params.colorPrefix + c)) {
+ return c;
+ }
+ }
+ }
+
const styles = window.getComputedStyle(this.$target[0]);
const cssProps = weUtils.CSS_SHORTHANDS[params.cssProperty] || [params.cssProperty];
const cssValues = cssProps.map(cssProp => {
@@ -1198,7 +1398,7 @@ const SnippetOptionWidget = Widget.extend({
* @returns {string}
*/
_normalizeWidgetValue: function (value) {
- value = `${value}`; // Force to string
+ value = `${value}`.trim(); // Force to a trimmed string
value = ColorpickerDialog.normalizeCSSColor(value); // If is a css color, normalize it
return value;
},
@@ -1683,137 +1883,6 @@ registry['sizing_y'] = registry.sizing.extend({
});
/**
- * Handles the edition of snippet's background color classes.
- */
-registry.colorpicker = SnippetOptionWidget.extend({
- xmlDependencies: ['/web_editor/static/src/xml/snippets.xml'],
- custom_events: {
- 'color_picked': '_onColorPicked',
- 'color_hover': '_onColorHovered',
- 'color_leave': '_onColorLeft',
- 'color_reset': '_onColorReset',
- },
-
- /**
- * @override
- */
- start: async function () {
- const _super = this._super.bind(this);
- const args = arguments;
-
- // Pre-instanciate the color palette widget
- const options = {
- selectedColor: this.$target.css('background-color'),
- targetClasses: [...this.$target[0].classList],
- };
- if (this.data.paletteExclude) {
- options.excluded = this.data.paletteExclude.replace(/ /g, '').split(',');
- }
- if (this.data.colorPrefix) {
- options.colorPrefix = this.data.colorPrefix;
- }
- this.colorPalette = new ColorPaletteWidget(this, options);
- await this.colorPalette.appendTo(document.createDocumentFragment());
-
- // Build the select element with a custom span to hold the color preview
- this.colorPreviewEl = document.createElement('span');
- const selectWidget = new SelectUserValueWidget(this, this.data.string, {
- classes: ['o_we_so_color_palette'],
- childNodes: [this.colorPalette.el],
- valueEl: this.colorPreviewEl,
- });
- await selectWidget.appendTo(document.createDocumentFragment());
-
- // Replace the colorpicker UI with the select element
- this.$el.empty().append(selectWidget.el);
- return _super(...args);
- },
- /**
- * @override
- */
- onFocus: function () {
- this.colorPalette.reloadColorPalette();
- },
-
- //--------------------------------------------------------------------------
- // Private
- //--------------------------------------------------------------------------
-
- /**
- * Change the color of the targeted background
- *
- * @private
- * @param {string} cssColor
- * @param {boolean} isClass
- */
- _changeTargetColor: function (cssColor, isClass) {
- this.$target[0].classList.remove(...this.colorPalette.getClasses());
- if (isClass) {
- this.$target.addClass(cssColor);
- } else {
- this.$target.css('background-color', cssColor);
- }
- },
- /**
- * @override
- */
- _updateUI: async function () {
- await this._super.apply(this, arguments);
- this.colorPreviewEl.style.backgroundColor = this.$target.css('background-color');
- },
-
- //--------------------------------------------------------------------------
- // Handlers
- //--------------------------------------------------------------------------
-
- /**
- * Called when a color button is clicked -> confirm the preview.
- *
- * @private
- * @param {Event} ev
- */
- _onColorPicked: function (ev) {
- this._changeTargetColor(ev.data.cssColor);
- this._updateUI().then(() => {
- this.$target.closest('.o_editable').trigger('content_changed');
- this.$target.trigger('background-color-event', false);
- });
- },
- /**
- * Called when a color button is entered -> preview the background color.
- *
- * @private
- * @param {Event} ev
- */
- _onColorHovered: function (ev) {
- this._changeTargetColor(ev.data.cssColor, ev.data.isClass);
- this.$target.trigger('background-color-event', true);
- },
- /**
- * Called when a color button is left -> cancel the preview.
- *
- * @private
- * @param {Event} ev
- */
- _onColorLeft: function (ev) {
- this._changeTargetColor(ev.data.cssColor, ev.data.isClass);
- this.$target.trigger('background-color-event', 'reset');
- },
- /**
- * Called when the color reset button is clicked -> remove all background
- * color classes.
- *
- * @private
- */
- _onColorReset: function () {
- this._changeTargetColor('');
- this._updateUI().then(() => {
- this.$target.trigger('content_changed');
- });
- },
-});
-
-/**
* Handles the edition of snippet's background image.
*/
registry.background = SnippetOptionWidget.extend({
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js b/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js
index 38edef463b6..04a21c79378 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/color_palette.js
@@ -27,8 +27,6 @@ const ColorPaletteWidget = Widget.extend({
*
* @param {Object} [options]
* @param {string} [options.selectedColor] The class or css attribute color selected by default.
- * @param {string[]} [options.targetClasses]
- * @param {string} [options.colorPrefix='bg-'] Used for the class based colors (theme).
* @param {boolean} [options.resetButton=true] Whether to display or not the reset button.
* @param {string[]} [options.excluded=[]] Sections not to display.
* @param {string[]} [options.excludeSectionOf] Extra section to exclude: the one containing the named color.
@@ -39,8 +37,6 @@ const ColorPaletteWidget = Widget.extend({
this.summernoteCustomColorsArray = [].concat(...summernoteCustomColors);
this.options = _.extend({
selectedColor: false,
- targetClasses: [],
- colorPrefix: 'bg-',
resetButton: true,
excluded: [],
excludeSectionOf: null,
@@ -124,31 +120,26 @@ const ColorPaletteWidget = Widget.extend({
this.$el.append($('<button/>', {'class': 'd-none', 'data-color': 'secondary'}));
// Compute class colors
- this.classes = [];
+ this.colorNames = [];
+ const style = window.getComputedStyle(document.documentElement);
this.el.querySelectorAll('button[data-color]').forEach(elem => {
- const color = elem.dataset.color;
+ const colorName = elem.dataset.color;
+ const color = ColorpickerDialog.normalizeCSSColor(style.getPropertyValue('--' + colorName).trim());
const $color = $(elem);
- $color.addClass('bg-' + color);
- const className = this.options.colorPrefix + color;
- if (this.options.targetClasses.includes(className)) {
+ $color.addClass('bg-' + colorName);
+ if (this.options.selectedColor && (this.options.selectedColor === colorName || this.options.selectedColor === color)) {
$color.addClass('selected');
}
- this.classes.push(className);
+ this.colorNames.push(colorName);
});
return res;
},
/**
- * Return a list of the color classes used in the color palette
+ * Return a list of the color names used in the color palette
*/
- getClasses: function () {
- return this.classes;
- },
- /**
- * Reloads the color palette to get other custom colors on the page
- */
- reloadColorPalette: function () {
- this._buildCustomColors();
+ getColorNames: function () {
+ return this.colorNames;
},
//--------------------------------------------------------------------------
@@ -215,10 +206,9 @@ const ColorPaletteWidget = Widget.extend({
*/
_getButtonInfo: function (buttonEl) {
const bgColor = buttonEl.style.backgroundColor;
- const cssColor = bgColor ? ColorpickerDialog.normalizeCSSColor(bgColor) : (this.options.colorPrefix + buttonEl.dataset.color);
+ const color = bgColor ? ColorpickerDialog.normalizeCSSColor(bgColor) : buttonEl.dataset.color;
return {
- cssColor: cssColor,
- isClass: !!buttonEl.dataset.color,
+ color: color,
target: buttonEl,
};
},
@@ -263,8 +253,7 @@ const ColorPaletteWidget = Widget.extend({
params = this._getButtonInfo(selected);
} else {
params = {
- cssColor: '',
- isClass: false,
+ color: '',
};
}
params.target = ev.target;
@@ -297,8 +286,7 @@ const ColorPaletteWidget = Widget.extend({
colorpicker.on('colorpicker:saved', this, ev => {
this._addCustomColorButton(ev.data.cssColor, ['selected']);
this.trigger_up('color_picked', {
- cssColor: ev.data.cssColor,
- isClass: false,
+ color: ev.data.cssColor,
target: target,
});
});
diff --git a/addons/web_editor/static/src/scss/web_editor.common.scss b/addons/web_editor/static/src/scss/web_editor.common.scss
index baac51c3adf..2de7d389843 100644
--- a/addons/web_editor/static/src/scss/web_editor.common.scss
+++ b/addons/web_editor/static/src/scss/web_editor.common.scss
@@ -2,6 +2,12 @@
/// This file regroups basic style rules for web_editor enable page edition and backend utils.
///
+:root {
+ @each $color, $value in $grays {
+ --#{$color}: #{$value};
+ }
+}
+
html, body {
position: relative;
width: 100%;
diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
index 404bccc7b53..b1f8d1259ae 100644
--- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
+++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss
@@ -297,26 +297,6 @@ body.editor_enable.editor_has_snippets {
@include we-icon-button('\f107');
}
- we-row {
- display: block;
-
- > div {
- display: flex;
- align-items: center;
-
- > * {
- flex: 1 1 auto;
-
- + * {
- margin-left: 0.5em;
- }
- }
- > .fa, > .fas, > .fab, > .far {
- flex: 0 0 auto;
- }
- }
- }
-
we-select {
position: relative;
display: flex;
@@ -374,13 +354,8 @@ body.editor_enable.editor_has_snippets {
}
we-input {
- display: flex;
- align-items: center;
+ @extend we-row.o_we_inline;
- > we-title {
- min-width: 60%;
- margin-right: 5%;
- }
> input {
flex: 1 1 auto;
width: 0;
@@ -398,11 +373,47 @@ body.editor_enable.editor_has_snippets {
}
}
- we-row we-input {
- flex-wrap: wrap;
+ we-row {
+ position: relative;
+ display: block;
- > we-title {
- min-width: 95%;
+ > div {
+ flex: 1 1 auto;
+ display: flex;
+ align-items: center;
+
+ > * {
+ flex: 1 1 auto;
+
+ + * {
+ margin-left: 0.5em;
+ }
+ }
+ > .fa, > .fas, > .fab, > .far {
+ flex: 0 0 auto;
+ }
+ }
+
+ &.o_we_inline {
+ display: flex;
+ align-items: center;
+
+ > we-title {
+ min-width: 60%;
+ margin-right: 5%;
+ }
+ }
+
+ we-select {
+ position: static;
+ }
+
+ we-input {
+ flex-wrap: wrap;
+
+ > we-title {
+ min-width: 95%;
+ }
}
}