diff --git a/packages/client/ui-primitives/src/markdown/remarkMathCompatibility.ts b/packages/client/ui-primitives/src/markdown/remarkMathCompatibility.ts
index c9dfc9a5d3..dcd8c32362 100644
--- a/packages/client/ui-primitives/src/markdown/remarkMathCompatibility.ts
+++ b/packages/client/ui-primitives/src/markdown/remarkMathCompatibility.ts
@@ -1,3 +1,5 @@
+/** Extend upstream dollar-only math syntax with TeX delimiters while reusing its token vocabulary. */
+
import { factorySpace } from 'micromark-factory-space'
import type {} from 'micromark-extension-math'
import { markdownLineEnding } from 'micromark-util-character'
@@ -11,8 +13,11 @@ interface RemarkProcessor {
}
const previousBackslash: Previous = function (code) {
- /* v8 ignore next -- micromark calls previous after an event has been emitted. */
- return code !== codes.backslash || this.events.at(-1)?.[1].type === types.characterEscape
+ if (code !== codes.backslash) return true
+ const tail = this.events.at(-1)
+ /* v8 ignore next -- a previous code necessarily has a preceding event. */
+ if (tail === undefined) return false
+ return tail[1].type === types.characterEscape
}
const tokenizeBackslashMathText: Tokenizer = function (effects, ok, nok) {
diff --git a/packages/client/ui-primitives/tests/markdown.spec.tsx b/packages/client/ui-primitives/tests/markdown.spec.tsx
index 057e807b1d..7a858c1199 100644
--- a/packages/client/ui-primitives/tests/markdown.spec.tsx
+++ b/packages/client/ui-primitives/tests/markdown.spec.tsx
@@ -203,6 +203,12 @@ describe('MarkdownText', () => {
math: 1,
display: 0,
},
+ {
+ source: String.raw`\\\(x\)`,
+ math: 1,
+ display: 0,
+ value: 'x',
+ },
{
source: '\\(\\frac{1}{5}\n+\\frac{1}{7}\\)',
math: 1,
@@ -238,31 +244,51 @@ describe('MarkdownText', () => {
rendered.unmount()
}
- const literal = render()
- expect(literal.container.querySelectorAll('.katex')).toHaveLength(1)
+ const literal = render()
+ expect(literal.container.querySelectorAll('.katex')).toHaveLength(0)
expect(literal.container.querySelector('.katex-display')).toBeNull()
expect(literal.container.textContent).toContain('[x')
- expect(literal.container.textContent).toContain('xxx trailing')
})
it('keeps ordinary dollar blocks and incomplete delimiter candidates parseable', () => {
- const sources = [
- '$$\n\\theta\n$$',
- '$$$\\theta$$$',
- '$$a$b\nc',
- ' \\[\n \\theta\n \\]',
- '\\(\\theta',
- '\\[\n\\[',
- '> \\[\nnot a quoted continuation\n\\]',
+ const cases = [
+ { source: '$$\n\\theta\n$$', math: 1, display: 1 },
+ { source: '$$$\\theta$$$', math: 1, display: 0 },
+ { source: '$$a$b\nc', math: 0, display: 0 },
+ { source: ' \\[\n \\theta\n \\]', math: 1, display: 1 },
+ { source: '\\(\\theta', math: 0, display: 0 },
+ { source: String.raw`\(a\\)`, math: 0, display: 0 },
+ { source: '\\[\n\\[', math: 0, display: 0 },
+ { source: '> \\[\nnot a quoted continuation\n\\]', math: 0, display: 0 },
]
- for (const source of sources) {
- const rendered = render()
+ for (const item of cases) {
+ const rendered = render()
+ expect(rendered.container.querySelectorAll('.katex')).toHaveLength(item.math)
+ expect(rendered.container.querySelectorAll('.katex-display')).toHaveLength(item.display)
expect(rendered.container.querySelector('.katex-error')).toBeNull()
rendered.unmount()
}
})
+ it('lets display math interrupt an open paragraph', () => {
+ for (const source of ['Prose line\n\\[x\\]', 'Prose line\n$$x$$']) {
+ const rendered = render()
+ expect(rendered.container.querySelectorAll('p')).toHaveLength(1)
+ expect(rendered.container.querySelectorAll('.katex-display')).toHaveLength(1)
+ rendered.unmount()
+ }
+ })
+
+ it('leaves a dollar block with trailing text to upstream inline math', () => {
+ const { container } = render()
+
+ expect(container.querySelectorAll('.katex')).toHaveLength(1)
+ expect(container.querySelector('.katex-display')).toBeNull()
+ expect(container.querySelector('annotation')?.textContent).toBe('x')
+ expect(container.textContent).toContain('trailing')
+ })
+
it('renders escaped dollars and even backslash pairs before closing fences', () => {
const source = [
String.raw`$$100\$$$`,