From 6e75bdd662f43d27909dc61d90b3804277dacaf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Wed, 13 Nov 2024 16:12:31 +0100 Subject: [PATCH 1/2] feat(ffi): generate formatted captions for `send_*` media fns Changelog: For `Timeline::send_*` fns, treat the passed `caption` parameter as markdown and use the HTML generated from it as the `formatted_caption` if there is none. --- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 28 ++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index 3b1ace9d3d2..4162af56582 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -48,8 +48,8 @@ use ruma::{ }, receipt::ReceiptThread, room::message::{ - ForwardThread, LocationMessageEventContent, MessageType, - RoomMessageEventContentWithoutRelation, + FormattedBody as RumaFormattedBody, ForwardThread, LocationMessageEventContent, + MessageType, RoomMessageEventContentWithoutRelation, }, AnyMessageLikeEventContent, }, @@ -289,6 +289,7 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { + let formatted_caption = formatted_caption_from(&caption, &formatted_caption); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_image_info = BaseImageInfo::try_from(&image_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -297,7 +298,7 @@ impl Timeline { let attachment_config = build_thumbnail_info(thumbnail_url, image_info.thumbnail_info)? .info(attachment_info) .caption(caption) - .formatted_caption(formatted_caption.map(Into::into)); + .formatted_caption(formatted_caption); self.send_attachment( url, @@ -321,6 +322,7 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { + let formatted_caption = formatted_caption_from(&caption, &formatted_caption); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_video_info: BaseVideoInfo = BaseVideoInfo::try_from(&video_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -351,6 +353,7 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { + let formatted_caption = formatted_caption_from(&caption, &formatted_caption); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_audio_info: BaseAudioInfo = BaseAudioInfo::try_from(&audio_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -383,6 +386,7 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { + let formatted_caption = formatted_caption_from(&caption, &formatted_caption); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_audio_info: BaseAudioInfo = BaseAudioInfo::try_from(&audio_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -710,6 +714,24 @@ impl Timeline { } } +/// Given a pair of optional `caption` and `formatted_caption` parameters, +/// return a formatted caption: +/// +/// - If a `formatted_caption` exists, return it. +/// - If it doesn't exist but there is a `caption`, parse it as markdown and +/// return the result. +/// - Return `None` if there are no `caption` or `formatted_caption` parameters. +fn formatted_caption_from( + caption: &Option, + formatted_caption: &Option, +) -> Option { + match (&caption, formatted_caption) { + (None, None) => None, + (Some(body), None) => RumaFormattedBody::markdown(body), + (_, Some(formatted_body)) => Some(formatted_body.clone().into()), + } +} + /// A handle to perform actions onto a local echo. #[derive(uniffi::Object)] pub struct SendHandle { From 5d77f324063c38abe7b1b869138b6e3fd9993949 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jorge=20Mart=C3=ADn?= Date: Thu, 14 Nov 2024 09:38:56 +0100 Subject: [PATCH 2/2] refactor(sdk): move `formatted_caption_from` to the SDK, rename it Add the `markdown` feature to the SDK crate, otherwise we can't use `FormattedBody::markdown`. Refactor the pattern matching into an if, add tests to check its behaviour. --- bindings/matrix-sdk-ffi/src/timeline/mod.rs | 35 ++++------ crates/matrix-sdk/src/utils.rs | 72 +++++++++++++++++++++ 2 files changed, 83 insertions(+), 24 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/timeline/mod.rs b/bindings/matrix-sdk-ffi/src/timeline/mod.rs index 4162af56582..99b9032d499 100644 --- a/bindings/matrix-sdk-ffi/src/timeline/mod.rs +++ b/bindings/matrix-sdk-ffi/src/timeline/mod.rs @@ -48,8 +48,8 @@ use ruma::{ }, receipt::ReceiptThread, room::message::{ - FormattedBody as RumaFormattedBody, ForwardThread, LocationMessageEventContent, - MessageType, RoomMessageEventContentWithoutRelation, + ForwardThread, LocationMessageEventContent, MessageType, + RoomMessageEventContentWithoutRelation, }, AnyMessageLikeEventContent, }, @@ -81,6 +81,7 @@ use crate::{ mod content; pub use content::MessageContent; +use matrix_sdk::utils::formatted_body_from; use crate::error::QueueWedgeError; @@ -289,7 +290,8 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { - let formatted_caption = formatted_caption_from(&caption, &formatted_caption); + let formatted_caption = + formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into)); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_image_info = BaseImageInfo::try_from(&image_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -322,7 +324,8 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { - let formatted_caption = formatted_caption_from(&caption, &formatted_caption); + let formatted_caption = + formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into)); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_video_info: BaseVideoInfo = BaseVideoInfo::try_from(&video_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -353,7 +356,8 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { - let formatted_caption = formatted_caption_from(&caption, &formatted_caption); + let formatted_caption = + formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into)); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_audio_info: BaseAudioInfo = BaseAudioInfo::try_from(&audio_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -386,7 +390,8 @@ impl Timeline { progress_watcher: Option>, use_send_queue: bool, ) -> Arc { - let formatted_caption = formatted_caption_from(&caption, &formatted_caption); + let formatted_caption = + formatted_body_from(caption.as_deref(), formatted_caption.map(Into::into)); SendAttachmentJoinHandle::new(RUNTIME.spawn(async move { let base_audio_info: BaseAudioInfo = BaseAudioInfo::try_from(&audio_info) .map_err(|_| RoomError::InvalidAttachmentData)?; @@ -714,24 +719,6 @@ impl Timeline { } } -/// Given a pair of optional `caption` and `formatted_caption` parameters, -/// return a formatted caption: -/// -/// - If a `formatted_caption` exists, return it. -/// - If it doesn't exist but there is a `caption`, parse it as markdown and -/// return the result. -/// - Return `None` if there are no `caption` or `formatted_caption` parameters. -fn formatted_caption_from( - caption: &Option, - formatted_caption: &Option, -) -> Option { - match (&caption, formatted_caption) { - (None, None) => None, - (Some(body), None) => RumaFormattedBody::markdown(body), - (_, Some(formatted_body)) => Some(formatted_body.clone().into()), - } -} - /// A handle to perform actions onto a local echo. #[derive(uniffi::Object)] pub struct SendHandle { diff --git a/crates/matrix-sdk/src/utils.rs b/crates/matrix-sdk/src/utils.rs index 327c0410176..b9da26e3ae0 100644 --- a/crates/matrix-sdk/src/utils.rs +++ b/crates/matrix-sdk/src/utils.rs @@ -21,6 +21,8 @@ use std::sync::{Arc, RwLock}; use futures_core::Stream; #[cfg(feature = "e2e-encryption")] use futures_util::StreamExt; +#[cfg(feature = "markdown")] +use ruma::events::room::message::FormattedBody; use ruma::{ events::{AnyMessageLikeEventContent, AnyStateEventContent}, serde::Raw, @@ -218,8 +220,32 @@ pub fn is_room_alias_format_valid(alias: String) -> bool { has_valid_format && is_lowercase && RoomAliasId::parse(alias).is_ok() } +/// Given a pair of optional `body` and `formatted_body` parameters, +/// returns a formatted body. +/// +/// Return the formatted body if available, or interpret the `body` parameter as +/// markdown, if provided. +#[cfg(feature = "markdown")] +pub fn formatted_body_from( + body: Option<&str>, + formatted_body: Option, +) -> Option { + if formatted_body.is_some() { + formatted_body + } else { + body.and_then(FormattedBody::markdown) + } +} + #[cfg(test)] mod test { + #[cfg(feature = "markdown")] + use assert_matches2::{assert_let, assert_matches}; + #[cfg(feature = "markdown")] + use ruma::events::room::message::FormattedBody; + + #[cfg(feature = "markdown")] + use crate::utils::formatted_body_from; use crate::utils::is_room_alias_format_valid; #[cfg(feature = "e2e-encryption")] @@ -282,4 +308,50 @@ mod test { fn test_is_room_alias_format_valid_when_has_valid_format() { assert!(is_room_alias_format_valid("#alias.test:domain.org".to_owned())) } + + #[test] + #[cfg(feature = "markdown")] + fn test_formatted_body_from_nothing_returns_none() { + assert_matches!(formatted_body_from(None, None), None); + } + + #[test] + #[cfg(feature = "markdown")] + fn test_formatted_body_from_only_formatted_body_returns_the_formatted_body() { + let formatted_body = FormattedBody::html(r"

Hello!

"); + + assert_let!( + Some(result_formatted_body) = formatted_body_from(None, Some(formatted_body.clone())) + ); + + assert_eq!(formatted_body.body, result_formatted_body.body); + assert_eq!(result_formatted_body.format, result_formatted_body.format); + } + + #[test] + #[cfg(feature = "markdown")] + fn test_formatted_body_from_markdown_body_returns_a_processed_formatted_body() { + let markdown_body = Some(r"# Parsed"); + + assert_let!(Some(result_formatted_body) = formatted_body_from(markdown_body, None)); + + let expected_formatted_body = FormattedBody::html("

Parsed

\n".to_owned()); + assert_eq!(expected_formatted_body.body, result_formatted_body.body); + assert_eq!(expected_formatted_body.format, result_formatted_body.format); + } + + #[test] + #[cfg(feature = "markdown")] + fn test_formatted_body_from_body_and_formatted_body_returns_the_formatted_body() { + let markdown_body = Some(r"# Markdown"); + let formatted_body = FormattedBody::html(r"

HTML

"); + + assert_let!( + Some(result_formatted_body) = + formatted_body_from(markdown_body, Some(formatted_body.clone())) + ); + + assert_eq!(formatted_body.body, result_formatted_body.body); + assert_eq!(formatted_body.format, result_formatted_body.format); + } }