Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions src/base.rs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ pub enum MessageAction {
/// and error when it doesn't recognize it. The second [bool] argument forces it to be
/// interpreted literally when it is `true`.
Unreact(Option<String>, bool),

}

/// An action taken in the currently selected space.
Expand Down Expand Up @@ -898,6 +899,9 @@ pub struct RoomInfo {
/// A map of message identifiers to thread replies.
threads: HashMap<OwnedEventId, Messages>,

/// Set of event IDs for messages that are live (being typed)
pub live_message_ids: HashSet<OwnedEventId>,

/// Whether the scrollback for this room is currently being fetched.
pub fetching: bool,

Expand Down Expand Up @@ -929,6 +933,7 @@ impl Default for RoomInfo {
user_receipts: Default::default(),
reactions: Default::default(),
threads: Default::default(),
live_message_ids: Default::default(),
fetching: Default::default(),
fetch_id: Default::default(),
fetch_last: Default::default(),
Expand Down Expand Up @@ -1087,6 +1092,7 @@ impl RoomInfo {
let event_id = msg.event_id;
let new_msgtype = msg.new_content;


let Some(EventLocation::Message(thread, key)) = self.keys.get(&event_id) else {
return;
};
Expand All @@ -1103,9 +1109,12 @@ impl RoomInfo {
return;
};

// Update live status based on live_message_ids
msg.is_live = self.live_message_ids.contains(&event_id);

match &mut msg.event {
MessageEvent::Original(orig) => {
orig.content.apply_replacement(new_msgtype);
orig.content.apply_replacement(new_msgtype.clone());
},
MessageEvent::Local(_, content) => {
content.apply_replacement(new_msgtype);
Expand Down Expand Up @@ -1165,22 +1174,37 @@ impl RoomInfo {
let event_id = msg.event_id().to_owned();
let key = (msg.origin_server_ts().into(), event_id.clone());

// Check if this is a live message
let is_live = self.live_message_ids.contains(&event_id);

let loc = EventLocation::Message(None, key.clone());
self.keys.insert(event_id, loc);
self.messages.insert_message(key, msg);
self.keys.insert(event_id.clone(), loc);

// Convert to Message and set is_live flag if needed
let mut message: Message = msg.into();
message.is_live = is_live;

self.messages.insert_message(key, message);
}

fn insert_thread(&mut self, msg: RoomMessageEvent, thread_root: OwnedEventId) {
let event_id = msg.event_id().to_owned();
let key = (msg.origin_server_ts().into(), event_id.clone());

// Check if this is a live message
let is_live = self.live_message_ids.contains(&event_id);

let replies = self
.threads
.entry(thread_root.clone())
.or_insert_with(|| Messages::thread(thread_root.clone()));
let loc = EventLocation::Message(Some(thread_root), key.clone());
self.keys.insert(event_id, loc);
replies.insert_message(key, msg);
self.keys.insert(event_id.clone(), loc);

let mut message: Message = msg.into();
message.is_live = is_live;

replies.insert_message(key, message);
}

/// Insert a new message event.
Expand Down
55 changes: 41 additions & 14 deletions src/message/compose.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use matrix_sdk::ruma::events::room::message::{
};

#[derive(Clone, Debug, Default)]
enum SlashCommand {
pub enum SlashCommand {
/// Send an emote message.
Emote,

Expand Down Expand Up @@ -47,6 +47,9 @@ enum SlashCommand {

/// Send a message with heart effects in clients that show them.
SpaceInvaders,

/// Send a live message that updates as you type (MSC4357)
Live,
}

impl SlashCommand {
Expand Down Expand Up @@ -95,6 +98,11 @@ impl SlashCommand {
Default::default(),
)?
},
SlashCommand::Live => {
// Live messages are handled specially in the chat window
// This should not be called directly
return Err(anyhow::anyhow!("Live message"));
},
};

Ok(msgtype)
Expand All @@ -104,6 +112,7 @@ impl SlashCommand {
fn parse_slash_command_inner(input: &str) -> IResult<&str, SlashCommand> {
let (input, _) = space0(input)?;
let (input, slash) = alt((
// Commands that require text after them (with space)
value(SlashCommand::Emote, tag("/me ")),
value(SlashCommand::Html, tag("/h ")),
value(SlashCommand::Html, tag("/html ")),
Expand All @@ -118,6 +127,7 @@ fn parse_slash_command_inner(input: &str) -> IResult<&str, SlashCommand> {
value(SlashCommand::Rainfall, tag("/rainfall ")),
value(SlashCommand::Snowfall, tag("/snowfall ")),
value(SlashCommand::SpaceInvaders, tag("/spaceinvaders ")),
value(SlashCommand::Live, tag("/live ")),
))(input)?;
let (input, _) = space0(input)?;

Expand Down Expand Up @@ -170,12 +180,23 @@ fn text_to_message_content(input: String) -> TextMessageEventContent {
}
}

#[allow(dead_code)]
pub fn text_to_message(input: String) -> RoomMessageEventContent {
let msg = parse_slash_command(input.as_str())
.and_then(|(input, slash)| slash.to_message(input))
.unwrap_or_else(|_| MessageType::Text(text_to_message_content(input)));
text_to_message_with_command(input).0
}

RoomMessageEventContent::new(msg)
pub fn text_to_message_with_command(input: String) -> (RoomMessageEventContent, Option<SlashCommand>) {
match parse_slash_command(input.as_str()) {
Ok((text, slash)) => {
let msg = slash.to_message(text)
.unwrap_or_else(|_| MessageType::Text(text_to_message_content(text.to_string())));
(RoomMessageEventContent::new(msg), Some(slash))
},
Err(_) => {
let msg = MessageType::Text(text_to_message_content(input));
(RoomMessageEventContent::new(msg), None)
}
}
}

#[cfg(test)]
Expand Down Expand Up @@ -330,8 +351,7 @@ pub mod tests {
assert_eq!(content.body, "<b>bold</b>");
assert_eq!(content.formatted.unwrap().body, "<b>bold</b>");

let MessageType::Text(content) = text_to_message("/plain <b>bold</b>".into()).msgtype
else {
let MessageType::Text(content) = text_to_message("/plain <b>bold</b>".into()).msgtype else {
panic!("Expected MessageType::Text");
};
assert_eq!(content.body, "<b>bold</b>");
Expand All @@ -343,33 +363,40 @@ pub mod tests {
assert_eq!(content.body, "<b>bold</b>");
assert!(content.formatted.is_none(), "{:?}", content.formatted);

let MessageType::Emote(content) = text_to_message("/me *bold*".into()).msgtype else {
let (msg, _) = text_to_message_with_command("/me *bold*".into());
let MessageType::Emote(content) = msg.msgtype else {
panic!("Expected MessageType::Emote");
};
assert_eq!(content.body, "*bold*");
assert_eq!(content.formatted.unwrap().body, "<p><em>bold</em></p>\n");

let content = text_to_message("/confetti hello".into()).msgtype;
let (msg, _) = text_to_message_with_command("/confetti hello".into());
let content = msg.msgtype;
assert_eq!(content.msgtype(), "nic.custom.confetti");
assert_eq!(content.body(), "hello");

let content = text_to_message("/fireworks hello".into()).msgtype;
let (msg, _) = text_to_message_with_command("/fireworks hello".into());
let content = msg.msgtype;
assert_eq!(content.msgtype(), "nic.custom.fireworks");
assert_eq!(content.body(), "hello");

let content = text_to_message("/hearts hello".into()).msgtype;
let (msg, _) = text_to_message_with_command("/hearts hello".into());
let content = msg.msgtype;
assert_eq!(content.msgtype(), "io.element.effect.hearts");
assert_eq!(content.body(), "hello");

let content = text_to_message("/rainfall hello".into()).msgtype;
let (msg, _) = text_to_message_with_command("/rainfall hello".into());
let content = msg.msgtype;
assert_eq!(content.msgtype(), "io.element.effect.rainfall");
assert_eq!(content.body(), "hello");

let content = text_to_message("/snowfall hello".into()).msgtype;
let (msg, _) = text_to_message_with_command("/snowfall hello".into());
let content = msg.msgtype;
assert_eq!(content.msgtype(), "io.element.effect.snowfall");
assert_eq!(content.body(), "hello");

let content = text_to_message("/spaceinvaders hello".into()).msgtype;
let (msg, _) = text_to_message_with_command("/spaceinvaders hello".into());
let content = msg.msgtype;
assert_eq!(content.msgtype(), "io.element.effects.space_invaders");
assert_eq!(content.body(), "hello");
}
Expand Down
Loading