mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #14664 from overleaf/ae-paste-rowspan
[visual] Handle rowspan on pasted table cells GitOrigin-RevId: 53dda99ae1c300b9c4ec8f711b70b16ef66392c8
This commit is contained in:
parent
6cde5e2e90
commit
4a157e7086
2 changed files with 121 additions and 19 deletions
|
@ -229,9 +229,58 @@ const processTables = (element: HTMLElement) => {
|
|||
|
||||
// move the table into the container
|
||||
container.append(table)
|
||||
|
||||
// add empty cells to account for rowspan
|
||||
for (const cell of table.querySelectorAll<HTMLTableCellElement>(
|
||||
'th[rowspan],td[rowspan]'
|
||||
)) {
|
||||
const rowspan = Number(cell.getAttribute('rowspan') || '1')
|
||||
const colspan = Number(cell.getAttribute('colspan') || '1')
|
||||
|
||||
let row: HTMLTableRowElement | null = cell.closest('tr')
|
||||
if (row) {
|
||||
let position = 0
|
||||
for (const child of row.cells) {
|
||||
if (child === cell) {
|
||||
break
|
||||
}
|
||||
position += Number(child.getAttribute('colspan') || '1')
|
||||
}
|
||||
for (let i = 1; i < rowspan; i++) {
|
||||
const nextElement: Element | null = row?.nextElementSibling
|
||||
if (!isTableRow(nextElement)) {
|
||||
break
|
||||
}
|
||||
row = nextElement
|
||||
|
||||
let targetCell: HTMLTableCellElement | undefined
|
||||
let targetPosition = 0
|
||||
for (const child of row.cells) {
|
||||
if (targetPosition === position) {
|
||||
targetCell = child
|
||||
break
|
||||
}
|
||||
targetPosition += Number(child.getAttribute('colspan') || '1')
|
||||
}
|
||||
|
||||
const fillerCells = Array.from({ length: colspan }, () =>
|
||||
document.createElement('td')
|
||||
)
|
||||
|
||||
if (targetCell) {
|
||||
targetCell.before(...fillerCells)
|
||||
} else {
|
||||
row.append(...fillerCells)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const isTableRow = (element: Element | null): element is HTMLTableRowElement =>
|
||||
element?.tagName === 'TR'
|
||||
|
||||
const cellAlignment = new Map([
|
||||
['left', 'l'],
|
||||
['center', 'c'],
|
||||
|
@ -370,9 +419,15 @@ const nextRowHasBorderStyle = (
|
|||
}
|
||||
|
||||
const startMulticolumn = (element: HTMLTableCellElement): string => {
|
||||
const colspan = element.getAttribute('colspan') ?? 1
|
||||
const colspan = Number(element.getAttribute('colspan') || 1)
|
||||
const alignment = cellAlignment.get(element.style.textAlign) ?? 'l'
|
||||
return `\\multicolumn{${Number(colspan)}}{${alignment}}{`
|
||||
return `\\multicolumn{${colspan}}{${alignment}}{`
|
||||
}
|
||||
|
||||
const startMultirow = (element: HTMLTableCellElement): string => {
|
||||
const rowspan = Number(element.getAttribute('rowspan') || 1)
|
||||
// NOTE: it would be useful to read cell width if specified, using `*` as a starting point
|
||||
return `\\multirow{${rowspan}}{*}{`
|
||||
}
|
||||
|
||||
const selectors = [
|
||||
|
@ -539,25 +594,30 @@ const selectors = [
|
|||
},
|
||||
}),
|
||||
createSelector({
|
||||
selector: 'tr > td:not(:last-child), tr > th:not(:last-child)',
|
||||
selector: 'tr > td, tr > th',
|
||||
start: (element: HTMLTableCellElement) => {
|
||||
const colspan = element.getAttribute('colspan')
|
||||
return colspan ? startMulticolumn(element) : ''
|
||||
let output = ''
|
||||
if (element.getAttribute('colspan')) {
|
||||
output += startMulticolumn(element)
|
||||
}
|
||||
// NOTE: multirow is nested inside multicolumn
|
||||
if (element.getAttribute('rowspan')) {
|
||||
output += startMultirow(element)
|
||||
}
|
||||
return output
|
||||
},
|
||||
end: element => {
|
||||
const colspan = element.getAttribute('colspan')
|
||||
return colspan ? `} & ` : ` & `
|
||||
},
|
||||
}),
|
||||
createSelector({
|
||||
selector: 'tr > td:last-child, tr > th:last-child',
|
||||
start: (element: HTMLTableCellElement) => {
|
||||
const colspan = element.getAttribute('colspan')
|
||||
return colspan ? startMulticolumn(element) : ''
|
||||
},
|
||||
end: element => {
|
||||
const colspan = element.getAttribute('colspan')
|
||||
return colspan ? `} \\\\` : ` \\\\`
|
||||
let output = ''
|
||||
// NOTE: multirow is nested inside multicolumn
|
||||
if (element.getAttribute('rowspan')) {
|
||||
output += '}'
|
||||
}
|
||||
if (element.getAttribute('colspan')) {
|
||||
output += '}'
|
||||
}
|
||||
const row = element.parentElement as HTMLTableRowElement
|
||||
const isLastChild = row.cells.item(row.cells.length - 1) === element
|
||||
return output + (isLastChild ? ' \\\\' : ' & ')
|
||||
},
|
||||
}),
|
||||
createSelector({
|
||||
|
|
|
@ -161,7 +161,7 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
|
|||
)
|
||||
})
|
||||
|
||||
it('handles a pasted table with merged cells', function () {
|
||||
it('handles a pasted table with merged columns', function () {
|
||||
mountEditor()
|
||||
|
||||
const data = [
|
||||
|
@ -182,6 +182,48 @@ describe('<CodeMirrorEditor/> paste HTML in Visual mode', function () {
|
|||
)
|
||||
})
|
||||
|
||||
it('handles a pasted table with merged rows', function () {
|
||||
mountEditor()
|
||||
|
||||
const data = [
|
||||
`<table><tbody>`,
|
||||
`<tr><td>test</td><td>test</td><td>test</td></tr>`,
|
||||
`<tr><td rowspan="2">test</td><td>test</td><td>test</td></tr>`,
|
||||
`<tr><td>test</td><td>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',
|
||||
'\\begin{tabular}{l l l}test & test & test ↩\\multirow{2}{*}{test} & test & test ↩ & test & test ↩\\end{tabular}'
|
||||
)
|
||||
})
|
||||
|
||||
it('handles a pasted table with merged rows and columns', function () {
|
||||
mountEditor()
|
||||
|
||||
const data = [
|
||||
`<table><tbody>`,
|
||||
`<tr><td colspan="2" rowspan="2">test</td><td>test</td></tr>`,
|
||||
`<tr><td>test</td></tr>`,
|
||||
`<tr><td>test</td><td>test</td><td>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',
|
||||
'\\begin{tabular}{l l l}\\multicolumn{2}{l}{\\multirow{2}{*}{test}} & test ↩ & & test ↩test & test & test ↩\\end{tabular}'
|
||||
)
|
||||
})
|
||||
|
||||
it('handles a pasted table with adjacent borders and merged cells', function () {
|
||||
mountEditor()
|
||||
|
||||
|
|
Loading…
Reference in a new issue