Merge pull request #14653 from overleaf/mj-column-spacing-parsing

[visual] Support cell spacing declarations

GitOrigin-RevId: 16b4ddc196558679301010378912b14f6295e05f
This commit is contained in:
Mathias Jakobsen 2023-09-05 09:27:12 +01:00 committed by Copybot
parent d98288605a
commit ad38ac233b
5 changed files with 101 additions and 30 deletions

View file

@ -29,6 +29,8 @@ export type ColumnDefinition = {
borderLeft: number
borderRight: number
content: string
cellSpacingLeft: string
cellSpacingRight: string
}
export type CellData = {

View file

@ -71,7 +71,9 @@ export const setBorders = (
],
})
} else if (theme === BorderTheme.FULLY_BORDERED) {
const newSpec = addColumnBordersToSpecification(specification)
const newSpec = generateColumnSpecification(
addColumnBordersToSpecification(table.columns)
)
const insertColumns = view.state.changes({
from: positions.columnDeclarations.from,
@ -110,14 +112,14 @@ export const setBorders = (
for (const row of table.rows) {
for (const cell of row.cells) {
if (cell.multiColumn) {
const specification = view.state.sliceDoc(
cell.multiColumn.columns.from,
cell.multiColumn.columns.to
)
addMulticolumnBorders.push({
from: cell.multiColumn.columns.from,
to: cell.multiColumn.columns.to,
insert: addColumnBordersToSpecification(specification),
insert: generateColumnSpecification(
addColumnBordersToSpecification(
cell.multiColumn.columns.specification
)
),
})
}
}
@ -129,24 +131,13 @@ export const setBorders = (
}
}
const addColumnBordersToSpecification = (specification: string) => {
let newSpec = '|'
let consumingBrackets = 0
for (const char of specification) {
if (char === '{') {
consumingBrackets++
}
if (char === '}' && consumingBrackets > 0) {
consumingBrackets--
}
if (consumingBrackets) {
newSpec += char
}
if (char === '|') {
continue
}
newSpec += char + '|'
}
const addColumnBordersToSpecification = (specification: ColumnDefinition[]) => {
const newSpec = specification.map(column => ({
...column,
borderLeft: 1,
borderRight: 0,
}))
newSpec[newSpec.length - 1].borderRight = 1
return newSpec
}
@ -216,8 +207,18 @@ export const setAlignment = (
const generateColumnSpecification = (columns: ColumnDefinition[]) => {
return columns
.map(
({ borderLeft, borderRight, content }) =>
`${'|'.repeat(borderLeft)}${content}${'|'.repeat(borderRight)}`
({
borderLeft,
borderRight,
content,
cellSpacingLeft,
cellSpacingRight,
}) =>
`${'|'.repeat(
borderLeft
)}${cellSpacingLeft}${content}${cellSpacingRight}${'|'.repeat(
borderRight
)}`
)
.join('')
}
@ -427,6 +428,8 @@ export const insertColumn = (
borderLeft: 0,
borderRight,
content: 'l',
cellSpacingLeft: '',
cellSpacingRight: '',
}))
)
if (targetIndex === 0 && borderTheme === BorderTheme.FULLY_BORDERED) {

View file

@ -12,6 +12,24 @@ export type RowPosition = {
hlines: { from: number; to: number }[]
}
function parseArgument(spec: string, startIndex: number): number {
if (spec.charAt(startIndex) !== '{') {
throw new Error('Missing opening brace')
}
let depth = 0
for (let i = startIndex; i < spec.length; i++) {
if (spec.charAt(i) === '{') {
depth++
} else if (spec.charAt(i) === '}') {
depth--
}
if (depth === 0) {
return i
}
}
throw new Error('Missing closing brace')
}
export function parseColumnSpecifications(
specification: string
): ColumnDefinition[] {
@ -20,6 +38,8 @@ export function parseColumnSpecifications(
let currentBorderLeft = 0
let currentBorderRight = 0
let currentContent = ''
let currentCellSpacingLeft = ''
let currentCellSpacingRight = ''
function maybeCommit() {
if (currentAlignment !== undefined) {
columns.push({
@ -27,11 +47,15 @@ export function parseColumnSpecifications(
borderLeft: currentBorderLeft,
borderRight: currentBorderRight,
content: currentContent,
cellSpacingLeft: currentCellSpacingLeft,
cellSpacingRight: currentCellSpacingRight,
})
currentAlignment = undefined
currentBorderLeft = 0
currentBorderRight = 0
currentContent = ''
currentCellSpacingLeft = ''
currentCellSpacingRight = ''
}
}
for (let i = 0; i < specification.length; i++) {
@ -65,12 +89,31 @@ export function parseColumnSpecifications(
currentAlignment = 'paragraph'
currentContent += 'p'
// TODO: Parse these details
while (i < specification.length && specification.charAt(i) !== '}') {
i++
currentContent += specification.charAt(i)
const argumentEnd = parseArgument(specification, i + 1)
// Don't include the p twice
currentContent += specification.slice(i + 1, argumentEnd + 1)
i = argumentEnd
break
}
case '@':
case '!': {
const argumentEnd = parseArgument(specification, i + 1)
// Include the @/!
const argument = specification.slice(i, argumentEnd + 1)
i = argumentEnd
if (currentAlignment) {
// We have a cell, so this is right cell spacing
currentCellSpacingRight = argument
} else {
currentCellSpacingLeft = argument
}
break
}
case ' ':
case '\n':
case '\t':
currentContent += char
break
}
}
maybeCommit()
@ -279,7 +322,6 @@ function parseTabularBody(
} else if (isHLine(currentChild)) {
const lastCell = getLastCell()
if (lastCell?.content.trim()) {
console.error(lastCell)
throw new Error('\\hline must be at the start of a row')
}
// push start of cell past the hline

View file

@ -22,6 +22,7 @@ export class TabularWidget extends WidgetType {
try {
this.parseResult = generateTable(tabularNode, state)
} catch (e) {
console.error(e)
this.parseResult = null
}
}

View file

@ -422,5 +422,28 @@ cell 3 & cell 4 \\\\
cy.get('.ol-cm-command-caption').should('not.exist')
cy.get('.ol-cm-command-label').should('not.exist')
})
it('Renders a table with custom column spacing', function () {
mountEditor(`
\\begin{tabular}{@{}c@{}l!{}}
cell 1 & cell 2 \\\\
cell 3 & cell 4 \\\\
\\end{tabular}`)
checkTable([
['cell 1', 'cell 2'],
['cell 3', 'cell 4'],
])
cy.get('.table-generator-cell').first().click()
cy.get('.table-generator-floating-toolbar').as('toolbar').should('exist')
cy.get('@toolbar').findByText('No borders').click()
cy.get('.table-generator').findByText('All borders').click()
// The element is partially covered, but we can still click it
cy.get('.cm-line').first().click({ force: true })
checkTable([
['cell 1', 'cell 2'],
['cell 3', 'cell 4'],
])
checkBordersWithNoMultiColumn([true, true, true], [true, true, true])
})
})
})