events: Add thread-aware methods to RoomMessageEventContent

This commit is contained in:
Kévin Commaille 2022-09-30 18:18:28 +02:00 committed by Kévin Commaille
parent 003f0abebf
commit 994136b9b8
3 changed files with 38 additions and 82 deletions

View File

@ -15,6 +15,8 @@ Improvements:
* Stabilize support for private read receipts * Stabilize support for private read receipts
* Add stable support for threads * Add stable support for threads
* Move `Relation::Thread` and associated types and methods out of `unstable-msc3440` * Move `Relation::Thread` and associated types and methods out of `unstable-msc3440`
* Add parameter to `RoomMessageEventContent::make_reply_to` to be thread-aware
* Add `RoomMessageEventContent::make_for_reply`
# 0.10.3 # 0.10.3

View File

@ -106,14 +106,20 @@ impl RoomMessageEventContent {
/// Turns `self` into a reply to the given message. /// Turns `self` into a reply to the given message.
/// ///
/// Takes the `body` / `formatted_body` (if any) in `self` for the main text and prepends a /// Takes the `body` / `formatted_body` (if any) in `self` for the main text and prepends a
/// quoted version of `original_message`. Also sets the `in_reply_to` field inside `relates_to`. /// quoted version of `original_message`. Also sets the `in_reply_to` field inside `relates_to`,
/// and optionally the `rel_type` to `m.thread` if the `original_message is in a thread and
/// thread forwarding is enabled.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))] #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
/// ///
/// # Panics /// # Panics
/// ///
/// Panics if `self` has a `formatted_body` with a format other than HTML. /// Panics if `self` has a `formatted_body` with a format other than HTML.
#[track_caller] #[track_caller]
pub fn make_reply_to(mut self, original_message: &OriginalRoomMessageEvent) -> Self { pub fn make_reply_to(
mut self,
original_message: &OriginalRoomMessageEvent,
forward_thread: ForwardThread,
) -> Self {
let empty_formatted_body = || FormattedBody::html(String::new()); let empty_formatted_body = || FormattedBody::html(String::new());
let (body, formatted) = { let (body, formatted) = {
@ -154,43 +160,6 @@ impl RoomMessageEventContent {
); );
} }
self.relates_to = Some(Relation::Reply {
in_reply_to: InReplyTo { event_id: original_message.event_id.to_owned() },
});
self
}
/// Create a new reply with the given message and optionally forwards the [`Relation::Thread`].
///
/// If `message` is a text, an emote or a notice message, it is modified to include the rich
/// reply fallback.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
pub fn reply(
message: MessageType,
original_message: &OriginalRoomMessageEvent,
forward_thread: ForwardThread,
) -> Self {
let make_reply = |body, formatted: Option<FormattedBody>| {
reply::plain_and_formatted_reply_body(body, formatted.map(|f| f.body), original_message)
};
let msgtype = match message {
MessageType::Text(TextMessageEventContent { body, formatted, .. }) => {
let (body, html_body) = make_reply(body, formatted);
MessageType::Text(TextMessageEventContent::html(body, html_body))
}
MessageType::Emote(EmoteMessageEventContent { body, formatted, .. }) => {
let (body, html_body) = make_reply(body, formatted);
MessageType::Emote(EmoteMessageEventContent::html(body, html_body))
}
MessageType::Notice(NoticeMessageEventContent { body, formatted, .. }) => {
let (body, html_body) = make_reply(body, formatted);
MessageType::Notice(NoticeMessageEventContent::html(body, html_body))
}
_ => message,
};
let relates_to = if let Some(Relation::Thread(Thread { event_id, .. })) = original_message let relates_to = if let Some(Relation::Thread(Thread { event_id, .. })) = original_message
.content .content
.relates_to .relates_to
@ -203,48 +172,34 @@ impl RoomMessageEventContent {
in_reply_to: InReplyTo { event_id: original_message.event_id.clone() }, in_reply_to: InReplyTo { event_id: original_message.event_id.clone() },
} }
}; };
self.relates_to = Some(relates_to);
Self { msgtype, relates_to: Some(relates_to) } self
} }
/// Create a new message for a thread that is optionally a reply. /// Turns `self` into a new message for a thread, that is optionally a reply.
/// ///
/// Looks for a [`Relation::Thread`] in `previous_message`. If it exists, a message for the same /// Looks for a [`Relation::Thread`] in `previous_message`. If it exists, this message will be
/// thread is created. If it doesn't, a new thread with `previous_message` as the root is /// in the same thread. If it doesn't, a new thread with `previous_message` as the root is
/// created. /// created.
/// ///
/// If `message` is a text, an emote or a notice message, and this is a reply in the thread, it /// If this is a reply within the thread, takes the `body` / `formatted_body` (if any) in `self`
/// is modified to include the rich reply fallback. /// for the main text and prepends a quoted version of `previous_message`. Also sets the
/// `in_reply_to` field inside `relates_to`.
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))] #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
pub fn for_thread( ///
message: MessageType, /// # Panics
///
/// Panics if this is a reply within the thread and `self` has a `formatted_body` with a format
/// other than HTML.
pub fn make_for_thread(
mut self,
previous_message: &OriginalRoomMessageEvent, previous_message: &OriginalRoomMessageEvent,
is_reply: ReplyWithinThread, is_reply: ReplyWithinThread,
) -> Self { ) -> Self {
let make_reply = |body, formatted: Option<FormattedBody>| { if is_reply == ReplyWithinThread::Yes {
reply::plain_and_formatted_reply_body(body, formatted.map(|f| f.body), previous_message) self = self.make_reply_to(previous_message, ForwardThread::No);
}; }
let msgtype = if is_reply == ReplyWithinThread::Yes {
// If this is a real reply, add the rich reply fallback.
match message {
MessageType::Text(TextMessageEventContent { body, formatted, .. }) => {
let (body, html_body) = make_reply(body, formatted);
MessageType::Text(TextMessageEventContent::html(body, html_body))
}
MessageType::Emote(EmoteMessageEventContent { body, formatted, .. }) => {
let (body, html_body) = make_reply(body, formatted);
MessageType::Emote(EmoteMessageEventContent::html(body, html_body))
}
MessageType::Notice(NoticeMessageEventContent { body, formatted, .. }) => {
let (body, html_body) = make_reply(body, formatted);
MessageType::Notice(NoticeMessageEventContent::html(body, html_body))
}
_ => message,
}
} else {
message
};
let thread_root = if let Some(Relation::Thread(Thread { event_id, .. })) = let thread_root = if let Some(Relation::Thread(Thread { event_id, .. })) =
&previous_message.content.relates_to &previous_message.content.relates_to
@ -254,14 +209,13 @@ impl RoomMessageEventContent {
previous_message.event_id.clone() previous_message.event_id.clone()
}; };
Self { self.relates_to = Some(Relation::Thread(Thread {
msgtype, event_id: thread_root,
relates_to: Some(Relation::Thread(Thread { in_reply_to: InReplyTo { event_id: previous_message.event_id.clone() },
event_id: thread_root, is_falling_back: is_reply == ReplyWithinThread::No,
in_reply_to: InReplyTo { event_id: previous_message.event_id.clone() }, }));
is_falling_back: is_reply == ReplyWithinThread::No,
})), self
}
} }
/// Returns a reference to the `msgtype` string. /// Returns a reference to the `msgtype` string.

View File

@ -486,7 +486,7 @@ fn content_deserialization_failure() {
#[test] #[test]
#[cfg(feature = "unstable-sanitize")] #[cfg(feature = "unstable-sanitize")]
fn reply_sanitize() { fn reply_sanitize() {
use ruma_common::events::room::message::TextMessageEventContent; use ruma_common::events::room::message::{ForwardThread, TextMessageEventContent};
let first_message = OriginalRoomMessageEvent { let first_message = OriginalRoomMessageEvent {
content: RoomMessageEventContent::text_html( content: RoomMessageEventContent::text_html(
@ -504,7 +504,7 @@ fn reply_sanitize() {
"This is the _second_ message", "This is the _second_ message",
"This is the <em>second</em> message", "This is the <em>second</em> message",
) )
.make_reply_to(&first_message), .make_reply_to(&first_message, ForwardThread::Yes),
event_id: event_id!("$143273582443PhrSn:example.org").to_owned(), event_id: event_id!("$143273582443PhrSn:example.org").to_owned(),
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)), origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
room_id: room_id!("!testroomid:example.org").to_owned(), room_id: room_id!("!testroomid:example.org").to_owned(),
@ -515,7 +515,7 @@ fn reply_sanitize() {
"This is **my** reply", "This is **my** reply",
"This is <strong>my</strong> reply", "This is <strong>my</strong> reply",
) )
.make_reply_to(&second_message); .make_reply_to(&second_message, ForwardThread::Yes);
let (body, formatted) = assert_matches!( let (body, formatted) = assert_matches!(
first_message.content.msgtype, first_message.content.msgtype,