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.
This commit is contained in:
Ivan Enderlin 2023-08-24 11:12:37 +02:00 committed by GitHub
parent 73b6113819
commit ea41901211
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 60 additions and 14 deletions

View File

@ -40,6 +40,7 @@ Breaking changes:
- `RedactedRoomMemberEventContent` - `RedactedRoomMemberEventContent`
- `RoomMessageEventContent::make_reply_to()` and `make_for_thread()` have an extra parameter to - `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 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: Improvements:

View File

@ -942,10 +942,15 @@ pub(crate) fn parse_markdown(text: &str) -> Option<String> {
let mut found_first_paragraph = false; 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 has_markdown = parser_events.iter().any(|ref event| {
let is_text = matches!(event, Event::Text(_)); 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)) { let is_first_paragraph_start = if matches!(event, Event::Start(Tag::Paragraph)) {
if found_first_paragraph { if found_first_paragraph {
false false

View File

@ -108,42 +108,82 @@ fn text_msgtype_plain_text_serialization() {
fn text_msgtype_markdown_serialization() { fn text_msgtype_markdown_serialization() {
use ruma_common::events::room::message::TextMessageEventContent; use ruma_common::events::room::message::TextMessageEventContent;
let formatted_message = RoomMessageEventContent::new(MessageType::Text( let text = "Testing **bold** and _italic_!";
TextMessageEventContent::markdown("Testing **bold** and _italic_!"), let formatted_message =
)); RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!( assert_eq!(
to_json_value(&formatted_message).unwrap(), to_json_value(&formatted_message).unwrap(),
json!({ json!({
"body": "Testing **bold** and _italic_!", "body": text,
"formatted_body": "<p>Testing <strong>bold</strong> and <em>italic</em>!</p>\n", "formatted_body": "<p>Testing <strong>bold</strong> and <em>italic</em>!</p>\n",
"format": "org.matrix.custom.html", "format": "org.matrix.custom.html",
"msgtype": "m.text" "msgtype": "m.text"
}) })
); );
let plain_message_simple = RoomMessageEventContent::new(MessageType::Text( let text = "Testing a simple phrase…";
TextMessageEventContent::markdown("Testing a simple phrase…"), let plain_message_simple =
)); RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!( assert_eq!(
to_json_value(&plain_message_simple).unwrap(), to_json_value(&plain_message_simple).unwrap(),
json!({ json!({
"body": "Testing a simple phrase…", "body": text,
"msgtype": "m.text" "msgtype": "m.text"
}) })
); );
let plain_message_paragraphs = RoomMessageEventContent::new(MessageType::Text( let text = "Testing\n\nSeveral\n\nParagraphs.";
TextMessageEventContent::markdown("Testing\n\nSeveral\n\nParagraphs."), let plain_message_paragraphs =
)); RoomMessageEventContent::new(MessageType::Text(TextMessageEventContent::markdown(text)));
assert_eq!( assert_eq!(
to_json_value(&plain_message_paragraphs).unwrap(), to_json_value(&plain_message_paragraphs).unwrap(),
json!({ json!({
"body": "Testing\n\nSeveral\n\nParagraphs.", "body": text,
"formatted_body": "<p>Testing</p>\n<p>Several</p>\n<p>Paragraphs.</p>\n", "formatted_body": "<p>Testing</p>\n<p>Several</p>\n<p>Paragraphs.</p>\n",
"format": "org.matrix.custom.html", "format": "org.matrix.custom.html",
"msgtype": "m.text" "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#"<p>Testing</p>
<p>A paragraph<br />
with<br />
a soft line break</p>
<ul>
<li>item 1</li>
<li>item 2<br />
item 2 (cont'd)</li>
<li>item 3</li>
</ul>
<pre><code>line 1
line 2
</code></pre>
"#,
"format": "org.matrix.custom.html",
"msgtype": "m.text"
})
);
} }
#[test] #[test]