diff --git a/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts b/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts index ec9e3bd7bd..01db7b18ba 100644 --- a/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts +++ b/services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts @@ -487,6 +487,7 @@ const tabular = (element: HTMLTableElement) => { alignment: string borderLeft: boolean borderRight: boolean + inferred?: boolean }> = [] const rows = element.querySelectorAll('tr') @@ -500,16 +501,29 @@ const tabular = (element: HTMLTableElement) => { for (const cell of cells) { // NOTE: reading the alignment and borders from the first cell definition in each column - if (definitions[index] === undefined) { - const { textAlign, borderLeftStyle, borderRightStyle } = cell.style + const colspan = Number(cell.getAttribute('colspan') ?? 1) + const { textAlign, borderLeftStyle, borderRightStyle } = cell.style - definitions[index] = { - alignment: textAlign, - borderLeft: visibleBorderStyle(borderLeftStyle), - borderRight: visibleBorderStyle(borderRightStyle), + for (let i = 0; i < colspan; i++) { + if ( + // There's no definition for this column + definitions[index + i] === undefined || + // There's an inferred definition of the column, and we're a cell that + // can accurately represent the whole column, since we're not a + // multicolumn cell ourselves. + (colspan === 1 && definitions[index + i].inferred) + ) { + definitions[index + i] = { + alignment: textAlign, + borderLeft: visibleBorderStyle(borderLeftStyle), + borderRight: visibleBorderStyle(borderRightStyle), + // We can't trust the details from a multicolumn cell to represent the + // whole column, so we mark it as inferred. + inferred: colspan > 1, + } } } - index += Number(cell.getAttribute('colspan') ?? 1) + index += colspan } } @@ -614,9 +628,12 @@ const nextRowHasBorderStyle = ( } const startMulticolumn = (element: HTMLTableCellElement): string => { + const { textAlign, borderLeftStyle, borderRightStyle } = element.style const colspan = Number(element.getAttribute('colspan') || 1) - const alignment = cellAlignment.get(element.style.textAlign) ?? 'l' - return `\\multicolumn{${colspan}}{${alignment}}{` + const alignment = cellAlignment.get(textAlign) ?? 'l' + const borderLeft = visibleBorderStyle(borderLeftStyle) + const borderRight = visibleBorderStyle(borderRightStyle) + return `\\multicolumn{${colspan}}{${borderLeft ? '|' : ''}${alignment}${borderRight ? '|' : ''}}{` } const startMultirow = (element: HTMLTableCellElement): string => { diff --git a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx index 450af37a5a..9220d6ccdf 100644 --- a/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx +++ b/services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx @@ -227,6 +227,40 @@ describe(' paste HTML in Visual mode', function () { cy.get('.table-generator-cell[colspan="2"]').should('have.length', 2) }) + it('handles a pasted 1-row table with merged columns', function () { + mountEditor() + + const data = [ + ``, + ``, + `
testtest
`, + ].join('') + + const clipboardData = new DataTransfer() + clipboardData.setData('text/html', data) + cy.get('@content').trigger('paste', { clipboardData }) + + cy.get('@content').should('have.text', 'testtest' + menuIconsText) + cy.get('.table-generator-cell').should('have.length', 2) + cy.get('.table-generator-cell[colspan="2"]').should('have.length', 1) + }) + + it('handles a pasted table with a bordered merged column', function () { + mountEditor() + + const data = [ + ``, + ``, + `
test
`, + ].join('') + + const clipboardData = new DataTransfer() + clipboardData.setData('text/html', data) + cy.get('@content').trigger('paste', { clipboardData }) + cy.get('.table-generator-cell-border-right').should('have.length', 1) + cy.get('.table-generator-cell-border-left').should('have.length', 1) + }) + it('handles a pasted table with merged rows', function () { mountEditor() @@ -312,8 +346,8 @@ describe(' paste HTML in Visual mode', function () { cy.get('@content').should('have.text', 'foofoobarfoobar' + menuIconsText) cy.get('.table-generator-cell').should('have.length', 5) cy.get('.table-generator-cell[colspan="2"]').should('have.length', 1) - cy.get('.table-generator-cell-border-left').should('have.length', 2) - cy.get('.table-generator-cell-border-right').should('have.length', 4) + cy.get('.table-generator-cell-border-left').should('have.length', 3) + cy.get('.table-generator-cell-border-right').should('have.length', 5) cy.get('.table-generator-row-border-top').should('have.length', 5) cy.get('.table-generator-row-border-bottom').should('have.length', 2) })