diff --git a/addons/web_editor/static/src/js/backend/convert_inline.js b/addons/web_editor/static/src/js/backend/convert_inline.js index f501e7fc0c7..1fe6797afc1 100644 --- a/addons/web_editor/static/src/js/backend/convert_inline.js +++ b/addons/web_editor/static/src/js/backend/convert_inline.js @@ -1136,9 +1136,9 @@ function listGroupToTable(editable) { function normalizeColors($editable) { const editable = $editable.get(0); for (const node of editable.querySelectorAll('[style*="rgb"]')) { - const rgbMatch = node.getAttribute('style').match(/rgb?\(([\d\.]*,?\s?){3,4}\)/g); - for (const rgb of rgbMatch || []) { - node.setAttribute('style', node.getAttribute('style').replace(rgb, rgbToHex(rgb))); + const rgbaMatch = node.getAttribute('style').match(/rgba?\(([\d\.]+\s*,?\s*){3,4}\)/g); + for (const rgb of rgbaMatch || []) { + node.setAttribute('style', node.getAttribute('style').replace(rgb, rgbToHex(rgb, node))); } } } diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js index 8f95ae89971..032a88b761a 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/utils/utils.js @@ -2456,9 +2456,49 @@ export function enforceWhitespace(el, offset, direction, rule) { } } -export function rgbToHex(rgb = '') { +/** + * Takes a color (rgb, rgba or hex) and returns its hex representation. If the + * color is given in rgba, the background color of the node whose color we're + * converting is used in conjunction with the alpha to compute the resulting + * color (using the formula: `alpha*color + (1 - alpha)*background` for each + * channel). + * + * @param {string} rgb + * @param {HTMLElement} [node] + * @returns {string} hexadecimal color (#RRGGBB) + */ +export function rgbToHex(rgb = '', node = null) { if (rgb.startsWith('#')) { return rgb; + } else if (rgb.startsWith('rgba')) { + const values = rgb.match(/[\d\.]{1,5}/g) || []; + const alpha = parseFloat(values.pop()); + // Retrieve the background color. + let bgRgbValues = []; + if (node) { + let bgColor = getComputedStyle(node).backgroundColor; + if (bgColor.startsWith('rgba')) { + // The background color is itself rgba so we need to compute + // the resulting color using the background color of its + // parent. + bgColor = rgbToHex(bgColor, node.parentElement); + } + if (bgColor && bgColor.startsWith('#')) { + bgRgbValues = (bgColor.match(/[\da-f]{2}/gi) || []).map(val => parseInt(val, 16)); + } else if (bgColor && bgColor.startsWith('rgb')) { + bgRgbValues = (bgColor.match(/[\d\.]{1,5}/g) || []).map(val => parseInt(val)); + } + } + bgRgbValues = bgRgbValues.length ? bgRgbValues : [255, 255, 255]; // Default to white. + + return ( + '#' + + values.map((value, index) => { + const converted = Math.floor(alpha * parseInt(value) + (1 - alpha) * bgRgbValues[index]); + const hex = parseInt(converted).toString(16); + return hex.length === 1 ? '0' + hex : hex; + }).join('') + ); } else { return ( '#' + diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/color.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/color.test.js index f9967cdf21c..c31cd4c4d9e 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/color.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/color.test.js @@ -1,6 +1,7 @@ /** @odoo-module */ import { BasicEditor, testEditor, unformat } from '../utils.js'; +import { rgbToHex } from '../../src/utils/utils.js'; const setColor = (color, mode) => { return async editor => { @@ -106,3 +107,53 @@ describe('applyColor', () => { }); }); }); +describe('rgbToHex', () => { + it('should convert an rgb color to hexadecimal', async () => { + window.chai.expect(rgbToHex('rgb(0, 0, 255)')).to.be.equal('#0000ff'); + window.chai.expect(rgbToHex('rgb(0,0,255)')).to.be.equal('#0000ff'); + }); + it('should convert an rgba color to hexadecimal (background is hexadecimal)', async () => { + const parent = document.createElement('div'); + const node = document.createElement('div'); + parent.style.backgroundColor = '#ff0000'; // red, should be irrelevant + node.style.backgroundColor = '#0000ff'; // blue + parent.append(node); + document.body.append(parent); + // white with 50% opacity over blue = light blue + window.chai.expect(rgbToHex('rgba(255, 255, 255, 0.5)', node)).to.be.equal('#7f7fff'); + parent.remove(); + }); + it('should convert an rgba color to hexadecimal (background is color name)', async () => { + const parent = document.createElement('div'); + const node = document.createElement('div'); + parent.style.backgroundColor = '#ff0000'; // red, should be irrelevant + node.style.backgroundColor = 'blue'; // blue + parent.append(node); + document.body.append(parent); + // white with 50% opacity over blue = light blue + window.chai.expect(rgbToHex('rgba(255, 255, 255, 0.5)', node)).to.be.equal('#7f7fff'); + parent.remove(); + }); + it('should convert an rgba color to hexadecimal (background is rgb)', async () => { + const parent = document.createElement('div'); + const node = document.createElement('div'); + parent.style.backgroundColor = '#ff0000'; // red, should be irrelevant + node.style.backgroundColor = 'rgb(0, 0, 255)'; // blue + parent.append(node); + document.body.append(parent); + // white with 50% opacity over blue = light blue + window.chai.expect(rgbToHex('rgba(255, 255, 255, 0.5)', node)).to.be.equal('#7f7fff'); + parent.remove(); + }); + it('should convert an rgba color to hexadecimal (background is rgba)', async () => { + const parent = document.createElement('div'); + const node = document.createElement('div'); + parent.style.backgroundColor = 'rgb(255, 0, 0)'; // red + node.style.backgroundColor = 'rgba(0, 0, 255, 0.5)'; // blue + parent.append(node); + document.body.append(parent); + // white with 50% opacity over blue with 50% opacity over red = light purple + window.chai.expect(rgbToHex('rgba(255, 255, 255, 0.5)', node)).to.be.equal('#bf7fbf'); + parent.remove(); + }); +});