fix(composer): no LaTeX conversion inside a typed markdown fence or backtick span (#184 O3)
In markdown mode each paragraph line is serialised before parseBlockMD joins them, so $x$ inside a ``` fence became data-mx-maths markup inside the resulting <pre><code> (rendered as math in a code block). Track fence state across lines and skip math for fenced lines and for backtick code spans. Unit tests added; verified in the browser. Also: scripts/dev-homeserver.sh enables MSC4140 delayed events so scheduled messages work locally. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA
This commit is contained in:
@@ -43,6 +43,10 @@ rc_joins: { local: { per_second: 1000, burst_count: 10000 }, remote: { per_secon
|
||||
rc_presence: { per_user: { per_second: 1000, burst_count: 10000 } }
|
||||
max_upload_size: 50M
|
||||
suppress_key_server_warning: true
|
||||
# scheduled messages (MSC4140 delayed events)
|
||||
max_event_delay_duration: 24h
|
||||
experimental_features:
|
||||
msc4140_enabled: true
|
||||
"""
|
||||
open(p, "w").write(s)
|
||||
PY
|
||||
|
||||
@@ -65,3 +65,27 @@ test('no math conversion inside a code block', () => {
|
||||
test('a message with no math is unchanged', () => {
|
||||
assert.equal(toMatrixCustomHTML(txt('just hello'), OPTS), 'just hello');
|
||||
});
|
||||
|
||||
// Gitea #184 O3 — markdown mode: fences and backtick spans typed as plain
|
||||
// paragraphs are literal too (the block markdown parser only sees the fence
|
||||
// after the lines are joined, so math must be skipped while serialising them).
|
||||
const MD_OPTS = { ...OPTS, allowInlineMarkdown: true, allowBlockMarkdown: true };
|
||||
|
||||
test('markdown: no math conversion inside a typed ``` fence', () => {
|
||||
const paragraphs = [
|
||||
el(BlockType.Paragraph, [txt('```')]),
|
||||
el(BlockType.Paragraph, [txt('$x$ literal')]),
|
||||
el(BlockType.Paragraph, [txt('```')]),
|
||||
el(BlockType.Paragraph, [txt('after $y$')]),
|
||||
];
|
||||
const out = toMatrixCustomHTML(paragraphs, MD_OPTS);
|
||||
assert.ok(out.includes('<pre'), 'fence became a code block');
|
||||
assert.ok(out.includes('$x$ literal'), 'code block content stays literal');
|
||||
assert.ok(out.includes('data-mx-maths="y"'), 'math after the fence still converts');
|
||||
});
|
||||
|
||||
test('markdown: no math conversion inside a backtick span, math outside still converts', () => {
|
||||
const out = toMatrixCustomHTML(el(BlockType.Paragraph, [txt('use `$x$` and $y$')]), MD_OPTS);
|
||||
assert.ok(/<code[^>]*>\$x\$<\/code>/.test(out), 'backtick span stays literal');
|
||||
assert.ok(out.includes('data-mx-maths="y"'));
|
||||
});
|
||||
|
||||
@@ -36,6 +36,18 @@ const textToCustomHtml = (node: Text, opts: OutputOptions): string => {
|
||||
// applied inside inline code. Non-math text recurses with allowMath off so it
|
||||
// still gets the normal marks + inline-markdown treatment.
|
||||
if (opts.allowMath && !node.code) {
|
||||
// Markdown inline code spans (`…`) are literal too: apply math only to the
|
||||
// text between them (Gitea #184 O3 — `$x$` inside backticks stayed math).
|
||||
if (opts.allowInlineMarkdown && /`[^`]*`/.test(node.text)) {
|
||||
return node.text
|
||||
.split(/(`+[^`]*`+)/)
|
||||
.map((part) =>
|
||||
part.startsWith('`')
|
||||
? textToCustomHtml({ ...node, text: part }, { ...opts, allowMath: false })
|
||||
: textToCustomHtml({ ...node, text: part }, opts),
|
||||
)
|
||||
.join('');
|
||||
}
|
||||
const segments = splitMathSegments(node.text);
|
||||
if (segments.some((seg) => seg.type !== 'text')) {
|
||||
return segments
|
||||
@@ -128,12 +140,20 @@ export const toMatrixCustomHTML = (
|
||||
opts: OutputOptions,
|
||||
): string => {
|
||||
let markdownLines = '';
|
||||
// Inside a markdown ``` fence every line is literal: no `$…$` math conversion
|
||||
// (the fence is only recognised by parseBlockMD after the lines are joined,
|
||||
// which is too late — Gitea #184 O3).
|
||||
let inFence = false;
|
||||
const parseNode = (n: Descendant, index: number, targetNodes: Descendant[]) => {
|
||||
if (opts.allowBlockMarkdown && 'type' in n && n.type === BlockType.Paragraph) {
|
||||
const isFenceLine = /^\s*```/.test(toPlainText(n, false));
|
||||
const literal = inFence || isFenceLine;
|
||||
if (isFenceLine) inFence = !inFence;
|
||||
const line = toMatrixCustomHTML(n, {
|
||||
...opts,
|
||||
allowInlineMarkdown: false,
|
||||
allowBlockMarkdown: false,
|
||||
allowMath: opts.allowMath && !literal,
|
||||
})
|
||||
.replace(/<br\/>$/, '\n')
|
||||
.replace(/^(\\*)>/, '$1>');
|
||||
|
||||
Reference in New Issue
Block a user