From ea4190121192f03564f3c9c534bd3d602bad43e2 Mon Sep 17 00:00:00 2001 From: Ivan Enderlin Date: Thu, 24 Aug 2023 11:12:37 +0200 Subject: [PATCH] events: Transform Markdown soft line breaks into hard line breaks This patch transforms Markdown soft line breaks into hard line breaks when rendering to HTML. The [CommonMark specification about soft line breaks](https://spec.commonmark.org/0.30/#soft-line-breaks) specifies: > A renderer may also provide an option to render soft line breaks as > hard line breaks. Refering to https://github.com/vector-im/element-x-ios/issues/1418, some people are expecting to get soft line breaks rendered at hard ones. This patch updates the Markdown test to include this conversion of soft to hard line breaks. It includes a list and a code block, to ensure not _all_ soft breaks are transformed into hard breaks; only the ones we expect. --- crates/ruma-common/CHANGELOG.md | 1 + crates/ruma-common/src/events/room/message.rs | 9 ++- .../ruma-common/tests/events/room_message.rs | 64 +++++++++++++++---- 3 files changed, 60 insertions(+), 14 deletions(-) diff --git a/crates/ruma-common/CHANGELOG.md b/crates/ruma-common/CHANGELOG.md index d4f47ec8..cf75ec83 100644 --- a/crates/ruma-common/CHANGELOG.md +++ b/crates/ruma-common/CHANGELOG.md @@ -40,6 +40,7 @@ Breaking changes: - `RedactedRoomMemberEventContent` - `RoomMessageEventContent::make_reply_to()` and `make_for_thread()` have an extra parameter to support the recommended behavior for intentional mentions in replies according to Matrix 1.7 +- In Markdown, soft line breaks are transformed into hard line breaks when compiled into HTML. Improvements: diff --git a/crates/ruma-common/src/events/room/message.rs b/crates/ruma-common/src/events/room/message.rs index 3cccb61a..bed1172e 100644 --- a/crates/ruma-common/src/events/room/message.rs +++ b/crates/ruma-common/src/events/room/message.rs @@ -942,10 +942,15 @@ pub(crate) fn parse_markdown(text: &str) -> Option { let mut found_first_paragraph = false; - let parser_events: Vec<_> = Parser::new_ext(text, OPTIONS).collect(); + let parser_events: Vec<_> = Parser::new_ext(text, OPTIONS) + .map(|event| match event { + Event::SoftBreak => Event::HardBreak, + _ => event, + }) + .collect(); let has_markdown = parser_events.iter().any(|ref event| { let is_text = matches!(event, Event::Text(_)); - let is_break = matches!(event, Event::SoftBreak | Event::HardBreak); + let is_break = matches!(event, Event::HardBreak); let is_first_paragraph_start = if matches!(event, Event::Start(Tag::Paragraph)) { if found_first_paragraph { false diff --git a/crates/ruma-common/tests/events/room_message.rs b/crates/ruma-common/tests/events/room_message.rs index 6c732ed6..8e867e91 100644 --- a/crates/ruma-common/tests/events/room_message.rs +++ b/crates/ruma-common/tests/events/room_message.rs @@ -108,42 +108,82 @@ fn text_msgtype_plain_text_serialization() { fn text_msgtype_markdown_serialization() { use ruma_common::events::room::message::TextMessageEventContent; - let formatted_message = RoomMessageEventContent::new(MessageType::Text( - TextMessageEventContent::markdown("Testing **bold** and _italic_!"), - )); + let text = "Testing **bold** and _italic_!"; + let formatted_message = + RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text))); assert_eq!( to_json_value(&formatted_message).unwrap(), json!({ - "body": "Testing **bold** and _italic_!", + "body": text, "formatted_body": "

Testing bold and italic!

\n", "format": "org.matrix.custom.html", "msgtype": "m.text" }) ); - let plain_message_simple = RoomMessageEventContent::new(MessageType::Text( - TextMessageEventContent::markdown("Testing a simple phrase…"), - )); + let text = "Testing a simple phrase…"; + let plain_message_simple = + RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text))); assert_eq!( to_json_value(&plain_message_simple).unwrap(), json!({ - "body": "Testing a simple phrase…", + "body": text, "msgtype": "m.text" }) ); - let plain_message_paragraphs = RoomMessageEventContent::new(MessageType::Text( - TextMessageEventContent::markdown("Testing\n\nSeveral\n\nParagraphs."), - )); + let text = "Testing\n\nSeveral\n\nParagraphs."; + let plain_message_paragraphs = + RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text))); assert_eq!( to_json_value(&plain_message_paragraphs).unwrap(), json!({ - "body": "Testing\n\nSeveral\n\nParagraphs.", + "body": text, "formatted_body": "

Testing

\n

Several

\n

Paragraphs.

\n", "format": "org.matrix.custom.html", "msgtype": "m.text" }) ); + + let text = r#"Testing + +A paragraph +with +a soft line break + +* item 1 +* item 2 + item 2 (cont'd) +* item 3 + +``` +line 1 +line 2 +```"#; + let plain_message_paragraphs = + RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text))); + assert_eq!( + to_json_value(&plain_message_paragraphs).unwrap(), + json!({ + "body": text, + "formatted_body": r#"

Testing

+

A paragraph
+with
+a soft line break

+ +
line 1
+line 2
+
+"#, + "format": "org.matrix.custom.html", + "msgtype": "m.text" + }) + ); } #[test]