[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
This commit is contained in:
Antoine Guenet
2022-08-26 17:16:32 +02:00
parent f3cff78472
commit aaea489f6d
3 changed files with 302 additions and 17 deletions
@@ -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 <font> 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 } = {}) => {
@@ -4101,6 +4101,119 @@ X[]
});
});
});
describe('color', () => {
it('should apply a color to some characters and a table', async () => {
await testEditor(BasicEditor, {
contentBefore: '<p>a[bc</p><table><tbody><tr>' +
'<td>a]b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr></tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<p>a<font style="color: aquamarine;">[bc</font></p>' +
'<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a]b</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr></tbody></table>',
});
});
it('should apply a color to a table and some characters', async () => {
await testEditor(BasicEditor, {
contentBefore: '<table><tbody><tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>e[f</td>' +
'</tr></tbody></table><p>a]bc</p>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">e[f</td>' +
'</tr></tbody></table>' +
'<p><font style="color: aquamarine;">a]</font>bc</p>',
});
});
it('should apply a color to some characters, a table and some more characters', async () => {
await testEditor(BasicEditor, {
contentBefore: '<p>a[bc</p>' +
'<table><tbody><tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr></tbody></table>' +
'<p>a]bc</p>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<p>a<font style="color: aquamarine;">[bc</font></p>' +
'<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr></tbody></table>' +
'<p><font style="color: aquamarine;">a]</font>bc</p>',
});
});
it('should apply a color to some characters, a table, some more characters and another table', async () => {
await testEditor(BasicEditor, {
contentBefore: '<p>a[bc</p>' +
'<table><tbody><tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr></tbody></table>' +
'<p>abc</p>' +
'<table><tbody><tr>' +
'<td>a]b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr></tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<p>a<font style="color: aquamarine;">[bc</font></p>' +
'<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr></tbody></table>' +
'<p><font style="color: aquamarine;">abc</font></p>' +
'<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a]b</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr></tbody></table>',
});
});
it('should apply a color to some characters, a table, some more characters, another table and some more characters', async () => {
await testEditor(BasicEditor, {
contentBefore: '<p>a[bc</p>' +
'<table><tbody><tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr></tbody></table>' +
'<p>abc</p>' +
'<table><tbody><tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr></tbody></table>' +
'<p>a]bc</p>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<p>a<font style="color: aquamarine;">[bc</font></p>' +
'<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr></tbody></table>' +
'<p><font style="color: aquamarine;">abc</font></p>' +
'<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr></tbody></table>' +
'<p><font style="color: aquamarine;">a]</font>bc</p>',
});
});
});
});
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: '<table><tbody><tr>' +
'<td>a[b</td>' +
'<td>c]d</td>' +
'<td>ef</td>' +
'</tr></tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a[b</td>' +
'<td class="o_selected_td" style="color: aquamarine;">c]d</td>' +
'<td>ef</td>' +
'</tr></tbody></table>',
});
});
it('should apply a color to a whole row', async () => {
await testEditor(BasicEditor, {
contentBefore: '<table><tbody><tr>' +
'<td>a[b</td>' +
'<td>cd</td>' +
'<td>e]f</td>' +
'</tr><tr><td>ab</td><td>cd</td><td>ef</td></tr></tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<table class="o_selected_table"><tbody><tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a[b</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">e]f</td>' +
'</tr><tr><td>ab</td><td>cd</td><td>ef</td></tr></tbody></table>',
});
});
it('should apply a color to a whole column', async () => {
await testEditor(BasicEditor, {
contentBefore: '<table><tbody>' +
'<tr>' +
'<td>a[b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>a]b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'</tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<table class="o_selected_table"><tbody>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a[b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a]b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'</tbody></table>',
});
});
it('should apply a color from (0,0) to (1,1) in a 3x3 table', async () => {
await testEditor(BasicEditor, {
contentBefore: '<table><tbody>' +
'<tr>' +
'<td>a[b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>ab</td>' +
'<td>c]d</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'</tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<table class="o_selected_table"><tbody>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a[b</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">c]d</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'</tbody></table>',
});
});
it('should apply a color to a whole table', async () => {
await testEditor(BasicEditor, {
contentBefore: '<table><tbody>' +
'<tr>' +
'<td>a[b</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>ef</td>' +
'</tr>' +
'<tr>' +
'<td>ab</td>' +
'<td>cd</td>' +
'<td>e]f</td>' +
'</tr>' +
'</tbody></table>',
stepFunction: async editor => editor.execCommand('applyColor', 'aquamarine', 'color'),
contentAfterEdit: '<table class="o_selected_table"><tbody>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">a[b</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">ef</td>' +
'</tr>' +
'<tr>' +
'<td class="o_selected_td" style="color: aquamarine;">ab</td>' +
'<td class="o_selected_td" style="color: aquamarine;">cd</td>' +
'<td class="o_selected_td" style="color: aquamarine;">e]f</td>' +
'</tr>' +
'</tbody></table>',
});
});
});
});
});
});
@@ -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({