[fractal/fractal-next] mentions: Add initial support for sending mentions



commit 978e048500634a752aa059e6c21e18f5ab3560ad
Author: Enterprisey <59171-enterprisey users noreply gitlab gnome org>
Date:   Sat Dec 4 21:54:10 2021 -0800

    mentions: Add initial support for sending mentions
    
    A Pill in the message field will now result in a ping. There is
    currently no way to add a Pill; you'll have to hack one in.

 src/session/content/room_history/mod.rs | 93 +++++++++++++++++++++++++++++++--
 src/session/room/mod.rs                 | 39 +++-----------
 2 files changed, 95 insertions(+), 37 deletions(-)
---
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index 9dc7dbd9..dec58d4e 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -12,11 +12,16 @@ use gtk::{
     gdk, glib, glib::clone, glib::signal::Inhibit, prelude::*, subclass::prelude::*,
     CompositeTemplate,
 };
+use matrix_sdk::ruma::events::room::message::{
+    EmoteMessageEventContent, FormattedBody, MessageType, RoomMessageEventContent,
+    TextMessageEventContent,
+};
 use sourceview::prelude::*;
 
-use crate::components::{CustomEntry, RoomTitle};
+use crate::components::{CustomEntry, Pill, RoomTitle};
 use crate::session::content::{MarkdownPopover, RoomDetails};
 use crate::session::room::{Item, Room, RoomType};
+use crate::session::user::UserExt;
 
 mod imp {
     use super::*;
@@ -373,12 +378,92 @@ impl RoomHistory {
         let priv_ = imp::RoomHistory::from_instance(self);
         let buffer = priv_.message_entry.buffer();
         let (start_iter, end_iter) = buffer.bounds();
-        let body = buffer.text(&start_iter, &end_iter, true);
+        let body_len = buffer.text(&start_iter, &end_iter, true).len();
+
+        let is_markdown = priv_.md_enabled.get();
+        let mut has_mentions = false;
+        let mut plain_body = String::with_capacity(body_len);
+        // formatted_body is Markdown if is_markdown is true, and HTML if false.
+        let mut formatted_body = String::with_capacity(body_len);
+        // uncopied_text_location is the start of the text we haven't copied to plain_body and 
formatted_body.
+        let mut uncopied_text_location = start_iter.clone();
+
+        let mut iter = start_iter;
+        loop {
+            if let Some(anchor) = iter.child_anchor() {
+                let widgets = anchor.widgets();
+                let pill = widgets.first().unwrap().downcast_ref::<Pill>().unwrap();
+                let (url, label) = pill
+                    .user()
+                    .map(|user| {
+                        (
+                            user.user_id().matrix_to_url().to_string(),
+                            user.display_name(),
+                        )
+                    })
+                    .or(pill.room().map(|room| {
+                        (
+                            // No server name needed. matrix.to URIs for mentions aren't routable
+                            room.room_id().matrix_to_url([]).to_string(),
+                            room.display_name(),
+                        )
+                    }))
+                    .unwrap();
+
+                // Add more uncopied characters from message
+                let some_text = buffer.text(&uncopied_text_location, &iter, false);
+                plain_body.push_str(&some_text);
+                formatted_body.push_str(&some_text);
+                uncopied_text_location = iter.clone();
+
+                // Add mention
+                has_mentions = true;
+                plain_body.push_str(&label);
+                formatted_body.push_str(&if is_markdown {
+                    format!("[{}]({})", label, url)
+                } else {
+                    format!("<a href='{}'>{}</a>", url, label)
+                });
+            }
+            if !iter.forward_char() {
+                // Add remaining uncopied characters
+                let some_text = buffer.text(&uncopied_text_location, &iter, false);
+                plain_body.push_str(&some_text);
+                formatted_body.push_str(&some_text);
+                break;
+            }
+        }
 
-        if let Some(room) = &*priv_.room.borrow() {
-            room.send_text_message(body.as_str(), priv_.md_enabled.get());
+        let is_emote = plain_body.starts_with("/me ");
+        if is_emote {
+            plain_body.replace_range(.."/me ".len(), "");
+            formatted_body.replace_range(.."/me ".len(), "");
         }
 
+        let html_body = if is_markdown {
+            FormattedBody::markdown(formatted_body).map(|b| b.body)
+        } else if has_mentions {
+            // Already formatted with HTML
+            Some(formatted_body)
+        } else {
+            None
+        };
+
+        let content = RoomMessageEventContent::new(if is_emote {
+            MessageType::Emote(if let Some(html_body) = html_body {
+                EmoteMessageEventContent::html(plain_body, html_body)
+            } else {
+                EmoteMessageEventContent::plain(plain_body)
+            })
+        } else {
+            MessageType::Text(if let Some(html_body) = html_body {
+                TextMessageEventContent::html(plain_body, html_body)
+            } else {
+                TextMessageEventContent::plain(plain_body)
+            })
+        });
+
+        self.room().unwrap().send_message(content);
         buffer.set_text("");
     }
 
diff --git a/src/session/room/mod.rs b/src/session/room/mod.rs
index 085536d8..c3f601c8 100644
--- a/src/session/room/mod.rs
+++ b/src/session/room/mod.rs
@@ -33,13 +33,8 @@ use matrix_sdk::{
         api::client::r0::sync::sync_events::InvitedRoom,
         events::{
             room::{
-                member::MembershipState,
-                message::{
-                    EmoteMessageEventContent, MessageType, RoomMessageEventContent,
-                    TextMessageEventContent,
-                },
-                name::RoomNameEventContent,
-                topic::RoomTopicEventContent,
+                member::MembershipState, message::RoomMessageEventContent,
+                name::RoomNameEventContent, topic::RoomTopicEventContent,
             },
             tag::TagName,
             AnyRoomAccountDataEvent, AnyStateEventContent, AnyStrippedStateEvent,
@@ -846,40 +841,18 @@ impl Room {
         );
     }
 
-    pub fn send_text_message(&self, body: &str, markdown_enabled: bool) {
-        let content = if let Some(body) = body.strip_prefix("/me ") {
-            let emote = if markdown_enabled {
-                EmoteMessageEventContent::markdown(body)
-            } else {
-                EmoteMessageEventContent::plain(body)
-            };
-            RoomMessageEventContent::new(MessageType::Emote(emote))
-        } else {
-            let text = if markdown_enabled {
-                TextMessageEventContent::markdown(body)
-            } else {
-                TextMessageEventContent::plain(body)
-            };
-            RoomMessageEventContent::new(MessageType::Text(text))
-        };
+    pub fn send_message(&self, content: RoomMessageEventContent) {
+        let priv_ = imp::Room::from_instance(self);
 
         let txn_id = Uuid::new_v4();
-
-        let pending_event = AnySyncMessageEvent::RoomMessage(SyncMessageEvent {
-            content,
+        let event = AnySyncMessageEvent::RoomMessage(SyncMessageEvent {
+            content: content.clone(),
             event_id: EventId::try_from(format!("${}:fractal.gnome.org", txn_id)).unwrap(),
             sender: self.session().user().unwrap().user_id().clone(),
             origin_server_ts: MilliSecondsSinceUnixEpoch::now(),
             unsigned: Unsigned::default(),
         });
 
-        self.send_message(txn_id, pending_event);
-    }
-
-    pub fn send_message(&self, txn_id: Uuid, event: AnySyncMessageEvent) {
-        let priv_ = imp::Room::from_instance(self);
-        let content = event.content();
-
         if let MatrixRoom::Joined(matrix_room) = self.matrix_room() {
             let json = serde_json::to_string(&AnySyncRoomEvent::Message(event)).unwrap();
             let raw_event: Raw<AnySyncRoomEvent> =


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]