From aaea489f6d30334fd8de9b0b71e4bf608fceb3c2 Mon Sep 17 00:00:00 2001 From: Antoine Guenet Date: Tue, 14 Jun 2022 08:13:53 +0000 Subject: [PATCH] [ADD] web_editor: apply colors to whole selected table cells With this, when whole table cells are selected and the user picks a color in the toolbar's colorpicker, that color is applied to the whole cell rather than to the text only. task-2858455 Part-of: odoo/odoo#92200 --- .../odoo-editor/src/commands/commands.js | 15 +- .../odoo-editor/test/spec/editor.test.js | 262 ++++++++++++++++++ .../static/src/js/wysiwyg/wysiwyg.js | 42 ++- 3 files changed, 302 insertions(+), 17 deletions(-) diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js index 690417b5333..fece7d19fc9 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/src/commands/commands.js @@ -778,13 +778,20 @@ export const editorCommands = { * @param {Element} [element] */ applyColor: (editor, color, mode, element) => { - if (element) { + const selectedTds = editor.editable.querySelectorAll('td.o_selected_td'); + let coloredTds = []; + if (selectedTds.length) { + for (const td of selectedTds) { + colorElement(td, color, mode); + } + coloredTds = [...selectedTds]; + } else if (element) { colorElement(element, color, mode); return [element]; } const selection = editor.document.getSelection(); let wasCollapsed = false; - if (selection.getRangeAt(0).collapsed) { + if (selection.getRangeAt(0).collapsed && !selectedTds.length) { insertAndSelectZws(selection); wasCollapsed = true; } @@ -792,7 +799,7 @@ export const editorCommands = { if (!range) return; const restoreCursor = preserveCursor(editor.document); // Get the nodes to color - const selectedNodes = getSelectedNodes(editor.editable); + const selectedNodes = getSelectedNodes(editor.editable).filter(node => !closestElement(node, 'table.o_selected_table')); const fonts = selectedNodes.flatMap(node => { let font = closestElement(node, 'font'); const children = font && descendants(font); @@ -850,7 +857,7 @@ export const editorCommands = { newSelection.removeAllRanges(); newSelection.addRange(range); } - return fonts; + return [...fonts, ...coloredTds]; }, // Table insertTable: (editor, { rowNumber = 2, colNumber = 2 } = {}) => { diff --git a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js index e31231c0a95..9d47da9183e 100644 --- a/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js +++ b/addons/web_editor/static/src/js/editor/odoo-editor/test/spec/editor.test.js @@ -4101,6 +4101,119 @@ X[] }); }); }); + describe('color', () => { + it('should apply a color to some characters and a table', async () => { + await testEditor(BasicEditor, { + contentBefore: '

a[bc

' + + '' + + '' + + '' + + '
a]bcdef
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '

a[bc

' + + '' + + '' + + '' + + '' + + '
a]bcdef
', + }); + }); + it('should apply a color to a table and some characters', async () => { + await testEditor(BasicEditor, { + contentBefore: '' + + '' + + '' + + '' + + '
abcde[f

a]bc

', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '' + + '' + + '' + + '' + + '
abcde[f
' + + '

a]bc

', + }); + }); + it('should apply a color to some characters, a table and some more characters', async () => { + await testEditor(BasicEditor, { + contentBefore: '

a[bc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

a]bc

', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '

a[bc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

a]bc

', + }); + }); + it('should apply a color to some characters, a table, some more characters and another table', async () => { + await testEditor(BasicEditor, { + contentBefore: '

a[bc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

abc

' + + '' + + '' + + '' + + '' + + '
a]bcdef
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '

a[bc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

abc

' + + '' + + '' + + '' + + '' + + '
a]bcdef
', + }); + }); + it('should apply a color to some characters, a table, some more characters, another table and some more characters', async () => { + await testEditor(BasicEditor, { + contentBefore: '

a[bc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

abc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

a]bc

', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '

a[bc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

abc

' + + '' + + '' + + '' + + '' + + '
abcdef
' + + '

a]bc

', + }); + }); + }); }); describe('select columns on cross over', () => { describe('select', () => { @@ -4352,6 +4465,155 @@ X[] }); }); }); + describe('color', () => { + it('should apply a color to two columns', async () => { + await testEditor(BasicEditor, { + contentBefore: '' + + '' + + '' + + '' + + '
a[bc]def
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '' + + '' + + '' + + '' + + '
a[bc]def
', + }); + }); + it('should apply a color to a whole row', async () => { + await testEditor(BasicEditor, { + contentBefore: '' + + '' + + '' + + '' + + '
a[bcde]f
abcdef
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '' + + '' + + '' + + '' + + '
a[bcde]f
abcdef
', + }); + }); + it('should apply a color to a whole column', async () => { + await testEditor(BasicEditor, { + contentBefore: '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
a[bcdef
abcdef
a]bcdef
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
a[bcdef
abcdef
a]bcdef
', + }); + }); + it('should apply a color from (0,0) to (1,1) in a 3x3 table', async () => { + await testEditor(BasicEditor, { + contentBefore: '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
a[bcdef
abc]def
abcdef
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
a[bcdef
abc]def
abcdef
', + }); + }); + it('should apply a color to a whole table', async () => { + await testEditor(BasicEditor, { + contentBefore: '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
a[bcdef
abcdef
abcde]f
', + stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'), + contentAfterEdit: '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
a[bcdef
abcdef
abcde]f
', + }); + }); + }); }); }); }); diff --git a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js index 156fb2ee820..a7d396512f0 100644 --- a/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js +++ b/addons/web_editor/static/src/js/wysiwyg/wysiwyg.js @@ -36,6 +36,7 @@ const preserveCursor = OdooEditorLib.preserveCursor; const closestElement = OdooEditorLib.closestElement; const setSelection = OdooEditorLib.setSelection; const endPos = OdooEditorLib.endPos; +const hasValidSelection = OdooEditorLib.hasValidSelection; var id = 0; const basicMediaSelector = 'img, .fa, .o_image, .media_iframe_video'; @@ -1576,6 +1577,12 @@ const Wysiwyg = Widget.extend({ // Unstash the mutations now that the color is picked. this.odooEditor.historyUnstash(); this._processAndApplyColor(eventName, ev.data.color); + // Deselect tables so the applied color can be seen + // without using `!important` (otherwise the selection + // hides it). + if (this.odooEditor.deselectTable() && hasValidSelection(this.odooEditor.editable)) { + this.odooEditor.document.getSelection().collapseToStart(); + } this._updateEditorUI(); }); colorpicker.on('color_hover', null, ev => { @@ -1584,7 +1591,7 @@ const Wysiwyg = Widget.extend({ } this.odooEditor.historyPauseSteps(); try { - this._processAndApplyColor(eventName, ev.data.color); + this._processAndApplyColor(eventName, ev.data.color, true); } finally { this.odooEditor.historyUnpauseSteps(); } @@ -1615,25 +1622,34 @@ const Wysiwyg = Widget.extend({ }); } }, - _processAndApplyColor: function (eventName, color) { + _processAndApplyColor: function (eventName, color, previewMode) { if (!color) { color = 'inherit'; } else if (!ColorpickerWidget.isCSSColor(color) && !weUtils.isColorGradient(color)) { color = (eventName === "foreColor" ? 'text-' : 'bg-') + color; } - const fonts = this.odooEditor.execCommand('applyColor', color, eventName === 'foreColor' ? 'color' : 'backgroundColor', this.lastMediaClicked); + const coloredElements = this.odooEditor.execCommand('applyColor', color, eventName === 'foreColor' ? 'color' : 'backgroundColor', this.lastMediaClicked); - // Ensure the selection in the fonts tags, otherwise an undetermined - // race condition could generate a wrong selection later. - const first = fonts[0]; - const last = fonts[fonts.length - 1]; + const coloredTds = coloredElements.filter(coloredElement => coloredElement.classList.contains('o_selected_td')); + if (coloredTds.length) { + const propName = eventName === 'foreColor' ? 'color' : 'background-color'; + for (const td of coloredTds) { + // Make it important so it has priority over selection color. + td.style.setProperty(propName, td.style[propName], previewMode ? 'important' : ''); + } + } else { + // Ensure the selection in the fonts tags, otherwise an undetermined + // race condition could generate a wrong selection later. + const first = coloredElements[0]; + const last = coloredElements[coloredElements.length - 1]; - const sel = this.odooEditor.document.getSelection(); - sel.removeAllRanges(); - const range = new Range(); - range.setStart(first, 0); - range.setEnd(...endPos(last)); - sel.addRange(range); + const sel = this.odooEditor.document.getSelection(); + sel.removeAllRanges(); + const range = new Range(); + range.setStart(first, 0); + range.setEnd(...endPos(last)); + sel.addRange(range); + } const hexColor = this._colorToHex(color); this.odooEditor.updateColorpickerLabels({