[fractal] room-history: Allow to send replies
- From: Kévin Commaille <kcommaille src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [fractal] room-history: Allow to send replies
- Date: Thu, 15 Sep 2022 16:56:39 +0000 (UTC)
commit e35c2b44644613654ffd0f2df890225a2188e90d
Author: Kévin Commaille <zecakeh tedomum fr>
Date: Fri Aug 19 13:34:37 2022 +0200
room-history: Allow to send replies
Cargo.lock | 24 +++
Cargo.toml | 7 +-
data/resources/style.css | 22 ++-
data/resources/ui/content-room-history.ui | 194 ++++++++++++++--------
data/resources/ui/event-menu.ui | 4 +
src/session/content/room_history/item_row.rs | 1 -
src/session/content/room_history/mod.rs | 237 +++++++++++++++++++++++++--
src/session/room/event_actions.rs | 18 +-
8 files changed, 423 insertions(+), 84 deletions(-)
---
diff --git a/Cargo.lock b/Cargo.lock
index 6964af93d..35889f7a6 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -3100,7 +3100,9 @@ version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259"
dependencies = [
+ "phf_macros",
"phf_shared 0.10.0",
+ "proc-macro-hack",
]
[[package]]
@@ -3143,6 +3145,20 @@ dependencies = [
"rand 0.8.5",
]
+[[package]]
+name = "phf_macros"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0"
+dependencies = [
+ "phf_generator 0.10.0",
+ "phf_shared 0.10.0",
+ "proc-macro-hack",
+ "proc-macro2 1.0.43",
+ "quote 1.0.21",
+ "syn 1.0.99",
+]
+
[[package]]
name = "phf_shared"
version = "0.8.0"
@@ -3328,6 +3344,12 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "proc-macro-hack"
+version = "0.5.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5"
+
[[package]]
name = "proc-macro2"
version = "0.4.30"
@@ -3657,6 +3679,7 @@ dependencies = [
"bytes",
"form_urlencoded",
"getrandom 0.2.7",
+ "html5ever 0.25.2",
"http",
"indexmap",
"itoa",
@@ -3664,6 +3687,7 @@ dependencies = [
"js_int",
"js_option",
"percent-encoding",
+ "phf 0.10.1",
"pulldown-cmark",
"rand 0.8.5",
"regex",
diff --git a/Cargo.toml b/Cargo.toml
index 561777613..9a2f18e2a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -78,4 +78,9 @@ features = ["socks", "sso-login", "markdown", "qrcode", "experimental-timeline"]
[dependencies.ruma]
git = "https://github.com/ruma/ruma"
rev = "c745d3baf720b38a254e640a526717864e87a065"
-features = ["unstable-pre-spec", "client-api-c"]
+features = [
+ "unstable-pre-spec",
+ "client-api-c",
+ "unstable-msc3440",
+ "unstable-sanitize",
+]
diff --git a/data/resources/style.css b/data/resources/style.css
index 9ec717352..b0c84e9d2 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -418,6 +418,10 @@ login {
margin-right: 46px;
}
+.room-history row.highlight {
+ background-color: alpha(@accent_bg_color, .1);
+}
+
.room-history .event-content .emoji {
font-size: 3em;
}
@@ -438,7 +442,8 @@ login {
padding: 2px 5px;
}
-.room-history .event-content .quote {
+.room-history .event-content .quote,
+.related-event-content {
border-left: 2px solid @accent_bg_color;
padding-left: 6px;
opacity: 0.7;
@@ -525,6 +530,21 @@ message-reactions .reaction-count {
margin-bottom: 0px;
}
+.related-event-toolbar {
+ padding: 0 6px 0 12px;
+}
+
+.related-event-toolbar button {
+ margin: 12px 6px;
+ min-height: 24px;
+ min-width: 24px;
+}
+
+.related-event-content {
+ padding-top: 2px;
+ padding-bottom: 2px;
+}
+
/* Event Source Dialog */
diff --git a/data/resources/ui/content-room-history.ui b/data/resources/ui/content-room-history.ui
index 8be8f7541..9c681e203 100644
--- a/data/resources/ui/content-room-history.ui
+++ b/data/resources/ui/content-room-history.ui
@@ -229,83 +229,143 @@
<property name="tightening-threshold">550</property>
<child>
<object class="GtkBox">
- <style>
- <class name="toolbar"/>
- </style>
- <child>
- <object class="GtkMenuButton" id="markdown_button">
- <property name="valign">end</property>
- <property name="direction">up</property>
- <property name="icon-name">format-justify-left-symbolic</property>
- <property name="popover">
- <object class="MarkdownPopover">
- <property name="markdown-enabled" bind-source="ContentRoomHistory"
bind-property="markdown-enabled" bind-flags="sync-create | bidirectional"/>
- </object>
- </property>
- <accessibility>
- <property name="label" translatable="yes">Enable Markdown Formatting</property>
- </accessibility>
- </object>
- </child>
+ <property name="orientation">vertical</property>
<child>
- <object class="CustomEntry">
+ <object class="GtkBox" id="related_event_toolbar">
+ <style>
+ <class name="related-event-toolbar"/>
+ </style>
+ <property name="spacing">12</property>
+ <binding name="visible">
+ <closure type="gboolean" function="object_is_some">
+ <lookup name="related-event">ContentRoomHistory</lookup>
+ </closure>
+ </binding>
<child>
- <object class="GtkScrolledWindow">
- <property name="vexpand">True</property>
- <property name="hexpand">True</property>
- <property name="vscrollbar-policy">external</property>
- <property name="max-content-height">200</property>
- <property name="propagate-natural-height">True</property>
- <property name="child">
- <object class="GtkSourceView" id="message_entry">
- <property name="hexpand">True</property>
- <property name="accepts-tab">False</property>
- <property name="top-margin">7</property>
- <property name="bottom-margin">7</property>
- <property name="wrap-mode">word</property>
- <accessibility>
- <property name="label" translatable="yes">Message Entry</property>
- </accessibility>
+ <object class="GtkBox">
+ <property name="margin-bottom">6</property>
+ <property name="margin-top">8</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="LabelWithWidgets" id="related_event_header">
+ <style>
+ <class name="heading"/>
+ </style>
+ <property name="valign">center</property>
+ <property name="hexpand">true</property>
+ <property name="margin-top">2</property>
</object>
- </property>
+ </child>
+ <child>
+ <object class="ContentMessageContent" id="related_event_content">
+ <style>
+ <class name="related-event-content"/>
+ <class name="dim-label"/>
+ </style>
+ <property name="format">ellipsized</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkGestureClick">
+ <signal name="pressed" handler="handle_related_event_click" swapped="yes"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <style>
+ <class name="circular"/>
+ </style>
+ <property name="halign">end</property>
+ <property name="valign">start</property>
+ <property name="icon-name">window-close-symbolic</property>
+ <property name="action-name">room-history.clear-related-event</property>
</object>
</child>
</object>
</child>
<child>
- <object class="GtkButton">
- <property name="valign">end</property>
- <property name="icon-name">emoji-people-symbolic</property>
- <property name="action-name">room-history.open-emoji</property>
- <accessibility>
- <property name="label" translatable="yes">Open Emoji Picker</property>
- </accessibility>
- </object>
- </child>
- <child>
- <object class="GtkMenuButton">
- <property name="valign">end</property>
- <property name="direction">up</property>
- <property name="icon-name">view-more-horizontal-symbolic</property>
- <property name="menu-model">message-menu-model</property>
- <accessibility>
- <property name="label" translatable="yes">Open Message Menu</property>
- </accessibility>
- </object>
- </child>
- <child>
- <object class="GtkButton">
- <property name="valign">end</property>
- <property name="icon-name">send-symbolic</property>
- <property name="focus-on-click">False</property>
- <property name="action-name">room-history.send-text-message</property>
+ <object class="GtkBox">
<style>
- <class name="suggested-action"/>
- <class name="circular"/>
+ <class name="toolbar"/>
</style>
- <accessibility>
- <property name="label" translatable="yes">Send Message</property>
- </accessibility>
+ <child>
+ <object class="GtkMenuButton" id="markdown_button">
+ <property name="valign">end</property>
+ <property name="direction">up</property>
+ <property name="icon-name">format-justify-left-symbolic</property>
+ <property name="popover">
+ <object class="MarkdownPopover">
+ <property name="markdown-enabled" bind-source="ContentRoomHistory"
bind-property="markdown-enabled" bind-flags="sync-create | bidirectional"/>
+ </object>
+ </property>
+ <accessibility>
+ <property name="label" translatable="yes">Enable Markdown Formatting</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="CustomEntry">
+ <child>
+ <object class="GtkScrolledWindow">
+ <property name="vexpand">True</property>
+ <property name="hexpand">True</property>
+ <property name="vscrollbar-policy">external</property>
+ <property name="max-content-height">200</property>
+ <property name="propagate-natural-height">True</property>
+ <property name="child">
+ <object class="GtkSourceView" id="message_entry">
+ <property name="hexpand">True</property>
+ <property name="accepts-tab">False</property>
+ <property name="top-margin">7</property>
+ <property name="bottom-margin">7</property>
+ <property name="wrap-mode">word</property>
+ <accessibility>
+ <property name="label" translatable="yes">Message Entry</property>
+ </accessibility>
+ </object>
+ </property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="valign">end</property>
+ <property name="icon-name">emoji-people-symbolic</property>
+ <property name="action-name">room-history.open-emoji</property>
+ <accessibility>
+ <property name="label" translatable="yes">Open Emoji Picker</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuButton">
+ <property name="valign">end</property>
+ <property name="direction">up</property>
+ <property name="icon-name">view-more-horizontal-symbolic</property>
+ <property name="menu-model">message-menu-model</property>
+ <accessibility>
+ <property name="label" translatable="yes">Open Message Menu</property>
+ </accessibility>
+ </object>
+ </child>
+ <child>
+ <object class="GtkButton">
+ <property name="valign">end</property>
+ <property name="icon-name">send-symbolic</property>
+ <property name="focus-on-click">False</property>
+ <property name="action-name">room-history.send-text-message</property>
+ <style>
+ <class name="suggested-action"/>
+ <class name="circular"/>
+ </style>
+ <accessibility>
+ <property name="label" translatable="yes">Send Message</property>
+ </accessibility>
+ </object>
+ </child>
</object>
</child>
</object>
diff --git a/data/resources/ui/event-menu.ui b/data/resources/ui/event-menu.ui
index 3a8cb63c3..fdae72c88 100644
--- a/data/resources/ui/event-menu.ui
+++ b/data/resources/ui/event-menu.ui
@@ -8,16 +8,19 @@
</section>
<section>
<item>
+ <!-- Translators: In this string, 'Reply' is a verb. -->
<attribute name="label" translatable="yes">_Reply</attribute>
<attribute name="action">event.reply</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
+ <!-- Translators: In this string, 'Edit' is a verb. -->
<attribute name="label" translatable="yes">_Edit</attribute>
<attribute name="action">event.edit</attribute>
<attribute name="hidden-when">action-missing</attribute>
</item>
<item>
+ <!-- Translators: In this string, 'Forward' is a verb. -->
<attribute name="label" translatable="yes">_Forward</attribute>
<attribute name="action">event.forward</attribute>
<attribute name="hidden-when">action-missing</attribute>
@@ -108,6 +111,7 @@
<menu id="state_menu_model">
<section>
<item>
+ <!-- Translators: In this string, 'Forward' is a verb. -->
<attribute name="label" translatable="yes">_Forward</attribute>
<attribute name="action">event.forward</attribute>
<attribute name="hidden-when">action-missing</attribute>
diff --git a/src/session/content/room_history/item_row.rs b/src/session/content/room_history/item_row.rs
index 1511cf5cb..67b1a20ba 100644
--- a/src/session/content/room_history/item_row.rs
+++ b/src/session/content/room_history/item_row.rs
@@ -5,7 +5,6 @@ use matrix_sdk::ruma::events::AnySyncTimelineEvent;
use crate::{
components::{ContextMenuBin, ContextMenuBinExt, ContextMenuBinImpl, ReactionChooser},
- prelude::*,
session::{
content::room_history::{message_row::MessageRow, DividerRow, RoomHistory, StateRow},
room::{
diff --git a/src/session/content/room_history/mod.rs b/src/session/content/room_history/mod.rs
index e4af34750..21203885a 100644
--- a/src/session/content/room_history/mod.rs
+++ b/src/session/content/room_history/mod.rs
@@ -17,24 +17,33 @@ use futures::TryFutureExt;
use gettextrs::gettext;
use gtk::{
gdk, gio, glib,
- glib::{clone, signal::Inhibit},
+ glib::{clone, signal::Inhibit, FromVariant},
prelude::*,
CompositeTemplate,
};
use log::{error, warn};
-use matrix_sdk::ruma::events::room::message::{
- EmoteMessageEventContent, FormattedBody, MessageType, RoomMessageEventContent,
- TextMessageEventContent,
+use matrix_sdk::ruma::{
+ events::{
+ room::message::{
+ EmoteMessageEventContent, FormattedBody, MessageType, TextMessageEventContent,
+ },
+ AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent,
+ },
+ EventId,
+};
+use ruma::events::{
+ room::message::{ForwardThread, LocationMessageEventContent, RoomMessageEventContent},
+ AnyMessageLikeEventContent,
};
-use ruma::events::{room::message::LocationMessageEventContent, AnyMessageLikeEventContent};
use sourceview::prelude::*;
use self::{
attachment_dialog::AttachmentDialog, completion::CompletionPopover, divider_row::DividerRow,
- item_row::ItemRow, state_row::StateRow, verification_info_bar::VerificationInfoBar,
+ item_row::ItemRow, message_row::content::MessageContent, state_row::StateRow,
+ verification_info_bar::VerificationInfoBar,
};
use crate::{
- components::{CustomEntry, DragOverlay, Pill, ReactionChooser, RoomTitle},
+ components::{CustomEntry, DragOverlay, LabelWithWidgets, Pill, ReactionChooser, RoomTitle},
i18n::gettext_f,
session::{
content::{room_details, MarkdownPopover, RoomDetails},
@@ -42,9 +51,23 @@ use crate::{
user::UserExt,
},
spawn, spawn_tokio, toast,
- utils::filename_for_mime,
+ utils::{filename_for_mime, TemplateCallbacks},
};
+#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy, glib::Enum)]
+#[repr(i32)]
+#[enum_type(name = "RelatedEventType")]
+pub enum RelatedEventType {
+ None = 0,
+ Reply = 1,
+}
+
+impl Default for RelatedEventType {
+ fn default() -> Self {
+ Self::None
+ }
+}
+
mod imp {
use std::cell::{Cell, RefCell};
@@ -98,6 +121,12 @@ mod imp {
#[template_child]
pub drag_overlay: TemplateChild<DragOverlay>,
pub invite_action_watch: RefCell<Option<gtk::ExpressionWatch>>,
+ #[template_child]
+ pub related_event_header: TemplateChild<LabelWithWidgets>,
+ #[template_child]
+ pub related_event_content: TemplateChild<MessageContent>,
+ pub related_event_type: Cell<RelatedEventType>,
+ pub related_event: RefCell<Option<SupportedEvent>>,
}
#[glib::object_subclass]
@@ -113,6 +142,8 @@ mod imp {
VerificationInfoBar::static_type();
Timeline::static_type();
Self::bind_template(klass);
+ Self::Type::bind_template_callbacks(klass);
+ TemplateCallbacks::bind_template_callbacks(klass);
klass.set_accessible_role(gtk::AccessibleRole::Group);
klass.install_action(
"room-history.send-text-message",
@@ -174,6 +205,27 @@ mod imp {
}
}));
});
+
+ klass.install_action(
+ "room-history.clear-related-event",
+ None,
+ move |widget, _, _| widget.clear_related_event(),
+ );
+
+ klass.install_action("room-history.reply", Some("s"), move |widget, _, v| {
+ if let Some(event_id) = v
+ .and_then(String::from_variant)
+ .and_then(|s| EventId::parse(s).ok())
+ {
+ if let Some(event) = widget
+ .room()
+ .and_then(|room| room.timeline().event_by_id(&event_id))
+ .and_then(|event| event.downcast().ok())
+ {
+ widget.set_reply_to(event);
+ }
+ }
+ });
}
fn instance_init(obj: &InitializingObject<Self>) {
@@ -221,6 +273,21 @@ mod imp {
true,
glib::ParamFlags::READWRITE,
),
+ glib::ParamSpecEnum::new(
+ "related-event-type",
+ "Related event type",
+ "The type of related event of the composer",
+ RelatedEventType::static_type(),
+ RelatedEventType::default() as i32,
+ glib::ParamFlags::READABLE,
+ ),
+ glib::ParamSpecObject::new(
+ "related-event",
+ "Related Event",
+ "The related event of the composer",
+ SupportedEvent::static_type(),
+ glib::ParamFlags::READABLE,
+ ),
]
});
@@ -264,6 +331,8 @@ mod imp {
"empty" => obj.room().is_none().to_value(),
"markdown-enabled" => self.md_enabled.get().to_value(),
"sticky" => obj.sticky().to_value(),
+ "related-event-type" => obj.related_event_type().to_value(),
+ "related-event" => obj.related_event().to_value(),
_ => unimplemented!(),
}
}
@@ -319,6 +388,12 @@ mod imp {
}
obj.start_loading();
}));
+ adj.connect_page_size_notify(clone!(@weak obj => move |_| {
+ if obj.sticky() {
+ obj.scroll_down();
+ }
+ obj.start_loading();
+ }));
let key_events = gtk::EventControllerKey::new();
self.message_entry.add_controller(&key_events);
@@ -349,9 +424,12 @@ mod imp {
key_events
.connect_key_pressed(clone!(@weak obj => @default-return Inhibit(false), move |_, key, _,
modifier| {
- if !modifier.contains(gdk::ModifierType::SHIFT_MASK) && (key == gdk::Key::Return || key ==
gdk::Key::KP_Enter) {
+ if modifier.is_empty() && (key == gdk::Key::Return || key == gdk::Key::KP_Enter) {
obj.activate_action("room-history.send-text-message", None).unwrap();
Inhibit(true)
+ } else if modifier.is_empty() && key == gdk::Key::Escape && obj.related_event_type() !=
RelatedEventType::None {
+ obj.clear_related_event();
+ Inhibit(true)
} else {
Inhibit(false)
}
@@ -408,6 +486,7 @@ glib::wrapper! {
@extends gtk::Widget, adw::Bin, @implements gtk::Accessible;
}
+#[gtk::template_callbacks]
impl RoomHistory {
pub fn new() -> Self {
glib::Object::new(&[]).expect("Failed to create RoomHistory")
@@ -436,6 +515,8 @@ impl RoomHistory {
if let Some(invite_action) = priv_.invite_action_watch.take() {
invite_action.unwatch();
}
+
+ self.clear_related_event();
}
if let Some(ref room) = room {
@@ -491,6 +572,64 @@ impl RoomHistory {
self.imp().room.borrow().clone()
}
+ pub fn related_event_type(&self) -> RelatedEventType {
+ self.imp().related_event_type.get()
+ }
+
+ fn set_related_event_type(&self, related_type: RelatedEventType) {
+ if self.related_event_type() == related_type {
+ return;
+ }
+
+ self.imp().related_event_type.set(related_type);
+ self.notify("related-event-type");
+ }
+
+ pub fn related_event(&self) -> Option<SupportedEvent> {
+ self.imp().related_event.borrow().clone()
+ }
+
+ fn set_related_event(&self, event: Option<SupportedEvent>) {
+ let prev_event = self.related_event();
+
+ if prev_event == event {
+ return;
+ }
+
+ if let Some(event) = &prev_event {
+ self.set_event_highlight(&event.event_id(), false);
+ }
+ if let Some(event) = &event {
+ self.set_event_highlight(&event.event_id(), true);
+ }
+
+ self.imp().related_event.replace(event);
+ self.notify("related-event");
+ }
+
+ pub fn clear_related_event(&self) {
+ self.set_related_event(None);
+ self.set_related_event_type(RelatedEventType::default());
+ }
+
+ pub fn set_reply_to(&self, event: SupportedEvent) {
+ let priv_ = self.imp();
+ priv_
+ .related_event_header
+ .set_widgets(vec![Pill::for_user(event.sender().upcast_ref())]);
+ priv_
+ .related_event_header
+ // Translators: Do NOT translate the content between '{' and '}',
+ // this is a variable name. In this string, 'Reply' is a noun.
+ .set_label(Some(gettext_f("Reply to {user}", &[("user", "<widget>")])));
+
+ priv_.related_event_content.update_for_event(&event);
+
+ self.set_related_event_type(RelatedEventType::Reply);
+ self.set_related_event(Some(event));
+ priv_.message_entry.grab_focus();
+ }
+
/// Get an iterator over chunks of the message entry's text between the
/// given start and end, split by mentions.
fn split_buffer_mentions(&self, start: gtk::TextIter, end: gtk::TextIter) -> SplitMentions {
@@ -542,22 +681,47 @@ impl RoomHistory {
None
};
- let content = RoomMessageEventContent::new(if is_emote {
+ let content = if is_emote {
MessageType::Emote(if let Some(html_body) = html_body {
EmoteMessageEventContent::html(plain_body, html_body)
} else {
EmoteMessageEventContent::plain(plain_body)
})
+ .into()
} else {
- MessageType::Text(if let Some(html_body) = html_body {
+ let msg_type = MessageType::Text(if let Some(html_body) = html_body {
TextMessageEventContent::html(plain_body, html_body)
} else {
TextMessageEventContent::plain(plain_body)
- })
- });
+ });
+
+ if self.related_event_type() == RelatedEventType::Reply {
+ // TODO: Use replacement event.
+ let related_event = self.related_event().unwrap().matrix_event();
+ if let AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
+ SyncMessageLikeEvent::Original(related_message_event),
+ )) = related_event
+ {
+ let full_related_message_event = related_message_event
+ .into_full_event(self.room().unwrap().room_id().to_owned());
+ RoomMessageEventContent::reply(
+ msg_type,
+ &full_related_message_event,
+ ForwardThread::Yes,
+ )
+ } else {
+ // The message was redacted after being selected so ignore
+ // the reply.
+ msg_type.into()
+ }
+ } else {
+ msg_type.into()
+ }
+ };
self.room().unwrap().send_room_message_event(content);
buffer.set_text("");
+ self.clear_related_event();
}
pub fn leave(&self) {
@@ -992,6 +1156,53 @@ impl RoomHistory {
self.clipboard().set_text(&content);
}
}
+
+ #[template_callback]
+ fn handle_related_event_click(&self, n_pressed: i32) {
+ if n_pressed == 1 {
+ if let Some(related_event) = &*self.imp().related_event.borrow() {
+ self.scroll_to_event(&related_event.event_id());
+ }
+ }
+ }
+
+ fn scroll_to_event(&self, event_id: &EventId) {
+ let room = match self.room() {
+ Some(room) => room,
+ None => return,
+ };
+
+ if let Some(pos) = room.timeline().find_event_position(event_id) {
+ let pos = pos as u32;
+ let _ = self
+ .imp()
+ .listview
+ .activate_action("list.scroll-to-item", Some(&pos.to_variant()));
+ }
+ }
+
+ fn set_event_highlight(&self, event_id: &EventId, highlight: bool) {
+ let mut child = self.imp().listview.first_child();
+ while let Some(widget) = child {
+ if widget
+ .first_child()
+ .and_then(|w| w.downcast::<ItemRow>().ok())
+ .and_then(|row| row.item())
+ .and_then(|item| item.downcast::<SupportedEvent>().ok())
+ .filter(|event| event.event_id() == event_id)
+ .is_some()
+ {
+ if highlight && !widget.has_css_class("highlight") {
+ widget.add_css_class("highlight");
+ } else if !highlight && widget.has_css_class("highlight") {
+ widget.remove_css_class("highlight");
+ }
+
+ break;
+ }
+ child = widget.next_sibling();
+ }
+ }
}
impl Default for RoomHistory {
diff --git a/src/session/room/event_actions.rs b/src/session/room/event_actions.rs
index 2e750c4a9..0e2fb165f 100644
--- a/src/session/room/event_actions.rs
+++ b/src/session/room/event_actions.rs
@@ -125,6 +125,8 @@ where
.map(|user| user.user_id())
.unwrap();
let user = event.room().members().member_by_id(user_id);
+
+ // Remove message
if event.sender() == user
|| event
.room()
@@ -132,7 +134,6 @@ where
.min_level_for_room_action(&RoomAction::Redact)
<= user.power_level()
{
- // Remove message
gtk_macros::action!(
&action_group,
"remove",
@@ -141,6 +142,7 @@ where
})
);
}
+
// Send/redact a reaction
gtk_macros::action!(
&action_group,
@@ -161,6 +163,20 @@ where
}
})
);
+
+ // Reply
+ gtk_macros::action!(
+ &action_group,
+ "reply",
+ None,
+ clone!(@weak event, @weak self as widget => move |_, _| {
+ let _ = widget.activate_action(
+ "room-history.reply",
+ Some(&event.event_id().as_str().to_variant())
+ );
+ })
+ );
+
match message.msgtype {
// Copy Text-Message
MessageType::Text(text_message) => {
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]