diff --git a/scripts/dev-homeserver.sh b/scripts/dev-homeserver.sh index 31903b319..ed9cbf80b 100755 --- a/scripts/dev-homeserver.sh +++ b/scripts/dev-homeserver.sh @@ -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 diff --git a/src/app/components/editor/output.test.ts b/src/app/components/editor/output.test.ts index 0b1138244..3f9c2ca87 100644 --- a/src/app/components/editor/output.test.ts +++ b/src/app/components/editor/output.test.ts @@ -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('
{
+ const out = toMatrixCustomHTML(el(BlockType.Paragraph, [txt('use `$x$` and $y$')]), MD_OPTS);
+ assert.ok(/]*>\$x\$<\/code>/.test(out), 'backtick span stays literal');
+ assert.ok(out.includes('data-mx-maths="y"'));
+});
diff --git a/src/app/components/editor/output.ts b/src/app/components/editor/output.ts
index 1b98bbd58..07ba8c514 100644
--- a/src/app/components/editor/output.ts
+++ b/src/app/components/editor/output.ts
@@ -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(/
$/, '\n')
.replace(/^(\\*)>/, '$1>');