events: Add RoomMessageEventContent::make_reply_to
… and deprecate reply constructors.
This commit is contained in:
parent
5c3610b9b7
commit
b7b7d043f3
@ -1,5 +1,5 @@
|
||||
<!-- Keep this comment so the content is always included as a new paragraph -->
|
||||
This constructor requires an [`OriginalRoomMessageEvent`] since it creates a permalink to
|
||||
This function requires an [`OriginalRoomMessageEvent`] since it creates a permalink to
|
||||
the previous message, for which the room ID is required. If you want to reply to an
|
||||
[`OriginalSyncRoomMessageEvent`], you have to convert it first by calling
|
||||
[`.into_full_event()`][crate::events::OriginalSyncMessageLikeEvent::into_full_event].
|
||||
|
@ -103,76 +103,116 @@ impl RoomMessageEventContent {
|
||||
Self::new(MessageType::Notice(NoticeMessageEventContent::markdown(body)))
|
||||
}
|
||||
|
||||
/// 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
|
||||
/// quoted version of `original_message`. Also sets the `in_reply_to` field inside `relates_to`.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `self` has a `formatted_body` with a format other than HTML.
|
||||
#[track_caller]
|
||||
pub fn make_reply_to(mut self, original_message: &OriginalRoomMessageEvent) -> Self {
|
||||
let empty_formatted_body = || FormattedBody::html(String::new());
|
||||
|
||||
let (body, formatted) = {
|
||||
match &mut self.msgtype {
|
||||
MessageType::Emote(m) => {
|
||||
(&mut m.body, Some(m.formatted.get_or_insert_with(empty_formatted_body)))
|
||||
}
|
||||
MessageType::Notice(m) => {
|
||||
(&mut m.body, Some(m.formatted.get_or_insert_with(empty_formatted_body)))
|
||||
}
|
||||
MessageType::Text(m) => {
|
||||
(&mut m.body, Some(m.formatted.get_or_insert_with(empty_formatted_body)))
|
||||
}
|
||||
MessageType::Audio(m) => (&mut m.body, None),
|
||||
MessageType::File(m) => (&mut m.body, None),
|
||||
MessageType::Image(m) => (&mut m.body, None),
|
||||
MessageType::Location(m) => (&mut m.body, None),
|
||||
MessageType::ServerNotice(m) => (&mut m.body, None),
|
||||
MessageType::Video(m) => (&mut m.body, None),
|
||||
MessageType::VerificationRequest(m) => (&mut m.body, None),
|
||||
MessageType::_Custom(m) => (&mut m.body, None),
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(f) = formatted {
|
||||
assert_eq!(
|
||||
f.format,
|
||||
MessageFormat::Html,
|
||||
"make_reply_to can't handle non-HTML formatted messages"
|
||||
);
|
||||
|
||||
let formatted_body = &mut f.body;
|
||||
|
||||
(*body, *formatted_body) = reply::plain_and_formatted_reply_body(
|
||||
body.as_str(),
|
||||
(!formatted_body.is_empty()).then(|| formatted_body.as_str()),
|
||||
original_message,
|
||||
);
|
||||
}
|
||||
|
||||
self.relates_to = Some(Relation::Reply {
|
||||
in_reply_to: InReplyTo { event_id: original_message.event_id.to_owned() },
|
||||
});
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a plain text reply to a message.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
#[deprecated = "\
|
||||
use [`Self::text_plain`](#method.text_plain)`(reply).`\
|
||||
[`make_reply_to`](#method.make_reply_to)`(original_message)` instead\
|
||||
"]
|
||||
pub fn text_reply_plain(
|
||||
reply: impl fmt::Display,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
) -> Self {
|
||||
let formatted: Option<&str> = None;
|
||||
let (body, html_body) =
|
||||
reply::plain_and_formatted_reply_body(reply, formatted, original_message);
|
||||
|
||||
Self {
|
||||
relates_to: Some(Relation::Reply {
|
||||
in_reply_to: InReplyTo { event_id: original_message.event_id.to_owned() },
|
||||
}),
|
||||
..Self::text_html(body, html_body)
|
||||
}
|
||||
Self::text_plain(reply.to_string()).make_reply_to(original_message)
|
||||
}
|
||||
|
||||
/// Creates a html text reply to a message.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
#[deprecated = "\
|
||||
use [`Self::text_html`](#method.text_html)`(reply, html_reply).`\
|
||||
[`make_reply_to`](#method.make_reply_to)`(original_message)` instead\
|
||||
"]
|
||||
pub fn text_reply_html(
|
||||
reply: impl fmt::Display,
|
||||
html_reply: impl fmt::Display,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
) -> Self {
|
||||
let (body, html_body) =
|
||||
reply::plain_and_formatted_reply_body(reply, Some(html_reply), original_message);
|
||||
|
||||
Self {
|
||||
relates_to: Some(Relation::Reply {
|
||||
in_reply_to: InReplyTo { event_id: original_message.event_id.clone() },
|
||||
}),
|
||||
..Self::text_html(body, html_body)
|
||||
}
|
||||
Self::text_html(reply.to_string(), html_reply.to_string()).make_reply_to(original_message)
|
||||
}
|
||||
|
||||
/// Creates a plain text notice reply to a message.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
#[deprecated = "\
|
||||
use [`Self::notice_plain`](#method.notice_plain)`(reply).`\
|
||||
[`make_reply_to`](#method.make_reply_to)`(original_message)` instead\
|
||||
"]
|
||||
pub fn notice_reply_plain(
|
||||
reply: impl fmt::Display,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
) -> Self {
|
||||
let formatted: Option<&str> = None;
|
||||
let (body, html_body) =
|
||||
reply::plain_and_formatted_reply_body(reply, formatted, original_message);
|
||||
|
||||
Self {
|
||||
relates_to: Some(Relation::Reply {
|
||||
in_reply_to: InReplyTo { event_id: original_message.event_id.to_owned() },
|
||||
}),
|
||||
..Self::notice_html(body, html_body)
|
||||
}
|
||||
Self::notice_plain(reply.to_string()).make_reply_to(original_message)
|
||||
}
|
||||
|
||||
/// Creates a html text notice reply to a message.
|
||||
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/rich_reply.md"))]
|
||||
#[deprecated = "\
|
||||
use [`Self::notice_html`](#method.notice_html)`(reply, html_reply).`\
|
||||
[`make_reply_to`](#method.make_reply_to)`(original_message)` instead\
|
||||
"]
|
||||
pub fn notice_reply_html(
|
||||
reply: impl fmt::Display,
|
||||
html_reply: impl fmt::Display,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
) -> Self {
|
||||
let (body, html_body) =
|
||||
reply::plain_and_formatted_reply_body(reply, Some(html_reply), original_message);
|
||||
|
||||
Self {
|
||||
relates_to: Some(Relation::Reply {
|
||||
in_reply_to: InReplyTo { event_id: original_message.event_id.clone() },
|
||||
}),
|
||||
..Self::notice_html(body, html_body)
|
||||
}
|
||||
Self::notice_html(reply.to_string(), html_reply.to_string()).make_reply_to(original_message)
|
||||
}
|
||||
|
||||
/// Create a new reply with the given message and optionally forwards the [`Relation::Thread`].
|
||||
|
@ -8,61 +8,46 @@ use super::{
|
||||
use super::{sanitize_html, HtmlSanitizerMode, RemoveReplyFallback};
|
||||
|
||||
fn get_message_quote_fallbacks(original_message: &OriginalRoomMessageEvent) -> (String, String) {
|
||||
let get_quotes = |body: &str, formatted: Option<&FormattedBody>, is_emote: bool| {
|
||||
let OriginalRoomMessageEvent { room_id, event_id, sender, content, .. } = original_message;
|
||||
let is_reply = matches!(content.relates_to, Some(Relation::Reply { .. }));
|
||||
let emote_sign = is_emote.then(|| "* ").unwrap_or_default();
|
||||
let body = is_reply.then(|| remove_plain_reply_fallback(body)).unwrap_or(body);
|
||||
#[cfg(feature = "unstable-sanitize")]
|
||||
let html_body = formatted_or_plain_body(formatted, body, is_reply);
|
||||
#[cfg(not(feature = "unstable-sanitize"))]
|
||||
let html_body = formatted_or_plain_body(formatted, body);
|
||||
|
||||
(
|
||||
format!("> {emote_sign}<{sender}> {body}").replace('\n', "\n> "),
|
||||
format!(
|
||||
"<mx-reply>\
|
||||
<blockquote>\
|
||||
<a href=\"https://matrix.to/#/{room_id}/{event_id}\">In reply to</a> \
|
||||
{emote_sign}<a href=\"https://matrix.to/#/{sender}\">{sender}</a>\
|
||||
<br>\
|
||||
{html_body}\
|
||||
</blockquote>\
|
||||
</mx-reply>"
|
||||
),
|
||||
)
|
||||
};
|
||||
|
||||
match &original_message.content.msgtype {
|
||||
MessageType::Audio(_) => get_quotes("sent an audio file.", None, original_message, false),
|
||||
MessageType::Emote(content) => {
|
||||
get_quotes(&content.body, content.formatted.as_ref(), original_message, true)
|
||||
}
|
||||
MessageType::File(_) => get_quotes("sent a file.", None, original_message, false),
|
||||
MessageType::Image(_) => get_quotes("sent an image.", None, original_message, false),
|
||||
MessageType::Location(_) => get_quotes("sent a location.", None, original_message, false),
|
||||
MessageType::Notice(content) => {
|
||||
get_quotes(&content.body, content.formatted.as_ref(), original_message, false)
|
||||
}
|
||||
MessageType::ServerNotice(content) => {
|
||||
get_quotes(&content.body, None, original_message, false)
|
||||
}
|
||||
MessageType::Text(content) => {
|
||||
get_quotes(&content.body, content.formatted.as_ref(), original_message, false)
|
||||
}
|
||||
MessageType::Video(_) => get_quotes("sent a video.", None, original_message, false),
|
||||
MessageType::_Custom(content) => get_quotes(&content.body, None, original_message, false),
|
||||
MessageType::VerificationRequest(content) => {
|
||||
get_quotes(&content.body, None, original_message, false)
|
||||
}
|
||||
MessageType::Audio(_) => get_quotes("sent an audio file.", None, false),
|
||||
MessageType::Emote(c) => get_quotes(&c.body, c.formatted.as_ref(), true),
|
||||
MessageType::File(_) => get_quotes("sent a file.", None, false),
|
||||
MessageType::Image(_) => get_quotes("sent an image.", None, false),
|
||||
MessageType::Location(_) => get_quotes("sent a location.", None, false),
|
||||
MessageType::Notice(c) => get_quotes(&c.body, c.formatted.as_ref(), false),
|
||||
MessageType::ServerNotice(c) => get_quotes(&c.body, None, false),
|
||||
MessageType::Text(c) => get_quotes(&c.body, c.formatted.as_ref(), false),
|
||||
MessageType::Video(_) => get_quotes("sent a video.", None, false),
|
||||
MessageType::VerificationRequest(content) => get_quotes(&content.body, None, false),
|
||||
MessageType::_Custom(content) => get_quotes(&content.body, None, false),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_quotes(
|
||||
body: &str,
|
||||
formatted: Option<&FormattedBody>,
|
||||
original_message: &OriginalRoomMessageEvent,
|
||||
is_emote: bool,
|
||||
) -> (String, String) {
|
||||
let OriginalRoomMessageEvent { room_id, event_id, sender, content, .. } = original_message;
|
||||
let is_reply = matches!(content.relates_to, Some(Relation::Reply { .. }));
|
||||
let emote_sign = is_emote.then(|| "* ").unwrap_or_default();
|
||||
let body = is_reply.then(|| remove_plain_reply_fallback(body)).unwrap_or(body);
|
||||
#[cfg(feature = "unstable-sanitize")]
|
||||
let html_body = formatted_or_plain_body(formatted, body, is_reply);
|
||||
#[cfg(not(feature = "unstable-sanitize"))]
|
||||
let html_body = formatted_or_plain_body(formatted, body);
|
||||
|
||||
(
|
||||
format!("> {emote_sign}<{sender}> {body}").replace('\n', "\n> "),
|
||||
format!(
|
||||
"<mx-reply>\
|
||||
<blockquote>\
|
||||
<a href=\"https://matrix.to/#/{room_id}/{event_id}\">In reply to</a> \
|
||||
{emote_sign}<a href=\"https://matrix.to/#/{sender}\">{sender}</a>\
|
||||
<br>\
|
||||
{html_body}\
|
||||
</blockquote>\
|
||||
</mx-reply>"
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn formatted_or_plain_body(
|
||||
formatted: Option<&FormattedBody>,
|
||||
body: &str,
|
||||
@ -119,10 +104,9 @@ pub fn plain_and_formatted_reply_body(
|
||||
let (quoted, quoted_html) = get_message_quote_fallbacks(original_message);
|
||||
|
||||
let plain = format!("{quoted}\n{body}");
|
||||
let html = if let Some(formatted) = formatted {
|
||||
format!("{quoted_html}{formatted}")
|
||||
} else {
|
||||
format!("{quoted_html}{body}")
|
||||
let html = match formatted {
|
||||
Some(formatted) => format!("{quoted_html}{formatted}"),
|
||||
None => format!("{quoted_html}{body}"),
|
||||
};
|
||||
|
||||
(plain, html)
|
||||
@ -160,7 +144,7 @@ mod tests {
|
||||
<br>\
|
||||
multi<br>line\
|
||||
</blockquote>\
|
||||
</mx-reply>"
|
||||
</mx-reply>",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -500,22 +500,22 @@ fn reply_sanitize() {
|
||||
unsigned: MessageLikeUnsigned::default(),
|
||||
};
|
||||
let second_message = OriginalRoomMessageEvent {
|
||||
content: RoomMessageEventContent::text_reply_html(
|
||||
content: RoomMessageEventContent::text_html(
|
||||
"This is the _second_ message",
|
||||
"This is the <em>second</em> message",
|
||||
&first_message,
|
||||
),
|
||||
)
|
||||
.make_reply_to(&first_message),
|
||||
event_id: event_id!("$143273582443PhrSn:example.org").to_owned(),
|
||||
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(10_000)),
|
||||
room_id: room_id!("!testroomid:example.org").to_owned(),
|
||||
sender: user_id!("@user:example.org").to_owned(),
|
||||
unsigned: MessageLikeUnsigned::default(),
|
||||
};
|
||||
let final_reply = RoomMessageEventContent::text_reply_html(
|
||||
let final_reply = RoomMessageEventContent::text_html(
|
||||
"This is **my** reply",
|
||||
"This is <strong>my</strong> reply",
|
||||
&second_message,
|
||||
);
|
||||
)
|
||||
.make_reply_to(&second_message);
|
||||
|
||||
let (body, formatted) = assert_matches!(
|
||||
first_message.content.msgtype,
|
||||
|
Loading…
x
Reference in New Issue
Block a user