diff --git a/apps/mobile/src/lib/markdownDocument.ts b/apps/mobile/src/lib/markdownDocument.ts index af8fe2d02a15f..d79003da234cd 100644 --- a/apps/mobile/src/lib/markdownDocument.ts +++ b/apps/mobile/src/lib/markdownDocument.ts @@ -168,9 +168,14 @@ function markdownHtmlTags(content: string): RegExpExecArray[] { codeRanges.push({ start: opening.index, end: closing.index + closing[0].length }); index = closingIndex; } - return tags.filter( - (tag) => !codeRanges.some((range) => tag.index >= range.start && tag.index < range.end), - ); + let codeRangeIndex = 0; + return tags.filter((tag) => { + while (codeRanges[codeRangeIndex] && tag.index >= codeRanges[codeRangeIndex]!.end) { + codeRangeIndex += 1; + } + const range = codeRanges[codeRangeIndex]; + return !range || tag.index < range.start; + }); } /** diff --git a/apps/mobile/src/lib/markdownHtml.test.ts b/apps/mobile/src/lib/markdownHtml.test.ts index e27cf812ed436..1a600064fc539 100644 --- a/apps/mobile/src/lib/markdownHtml.test.ts +++ b/apps/mobile/src/lib/markdownHtml.test.ts @@ -106,6 +106,28 @@ describe("HTML in native Markdown", () => { }); }); +it("keeps code literal across many ranges while converting HTML between and after them", () => { + const blocks = Array.from( + { length: 100 }, + (_, index) => `\`\` `, + ); + const expected = Array.from( + { length: 100 }, + (_, index) => `\`\` ![]()`, + ); + expect(markdownHtmlContent(blocks.join("\n") + "
after")).toBe( + expected.join("\n") + " \nafter", + ); +}); + +it("keeps tags after an unclosed fence literal even when later inline ranges overlap it", () => { + const content = + '
before
\n```html\n\n``\n'; + expect(markdownHtmlContent(content)).toBe( + '\n\nbefore\n\n\n```html\n\n``\n', + ); +}); + describe("file embeds in native thread and file Markdown", () => { const input = { environmentId: EnvironmentId.make("environment-1"),