Просмотр исходного кода

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
Kristina 1 год назад
Родитель
Сommit
67a436b639

+ 26 - 9
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 => {

+ 36 - 2
services/web/test/frontend/features/source-editor/components/codemirror-editor-visual-paste-html.spec.tsx

@@ -227,6 +227,40 @@ describe('<CodeMirrorEditor/> 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 = [
+      `<table><tbody>`,
+      `<tr><td>test</td><td colspan="2">test</td></tr>`,
+      `</tbody></table>`,
+    ].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 = [
+      `<table><tbody>`,
+      `<tr><td style="border-right:1px solid black;border-left:1px solid black;">test</td></tr>`,
+      `</tbody></table>`,
+    ].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('<CodeMirrorEditor/> 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)
   })