From 9b854f5d39545c980355c6bf2ed088706abe9d0a Mon Sep 17 00:00:00 2001 From: Benoit Socias Date: Wed, 22 Sep 2021 08:05:07 +0000 Subject: [PATCH] [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) --- .../web_editor/static/src/js/common/utils.js | 14 ++ .../static/src/js/wysiwyg/widgets/link.js | 23 +- .../src/js/wysiwyg/widgets/link_tools.js | 238 ++++++++++-------- 3 files changed, 175 insertions(+), 100 deletions(-) diff --git a/addons/web_editor/static/src/js/common/utils.js b/addons/web_editor/static/src/js/common/utils.js index d4a9bac607e..73ef718697f 100644 --- a/addons/web_editor/static/src/js/common/utils.js +++ b/addons/web_editor/static/src/js/common/utils.js @@ -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, }; }); diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/link.js b/addons/web_editor/static/src/js/wysiwyg/widgets/link.js index 3b8e4c29052..e1a7f1f5ecf 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/link.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/link.js @@ -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. * diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/link_tools.js b/addons/web_editor/static/src/js/wysiwyg/widgets/link_tools.js index d6fd7e1b6ad..ad1533e2a7e 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/link_tools.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/link_tools.js @@ -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. *