diff --git a/common/src/state/mod.rs b/common/src/state/mod.rs index b2c8c100589..7bf80699102 100644 --- a/common/src/state/mod.rs +++ b/common/src/state/mod.rs @@ -24,7 +24,6 @@ pub use chats::{Chat, Chats}; use dioxus_desktop::tao::window::WindowId; pub use friends::Friends; pub use identity::Identity; -use regex::Regex; pub use route::Route; pub use settings::Settings; pub use ui::{Theme, ToastNotification, UI}; @@ -280,12 +279,7 @@ impl State { m.set_conversation_id(id); m.set_sender(sender); m.set_lines(msg); - let m = ui_adapter::Message { - inner: m, - in_reply_to: None, - key: Uuid::new_v4().to_string(), - ..Default::default() - }; + let m = ui_adapter::Message::new(m, None, Uuid::new_v4().to_string()); self.add_msg_to_chat(id, m); } // ===== Media ===== @@ -452,13 +446,8 @@ impl State { conversation_id, mut message, } => { - if let Some(ids) = self - .get_chat_by_id(conversation_id) - .map(|c| self.chat_participants(&c)) - { - message.insert_did(&ids, &self.get_own_identity().did_key()); - } - let ping = message.is_mention; + let own = self.get_own_identity().did_key(); + let ping = message.is_mention_self(&own); self.update_identity_status_hack(&message.inner.sender()); let id = self.identities.get(&message.inner.sender()).cloned(); // todo: don't load all the messages by default. if the user scrolled up, for example, this incoming message may not need to be fetched yet. @@ -535,15 +524,6 @@ impl State { self.update_identity_status_hack(&message.inner.sender()); let own = self.get_own_identity().did_key(); if let Some(chat) = self.chats.all.get_mut(&conversation_id) { - message.insert_did( - &chat - .participants - .iter() - .filter_map(|id| self.identities.get(id)) - .cloned() - .collect::>(), - &own, - ); let id = message.inner.id(); if let Some(msg) = chat.messages.iter_mut().find(|msg| msg.inner.id() == id) { *msg = message.clone(); @@ -559,7 +539,7 @@ impl State { *msg = message.inner.clone(); } - if message.is_mention { + if message.is_mention_self(&own) { if let Some(msg) = chat.mentions.iter_mut().find(|m| m.inner.id() == id) { *msg = message.clone(); } @@ -1928,24 +1908,3 @@ pub fn pending_group_messages<'a>( messages, }) } - -pub fn mention_regex_pattern(id: &Identity, username: bool) -> Regex { - Regex::new(&format!( - "(^| )@{}( |$)", - if username { - id.username() - } else { - id.did_key().to_string() - } - )) - .unwrap() -} - -pub fn mention_replacement_pattern(id: &Identity, visual: bool) -> String { - format!( - r#"
@{}
"#, - if visual { "visual-only" } else { "" }, - id.did_key(), - id.username() - ) -} diff --git a/common/src/state/pending_message.rs b/common/src/state/pending_message.rs index 650f1dd4dd6..9ead4cb1d9d 100644 --- a/common/src/state/pending_message.rs +++ b/common/src/state/pending_message.rs @@ -21,12 +21,7 @@ impl PendingMessage { inner.set_id(m_id); } inner.set_lines(text); - let message = Message { - inner, - in_reply_to: None, - key: String::new(), - ..Default::default() - }; + let message = Message::new(inner, None, Uuid::new_v4().to_string()); PendingMessage { attachments: attachments .iter() @@ -60,12 +55,7 @@ impl PendingMessage { .cloned() .collect::>(); - let message = Message { - inner, - in_reply_to: None, - key: Uuid::new_v4().to_string(), - ..Default::default() - }; + let message = Message::new(inner, None, Uuid::new_v4().to_string()); PendingMessage { attachments: attachments .iter() diff --git a/common/src/state/utils.rs b/common/src/state/utils.rs index a612225f30b..03540ac01d1 100644 --- a/common/src/state/utils.rs +++ b/common/src/state/utils.rs @@ -3,13 +3,22 @@ use std::{ path::{Path, PathBuf}, }; +use once_cell::sync::Lazy; +use regex::{Captures, Regex, Replacer}; use titlecase::titlecase; use tracing::log; +use uuid::Uuid; use walkdir::WalkDir; +use warp::crypto::DID; use crate::{get_extras_dir, STATIC_ARGS}; -use super::{ui::Font, Theme}; +use super::{ui::Font, Identity, State, Theme}; + +pub static USER_NAME_TAGS_REGEX: Lazy = + Lazy::new(|| mention_regex_epattern("[A-z0-9]+#[A-z0-9]{8}")); +pub static USER_DID_TAGS_REGEX: Lazy = + Lazy::new(|| mention_regex_epattern("did:key:[A-z0-9]{48}")); pub fn get_available_themes() -> Vec { let mut themes = vec![]; @@ -96,6 +105,108 @@ pub fn get_available_fonts() -> Vec { fonts } +struct TagReplacer<'a, F: Fn(&Identity) -> String> { + participants: &'a [Identity], + own: &'a DID, + is_mention: bool, + is_username: bool, + replacement: F, +} + +impl String> Replacer for TagReplacer<'_, F> { + fn replace_append(&mut self, caps: &Captures<'_>, dst: &mut String) { + if !caps[0].starts_with('`') { + let value = &caps[2]; + let key = &value[1..]; + if key.eq(&self.own.to_string()) { + self.is_mention = true; + } + dst.push_str(&caps[1]); + if let Some(id) = self.participants.iter().find(|id| { + if self.is_username { + let name = format!("{}#{}", id.username(), id.short_id()); + name.eq(key) + } else { + id.did_key().to_string().eq(key) + } + }) { + dst.push_str(&(self.replacement)(id)) + } else { + dst.push_str(value); + }; + dst.push_str(&caps[3]); + } else { + dst.push_str(&caps[0]); + } + } +} + +pub fn mention_regex_epattern(value: &str) -> Regex { + // This detects codeblocks + // When replacing this needs to be explicitly checked + let mut pattern = String::from(r#"(?:`{3}|`{1,2})+[^`]*(?:`{3}|`{1,2})"#); + // Second capture group contains the mention + // Since codeblocks are checked before they are basically "excluded" + // First and third are any leading/trailing whitespaces + pattern.push_str(&format!(r#"|(^|\s)(@{})($|\s)"#, value)); + Regex::new(&pattern).unwrap() +} + +pub fn parse_mention_state( + message: &str, + state: &State, + chat: Uuid, + replacement: impl Fn(&Identity) -> String, +) -> (String, bool) { + parse_mentions( + message, + &state + .get_chat_by_id(chat) + .map(|c| state.chat_participants(&c)) + .unwrap_or_default(), + &state.did_key(), + false, + replacement, + ) +} + +// Parse a message replacing mentions with a given function +pub fn parse_mentions( + message: &str, + participants: &[Identity], + own: &DID, + is_username: bool, + replacement: impl Fn(&Identity) -> String, +) -> (String, bool) { + let mut replacer = TagReplacer { + participants, + own, + is_username, + is_mention: false, + replacement, + }; + let result = if is_username { + USER_NAME_TAGS_REGEX.replace_all(message, replacer.by_ref()) + } else { + USER_DID_TAGS_REGEX.replace_all(message, replacer.by_ref()) + }; + (result.to_string(), replacer.is_mention) +} + +pub fn mention_to_did_key(id: &Identity) -> String { + format!("@{}", id.did_key()) +} + +// Replacement pattern converting a user tag to a highlight div +pub fn mention_replacement_pattern(id: &Identity, visual: bool) -> String { + format!( + r#"
@{}
"#, + if visual { "visual-only" } else { "" }, + id.did_key(), + id.username() + ) +} + #[cfg(test)] mod test { use super::*; diff --git a/common/src/testing/mock.rs b/common/src/testing/mock.rs index 7fa7d963b6d..179460d223e 100644 --- a/common/src/testing/mock.rs +++ b/common/src/testing/mock.rs @@ -105,12 +105,11 @@ fn generate_fake_chat(participants: Vec, conversation: Uuid) -> Chat { default_message.set_sender(sender.did_key()); default_message.set_lines(vec![lipsum(word_count)]); - messages.push_back(ui_adapter::Message { - inner: default_message, - in_reply_to: None, - key: Uuid::new_v4().to_string(), - ..Default::default() - }); + messages.push_back(ui_adapter::Message::new( + default_message, + None, + Uuid::new_v4().to_string(), + )); } let pinned_messages: Vec<_> = messages @@ -258,12 +257,7 @@ fn generate_fake_message(conversation_id: Uuid, identities: &[Identity]) -> ui_a default_message.set_replied(None); default_message.set_lines(vec![text.into()]); - ui_adapter::Message { - inner: default_message, - in_reply_to: None, - key: Uuid::new_v4().to_string(), - ..Default::default() - } + ui_adapter::Message::new(default_message, None, Uuid::new_v4().to_string()) } fn generate_fake_storage() -> Storage { diff --git a/common/src/warp_runner/ui_adapter/mod.rs b/common/src/warp_runner/ui_adapter/mod.rs index f67ca595bfe..9f823604e26 100644 --- a/common/src/warp_runner/ui_adapter/mod.rs +++ b/common/src/warp_runner/ui_adapter/mod.rs @@ -12,7 +12,7 @@ pub use multipass_event::{convert_multipass_event, MultiPassEvent}; pub use raygun_event::{convert_raygun_event, RayGunEvent}; use uuid::Uuid; -use crate::state::{self, chats, Identity, MAX_PINNED_MESSAGES}; +use crate::state::{self, chats, utils::mention_regex_epattern, Identity, MAX_PINNED_MESSAGES}; use futures::{stream::FuturesOrdered, FutureExt, StreamExt}; use serde::{Deserialize, Serialize}; use std::{ @@ -39,8 +39,7 @@ use super::{ pub struct Message { pub inner: warp::raygun::Message, pub in_reply_to: Option<(String, Vec, DID)>, - pub lines_to_render: Option, - pub is_mention: bool, + is_mention: Option, /// this field exists so that the UI can tell Dioxus when a message has been edited and thus /// needs to be re-rendered. Before the addition of this field, the compose view was /// using the message Uuid, but this doesn't change when a message is edited. @@ -48,40 +47,31 @@ pub struct Message { } impl Message { - // resolve the message lines to e.g. format user mentions correctly - pub fn insert_did(&mut self, participants: &[state::Identity], own: &DID) { - if self.lines_to_render.is_some() { - return; + pub fn new( + inner: warp::raygun::Message, + in_reply_to: Option<(String, Vec, DID)>, + key: String, + ) -> Message { + Message { + inner, + in_reply_to, + key, + ..Default::default() } - // Better if warp provides a way of saving mentions in the message - // so dont need to loop over all participants - let lines = self.inner.lines().join("\n"); - let (lines, is_mention) = format_mentions(lines, participants, own, false); - self.is_mention = is_mention; - self.lines_to_render = Some(lines); } -} -pub fn format_mentions( - message: String, - participants: &[state::Identity], - own: &DID, - visual: bool, -) -> (String, bool) { - if !message.contains('@') { - return (message, false); - } - let mut result = message; - let mut is_mention = false; - participants.iter().for_each(|id| { - let reg = state::mention_regex_pattern(id, false); - let replaced = reg.replace_all(&result, state::mention_replacement_pattern(id, visual)); - if own.eq(&id.did_key()) && !replaced.eq(&result) { - is_mention = true; + // Lazily evaluate if the user is mentioned + pub fn is_mention_self(&mut self, own: &DID) -> bool { + if self.is_mention.is_none() { + let reg = mention_regex_epattern(&own.to_string()); + self.is_mention = Some( + reg.find(&self.inner.lines().join("\n")) + .map(|c| !c.as_str().starts_with('`')) + .unwrap_or_default(), + ); } - result = replaced.to_string(); - }); - (result, is_mention) + self.is_mention.unwrap() + } } #[derive(Clone)] diff --git a/kit/src/components/message/mod.rs b/kit/src/components/message/mod.rs index ee580465a01..2e1a04e006e 100644 --- a/kit/src/components/message/mod.rs +++ b/kit/src/components/message/mod.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use std::{collections::HashSet, str::FromStr}; use common::language::{get_local_text, get_local_text_with_args}; +use common::state::utils::{mention_replacement_pattern, parse_mentions}; use common::state::{Action, Identity, State, ToastNotification}; use common::warp_runner::{thumbnail_to_base64, MultiPassCmd, WarpCmd}; use common::{state::pending_message::progress_file, WARP_CMD_CH}; @@ -73,8 +74,6 @@ pub struct Props<'a> { // An optional field that, if set, will be used as the text content of a nested p element with a class of "text". with_text: Option, - tagged_text: Option, - reactions: Vec, // An optional field that, if set to true, will add a CSS class of "remote" to the div element. @@ -114,6 +113,10 @@ pub struct Props<'a> { pinned: bool, is_mention: bool, + + state: &'a UseSharedState, + + chat: Uuid, } fn wrap_links_with_a_tags(text: &str) -> String { @@ -140,11 +143,6 @@ pub fn Message<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { // omitting the class will display the reactions starting from the bottom right corner let remote_class = ""; //if is_remote { "remote" } else { "" }; let reactions_class = format!("message-reactions-container {remote_class}"); - let rendered_text = cx - .props - .tagged_text - .as_ref() - .or(cx.props.with_text.as_ref()); let has_attachments = cx .props @@ -263,10 +261,12 @@ pub fn Message<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { ), (cx.props.with_text.is_some() && !cx.props.editing).then(|| rsx!( ChatText { - text: rendered_text.cloned().unwrap_or_default(), + text: cx.props.with_text.as_ref().cloned().unwrap_or_default(), remote: is_remote, pending: cx.props.pending, markdown: cx.props.parse_markdown, + state: cx.props.state, + chat: cx.props.chat, ascii_emoji: cx.props.transform_ascii_emojis, } )), @@ -345,18 +345,19 @@ fn EditMsg<'a>(cx: Scope<'a, EditProps<'a>>) -> Element<'a> { })) } -#[derive(Props, PartialEq)] -pub struct ChatMessageProps { +#[derive(Props)] +pub struct ChatMessageProps<'a> { text: String, remote: bool, pending: bool, markdown: bool, ascii_emoji: bool, - participants: Option>, + state: &'a UseSharedState, + chat: Uuid, } #[allow(non_snake_case)] -pub fn ChatText(cx: Scope) -> Element { +pub fn ChatText<'a>(cx: Scope<'a, ChatMessageProps<'a>>) -> Element<'a> { // DID::from_str panics if text is 'z'. simple fix is to ensure string is long enough. if cx.props.text.len() > 2 { if let Ok(id) = DID::from_str(&cx.props.text) { @@ -364,9 +365,13 @@ pub fn ChatText(cx: Scope) -> Element { } } - let mut formatted_text = format_text(&cx.props.text, cx.props.markdown, cx.props.ascii_emoji); + let mut formatted_text = format_text( + &cx.props.text, + cx.props.markdown, + cx.props.ascii_emoji, + Some((&cx.props.state.read(), &cx.props.chat, false)), + ); formatted_text = wrap_links_with_a_tags(&formatted_text); - let finder = LinkFinder::new(); let links: Vec = finder .spans(&formatted_text) @@ -411,7 +416,12 @@ pub fn ChatText(cx: Scope) -> Element { )) } -pub fn format_text(text: &str, should_markdown: bool, emojis: bool) -> String { +pub fn format_text( + text: &str, + should_markdown: bool, + emojis: bool, + data: Option<(&State, &Uuid, bool)>, +) -> String { // warning: this will probably break markdown regarding block quotes. still seems like an improvement. let safe_text = text .replace('&', "&") @@ -420,9 +430,21 @@ pub fn format_text(text: &str, should_markdown: bool, emojis: bool) -> String { .replace('\"', """) .replace('\'', "'") .replace('\n', "  \n"); - let text = &safe_text; + let mut text = safe_text; + // We want to do this after we escape html tags + if let Some((state, chat, visual)) = data { + if let Some(participants) = state + .get_chat_by_id(*chat) + .map(|c| state.chat_participants(&c)) + { + let (line, _) = parse_mentions(&text, &participants, &state.did_key(), false, |id| { + mention_replacement_pattern(id, visual) + }); + text = line; + } + } if should_markdown { - markdown(text, emojis) + markdown(&text, emojis) } else if emojis { let s = replace_emojis(text.trim()); if is_only_emojis(&s) { @@ -886,8 +908,8 @@ mod tests2 { fn test_format_text1() { let input = ":) "; let expected = "🙂"; - assert_eq!(&format_text(input, true, true), expected); - assert_eq!(&format_text(input, false, true), expected); + assert_eq!(&format_text(input, true, true, None), expected); + assert_eq!(&format_text(input, false, true, None), expected); } } diff --git a/kit/src/components/message_reply/mod.rs b/kit/src/components/message_reply/mod.rs index 64b62f10366..0bea7d81c5f 100644 --- a/kit/src/components/message_reply/mod.rs +++ b/kit/src/components/message_reply/mod.rs @@ -1,7 +1,8 @@ -use common::warp_runner::thumbnail_to_base64; +use common::{state::State, warp_runner::thumbnail_to_base64}; use derive_more::Display; use dioxus::prelude::*; +use uuid::Uuid; use warp::{constellation::file::File, crypto::DID}; use crate::components::embeds::file_embed::FileEmbed; @@ -42,6 +43,8 @@ pub struct Props<'a> { replier_did: Option, markdown: Option, transform_ascii_emojis: Option, + state: &'a UseSharedState, + chat: Uuid, } #[allow(non_snake_case)] @@ -50,6 +53,7 @@ pub fn MessageReply<'a>(cx: Scope<'a, Props<'a>>) -> Element<'a> { &cx.props.with_text.clone().unwrap_or_default(), cx.props.markdown.unwrap_or_default(), cx.props.transform_ascii_emojis.unwrap_or_default(), + Some((&cx.props.state.read(), &cx.props.chat, true)), ); let prefix = cx.props.with_prefix.clone().unwrap_or_default(); let loading = cx.props.loading.unwrap_or_default(); diff --git a/kit/src/layout/chatbar/mod.rs b/kit/src/layout/chatbar/mod.rs index 57a7db72ee7..45921926eb5 100644 --- a/kit/src/layout/chatbar/mod.rs +++ b/kit/src/layout/chatbar/mod.rs @@ -1,6 +1,7 @@ -use common::state::Identity; +use common::state::{Identity, State}; use dioxus::prelude::*; use dioxus_elements::input_data::keyboard_types::Code; +use uuid::Uuid; use warp::constellation::file::File; use crate::{ @@ -88,6 +89,8 @@ pub struct ReplyProps<'a> { children: Element<'a>, markdown: Option, transform_ascii_emojis: Option, + state: &'a UseSharedState, + chat: Uuid, } #[allow(non_snake_case)] @@ -97,6 +100,7 @@ pub fn Reply<'a>(cx: Scope<'a, ReplyProps<'a>>) -> Element<'a> { &cx.props.message, cx.props.markdown.unwrap_or_default(), cx.props.transform_ascii_emojis.unwrap_or_default(), + Some((&cx.props.state.read(), &cx.props.chat, true)), ); let has_attachments = cx diff --git a/ui/src/components/friends/friends_list/mod.rs b/ui/src/components/friends/friends_list/mod.rs index d131c72fcff..6775c4f1c9d 100644 --- a/ui/src/components/friends/friends_list/mod.rs +++ b/ui/src/components/friends/friends_list/mod.rs @@ -474,7 +474,7 @@ pub fn ShareFriendsModal(cx: Scope) -> Element { }; let unwrapped_message = match chat.messages.iter().last() {Some(m) => m.inner.clone(),None => raygun::Message::default()}; let subtext_val = match unwrapped_message.lines().iter().map(|x| x.trim()).find(|x| !x.is_empty()) { - Some(v) => format_text(v, state.read().ui.should_transform_markdown_text(), state.read().ui.should_transform_ascii_emojis()), + Some(v) => format_text(v, state.read().ui.should_transform_markdown_text(), state.read().ui.should_transform_ascii_emojis(), Some((&state.read(), &chat.id, true))), _ => match &unwrapped_message.attachments()[..] { [] => get_local_text("sidebar.chat-new"), [ file ] => file.name(), diff --git a/ui/src/layouts/chats/data/msg_group.rs b/ui/src/layouts/chats/data/msg_group.rs index 12d04c87a11..de17ce51c2a 100644 --- a/ui/src/layouts/chats/data/msg_group.rs +++ b/ui/src/layouts/chats/data/msg_group.rs @@ -58,9 +58,7 @@ pub fn create_message_groups( let mut other_ids = other_ids.clone(); other_ids.push(my_id.clone()); - for mut msg in input.drain(..) { - msg.insert_did(&other_ids, &my_id.did_key()); - + for msg in input.drain(..) { if let Some(group) = messages.iter_mut().last() { if let Some(last_group_message) = group.messages.last() { if group.sender == msg.inner.sender() @@ -114,8 +112,7 @@ pub fn pending_group_messages( let mut messages: Vec = vec![]; let size = pending.len(); for (i, msg) in pending.iter().enumerate() { - let mut message = msg.message.clone(); - message.insert_did(&other_ids, &my_id.did_key()); + let message = msg.message.clone(); if i == size - 1 { let g = MessageGroupMsg { message, diff --git a/ui/src/layouts/chats/presentation/chat/pinned_messages/mod.rs b/ui/src/layouts/chats/presentation/chat/pinned_messages/mod.rs index 924d5689c91..6871fe7c61e 100644 --- a/ui/src/layouts/chats/presentation/chat/pinned_messages/mod.rs +++ b/ui/src/layouts/chats/presentation/chat/pinned_messages/mod.rs @@ -146,6 +146,7 @@ pub fn PinnedMessages(cx: Scope<'_, Props>) -> Element<'_> { let message_date = message.date(); rsx!(PinnedMessage { message: message.clone(), + chat: chat_data.read().active_chat.id(), sender: sender, onremove: move |(_,msg): (Event, warp::raygun::Message)| { let conv = &msg.conversation_id(); @@ -165,6 +166,7 @@ pub fn PinnedMessages(cx: Scope<'_, Props>) -> Element<'_> { #[derive(Props)] pub struct PinnedMessageProp<'a> { message: warp::raygun::Message, + chat: Uuid, #[props(!optional)] sender: Option, onremove: EventHandler<'a, (Event, warp::raygun::Message)>, @@ -254,6 +256,8 @@ pub fn PinnedMessage<'a>(cx: Scope<'a, PinnedMessageProp<'a>>) -> Element<'a> { text: message.lines().join("\n"), remote: true, pending: false, + state: &state, + chat: cx.props.chat, markdown: state.read().ui.should_transform_markdown_text(), ascii_emoji: state.read().ui.should_transform_ascii_emojis(), } diff --git a/ui/src/layouts/chats/presentation/chatbar/mod.rs b/ui/src/layouts/chats/presentation/chatbar/mod.rs index 78d7acba3c2..6480c78d348 100644 --- a/ui/src/layouts/chats/presentation/chatbar/mod.rs +++ b/ui/src/layouts/chats/presentation/chatbar/mod.rs @@ -5,7 +5,10 @@ use std::{path::PathBuf, time::Duration}; use common::{ icons::{self}, language::{get_local_text, get_local_text_with_args}, - state::{Action, Identity, State}, + state::{ + utils::{mention_to_did_key, parse_mentions}, + Action, Identity, State, + }, MAX_FILES_PER_MESSAGE, STATIC_ARGS, }; use dioxus::prelude::*; @@ -205,6 +208,7 @@ pub fn get_chatbar<'a>(cx: &'a Scoped<'a, ChatProps>) -> Element<'a> { }) .unwrap_or_default(); let chat_participants_2 = chat_participants.clone(); + let chat_participants_3 = chat_participants.clone(); let submit_fn = move || { local_typing_ch.send(TypingIndicator::NotTyping); @@ -222,16 +226,14 @@ pub fn get_chatbar<'a>(cx: &'a Scoped<'a, ChatProps>) -> Element<'a> { .get_active_chat() .as_ref() .and_then(|d| d.draft.clone()) + .map(|msg| { + let (txt, _) = + parse_mentions(&msg, &chat_participants_3, &my_id, true, mention_to_did_key); + txt + }) .unwrap_or_default() .lines() - .map(|x| { - let mut s = x.to_string(); - mentions - .read() - .iter() - .for_each(|(did, name)| s = x.replace(name, &format!("{}", did))); - s - }) + .map(|x| x.trim_end().to_string()) .collect::>(); if !active_chat_id.is_nil() { @@ -482,6 +484,8 @@ pub fn get_chatbar<'a>(cx: &'a Scoped<'a, ChatProps>) -> Element<'a> { message: msg.lines().join("\n"), markdown: state.read().ui.should_transform_markdown_text(), transform_ascii_emojis: state.read().ui.should_transform_ascii_emojis(), + state: state, + chat: chat_data.read().active_chat.id(), UserImage { image: profile_picture, platform: platform, diff --git a/ui/src/layouts/chats/presentation/messages/mod.rs b/ui/src/layouts/chats/presentation/messages/mod.rs index 6ff13af5151..0c0a5196e1c 100644 --- a/ui/src/layouts/chats/presentation/messages/mod.rs +++ b/ui/src/layouts/chats/presentation/messages/mod.rs @@ -526,6 +526,8 @@ struct MessageProps<'a> { fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { //log::trace!("render message {}", &cx.props.message.message.key); let state = use_shared_state::(cx)?; + let chat_data = use_shared_state::(cx)?; + let pending_downloads = use_shared_state::(cx)?; let user_did = state.read().did_key(); @@ -573,12 +575,7 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { let should_transform_ascii_emojis = state.read().ui.should_transform_ascii_emojis(); let msg_lines = message.inner.lines().join("\n"); - let is_mention = message.is_mention; - let rendered_lines = message - .lines_to_render - .as_ref() - .unwrap_or(&msg_lines) - .clone(); + let is_mention = message.clone().is_mention_self(&user_did); let preview_file_in_the_message: &UseState<(bool, Option)> = use_state(cx, || (false, None)); @@ -632,6 +629,8 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { replier_did: user_did_2.clone(), markdown: render_markdown, transform_ascii_emojis: should_transform_ascii_emojis, + state: state, + chat: chat_data.read().active_chat.id(), user_image: cx.render(rsx!(UserImage { loading: false, platform: reply_user.platform().into(), @@ -646,9 +645,10 @@ fn render_message<'a>(cx: Scope<'a, MessageProps<'a>>) -> Element<'a> { editing: is_editing, remote: cx.props.is_remote, with_text: msg_lines, - tagged_text: rendered_lines, is_mention: is_mention, reactions: reactions_list, + state: state, + chat: chat_data.read().active_chat.id(), order: if grouped_message.is_first { Order::First } else if grouped_message.is_last { Order::Last } else { Order::Middle }, attachments: message .inner diff --git a/ui/src/layouts/chats/presentation/sidebar/mod.rs b/ui/src/layouts/chats/presentation/sidebar/mod.rs index 0e634e7d37a..e9a1023ebe1 100644 --- a/ui/src/layouts/chats/presentation/sidebar/mod.rs +++ b/ui/src/layouts/chats/presentation/sidebar/mod.rs @@ -3,7 +3,6 @@ mod search; use common::language::{get_local_text, get_local_text_with_args}; use common::state::{self, identity_search_result, Action, Chat, Identity, State}; -use common::warp_runner::ui_adapter::format_mentions; use common::warp_runner::{RayGunCmd, WarpCmd}; use common::{icons::outline::Shape as Icon, WARP_CMD_CH}; use dioxus::html::input_data::keyboard_types::Code; @@ -299,8 +298,7 @@ pub fn Sidebar(cx: Scope) -> Element { let subtext_val = match unwrapped_message.lines().iter().map(|x| x.trim()).find(|x| !x.is_empty()) { Some(v) => { - let (line, _) = format_mentions(v.to_string(), &participants, &state.read().get_own_identity().did_key(), true); - format_text(&line, markdown, should_transform_ascii_emojis) + format_text(v, markdown, should_transform_ascii_emojis, Some((&state.read(), &chat.id, true))) } _ => match &unwrapped_message.attachments()[..] { [] => get_local_text("sidebar.chat-new"), diff --git a/ui/src/layouts/storage/send_files_layout/mod.rs b/ui/src/layouts/storage/send_files_layout/mod.rs index 9bea337add5..50392bf815d 100644 --- a/ui/src/layouts/storage/send_files_layout/mod.rs +++ b/ui/src/layouts/storage/send_files_layout/mod.rs @@ -143,7 +143,7 @@ fn ChatsToSelect<'a>(cx: Scope<'a, ChatsToSelectProps<'a>>) -> Element<'a> { let is_checked = storage_controller.read().chats_selected_to_send.iter().any(|uuid| {uuid.eq(&chat.id)}); let unwrapped_message = match chat.messages.iter().last() {Some(m) => m.inner.clone(),None => raygun::Message::default()}; let subtext_val = match unwrapped_message.lines().iter().map(|x| x.trim()).find(|x| !x.is_empty()) { - Some(v) => format_text(v, state.read().ui.should_transform_markdown_text(), state.read().ui.should_transform_ascii_emojis()), + Some(v) => format_text(v, state.read().ui.should_transform_markdown_text(), state.read().ui.should_transform_ascii_emojis(), Some((&state.read(), &chat.id, true))), _ => match &unwrapped_message.attachments()[..] { [] => get_local_text("sidebar.chat-new"), [ file ] => file.name(),