fix: preserve table math markdown
This commit is contained in:
@@ -95,6 +95,58 @@ describe('math markdown round-trip', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('round-trips inline math inside table cells', () => {
|
||||
const tableCellMath = preProcessMathMarkdown({ markdown: '$a+b$' })
|
||||
const blocks = [{
|
||||
type: 'table',
|
||||
content: {
|
||||
type: 'tableContent',
|
||||
rows: [{
|
||||
cells: [{
|
||||
type: 'tableCell',
|
||||
content: [{ type: 'text', text: tableCellMath, styles: {} }],
|
||||
}],
|
||||
}],
|
||||
},
|
||||
children: [],
|
||||
}]
|
||||
|
||||
const [tableBlock] = injectMathInBlocks(blocks) as Array<{
|
||||
content: { rows: Array<{ cells: Array<{ content: unknown[] }> }> }
|
||||
}>
|
||||
|
||||
expect(tableBlock.content.rows[0].cells[0].content).toEqual([{
|
||||
type: MATH_INLINE_TYPE,
|
||||
props: { latex: 'a+b' },
|
||||
content: undefined,
|
||||
}])
|
||||
|
||||
const editor = {
|
||||
blocksToMarkdownLossy: vi.fn(() => '| Formula |\n| --- |\n| $a+b$ |'),
|
||||
}
|
||||
|
||||
expect(serializeMathAwareBlocks(editor, [tableBlock])).toBe('| Formula |\n| --- |\n| $a+b$ |')
|
||||
expect(editor.blocksToMarkdownLossy).toHaveBeenCalledWith([{
|
||||
type: 'table',
|
||||
content: {
|
||||
type: 'tableContent',
|
||||
rows: [{
|
||||
cells: [{
|
||||
type: 'tableCell',
|
||||
content: [{ type: 'text', text: '$a+b$' }],
|
||||
}],
|
||||
}],
|
||||
},
|
||||
children: [],
|
||||
}])
|
||||
})
|
||||
|
||||
it('leaves display-style math inside table cells as Markdown source', () => {
|
||||
expect(preProcessMathMarkdown({ markdown: '| Formula |\n| --- |\n| $$c$$ |' })).toBe(
|
||||
'| Formula |\n| --- |\n| $$c$$ |',
|
||||
)
|
||||
})
|
||||
|
||||
it('leaves inline code and fenced code math-looking text untouched', () => {
|
||||
const markdown = [
|
||||
'Keep `$not_math$` literal.',
|
||||
|
||||
@@ -19,12 +19,33 @@ interface InlineItem {
|
||||
|
||||
interface BlockLike {
|
||||
type?: string
|
||||
content?: InlineItem[]
|
||||
content?: BlockContent
|
||||
props?: Record<string, string>
|
||||
children?: BlockLike[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type BlockContent = InlineItem[] | TableContentLike | unknown
|
||||
|
||||
interface TableContentLike {
|
||||
type?: string
|
||||
rows?: TableRowLike[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface TableRowLike {
|
||||
cells?: TableCellValue[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
interface TableCellLike {
|
||||
content?: InlineItem[]
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
type TableCellValue = TableCellLike | string
|
||||
type InlineContentTransform = (content: InlineItem[]) => InlineItem[]
|
||||
|
||||
interface MarkdownSerializer {
|
||||
blocksToMarkdownLossy: (blocks: unknown[]) => string
|
||||
}
|
||||
@@ -111,7 +132,7 @@ function isCodeFence({ text: line }: { text: string }): boolean {
|
||||
}
|
||||
|
||||
function isSingleDollar({ text, index }: TextPosition): boolean {
|
||||
return text[index] === '$' && text[index + 1] !== '$'
|
||||
return text[index] === '$' && text[index - 1] !== '$' && text[index + 1] !== '$'
|
||||
}
|
||||
|
||||
function isInlineMathEnd(position: TextPosition): boolean {
|
||||
@@ -274,10 +295,47 @@ function restoreInlineMath(content: InlineItem[]): InlineItem[] {
|
||||
})
|
||||
}
|
||||
|
||||
function isTableContent(content: BlockContent): content is TableContentLike {
|
||||
return Boolean(
|
||||
content
|
||||
&& typeof content === 'object'
|
||||
&& !Array.isArray(content)
|
||||
&& (content as TableContentLike).type === 'tableContent'
|
||||
&& Array.isArray((content as TableContentLike).rows),
|
||||
)
|
||||
}
|
||||
|
||||
function transformTableCell(cell: TableCellValue, transform: InlineContentTransform): TableCellValue {
|
||||
if (typeof cell === 'string' || !Array.isArray(cell.content)) return cell
|
||||
return { ...cell, content: transform(cell.content) }
|
||||
}
|
||||
|
||||
function transformTableContent(
|
||||
content: TableContentLike,
|
||||
transform: InlineContentTransform,
|
||||
): TableContentLike {
|
||||
return {
|
||||
...content,
|
||||
rows: content.rows?.map((row) => ({
|
||||
...row,
|
||||
cells: row.cells?.map((cell) => transformTableCell(cell, transform)),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
function transformBlockContent(
|
||||
content: BlockContent,
|
||||
transform: InlineContentTransform,
|
||||
): BlockContent {
|
||||
if (Array.isArray(content)) return transform(content)
|
||||
if (isTableContent(content)) return transformTableContent(content, transform)
|
||||
return content
|
||||
}
|
||||
|
||||
function injectMathInBlock(block: BlockLike): BlockLike {
|
||||
const content = Array.isArray(block.content) ? expandInlineMath(block.content) : block.content
|
||||
const content = transformBlockContent(block.content, expandInlineMath)
|
||||
const children = Array.isArray(block.children) ? block.children.map(injectMathInBlock) : block.children
|
||||
const latex = readDisplayMathToken(content)
|
||||
const latex = Array.isArray(content) ? readDisplayMathToken(content) : null
|
||||
|
||||
if (latex !== null) {
|
||||
return buildMathBlock({ block, latex })
|
||||
@@ -303,7 +361,7 @@ function buildMathBlock({ block, latex }: { block: BlockLike } & LatexPayload):
|
||||
}
|
||||
|
||||
function restoreInlineMathInBlock(block: BlockLike): BlockLike {
|
||||
const content = Array.isArray(block.content) ? restoreInlineMath(block.content) : block.content
|
||||
const content = transformBlockContent(block.content, restoreInlineMath)
|
||||
const children = Array.isArray(block.children) ? block.children.map(restoreInlineMathInBlock) : block.children
|
||||
return { ...block, content, children }
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ let tempVaultDir: string
|
||||
const INLINE_LATEX = 'E=mc^2'
|
||||
const DISPLAY_LATEX = '\\int_0^1 x^2 \\, dx = \\frac{1}{3}'
|
||||
const MALFORMED_LATEX = '\\frac{'
|
||||
const TABLE_INLINE_LATEX = '\\frac{a}{b}+c'
|
||||
const TABLE_DISPLAY_STYLE_LATEX = '\\sum_{i=1}^{n} i'
|
||||
|
||||
test.beforeEach(async ({ page }, testInfo) => {
|
||||
testInfo.setTimeout(90_000)
|
||||
@@ -107,6 +109,11 @@ ${DISPLAY_LATEX}
|
||||
$$
|
||||
|
||||
Malformed math $${MALFORMED_LATEX}$ stays visible.
|
||||
|
||||
| Kind | Formula |
|
||||
| --- | --- |
|
||||
| inline | $${TABLE_INLINE_LATEX}$ |
|
||||
| display-style | $$${TABLE_DISPLAY_STYLE_LATEX}$$ |
|
||||
`
|
||||
|
||||
await setRawEditorContent(page, nextContent)
|
||||
@@ -117,15 +124,20 @@ Malformed math $${MALFORMED_LATEX}$ stays visible.
|
||||
await toggleRawMode(page, '.bn-editor')
|
||||
|
||||
await expectMathNode(page, '.math--inline', INLINE_LATEX)
|
||||
await expectMathNode(page, '.math--inline', TABLE_INLINE_LATEX)
|
||||
await expectMathNode(page, '.math--block', DISPLAY_LATEX)
|
||||
await expectMathNode(page, '.math--inline', MALFORMED_LATEX)
|
||||
await expect(page.locator('.math .katex, .math .katex-error')).toHaveCount(3)
|
||||
await expect(page.locator('table')).toHaveCount(1)
|
||||
await expect(page.locator('.math .katex, .math .katex-error')).toHaveCount(4)
|
||||
|
||||
await toggleRawMode(page, '.cm-content')
|
||||
const rawAfterRichMode = await getRawEditorContent(page)
|
||||
expect(rawAfterRichMode).toContain(`$${INLINE_LATEX}$`)
|
||||
expect(rawAfterRichMode).toContain(`$$\n${DISPLAY_LATEX}\n$$`)
|
||||
expect(rawAfterRichMode).toContain(`$${MALFORMED_LATEX}$`)
|
||||
expect(rawAfterRichMode).toContain(`$${TABLE_INLINE_LATEX}$`)
|
||||
expect(rawAfterRichMode).toContain(`$$${TABLE_DISPLAY_STYLE_LATEX}$$`)
|
||||
expect(rawAfterRichMode).not.toContain('@@TOLARIA_MATH')
|
||||
|
||||
await toggleRawMode(page, '.bn-editor')
|
||||
await openNote(page, 'Note C')
|
||||
@@ -136,4 +148,7 @@ Malformed math $${MALFORMED_LATEX}$ stays visible.
|
||||
expect(reopenedRaw).toContain(`$${INLINE_LATEX}$`)
|
||||
expect(reopenedRaw).toContain(`$$\n${DISPLAY_LATEX}\n$$`)
|
||||
expect(reopenedRaw).toContain(`$${MALFORMED_LATEX}$`)
|
||||
expect(reopenedRaw).toContain(`$${TABLE_INLINE_LATEX}$`)
|
||||
expect(reopenedRaw).toContain(`$$${TABLE_DISPLAY_STYLE_LATEX}$$`)
|
||||
expect(reopenedRaw).not.toContain('@@TOLARIA_MATH')
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user