mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #14653 from overleaf/mj-column-spacing-parsing
[visual] Support cell spacing declarations GitOrigin-RevId: 16b4ddc196558679301010378912b14f6295e05f
This commit is contained in:
parent
d98288605a
commit
ad38ac233b
5 changed files with 101 additions and 30 deletions
|
@ -29,6 +29,8 @@ export type ColumnDefinition = {
|
||||||
borderLeft: number
|
borderLeft: number
|
||||||
borderRight: number
|
borderRight: number
|
||||||
content: string
|
content: string
|
||||||
|
cellSpacingLeft: string
|
||||||
|
cellSpacingRight: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CellData = {
|
export type CellData = {
|
||||||
|
|
|
@ -71,7 +71,9 @@ export const setBorders = (
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
} else if (theme === BorderTheme.FULLY_BORDERED) {
|
} else if (theme === BorderTheme.FULLY_BORDERED) {
|
||||||
const newSpec = addColumnBordersToSpecification(specification)
|
const newSpec = generateColumnSpecification(
|
||||||
|
addColumnBordersToSpecification(table.columns)
|
||||||
|
)
|
||||||
|
|
||||||
const insertColumns = view.state.changes({
|
const insertColumns = view.state.changes({
|
||||||
from: positions.columnDeclarations.from,
|
from: positions.columnDeclarations.from,
|
||||||
|
@ -110,14 +112,14 @@ export const setBorders = (
|
||||||
for (const row of table.rows) {
|
for (const row of table.rows) {
|
||||||
for (const cell of row.cells) {
|
for (const cell of row.cells) {
|
||||||
if (cell.multiColumn) {
|
if (cell.multiColumn) {
|
||||||
const specification = view.state.sliceDoc(
|
|
||||||
cell.multiColumn.columns.from,
|
|
||||||
cell.multiColumn.columns.to
|
|
||||||
)
|
|
||||||
addMulticolumnBorders.push({
|
addMulticolumnBorders.push({
|
||||||
from: cell.multiColumn.columns.from,
|
from: cell.multiColumn.columns.from,
|
||||||
to: cell.multiColumn.columns.to,
|
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) => {
|
const addColumnBordersToSpecification = (specification: ColumnDefinition[]) => {
|
||||||
let newSpec = '|'
|
const newSpec = specification.map(column => ({
|
||||||
let consumingBrackets = 0
|
...column,
|
||||||
for (const char of specification) {
|
borderLeft: 1,
|
||||||
if (char === '{') {
|
borderRight: 0,
|
||||||
consumingBrackets++
|
}))
|
||||||
}
|
newSpec[newSpec.length - 1].borderRight = 1
|
||||||
if (char === '}' && consumingBrackets > 0) {
|
|
||||||
consumingBrackets--
|
|
||||||
}
|
|
||||||
if (consumingBrackets) {
|
|
||||||
newSpec += char
|
|
||||||
}
|
|
||||||
if (char === '|') {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
newSpec += char + '|'
|
|
||||||
}
|
|
||||||
return newSpec
|
return newSpec
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -216,8 +207,18 @@ export const setAlignment = (
|
||||||
const generateColumnSpecification = (columns: ColumnDefinition[]) => {
|
const generateColumnSpecification = (columns: ColumnDefinition[]) => {
|
||||||
return columns
|
return columns
|
||||||
.map(
|
.map(
|
||||||
({ borderLeft, borderRight, content }) =>
|
({
|
||||||
`${'|'.repeat(borderLeft)}${content}${'|'.repeat(borderRight)}`
|
borderLeft,
|
||||||
|
borderRight,
|
||||||
|
content,
|
||||||
|
cellSpacingLeft,
|
||||||
|
cellSpacingRight,
|
||||||
|
}) =>
|
||||||
|
`${'|'.repeat(
|
||||||
|
borderLeft
|
||||||
|
)}${cellSpacingLeft}${content}${cellSpacingRight}${'|'.repeat(
|
||||||
|
borderRight
|
||||||
|
)}`
|
||||||
)
|
)
|
||||||
.join('')
|
.join('')
|
||||||
}
|
}
|
||||||
|
@ -427,6 +428,8 @@ export const insertColumn = (
|
||||||
borderLeft: 0,
|
borderLeft: 0,
|
||||||
borderRight,
|
borderRight,
|
||||||
content: 'l',
|
content: 'l',
|
||||||
|
cellSpacingLeft: '',
|
||||||
|
cellSpacingRight: '',
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
if (targetIndex === 0 && borderTheme === BorderTheme.FULLY_BORDERED) {
|
if (targetIndex === 0 && borderTheme === BorderTheme.FULLY_BORDERED) {
|
||||||
|
|
|
@ -12,6 +12,24 @@ export type RowPosition = {
|
||||||
hlines: { from: number; to: number }[]
|
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(
|
export function parseColumnSpecifications(
|
||||||
specification: string
|
specification: string
|
||||||
): ColumnDefinition[] {
|
): ColumnDefinition[] {
|
||||||
|
@ -20,6 +38,8 @@ export function parseColumnSpecifications(
|
||||||
let currentBorderLeft = 0
|
let currentBorderLeft = 0
|
||||||
let currentBorderRight = 0
|
let currentBorderRight = 0
|
||||||
let currentContent = ''
|
let currentContent = ''
|
||||||
|
let currentCellSpacingLeft = ''
|
||||||
|
let currentCellSpacingRight = ''
|
||||||
function maybeCommit() {
|
function maybeCommit() {
|
||||||
if (currentAlignment !== undefined) {
|
if (currentAlignment !== undefined) {
|
||||||
columns.push({
|
columns.push({
|
||||||
|
@ -27,11 +47,15 @@ export function parseColumnSpecifications(
|
||||||
borderLeft: currentBorderLeft,
|
borderLeft: currentBorderLeft,
|
||||||
borderRight: currentBorderRight,
|
borderRight: currentBorderRight,
|
||||||
content: currentContent,
|
content: currentContent,
|
||||||
|
cellSpacingLeft: currentCellSpacingLeft,
|
||||||
|
cellSpacingRight: currentCellSpacingRight,
|
||||||
})
|
})
|
||||||
currentAlignment = undefined
|
currentAlignment = undefined
|
||||||
currentBorderLeft = 0
|
currentBorderLeft = 0
|
||||||
currentBorderRight = 0
|
currentBorderRight = 0
|
||||||
currentContent = ''
|
currentContent = ''
|
||||||
|
currentCellSpacingLeft = ''
|
||||||
|
currentCellSpacingRight = ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (let i = 0; i < specification.length; i++) {
|
for (let i = 0; i < specification.length; i++) {
|
||||||
|
@ -65,12 +89,31 @@ export function parseColumnSpecifications(
|
||||||
currentAlignment = 'paragraph'
|
currentAlignment = 'paragraph'
|
||||||
currentContent += 'p'
|
currentContent += 'p'
|
||||||
// TODO: Parse these details
|
// TODO: Parse these details
|
||||||
while (i < specification.length && specification.charAt(i) !== '}') {
|
const argumentEnd = parseArgument(specification, i + 1)
|
||||||
i++
|
// Don't include the p twice
|
||||||
currentContent += specification.charAt(i)
|
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
|
break
|
||||||
}
|
}
|
||||||
|
case ' ':
|
||||||
|
case '\n':
|
||||||
|
case '\t':
|
||||||
|
currentContent += char
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maybeCommit()
|
maybeCommit()
|
||||||
|
@ -279,7 +322,6 @@ function parseTabularBody(
|
||||||
} else if (isHLine(currentChild)) {
|
} else if (isHLine(currentChild)) {
|
||||||
const lastCell = getLastCell()
|
const lastCell = getLastCell()
|
||||||
if (lastCell?.content.trim()) {
|
if (lastCell?.content.trim()) {
|
||||||
console.error(lastCell)
|
|
||||||
throw new Error('\\hline must be at the start of a row')
|
throw new Error('\\hline must be at the start of a row')
|
||||||
}
|
}
|
||||||
// push start of cell past the hline
|
// push start of cell past the hline
|
||||||
|
|
|
@ -22,6 +22,7 @@ export class TabularWidget extends WidgetType {
|
||||||
try {
|
try {
|
||||||
this.parseResult = generateTable(tabularNode, state)
|
this.parseResult = generateTable(tabularNode, state)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
console.error(e)
|
||||||
this.parseResult = null
|
this.parseResult = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -422,5 +422,28 @@ cell 3 & cell 4 \\\\
|
||||||
cy.get('.ol-cm-command-caption').should('not.exist')
|
cy.get('.ol-cm-command-caption').should('not.exist')
|
||||||
cy.get('.ol-cm-command-label').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])
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in a new issue