From 3e399d3410cba293cc188b36e1400afc24074985 Mon Sep 17 00:00:00 2001 From: 7w1 Date: Sat, 9 May 2026 22:16:36 -0500 Subject: [PATCH] shield matrix to links --- .changeset/fix-matrix-to-link-wrapping.md | 5 +++ src/app/plugins/markdown/markdownToHtml.ts | 45 +++++++++++++++++++++- 2 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 .changeset/fix-matrix-to-link-wrapping.md diff --git a/.changeset/fix-matrix-to-link-wrapping.md b/.changeset/fix-matrix-to-link-wrapping.md new file mode 100644 index 000000000..ef186b922 --- /dev/null +++ b/.changeset/fix-matrix-to-link-wrapping.md @@ -0,0 +1,5 @@ +--- +default: patch +--- + +Matrix.to links sent without explicit markdown formatting are sent as raw links instead of html links. diff --git a/src/app/plugins/markdown/markdownToHtml.ts b/src/app/plugins/markdown/markdownToHtml.ts index c1fe0bf07..c58c0a22e 100644 --- a/src/app/plugins/markdown/markdownToHtml.ts +++ b/src/app/plugins/markdown/markdownToHtml.ts @@ -50,6 +50,39 @@ const decodeHtmlEntities = (text: string): string => { return result; }; +const MATRIX_TO_PLACEHOLDER_PREFIX = 'MATRIXTORAWLINKTOKEN'; + +const escapeHtml = (text: string): string => + text + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + +const shieldBareMatrixToLinks = ( + input: string +): { shielded: string; placeholders: Map } => { + const placeholders = new Map(); + let index = 0; + + const shielded = input.replace(/(? { + const key = `${MATRIX_TO_PLACEHOLDER_PREFIX}${index++}X`; + placeholders.set(key, url); + return key; + }); + + return { shielded, placeholders }; +}; + +const unshieldBareMatrixToLinks = (html: string, placeholders: Map): string => { + let result = html; + for (const [key, url] of placeholders.entries()) { + result = result.split(key).join(escapeHtml(url)); + } + return result; +}; + /** * Converts markdown string to sanitized Matrix-compatible HTML. * Uses marked for parsing and DOMPurify for sanitization per Matrix spec. @@ -67,7 +100,10 @@ export function markdownToHtml(markdown: string): string { const preprocessed = preprocessEmoticon(blockquotePrefixed); - const mathInput = shieldDollarRunsForMarked(maskDollarSignsInsideMarkdownCode(preprocessed)); + const { shielded: matrixToShielded, placeholders: matrixToPlaceholders } = + shieldBareMatrixToLinks(preprocessed); + + const mathInput = shieldDollarRunsForMarked(maskDollarSignsInsideMarkdownCode(matrixToShielded)); // Parse markdown to HTML using marked with our Matrix extensions const html = processor.parse(mathInput) as string; @@ -164,5 +200,10 @@ export function markdownToHtml(markdown: string): string { } ); - return restoredMxEmoticonHeight.replace(/
  • (

    <\/p>)?<\/li>/gi, '


  • '); + const unshieldedMatrixTo = unshieldBareMatrixToLinks( + restoredMxEmoticonHeight, + matrixToPlaceholders + ); + + return unshieldedMatrixTo.replace(/
  • (

    <\/p>)?<\/li>/gi, '


  • '); }