From 67a436b6392eedc5fa228cd6172fccae58de4fbd Mon Sep 17 00:00:00 2001
From: Kristina <7614497+khjrtbrg@users.noreply.github.com>
Date: Mon, 12 May 2025 11:29:17 +0200
Subject: [PATCH] Merge pull request #25469 from
overleaf/mj-paste-tables-multicol
[web] Improve borders and column definitions of pasted tables with multi-column cells
GitOrigin-RevId: fe9c44bd8ac6a34e8a8057f1a07d97771a116e1a
---
.../extensions/visual/paste-html.ts | 35 ++++++++++++-----
...demirror-editor-visual-paste-html.spec.tsx | 38 ++++++++++++++++++-
2 files changed, 62 insertions(+), 11 deletions(-)
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 = [
+ `
`,
+ ].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 = [
+ ``,
+ ].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)
})