[FIX] web_editor: make class-based colors selectable for custom buttons

Before this commit selecting a class-based color (gray scale,
o-color-1~5) was ignored when configuring a custom button colors.

After this commit the color's class is applied.
Additionally, upon switching between Custom and other types, the custom
UI is now cleaned up.

task-2653874

closes odoo/odoo#78720

X-original-commit: 26d37812f0217ae913c8ddf761bcb26b5c44bff5
Signed-off-by: Quentin Smetz (qsm) <qsm@odoo.com>
This commit is contained in:
Benoit Socias
2021-10-20 17:53:40 +00:00
parent 6087585f3b
commit 9b854f5d39
3 changed files with 175 additions and 100 deletions
@@ -317,6 +317,19 @@ function _isColorGradient(value) {
// FIXME duplicated in odoo-editor/utils.js
return value && value.includes('-gradient(');
}
/**
* Returns the class of the element that matches the specified prefix.
*
* @private
* @param {Element} el element from which to recover the color class
* @param {string[]} colorNames
* @param {string} prefix prefix of the color class to recover
* @returns {string} color class matching the prefix or an empty string
*/
function _getColorClass(el, colorNames, prefix) {
const prefixedColorNames = _computeColorClasses(colorNames, prefix);
return el.classList.value.split(' ').filter(cl => prefixedColorNames.includes(cl)).join(' ');
}
return {
CSS_SHORTHANDS: CSS_SHORTHANDS,
@@ -335,5 +348,6 @@ return {
getBgImageURL: _getBgImageURL,
backgroundImageCssToParts: _backgroundImageCssToParts,
backgroundImagePartsToCss: _backgroundImagePartsToCss,
getColorClass: _getColorClass,
};
});
@@ -5,6 +5,7 @@ const core = require('web.core');
const OdooEditorLib = require('@web_editor/../lib/odoo-editor/src/OdooEditor');
const wysiwygUtils = require('@web_editor/js/wysiwyg/wysiwyg_utils');
const Widget = require('web.Widget');
const {isColorGradient} = require('web_editor.utils');
const getDeepRange = OdooEditorLib.getDeepRange;
const getInSelection = OdooEditorLib.getInSelection;
@@ -93,9 +94,11 @@ const Link = Widget.extend({
this.data.isNewWindow = this.data.isNewWindow || this.linkEl.target === '_blank';
}
const allBtnColorPrefixes = /(^|\s+)(bg|text|border)(-[a-z0-9_-]*)?/gi;
const allBtnClassSuffixes = /(^|\s+)btn(-[a-z0-9_-]*)?/gi;
const allBtnShapes = /\s*(rounded-circle|flat)\s*/gi;
this.data.className = this.data.iniClassName
.replace(allBtnColorPrefixes, ' ')
.replace(allBtnClassSuffixes, ' ')
.replace(allBtnShapes, ' ');
// 'o_submit' class will force anchor to be handled as a button in linkdialog.
@@ -131,7 +134,6 @@ const Link = Widget.extend({
}
this._updateOptionsUI();
this._adaptPreview();
if (!this.options.noFocusUrl) {
// ensure the focus in the first input of the link modal
@@ -160,9 +162,16 @@ const Link = Widget.extend({
if (!data.classes.includes('btn') && this.data.iniClassName.includes("btn-link")) {
data.classes += " btn btn-link";
}
if (!['btn-custom', 'btn-outline-custom', 'btn-fill-custom'].some(className =>
if (['btn-custom', 'btn-outline-custom', 'btn-fill-custom'].some(className =>
data.classes.includes(className)
)) {
this.$link.css('color', data.classes.includes(data.customTextColor) ? '' : data.customTextColor);
this.$link.css('background-color', data.classes.includes(data.customFill) || isColorGradient(data.customFill) ? '' : data.customFill);
this.$link.css('background-image', isColorGradient(data.customFill) ? data.customFill : '');
this.$link.css('border-width', data.customBorderWidth);
this.$link.css('border-style', data.customBorderStyle);
this.$link.css('border-color', data.customBorder);
} else {
this.$link.css('color', '');
this.$link.css('background-color', '');
this.$link.css('background-image', '');
@@ -289,6 +298,7 @@ const Link = Widget.extend({
const customBorder = this._getLinkCustomBorder();
const customBorderWidth = this._getLinkCustomBorderWidth();
const customBorderStyle = this._getLinkCustomBorderStyle();
const customClasses = this._getLinkCustomClasses();
const size = this._getLinkSize();
const shape = this._getLinkShape();
const shapes = shape ? shape.split(',') : [];
@@ -296,6 +306,7 @@ const Link = Widget.extend({
const shapeClasses = shapes.slice(style ? 1 : 0).join(' ');
const classes = (this.data.className || '') +
(type ? (` btn btn-${style}${type}`) : '') +
(type === 'custom' ? customClasses : '') +
(type && shapeClasses ? (` ${shapeClasses}`) : '') +
(type && size ? (' btn-' + size) : '');
var isNewWindow = this._isNewWindow(url);
@@ -410,6 +421,14 @@ const Link = Widget.extend({
* @returns {string}
*/
_getLinkCustomFill: function () {},
/**
* Returns the custom text, fill and border color classes for custom type.
*
* @abstract
* @private
* @returns {string}
*/
_getLinkCustomClasses: function () {},
/**
* Abstract method: return true if the link should open in a new window.
*
@@ -3,9 +3,16 @@ odoo.define('wysiwyg.widgets.LinkTools', function (require) {
const Link = require('wysiwyg.widgets.Link');
const {ColorPaletteWidget} = require('web_editor.ColorPalette');
const {ColorpickerWidget} = require('web.Colorpicker');
const OdooEditorLib = require('@web_editor/../lib/odoo-editor/src/OdooEditor');
const dom = require('web.dom');
const {getNumericAndUnit, isColorGradient} = require('web_editor.utils');
const {
computeColorClasses,
getCSSVariableValue,
getColorClass,
getNumericAndUnit,
isColorGradient,
} = require('web_editor.utils');
const setSelection = OdooEditorLib.setSelection;
@@ -17,7 +24,6 @@ const LinkTools = Link.extend({
events: _.extend({}, Link.prototype.events, {
'click we-select we-button': '_onPickSelectOption',
'click we-checkbox': '_onClickCheckbox',
'click .link-custom-color span[data-toggle]': '_onClickCustomColor',
'change .link-custom-color-border input': '_onChangeCustomBorderWidth',
'keypress .link-custom-color-border input': '_onKeyPressCustomBorderWidth',
'click we-select [name="link_border_style"] we-button': '_onBorderStyleSelectOption',
@@ -33,7 +39,8 @@ const LinkTools = Link.extend({
}
const link = node || this.getOrCreateLink(editable);
this._super(parent, options, editable, data, $button, link);
// Keep track of each colorpicker.
// Keep track of each selected custom color and colorpicker.
this.customColors = {};
this.colorpickers = {};
},
/**
@@ -120,32 +127,45 @@ const LinkTools = Link.extend({
* @override
*/
_getLinkCustomTextColor: function () {
return this.$('we-selection-items[name="link-custom-color-text"] .o_we_color_preview').css('background-color') || '';
return this.customColors['color'];
},
/**
* @override
*/
_getLinkCustomBorder: function () {
return this.$('we-selection-items[name="link_border_color"] .o_we_color_preview').css('background-color') || '';
return this.customColors['border-color'];
},
/**
* @override
*/
_getLinkCustomBorderWidth: function () {
return this.$('we-selection-items[name="link_border_color"] input').val() || '';
return this.$('.link-custom-color-border input').val() || '';
},
/**
* @override
*/
_getLinkCustomBorderStyle: function () {
return this.$('we-selection-items[name="link_border_style"] we-button.active').data('value') || '';
return this.$('.link-custom-color-border we-button.active').data('value') || '';
},
/**
* @override
*/
_getLinkCustomFill: function () {
const $colorPreview = this.$('we-selection-items[name="link-custom-color-fill"] .o_we_color_preview');
return $colorPreview.css('background-image') || $colorPreview.css('background-color') || '';
return this.customColors['background-color'];
},
/**
* @override
*/
_getLinkCustomClasses: function () {
let textClass = this.customColors['color'];
if (!computeColorClasses(this.colorpickers['color'].getColorNames(), 'text-').includes(textClass)) {
textClass = '';
}
let fillClass = this.customColors['background-color'];
if (!computeColorClasses(this.colorpickers['background-color'].getColorNames(), 'bg-').includes(fillClass)) {
fillClass = '';
}
return ` ${textClass} ${fillClass}`;
},
/**
* @override
@@ -175,42 +195,118 @@ const LinkTools = Link.extend({
this.$('.link-size-row, .link-shape-row').toggleClass('d-none', !this.colorCombinationClass);
// Show custom colors only for Custom style.
this.$('.link-custom-color').toggleClass('d-none', el.dataset.value !== 'custom');
if (el.dataset.value === 'custom') {
const textColor = this.$link[0].style['color'];
this.$('.link-custom-color-text .o_we_color_preview').css('background-color', textColor);
if (this.colorpickers['color'] && textColor && textColor !== 'false') {
// Keep currently selected tab when setting color.
this.colorpickers['color'].setSelectedColor(null, textColor, false);
}
const backgroundColor = this.$link[0].style['background-color'];
this.$('.link-custom-color-fill .o_we_color_preview').css('background-color', backgroundColor);
const backgroundImage = this.$link[0].style['background-image'];
this.$('.link-custom-color-fill .o_we_color_preview').css('background-image', backgroundImage);
const backgroundFill = (backgroundImage !== 'false' && backgroundImage) || backgroundColor;
if (this.colorpickers['background-color'] && backgroundFill && backgroundFill !== 'false') {
// Keep currently selected tab when setting color.
this.colorpickers['background-color'].setSelectedColor(null, backgroundFill, false);
}
const borderWidth = this.$link[0].style['border-width'];
const numberAndUnit = getNumericAndUnit(borderWidth);
this.$('.link-custom-color-border input').val(numberAndUnit ? numberAndUnit[0] : "1");
let borderStyle = this.$link[0].style['border-style'];
if (!borderStyle || borderStyle === 'none') {
borderStyle = 'solid';
}
const $activeBorderStyleButton = this.$(`.link-custom-color-border [name="link_border_style"] we-button[data-value="${borderStyle}"]`);
$activeBorderStyleButton.addClass('active');
$activeBorderStyleButton.siblings('we-button').removeClass("active");
const $activeBorderStyleToggler = $activeBorderStyleButton.closest('we-select').find('we-toggler');
$activeBorderStyleToggler.empty();
$activeBorderStyleButton.find('div').clone().appendTo($activeBorderStyleToggler);
const borderColor = this.$link[0].style['border-color'];
this.$('.link-custom-color-border .o_we_color_preview').css('background-color', borderColor);
if (this.colorpickers['border-color'] && borderColor && borderColor !== 'false') {
// Keep currently selected tab when setting color.
this.colorpickers['border-color'].setSelectedColor(null, borderColor, false);
}
this._updateColorpicker('color');
this._updateColorpicker('background-color');
this._updateColorpicker('border-color');
const borderWidth = this.linkEl.style['border-width'];
const numberAndUnit = getNumericAndUnit(borderWidth);
this.$('.link-custom-color-border input').val(numberAndUnit ? numberAndUnit[0] : "1");
let borderStyle = this.linkEl.style['border-style'];
if (!borderStyle || borderStyle === 'none') {
borderStyle = 'solid';
}
const $activeBorderStyleButton = this.$(`.link-custom-color-border [name="link_border_style"] we-button[data-value="${borderStyle}"]`);
$activeBorderStyleButton.addClass('active');
$activeBorderStyleButton.siblings('we-button').removeClass("active");
const $activeBorderStyleToggler = $activeBorderStyleButton.closest('we-select').find('we-toggler');
$activeBorderStyleToggler.empty();
$activeBorderStyleButton.find('div').clone().appendTo($activeBorderStyleToggler);
}
},
/**
* Updates the colorpicker associated to a given property - updated with its selected color.
*
* @private
* @param {string} cssProperty
*/
_updateColorpicker: async function (cssProperty) {
const prefix = {
'color': 'text-',
'background-color': 'bg-',
}[cssProperty];
let colorpicker = this.colorpickers[cssProperty];
if (!colorpicker) {
colorpicker = new ColorPaletteWidget(this, {
excluded: ['transparent_grayscale'],
$editable: $(this.options.wysiwyg.odooEditor.editable),
withGradients: cssProperty === 'background-color',
});
this.colorpickers[cssProperty] = colorpicker;
const target = this.el.querySelector({
'color': '.link-custom-color-text .dropdown-menu',
'background-color': '.link-custom-color-fill .dropdown-menu',
'border-color': '.link-custom-color-border .o_we_so_color_palette .dropdown-menu',
}[cssProperty]);
await colorpicker.appendTo($(target));
colorpicker.on('custom_color_picked color_picked color_hover color_leave', this, (ev) => {
// Reset color styles in link content to make sure new color is not hidden.
// Only done when applied to avoid losing state during preview.
if (['custom_color_picked', 'color_picked'].includes(ev.name)) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(this.linkEl);
selection.removeAllRanges();
selection.addRange(range);
this.options.wysiwyg.odooEditor.execCommand('applyColor', '', 'color');
this.options.wysiwyg.odooEditor.execCommand('applyColor', '', 'backgroundColor');
}
let color = ev.data.color;
const colorNames = colorpicker.getColorNames();
const colorClasses = prefix ? computeColorClasses(colorNames, prefix) : [];
const colorClass = `${prefix}${color}`;
if (colorClasses.includes(colorClass)) {
color = colorClass;
} else if (colorNames.includes(color)) {
// Store as color value.
color = getCSSVariableValue(color);
}
this.customColors[cssProperty] = color;
this.applyLinkToDom(this._getData());
if (['custom_color_picked', 'color_picked'].includes(ev.name)) {
this.options.wysiwyg.odooEditor.historyStep();
this._updateOptionsUI();
}
});
}
// Update selected color.
const colorNames = colorpicker.getColorNames();
let color = this.linkEl.style[cssProperty];
const colorClasses = prefix ? computeColorClasses(colorNames, prefix) : [];
const colorClass = prefix && getColorClass(this.linkEl, colorNames, prefix);
const isColorClass = colorClasses.includes(colorClass);
if (isColorClass) {
color = colorClass;
} else if (cssProperty === 'background-color') {
const gradientColor = this.linkEl.style['background-image'];
if (isColorGradient(gradientColor)) {
color = gradientColor;
}
}
this.customColors[cssProperty] = color;
if (cssProperty === 'border-color') {
// Highlight matching named color if any.
const colorName = colorpicker.colorToColorNames[ColorpickerWidget.normalizeCSSColor(color)];
colorpicker.setSelectedColor(null, colorName || color, false);
} else {
colorpicker.setSelectedColor(null, isColorClass ? color.replace(prefix, '') : color, false);
}
// Update preview.
const $colorPreview = this.$('.link-custom-color-' + (cssProperty === 'border-color' ? 'border' : cssProperty === 'color' ? 'text' : 'fill') + ' .o_we_color_preview');
const previewClasses = computeColorClasses(colorNames, 'bg-');
$colorPreview[0].classList.remove(...previewClasses);
if (isColorClass) {
$colorPreview.addClass(`bg-${color.replace(prefix, '')}`);
$colorPreview.css('background-color', '');
$colorPreview.css('background-image', '');
} else {
$colorPreview.css('background-color', isColorGradient(color) ? 'rgba(0, 0, 0, 0)' : color);
$colorPreview.css('background-image', isColorGradient(color) ? color : '');
}
},
@@ -234,60 +330,6 @@ const LinkTools = Link.extend({
this._updateOptionsUI();
this._adaptPreview();
},
/**
* Sets the color on the link.
*
* @private
* @param {Event} ev
*/
_onClickCustomColor: function (ev) {
const cssProperty = ev.target.dataset.cssProperty;
let color = this.$link.css(cssProperty);
if (cssProperty === 'background-color') {
const gradientColor = this.$link.css('background-image');
if (isColorGradient(gradientColor)) {
color = gradientColor;
}
}
const colorpicker = new ColorPaletteWidget(this, {
excluded: ['transparent_grayscale'],
$editable: $(this.options.wysiwyg.odooEditor.editable),
selectedColor: color,
withGradients: cssProperty === 'background-color',
});
this.colorpickers[cssProperty] = colorpicker;
colorpicker.appendTo($(ev.target).closest('.dropdown').find('.dropdown-menu'));
colorpicker.on('custom_color_picked color_picked color_hover color_leave', this, (ev) => {
// Reset color styles in link content to make sure new color is not hidden.
// Only done when applied to avoid losing state during preview.
if (['custom_color_picked', 'color_picked'].includes(ev.name)) {
const selection = window.getSelection();
const range = document.createRange();
range.selectNodeContents(this.$link[0]);
selection.removeAllRanges();
selection.addRange(range);
this.options.wysiwyg.odooEditor.execCommand('applyColor', '', 'color');
this.options.wysiwyg.odooEditor.execCommand('applyColor', '', 'backgroundColor');
}
let color = ev.data.color;
let gradientColor = '';
if (cssProperty === 'background-color') {
if (isColorGradient(color)) {
gradientColor = color;
color = 'rgba(0, 0, 0, 0)';
}
this.$link.css('background-image', gradientColor);
}
this.$link.css(cssProperty, color);
if (['custom_color_picked', 'color_picked'].includes(ev.name)) {
const $colorPreview = this.$('.link-custom-color-' + (cssProperty === 'border-color' ? 'border' : cssProperty === 'color' ? 'text' : 'fill') + ' .o_we_color_preview');
$colorPreview.css('background-color', color);
$colorPreview.css('background-image', gradientColor);
this.options.wysiwyg.odooEditor.historyStep();
this._updateOptionsUI();
}
});
},
/**
* Sets the border width on the link.
*