From b7da5c804b03964fde684d60b235ec21d235a372 Mon Sep 17 00:00:00 2001 From: Renan Date: Sun, 28 Nov 2021 02:03:56 -0300 Subject: [PATCH 1/3] Fix textual message stripping new line Signed-off-by: Renan --- src/HtmlUtils.tsx | 48 +++++++++++++++++++++++++---------------------- 1 file changed, 26 insertions(+), 22 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index 7087386128d..e8722a68224 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -36,6 +36,7 @@ import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks" import { getEmojiFromUnicode } from "./emoji"; import ReplyChain from "./components/views/elements/ReplyChain"; import { mediaFromMxc } from "./customisations/Media"; +import Markdown from './Markdown'; linkifyMatrix(linkify); @@ -422,6 +423,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts let strippedBody: string; let safeBody: string; let isDisplayedWithHtml: boolean; + let hasHtmlWithFormattedBody = false; // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted @@ -445,6 +447,8 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts let formattedBody = typeof content.formatted_body === 'string' ? content.formatted_body : null; const plainBody = typeof content.body === 'string' ? content.body : ""; + const isPlainTextBody = new Markdown(plainBody).isPlainText(); + hasHtmlWithFormattedBody = !isPlainTextBody && isHtmlMessage; if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyChain.stripHTMLReply(formattedBody); strippedBody = opts.stripReplyFallback ? ReplyChain.stripPlainReply(plainBody) : plainBody; @@ -455,28 +459,28 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts if (isHtmlMessage) { isDisplayedWithHtml = true; safeBody = sanitizeHtml(formattedBody, sanitizeParams); + } - if (SettingsStore.getValue("feature_latex_maths")) { - const phtml = cheerio.load(safeBody, { - // @ts-ignore: The `_useHtmlParser2` internal option is the - // simplest way to both parse and render using `htmlparser2`. - _useHtmlParser2: true, - decodeEntities: false, - }); - // @ts-ignore - The types for `replaceWith` wrongly expect - // Cheerio instance to be returned. - phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { - return katex.renderToString( - AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), - { - throwOnError: false, - // @ts-ignore - `e` can be an Element, not just a Node - displayMode: e.name == 'div', - output: "htmlAndMathml", - }); - }); - safeBody = phtml.html(); - } + if (hasHtmlWithFormattedBody && SettingsStore.getValue("feature_latex_maths")) { + const phtml = cheerio.load(safeBody, { + // @ts-ignore: The `_useHtmlParser2` internal option is the + // simplest way to both parse and render using `htmlparser2`. + _useHtmlParser2: true, + decodeEntities: false, + }); + // @ts-ignore - The types for `replaceWith` wrongly expect + // Cheerio instance to be returned. + phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { + return katex.renderToString( + AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), + { + throwOnError: false, + // @ts-ignore - `e` can be an Element, not just a Node + displayMode: e.name == 'div', + output: "htmlAndMathml", + }); + }); + safeBody = phtml.html(); } } finally { delete sanitizeParams.textFilter; @@ -516,7 +520,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts const className = classNames({ 'mx_EventTile_body': true, 'mx_EventTile_bigEmoji': emojiBody, - 'markdown-body': isHtmlMessage && !emojiBody, + 'markdown-body': hasHtmlWithFormattedBody && !emojiBody, }); return isDisplayedWithHtml ? From 52c7ad1942345a989a0270d4e9923887f968c13e Mon Sep 17 00:00:00 2001 From: Renan Date: Wed, 1 Dec 2021 14:58:17 -0300 Subject: [PATCH 2/3] Add test for plain text formatted body Signed-off-by: Renan --- .../views/messages/TextualBody-test.js | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test/components/views/messages/TextualBody-test.js b/test/components/views/messages/TextualBody-test.js index 8387b274582..9a83a63efe5 100644 --- a/test/components/views/messages/TextualBody-test.js +++ b/test/components/views/messages/TextualBody-test.js @@ -282,6 +282,30 @@ describe("", () => { '!ZxbRYPQXDXKGmDnJNg:example.com with vias', ); }); + + it('renders formatted body without html corretly', () => { + const ev = mkEvent({ + type: "m.room.message", + room: "room_id", + user: "sender", + content: { + body: "escaped \\*markdown\\*", + msgtype: "m.text", + format: "org.matrix.custom.html", + formatted_body: "escaped *markdown*", + }, + event: true, + }); + + const wrapper = mount(); + + const content = wrapper.find(".mx_EventTile_body"); + expect(content.html()).toBe( + '' + + 'escaped *markdown*' + + '', + ); + }); }); it("renders url previews correctly", () => { From b1fd918f9b9d2af9a5310e00167f4a04cddd2958 Mon Sep 17 00:00:00 2001 From: Renan Date: Wed, 1 Dec 2021 15:27:00 -0300 Subject: [PATCH 3/3] Change when markdown-body is added Signed-off-by: Renan --- src/HtmlUtils.tsx | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/HtmlUtils.tsx b/src/HtmlUtils.tsx index e8722a68224..2f085fe5edf 100644 --- a/src/HtmlUtils.tsx +++ b/src/HtmlUtils.tsx @@ -36,7 +36,6 @@ import { tryTransformPermalinkToLocalHref } from "./utils/permalinks/Permalinks" import { getEmojiFromUnicode } from "./emoji"; import ReplyChain from "./components/views/elements/ReplyChain"; import { mediaFromMxc } from "./customisations/Media"; -import Markdown from './Markdown'; linkifyMatrix(linkify); @@ -412,8 +411,9 @@ export interface IOptsReturnString extends IOpts { export function bodyToHtml(content: IContent, highlights: string[], opts: IOptsReturnString): string; export function bodyToHtml(content: IContent, highlights: string[], opts: IOptsReturnNode): ReactNode; export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts = {}) { - const isHtmlMessage = content.format === "org.matrix.custom.html" && content.formatted_body; + const isFormattedBody = content.format === "org.matrix.custom.html" && content.formatted_body; let bodyHasEmoji = false; + let isHtmlMessage = false; let sanitizeParams = sanitizeHtmlParams; if (opts.forComposerQuote) { @@ -423,7 +423,6 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts let strippedBody: string; let safeBody: string; let isDisplayedWithHtml: boolean; - let hasHtmlWithFormattedBody = false; // XXX: We sanitize the HTML whilst also highlighting its text nodes, to avoid accidentally trying // to highlight HTML tags themselves. However, this does mean that we don't highlight textnodes which // are interrupted by HTML tags (not that we did before) - e.g. foobar won't get highlighted @@ -447,40 +446,41 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts let formattedBody = typeof content.formatted_body === 'string' ? content.formatted_body : null; const plainBody = typeof content.body === 'string' ? content.body : ""; - const isPlainTextBody = new Markdown(plainBody).isPlainText(); - hasHtmlWithFormattedBody = !isPlainTextBody && isHtmlMessage; if (opts.stripReplyFallback && formattedBody) formattedBody = ReplyChain.stripHTMLReply(formattedBody); strippedBody = opts.stripReplyFallback ? ReplyChain.stripPlainReply(plainBody) : plainBody; - bodyHasEmoji = mightContainEmoji(isHtmlMessage ? formattedBody : plainBody); + bodyHasEmoji = mightContainEmoji(isFormattedBody ? formattedBody : plainBody); // Only generate safeBody if the message was sent as org.matrix.custom.html - if (isHtmlMessage) { + if (isFormattedBody) { isDisplayedWithHtml = true; - safeBody = sanitizeHtml(formattedBody, sanitizeParams); - } - if (hasHtmlWithFormattedBody && SettingsStore.getValue("feature_latex_maths")) { + safeBody = sanitizeHtml(formattedBody, sanitizeParams); const phtml = cheerio.load(safeBody, { // @ts-ignore: The `_useHtmlParser2` internal option is the // simplest way to both parse and render using `htmlparser2`. _useHtmlParser2: true, decodeEntities: false, }); - // @ts-ignore - The types for `replaceWith` wrongly expect - // Cheerio instance to be returned. - phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { - return katex.renderToString( - AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), - { - throwOnError: false, - // @ts-ignore - `e` can be an Element, not just a Node - displayMode: e.name == 'div', - output: "htmlAndMathml", - }); - }); - safeBody = phtml.html(); + const isPlainText = phtml.html() === phtml.root().text(); + isHtmlMessage = isFormattedBody && !isPlainText; + + if (isHtmlMessage && SettingsStore.getValue("feature_latex_maths")) { + // @ts-ignore - The types for `replaceWith` wrongly expect + // Cheerio instance to be returned. + phtml('div, span[data-mx-maths!=""]').replaceWith(function(i, e) { + return katex.renderToString( + AllHtmlEntities.decode(phtml(e).attr('data-mx-maths')), + { + throwOnError: false, + // @ts-ignore - `e` can be an Element, not just a Node + displayMode: e.name == 'div', + output: "htmlAndMathml", + }); + }); + safeBody = phtml.html(); + } } } finally { delete sanitizeParams.textFilter; @@ -520,7 +520,7 @@ export function bodyToHtml(content: IContent, highlights: string[], opts: IOpts const className = classNames({ 'mx_EventTile_body': true, 'mx_EventTile_bigEmoji': emojiBody, - 'markdown-body': hasHtmlWithFormattedBody && !emojiBody, + 'markdown-body': isHtmlMessage && !emojiBody, }); return isDisplayedWithHtml ?