diff --git a/Cargo.lock b/Cargo.lock
index 40a96578..6964af93 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1062,11 +1062,13 @@ dependencies = [
"mime_guess",
"num_enum",
"once_cell",
+ "pulldown-cmark",
"qrcode",
"rand 0.8.5",
"regex",
"rqrr",
"ruma",
+ "secular",
"serde",
"serde_json",
"sourceview5",
@@ -1380,6 +1382,15 @@ dependencies = [
"version_check",
]
+[[package]]
+name = "getopts"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5"
+dependencies = [
+ "unicode-width",
+]
+
[[package]]
name = "getrandom"
version = "0.1.16"
@@ -3365,6 +3376,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d9cc634bc78768157b5cbfe988ffcd1dcba95cd2b2f03a88316c08c6d00ed63"
dependencies = [
"bitflags",
+ "getopts",
"memchr",
"unicase",
]
@@ -3761,6 +3773,15 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
+[[package]]
+name = "secular"
+version = "1.0.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3dc3eccdf599b53eba8a34a1190bd47394948258d1c43dca9cceb2426e25bb5"
+dependencies = [
+ "unicode-normalization",
+]
+
[[package]]
name = "security-framework"
version = "2.7.0"
diff --git a/Cargo.toml b/Cargo.toml
index d053f37e..56177761 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -50,6 +50,8 @@ mime_guess = "2.0.3"
num_enum = "0.5.6"
thiserror = "1.0.25"
rqrr = "0.4.0"
+secular = { version = "1.0.1", features = ["bmp", "normalization"] }
+pulldown-cmark = "0.9.2"
[dependencies.sourceview]
package = "sourceview5"
diff --git a/data/resources/resources.gresource.xml b/data/resources/resources.gresource.xml
index 2346a57a..2339f435 100644
--- a/data/resources/resources.gresource.xml
+++ b/data/resources/resources.gresource.xml
@@ -43,6 +43,8 @@
ui/components-password-entry-row.ui
ui/components-reaction-chooser.ui
ui/components-video-player.ui
+ ui/content-completion-popover.ui
+ ui/content-completion-row.ui
ui/content-divider-row.ui
ui/content-explore-item.ui
ui/content-explore.ui
diff --git a/data/resources/style.css b/data/resources/style.css
index 73ee84b2..c5727594 100644
--- a/data/resources/style.css
+++ b/data/resources/style.css
@@ -496,6 +496,32 @@ message-reactions .reaction-count {
color: @view_fg_color;
}
+.completion-popover contents {
+ padding: 0;
+}
+
+.completion-popover viewport {
+ padding: 8px;
+}
+
+.completion-popover list {
+ background-color: transparent;
+}
+
+.completion-popover .completion-row {
+ border-radius: 6px;
+ margin: 3px 0px;
+ padding: 6px;
+}
+
+.completion-popover .completion-row:first-child {
+ margin-top: 0px;
+}
+
+.completion-popover .completion-row:last-child {
+ margin-bottom: 0px;
+}
+
/* Event Source Dialog */
diff --git a/data/resources/ui/content-completion-popover.ui b/data/resources/ui/content-completion-popover.ui
new file mode 100644
index 00000000..b18caba8
--- /dev/null
+++ b/data/resources/ui/content-completion-popover.ui
@@ -0,0 +1,24 @@
+
+
+
+
+ false
+ false
+ top
+ start
+ center
+ 260
+
+
+
+
+
diff --git a/data/resources/ui/content-completion-row.ui b/data/resources/ui/content-completion-row.ui
new file mode 100644
index 00000000..ff77e0e6
--- /dev/null
+++ b/data/resources/ui/content-completion-row.ui
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+ 10
+
+
+ 40
+
+
+
+
+ 3
+ vertical
+
+
+ 0.0
+ True
+ end
+
+
+
+
+ 0.0
+ True
+ end
+
+
+
+
+
+
+
+
+
diff --git a/src/session/content/room_history/completion/completion_popover.rs b/src/session/content/room_history/completion/completion_popover.rs
new file mode 100644
index 00000000..bd99545e
--- /dev/null
+++ b/src/session/content/room_history/completion/completion_popover.rs
@@ -0,0 +1,849 @@
+use gtk::{
+ gdk, glib,
+ glib::{clone, closure},
+ prelude::*,
+ subclass::prelude::*,
+ CompositeTemplate,
+};
+use pulldown_cmark::{Event, Parser, Tag};
+use ruma::OwnedUserId;
+use secular::lower_lay_string;
+
+use super::CompletionRow;
+use crate::{
+ components::Pill,
+ prelude::*,
+ session::room::{Member, MemberList, Membership},
+};
+
+const MAX_MEMBERS: usize = 32;
+
+#[derive(Debug, Default)]
+pub struct MemberWatch {
+ handlers: Vec,
+ // The position of the member in the list model.
+ position: u32,
+}
+
+mod imp {
+ use std::{
+ cell::{Cell, RefCell},
+ collections::HashMap,
+ };
+
+ use glib::subclass::InitializingObject;
+ use once_cell::sync::Lazy;
+
+ use super::*;
+
+ #[derive(Debug, Default, CompositeTemplate)]
+ #[template(resource = "/org/gnome/Fractal/content-completion-popover.ui")]
+ pub struct CompletionPopover {
+ #[template_child]
+ pub list: TemplateChild,
+ /// The user ID of the current session.
+ pub user_id: RefCell