[FIX] web_editor: properly handle selection in nested table

Custom table selections going through nested tables weren't working as
expected.

closes odoo/odoo#107624

X-original-commit: 670b27d4f8b99a9650cf915025ccf304a095d7de
Signed-off-by: David Monjoie (dmo) <dmo@odoo.com>
This commit is contained in:
Antoine Guenet
2022-12-09 15:12:53 +01:00
parent 7c07b8ee96
commit edd48fc799
@@ -2214,26 +2214,34 @@ export class OdooEditor extends EventTarget {
range.setStart(startRange.startContainer, 0);
range.setEnd(endRange.startContainer, 0);
} else {
range = getDeepRange(this.editable);
// We need the triple click correction only for a bug in firefox
// where it gives a selection of a full cell as tr 0 tr 1. The
// correction makes it so it gives us the cell and not its neighbor.
// In all other cases we don't want to make that correction so as to
// avoid flicker when hovering borders.
range = getDeepRange(this.editable, { correctTripleClick: anchorNode && anchorNode.nodeName === 'TR' });
}
const startTd = closestElement(range.startContainer, 'td');
const endTd = closestElement(range.endContainer, 'td');
let appliedCustomSelection = false;
const startTable = closestElement(range.startContainer, 'table');
const endTable = closestElement(range.endContainer, 'table');
// Get the top table ancestors at range bounds.
const startTable = ancestors(range.startContainer, this.editable).filter(node => node.nodeName === 'TABLE').pop();
const endTable = ancestors(range.endContainer, this.editable).filter(node => node.nodeName === 'TABLE').pop();
if (startTd !== endTd && startTable === endTable) {
// The selection goes through at least two different cells -> select
// cells.
this._selectTableCells(range);
appliedCustomSelection = true;
} else if (!traversedNodes.every(node => !!closestElement(node, 'td'))) {
} else if (!traversedNodes.every(node => node.parentElement && closestElement(node.parentElement, 'table'))) {
// The selection goes through a table but also outside of it ->
// select the whole table.
this.observerUnactive('handleSelectionInTable');
for (const table of new Set(traversedNodes.map(node => closestElement(node, 'table')))) {
if (table) {
const traversedTables = new Set(traversedNodes.map(node => closestElement(node, 'table')));
for (const table of traversedTables) {
// Don't apply several nested levels of selection.
if (table && !ancestors(table, this.editable).some(node => [...traversedTables].includes(node))) {
table.classList.toggle('o_selected_table', true);
for (const td of table.querySelectorAll('td')) {
for (const td of [...table.querySelectorAll('td')].filter(td => closestElement(td, 'table') === table)) {
td.classList.toggle('o_selected_td', true);
}
appliedCustomSelection = true;
@@ -2275,20 +2283,24 @@ export class OdooEditor extends EventTarget {
*/
_selectTableCells(range) {
this.observerUnactive('_selectTableCells');
const table = closestElement(range.startContainer, 'table');
const table = closestElement(range.commonAncestorContainer, 'table');
const alreadyHadSelection = table.classList.contains('o_selected_table');
this.deselectTable(); // Undo previous selection.
table.classList.toggle('o_selected_table', true);
const columns = table.querySelectorAll('td');
const startCol = closestElement(range.startContainer, 'td') || columns[0];
const endCol = closestElement(range.endContainer, 'td') || columns[columns.length - 1];
const [startRow, endRow] = [closestElement(startCol, 'tr'), closestElement(range.endContainer, 'tr')];
const columns = [...table.querySelectorAll('td')].filter(td => closestElement(td, 'table') === table);
const startCol = [range.startContainer, ...ancestors(range.startContainer, this.editable)]
.find(node => node.nodeName === 'TD' && closestElement(node, 'table') === table) || columns[0];
const endCol = [range.endContainer, ...ancestors(range.endContainer, this.editable)]
.find(node => node.nodeName === 'TD' && closestElement(node, 'table') === table) || columns[columns.length - 1];
const [startRow, endRow] = [closestElement(startCol, 'tr'), closestElement(endCol, 'tr')];
const [startColIndex, endColIndex] = [getColumnIndex(startCol), getColumnIndex(endCol)];
const [startRowIndex, endRowIndex] = [getRowIndex(startRow), getRowIndex(endRow)];
const [minRowIndex, maxRowIndex] = [Math.min(startRowIndex, endRowIndex), Math.max(startRowIndex, endRowIndex)];
const [minColIndex, maxColIndex] = [Math.min(startColIndex, endColIndex), Math.max(startColIndex, endColIndex)];
// Create an array of arrays of tds (each of which is a row).
const grid = [...table.querySelectorAll('tr')].map(tr => [...tr.children].filter(child => child.nodeName === 'TD'));
const grid = [...table.querySelectorAll('tr')]
.filter(tr => closestElement(tr, 'table') === table)
.map(tr => [...tr.children].filter(child => child.nodeName === 'TD'));
for (const tds of grid.filter((_, index) => index >= minRowIndex && index <= maxRowIndex)) {
for (const td of tds.filter((_, index) => index >= minColIndex && index <= maxColIndex)) {
td.classList.toggle('o_selected_td', true);