mirror of
https://github.com/youwen5/iamb.git
synced 2025-06-20 05:39:52 -07:00
Show state events in the timeline (#437)
This commit is contained in:
parent
998e50f4a5
commit
84eaadc09a
7 changed files with 1145 additions and 79 deletions
20
src/base.rs
20
src/base.rs
|
@ -47,6 +47,7 @@ use matrix_sdk::{
|
||||||
},
|
},
|
||||||
room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
|
room::redaction::{OriginalSyncRoomRedactionEvent, SyncRoomRedactionEvent},
|
||||||
tag::{TagName, Tags},
|
tag::{TagName, Tags},
|
||||||
|
AnySyncStateEvent,
|
||||||
MessageLikeEvent,
|
MessageLikeEvent,
|
||||||
},
|
},
|
||||||
presence::PresenceState,
|
presence::PresenceState,
|
||||||
|
@ -836,6 +837,9 @@ pub enum EventLocation {
|
||||||
|
|
||||||
/// The [EventId] belongs to a reaction to the given event.
|
/// The [EventId] belongs to a reaction to the given event.
|
||||||
Reaction(OwnedEventId),
|
Reaction(OwnedEventId),
|
||||||
|
|
||||||
|
/// The [EventId] belongs to a state event in the main timeline of the room.
|
||||||
|
State(MessageKey),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventLocation {
|
impl EventLocation {
|
||||||
|
@ -1003,6 +1007,12 @@ impl RoomInfo {
|
||||||
|
|
||||||
match self.keys.get(redacts) {
|
match self.keys.get(redacts) {
|
||||||
None => return,
|
None => return,
|
||||||
|
Some(EventLocation::State(key)) => {
|
||||||
|
if let Some(msg) = self.messages.get_mut(key) {
|
||||||
|
let ev = SyncRoomRedactionEvent::Original(ev);
|
||||||
|
msg.redact(ev, room_version);
|
||||||
|
}
|
||||||
|
},
|
||||||
Some(EventLocation::Message(None, key)) => {
|
Some(EventLocation::Message(None, key)) => {
|
||||||
if let Some(msg) = self.messages.get_mut(key) {
|
if let Some(msg) = self.messages.get_mut(key) {
|
||||||
let ev = SyncRoomRedactionEvent::Original(ev);
|
let ev = SyncRoomRedactionEvent::Original(ev);
|
||||||
|
@ -1076,6 +1086,7 @@ impl RoomInfo {
|
||||||
content.apply_replacement(new_msgtype);
|
content.apply_replacement(new_msgtype);
|
||||||
},
|
},
|
||||||
MessageEvent::Redacted(_) |
|
MessageEvent::Redacted(_) |
|
||||||
|
MessageEvent::State(_) |
|
||||||
MessageEvent::EncryptedOriginal(_) |
|
MessageEvent::EncryptedOriginal(_) |
|
||||||
MessageEvent::EncryptedRedacted(_) => {
|
MessageEvent::EncryptedRedacted(_) => {
|
||||||
return;
|
return;
|
||||||
|
@ -1085,6 +1096,15 @@ impl RoomInfo {
|
||||||
msg.html = msg.event.html();
|
msg.html = msg.event.html();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn insert_any_state(&mut self, msg: AnySyncStateEvent) {
|
||||||
|
let event_id = msg.event_id().to_owned();
|
||||||
|
let key = (msg.origin_server_ts().into(), event_id.clone());
|
||||||
|
|
||||||
|
let loc = EventLocation::State(key.clone());
|
||||||
|
self.keys.insert(event_id, loc);
|
||||||
|
self.messages.insert_message(key, msg);
|
||||||
|
}
|
||||||
|
|
||||||
/// Indicates whether this room has unread messages.
|
/// Indicates whether this room has unread messages.
|
||||||
pub fn unreads(&self, settings: &ApplicationSettings) -> UnreadInfo {
|
pub fn unreads(&self, settings: &ApplicationSettings) -> UnreadInfo {
|
||||||
let last_message = self.messages.last_key_value();
|
let last_message = self.messages.last_key_value();
|
||||||
|
|
|
@ -10,10 +10,12 @@
|
||||||
//!
|
//!
|
||||||
//! This isn't as important for iamb, since it isn't a browser environment, but we do still map
|
//! This isn't as important for iamb, since it isn't a browser environment, but we do still map
|
||||||
//! input onto an enum of the safe list of tags to keep it easy to understand and process.
|
//! input onto an enum of the safe list of tags to keep it easy to understand and process.
|
||||||
|
use std::borrow::Cow;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use css_color_parser::Color as CssColor;
|
use css_color_parser::Color as CssColor;
|
||||||
use markup5ever_rcdom::{Handle, NodeData, RcDom};
|
use markup5ever_rcdom::{Handle, NodeData, RcDom};
|
||||||
|
use matrix_sdk::ruma::{OwnedRoomAliasId, OwnedRoomId, OwnedUserId};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
@ -34,6 +36,7 @@ use ratatui::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
config::ApplicationSettings,
|
||||||
message::printer::TextPrinter,
|
message::printer::TextPrinter,
|
||||||
util::{join_cell_text, space_text},
|
util::{join_cell_text, space_text},
|
||||||
};
|
};
|
||||||
|
@ -148,7 +151,12 @@ impl Table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_text(&self, width: usize, style: Style, emoji_shortcodes: bool) -> Text {
|
fn to_text<'a>(
|
||||||
|
&'a self,
|
||||||
|
width: usize,
|
||||||
|
style: Style,
|
||||||
|
settings: &'a ApplicationSettings,
|
||||||
|
) -> Text<'a> {
|
||||||
let mut text = Text::default();
|
let mut text = Text::default();
|
||||||
let columns = self.columns();
|
let columns = self.columns();
|
||||||
let cell_total = width.saturating_sub(columns).saturating_sub(1);
|
let cell_total = width.saturating_sub(columns).saturating_sub(1);
|
||||||
|
@ -167,7 +175,7 @@ impl Table {
|
||||||
if let Some(caption) = &self.caption {
|
if let Some(caption) = &self.caption {
|
||||||
let subw = width.saturating_sub(6);
|
let subw = width.saturating_sub(6);
|
||||||
let mut printer =
|
let mut printer =
|
||||||
TextPrinter::new(subw, style, true, emoji_shortcodes).align(Alignment::Center);
|
TextPrinter::new(subw, style, true, settings).align(Alignment::Center);
|
||||||
caption.print(&mut printer, style);
|
caption.print(&mut printer, style);
|
||||||
|
|
||||||
for mut line in printer.finish().lines {
|
for mut line in printer.finish().lines {
|
||||||
|
@ -214,7 +222,7 @@ impl Table {
|
||||||
CellType::Data => style,
|
CellType::Data => style,
|
||||||
};
|
};
|
||||||
|
|
||||||
cell.to_text(*w, style, emoji_shortcodes)
|
cell.to_text(*w, style, settings)
|
||||||
} else {
|
} else {
|
||||||
space_text(*w, style)
|
space_text(*w, style)
|
||||||
};
|
};
|
||||||
|
@ -271,13 +279,21 @@ pub enum StyleTreeNode {
|
||||||
Ruler,
|
Ruler,
|
||||||
Style(Box<StyleTreeNode>, Style),
|
Style(Box<StyleTreeNode>, Style),
|
||||||
Table(Table),
|
Table(Table),
|
||||||
Text(String),
|
Text(Cow<'static, str>),
|
||||||
Sequence(StyleTreeChildren),
|
Sequence(StyleTreeChildren),
|
||||||
|
RoomAlias(OwnedRoomAliasId),
|
||||||
|
RoomId(OwnedRoomId),
|
||||||
|
UserId(OwnedUserId),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleTreeNode {
|
impl StyleTreeNode {
|
||||||
pub fn to_text(&self, width: usize, style: Style, emoji_shortcodes: bool) -> Text {
|
pub fn to_text<'a>(
|
||||||
let mut printer = TextPrinter::new(width, style, true, emoji_shortcodes);
|
&'a self,
|
||||||
|
width: usize,
|
||||||
|
style: Style,
|
||||||
|
settings: &'a ApplicationSettings,
|
||||||
|
) -> Text<'a> {
|
||||||
|
let mut printer = TextPrinter::new(width, style, true, settings);
|
||||||
self.print(&mut printer, style);
|
self.print(&mut printer, style);
|
||||||
printer.finish()
|
printer.finish()
|
||||||
}
|
}
|
||||||
|
@ -312,6 +328,11 @@ impl StyleTreeNode {
|
||||||
StyleTreeNode::Ruler => {},
|
StyleTreeNode::Ruler => {},
|
||||||
StyleTreeNode::Text(_) => {},
|
StyleTreeNode::Text(_) => {},
|
||||||
StyleTreeNode::Break => {},
|
StyleTreeNode::Break => {},
|
||||||
|
|
||||||
|
// TODO: eventually these should turn into internal links:
|
||||||
|
StyleTreeNode::UserId(_) => {},
|
||||||
|
StyleTreeNode::RoomId(_) => {},
|
||||||
|
StyleTreeNode::RoomAlias(_) => {},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -430,14 +451,14 @@ impl StyleTreeNode {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
StyleTreeNode::Table(table) => {
|
StyleTreeNode::Table(table) => {
|
||||||
let text = table.to_text(width, style, printer.emoji_shortcodes());
|
let text = table.to_text(width, style, printer.settings);
|
||||||
printer.push_text(text);
|
printer.push_text(text);
|
||||||
},
|
},
|
||||||
StyleTreeNode::Break => {
|
StyleTreeNode::Break => {
|
||||||
printer.push_break();
|
printer.push_break();
|
||||||
},
|
},
|
||||||
StyleTreeNode::Text(s) => {
|
StyleTreeNode::Text(s) => {
|
||||||
printer.push_str(s.as_str(), style);
|
printer.push_str(s.as_ref(), style);
|
||||||
},
|
},
|
||||||
|
|
||||||
StyleTreeNode::Style(child, patch) => child.print(printer, style.patch(*patch)),
|
StyleTreeNode::Style(child, patch) => child.print(printer, style.patch(*patch)),
|
||||||
|
@ -446,13 +467,26 @@ impl StyleTreeNode {
|
||||||
child.print(printer, style);
|
child.print(printer, style);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
StyleTreeNode::UserId(user_id) => {
|
||||||
|
let style = printer.settings().get_user_style(user_id);
|
||||||
|
printer.push_str(user_id.as_str(), style);
|
||||||
|
},
|
||||||
|
StyleTreeNode::RoomId(room_id) => {
|
||||||
|
let bold = style.add_modifier(StyleModifier::BOLD);
|
||||||
|
printer.push_str(room_id.as_str(), bold);
|
||||||
|
},
|
||||||
|
StyleTreeNode::RoomAlias(alias) => {
|
||||||
|
let bold = style.add_modifier(StyleModifier::BOLD);
|
||||||
|
printer.push_str(alias.as_str(), bold);
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A processed HTML document.
|
/// A processed HTML document.
|
||||||
pub struct StyleTree {
|
pub struct StyleTree {
|
||||||
children: StyleTreeChildren,
|
pub(super) children: StyleTreeChildren,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl StyleTree {
|
impl StyleTree {
|
||||||
|
@ -466,14 +500,14 @@ impl StyleTree {
|
||||||
return links;
|
return links;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_text(
|
pub fn to_text<'a>(
|
||||||
&self,
|
&'a self,
|
||||||
width: usize,
|
width: usize,
|
||||||
style: Style,
|
style: Style,
|
||||||
hide_reply: bool,
|
hide_reply: bool,
|
||||||
emoji_shortcodes: bool,
|
settings: &'a ApplicationSettings,
|
||||||
) -> Text<'_> {
|
) -> Text<'a> {
|
||||||
let mut printer = TextPrinter::new(width, style, hide_reply, emoji_shortcodes);
|
let mut printer = TextPrinter::new(width, style, hide_reply, settings);
|
||||||
|
|
||||||
for child in self.children.iter() {
|
for child in self.children.iter() {
|
||||||
child.print(&mut printer, style);
|
child.print(&mut printer, style);
|
||||||
|
@ -661,7 +695,7 @@ fn h2t(hdl: &Handle, state: &mut TreeGenState) -> StyleTreeChildren {
|
||||||
|
|
||||||
let tree = match &node.data {
|
let tree = match &node.data {
|
||||||
NodeData::Document => *c2t(node.children.borrow().as_slice(), state),
|
NodeData::Document => *c2t(node.children.borrow().as_slice(), state),
|
||||||
NodeData::Text { contents } => StyleTreeNode::Text(contents.borrow().to_string()),
|
NodeData::Text { contents } => StyleTreeNode::Text(contents.borrow().to_string().into()),
|
||||||
NodeData::Element { name, attrs, .. } => {
|
NodeData::Element { name, attrs, .. } => {
|
||||||
match name.local.as_ref() {
|
match name.local.as_ref() {
|
||||||
// Message that this one replies to.
|
// Message that this one replies to.
|
||||||
|
@ -811,17 +845,19 @@ pub fn parse_matrix_html(s: &str) -> StyleTree {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::tests::mock_settings;
|
||||||
use crate::util::space_span;
|
use crate::util::space_span;
|
||||||
use pretty_assertions::assert_eq;
|
use pretty_assertions::assert_eq;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_header() {
|
fn test_header() {
|
||||||
|
let settings = mock_settings();
|
||||||
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
||||||
|
|
||||||
let s = "<h1>Header 1</h1>";
|
let s = "<h1>Header 1</h1>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
Span::styled(" ", bold),
|
Span::styled(" ", bold),
|
||||||
|
@ -833,7 +869,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<h2>Header 2</h2>";
|
let s = "<h2>Header 2</h2>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
|
@ -846,7 +882,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<h3>Header 3</h3>";
|
let s = "<h3>Header 3</h3>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
|
@ -860,7 +896,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<h4>Header 4</h4>";
|
let s = "<h4>Header 4</h4>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
|
@ -875,7 +911,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<h5>Header 5</h5>";
|
let s = "<h5>Header 5</h5>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
|
@ -891,7 +927,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<h6>Header 6</h6>";
|
let s = "<h6>Header 6</h6>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
Span::styled("#", bold),
|
Span::styled("#", bold),
|
||||||
|
@ -909,6 +945,7 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_style() {
|
fn test_style() {
|
||||||
|
let settings = mock_settings();
|
||||||
let def = Style::default();
|
let def = Style::default();
|
||||||
let bold = def.add_modifier(StyleModifier::BOLD);
|
let bold = def.add_modifier(StyleModifier::BOLD);
|
||||||
let italic = def.add_modifier(StyleModifier::ITALIC);
|
let italic = def.add_modifier(StyleModifier::ITALIC);
|
||||||
|
@ -918,7 +955,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<b>Bold!</b>";
|
let s = "<b>Bold!</b>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Bold", bold),
|
Span::styled("Bold", bold),
|
||||||
Span::styled("!", bold),
|
Span::styled("!", bold),
|
||||||
|
@ -927,7 +964,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<strong>Bold!</strong>";
|
let s = "<strong>Bold!</strong>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Bold", bold),
|
Span::styled("Bold", bold),
|
||||||
Span::styled("!", bold),
|
Span::styled("!", bold),
|
||||||
|
@ -936,7 +973,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<i>Italic!</i>";
|
let s = "<i>Italic!</i>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Italic", italic),
|
Span::styled("Italic", italic),
|
||||||
Span::styled("!", italic),
|
Span::styled("!", italic),
|
||||||
|
@ -945,7 +982,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<em>Italic!</em>";
|
let s = "<em>Italic!</em>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Italic", italic),
|
Span::styled("Italic", italic),
|
||||||
Span::styled("!", italic),
|
Span::styled("!", italic),
|
||||||
|
@ -954,7 +991,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<del>Strikethrough!</del>";
|
let s = "<del>Strikethrough!</del>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Strikethrough", strike),
|
Span::styled("Strikethrough", strike),
|
||||||
Span::styled("!", strike),
|
Span::styled("!", strike),
|
||||||
|
@ -963,7 +1000,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<strike>Strikethrough!</strike>";
|
let s = "<strike>Strikethrough!</strike>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Strikethrough", strike),
|
Span::styled("Strikethrough", strike),
|
||||||
Span::styled("!", strike),
|
Span::styled("!", strike),
|
||||||
|
@ -972,7 +1009,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<u>Underline!</u>";
|
let s = "<u>Underline!</u>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Underline", underl),
|
Span::styled("Underline", underl),
|
||||||
Span::styled("!", underl),
|
Span::styled("!", underl),
|
||||||
|
@ -981,7 +1018,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<font color=\"#ff0000\">Red!</u>";
|
let s = "<font color=\"#ff0000\">Red!</u>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Red", red),
|
Span::styled("Red", red),
|
||||||
Span::styled("!", red),
|
Span::styled("!", red),
|
||||||
|
@ -990,7 +1027,7 @@ pub mod tests {
|
||||||
|
|
||||||
let s = "<font color=\"red\">Red!</u>";
|
let s = "<font color=\"red\">Red!</u>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::styled("Red", red),
|
Span::styled("Red", red),
|
||||||
Span::styled("!", red),
|
Span::styled("!", red),
|
||||||
|
@ -1000,9 +1037,10 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_paragraph() {
|
fn test_paragraph() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<p>Hello world!</p><p>Content</p><p>Goodbye world!</p>";
|
let s = "<p>Hello world!</p><p>Content</p><p>Goodbye world!</p>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(10, Style::default(), false, false);
|
let text = tree.to_text(10, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines.len(), 7);
|
assert_eq!(text.lines.len(), 7);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1027,9 +1065,10 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_blockquote() {
|
fn test_blockquote() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<blockquote>Hello world!</blockquote>";
|
let s = "<blockquote>Hello world!</blockquote>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(10, Style::default(), false, false);
|
let text = tree.to_text(10, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines.len(), 2);
|
assert_eq!(text.lines.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1043,9 +1082,10 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_unordered() {
|
fn test_list_unordered() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<ul><li>List Item 1</li><li>List Item 2</li><li>List Item 3</li></ul>";
|
let s = "<ul><li>List Item 1</li><li>List Item 2</li><li>List Item 3</li></ul>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(8, Style::default(), false, false);
|
let text = tree.to_text(8, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines.len(), 6);
|
assert_eq!(text.lines.len(), 6);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1105,9 +1145,10 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_list_ordered() {
|
fn test_list_ordered() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<ol><li>List Item 1</li><li>List Item 2</li><li>List Item 3</li></ol>";
|
let s = "<ol><li>List Item 1</li><li>List Item 2</li><li>List Item 3</li></ol>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(9, Style::default(), false, false);
|
let text = tree.to_text(9, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines.len(), 6);
|
assert_eq!(text.lines.len(), 6);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1167,6 +1208,7 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_table() {
|
fn test_table() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<table>\
|
let s = "<table>\
|
||||||
<thead>\
|
<thead>\
|
||||||
<tr><th>Column 1</th><th>Column 2</th><th>Column 3</th></tr>
|
<tr><th>Column 1</th><th>Column 2</th><th>Column 3</th></tr>
|
||||||
|
@ -1177,7 +1219,7 @@ pub mod tests {
|
||||||
<tr><td>a</td><td>b</td><td>c</td></tr>\
|
<tr><td>a</td><td>b</td><td>c</td></tr>\
|
||||||
</tbody></table>";
|
</tbody></table>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(15, Style::default(), false, false);
|
let text = tree.to_text(15, Style::default(), false, &settings);
|
||||||
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
||||||
assert_eq!(text.lines.len(), 11);
|
assert_eq!(text.lines.len(), 11);
|
||||||
|
|
||||||
|
@ -1267,10 +1309,11 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_matrix_reply() {
|
fn test_matrix_reply() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<mx-reply>This was replied to</mx-reply>This is the reply";
|
let s = "<mx-reply>This was replied to</mx-reply>This is the reply";
|
||||||
|
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(10, Style::default(), false, false);
|
let text = tree.to_text(10, Style::default(), false, &settings);
|
||||||
assert_eq!(text.lines.len(), 4);
|
assert_eq!(text.lines.len(), 4);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1307,7 +1350,7 @@ pub mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(10, Style::default(), true, false);
|
let text = tree.to_text(10, Style::default(), true, &settings);
|
||||||
assert_eq!(text.lines.len(), 2);
|
assert_eq!(text.lines.len(), 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1332,9 +1375,10 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_self_closing() {
|
fn test_self_closing() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "Hello<br>World<br>Goodbye";
|
let s = "Hello<br>World<br>Goodbye";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(7, Style::default(), true, false);
|
let text = tree.to_text(7, Style::default(), true, &settings);
|
||||||
assert_eq!(text.lines.len(), 3);
|
assert_eq!(text.lines.len(), 3);
|
||||||
assert_eq!(text.lines[0], Line::from(vec![Span::raw("Hello"), Span::raw(" "),]));
|
assert_eq!(text.lines[0], Line::from(vec![Span::raw("Hello"), Span::raw(" "),]));
|
||||||
assert_eq!(text.lines[1], Line::from(vec![Span::raw("World"), Span::raw(" "),]));
|
assert_eq!(text.lines[1], Line::from(vec![Span::raw("World"), Span::raw(" "),]));
|
||||||
|
@ -1343,9 +1387,10 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_embedded_newline() {
|
fn test_embedded_newline() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = "<p>Hello\nWorld</p>";
|
let s = "<p>Hello\nWorld</p>";
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(15, Style::default(), true, false);
|
let text = tree.to_text(15, Style::default(), true, &settings);
|
||||||
assert_eq!(text.lines.len(), 1);
|
assert_eq!(text.lines.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1360,6 +1405,7 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_pre_tag() {
|
fn test_pre_tag() {
|
||||||
|
let settings = mock_settings();
|
||||||
let s = concat!(
|
let s = concat!(
|
||||||
"<pre><code class=\"language-rust\">",
|
"<pre><code class=\"language-rust\">",
|
||||||
"fn hello() -> usize {\n",
|
"fn hello() -> usize {\n",
|
||||||
|
@ -1368,7 +1414,7 @@ pub mod tests {
|
||||||
"</code></pre>\n"
|
"</code></pre>\n"
|
||||||
);
|
);
|
||||||
let tree = parse_matrix_html(s);
|
let tree = parse_matrix_html(s);
|
||||||
let text = tree.to_text(25, Style::default(), true, false);
|
let text = tree.to_text(25, Style::default(), true, &settings);
|
||||||
assert_eq!(text.lines.len(), 5);
|
assert_eq!(text.lines.len(), 5);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
text.lines[0],
|
text.lines[0],
|
||||||
|
@ -1432,6 +1478,11 @@ pub mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_emoji_shortcodes() {
|
fn test_emoji_shortcodes() {
|
||||||
|
let mut enabled = mock_settings();
|
||||||
|
enabled.tunables.message_shortcode_display = true;
|
||||||
|
let mut disabled = mock_settings();
|
||||||
|
disabled.tunables.message_shortcode_display = false;
|
||||||
|
|
||||||
for shortcode in ["exploding_head", "polar_bear", "canada"] {
|
for shortcode in ["exploding_head", "polar_bear", "canada"] {
|
||||||
let emoji = emojis::get_by_shortcode(shortcode).unwrap().as_str();
|
let emoji = emojis::get_by_shortcode(shortcode).unwrap().as_str();
|
||||||
let emoji_width = UnicodeWidthStr::width(emoji);
|
let emoji_width = UnicodeWidthStr::width(emoji);
|
||||||
|
@ -1440,13 +1491,13 @@ pub mod tests {
|
||||||
let s = format!("<p>{emoji}</p>");
|
let s = format!("<p>{emoji}</p>");
|
||||||
let tree = parse_matrix_html(s.as_str());
|
let tree = parse_matrix_html(s.as_str());
|
||||||
// Test with emojis_shortcodes set to false
|
// Test with emojis_shortcodes set to false
|
||||||
let text = tree.to_text(20, Style::default(), false, false);
|
let text = tree.to_text(20, Style::default(), false, &disabled);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::raw(emoji),
|
Span::raw(emoji),
|
||||||
space_span(20 - emoji_width, Style::default()),
|
space_span(20 - emoji_width, Style::default()),
|
||||||
]),]);
|
]),]);
|
||||||
// Test with emojis_shortcodes set to true
|
// Test with emojis_shortcodes set to true
|
||||||
let text = tree.to_text(20, Style::default(), false, true);
|
let text = tree.to_text(20, Style::default(), false, &enabled);
|
||||||
assert_eq!(text.lines, vec![Line::from(vec![
|
assert_eq!(text.lines, vec![Line::from(vec![
|
||||||
Span::raw(replacement.as_str()),
|
Span::raw(replacement.as_str()),
|
||||||
space_span(20 - replacement_width, Style::default()),
|
space_span(20 - replacement_width, Style::default()),
|
||||||
|
|
|
@ -35,6 +35,7 @@ use matrix_sdk::ruma::{
|
||||||
},
|
},
|
||||||
redaction::SyncRoomRedactionEvent,
|
redaction::SyncRoomRedactionEvent,
|
||||||
},
|
},
|
||||||
|
AnySyncStateEvent,
|
||||||
RedactContent,
|
RedactContent,
|
||||||
RedactedUnsigned,
|
RedactedUnsigned,
|
||||||
},
|
},
|
||||||
|
@ -67,8 +68,10 @@ use crate::{
|
||||||
mod compose;
|
mod compose;
|
||||||
mod html;
|
mod html;
|
||||||
mod printer;
|
mod printer;
|
||||||
|
mod state;
|
||||||
|
|
||||||
pub use self::compose::text_to_message;
|
pub use self::compose::text_to_message;
|
||||||
|
use self::state::{body_cow_state, html_state};
|
||||||
|
|
||||||
type ProtocolPreview<'a> = (&'a Protocol, u16, u16);
|
type ProtocolPreview<'a> = (&'a Protocol, u16, u16);
|
||||||
|
|
||||||
|
@ -427,6 +430,7 @@ pub enum MessageEvent {
|
||||||
EncryptedRedacted(Box<RedactedRoomEncryptedEvent>),
|
EncryptedRedacted(Box<RedactedRoomEncryptedEvent>),
|
||||||
Original(Box<OriginalRoomMessageEvent>),
|
Original(Box<OriginalRoomMessageEvent>),
|
||||||
Redacted(Box<RedactedRoomMessageEvent>),
|
Redacted(Box<RedactedRoomMessageEvent>),
|
||||||
|
State(Box<AnySyncStateEvent>),
|
||||||
Local(OwnedEventId, Box<RoomMessageEventContent>),
|
Local(OwnedEventId, Box<RoomMessageEventContent>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -437,6 +441,7 @@ impl MessageEvent {
|
||||||
MessageEvent::EncryptedRedacted(ev) => ev.event_id.as_ref(),
|
MessageEvent::EncryptedRedacted(ev) => ev.event_id.as_ref(),
|
||||||
MessageEvent::Original(ev) => ev.event_id.as_ref(),
|
MessageEvent::Original(ev) => ev.event_id.as_ref(),
|
||||||
MessageEvent::Redacted(ev) => ev.event_id.as_ref(),
|
MessageEvent::Redacted(ev) => ev.event_id.as_ref(),
|
||||||
|
MessageEvent::State(ev) => ev.event_id(),
|
||||||
MessageEvent::Local(event_id, _) => event_id.as_ref(),
|
MessageEvent::Local(event_id, _) => event_id.as_ref(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -447,6 +452,7 @@ impl MessageEvent {
|
||||||
MessageEvent::Original(ev) => Some(&ev.content),
|
MessageEvent::Original(ev) => Some(&ev.content),
|
||||||
MessageEvent::EncryptedRedacted(_) => None,
|
MessageEvent::EncryptedRedacted(_) => None,
|
||||||
MessageEvent::Redacted(_) => None,
|
MessageEvent::Redacted(_) => None,
|
||||||
|
MessageEvent::State(_) => None,
|
||||||
MessageEvent::Local(_, content) => Some(content),
|
MessageEvent::Local(_, content) => Some(content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,6 +470,7 @@ impl MessageEvent {
|
||||||
MessageEvent::Original(ev) => body_cow_content(&ev.content),
|
MessageEvent::Original(ev) => body_cow_content(&ev.content),
|
||||||
MessageEvent::EncryptedRedacted(ev) => body_cow_reason(&ev.unsigned),
|
MessageEvent::EncryptedRedacted(ev) => body_cow_reason(&ev.unsigned),
|
||||||
MessageEvent::Redacted(ev) => body_cow_reason(&ev.unsigned),
|
MessageEvent::Redacted(ev) => body_cow_reason(&ev.unsigned),
|
||||||
|
MessageEvent::State(ev) => body_cow_state(ev),
|
||||||
MessageEvent::Local(_, content) => body_cow_content(content),
|
MessageEvent::Local(_, content) => body_cow_content(content),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -474,6 +481,7 @@ impl MessageEvent {
|
||||||
MessageEvent::EncryptedRedacted(_) => return None,
|
MessageEvent::EncryptedRedacted(_) => return None,
|
||||||
MessageEvent::Original(ev) => &ev.content,
|
MessageEvent::Original(ev) => &ev.content,
|
||||||
MessageEvent::Redacted(_) => return None,
|
MessageEvent::Redacted(_) => return None,
|
||||||
|
MessageEvent::State(ev) => return Some(html_state(ev)),
|
||||||
MessageEvent::Local(_, content) => content,
|
MessageEvent::Local(_, content) => content,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -493,6 +501,7 @@ impl MessageEvent {
|
||||||
MessageEvent::EncryptedOriginal(_) => return,
|
MessageEvent::EncryptedOriginal(_) => return,
|
||||||
MessageEvent::EncryptedRedacted(_) => return,
|
MessageEvent::EncryptedRedacted(_) => return,
|
||||||
MessageEvent::Redacted(_) => return,
|
MessageEvent::Redacted(_) => return,
|
||||||
|
MessageEvent::State(_) => return,
|
||||||
MessageEvent::Local(_, _) => return,
|
MessageEvent::Local(_, _) => return,
|
||||||
MessageEvent::Original(ev) => {
|
MessageEvent::Original(ev) => {
|
||||||
let redacted = RedactedRoomMessageEvent {
|
let redacted = RedactedRoomMessageEvent {
|
||||||
|
@ -721,8 +730,7 @@ impl<'a> MessageFormatter<'a> {
|
||||||
) -> Option<ProtocolPreview<'a>> {
|
) -> Option<ProtocolPreview<'a>> {
|
||||||
let width = self.width();
|
let width = self.width();
|
||||||
let w = width.saturating_sub(2);
|
let w = width.saturating_sub(2);
|
||||||
let shortcodes = self.settings.tunables.message_shortcode_display;
|
let (mut replied, proto) = msg.show_msg(w, style, true, settings);
|
||||||
let (mut replied, proto) = msg.show_msg(w, style, true, shortcodes);
|
|
||||||
let mut sender = msg.sender_span(info, self.settings);
|
let mut sender = msg.sender_span(info, self.settings);
|
||||||
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
let sender_width = UnicodeWidthStr::width(sender.content.as_ref());
|
||||||
let trailing = w.saturating_sub(sender_width + 1);
|
let trailing = w.saturating_sub(sender_width + 1);
|
||||||
|
@ -760,7 +768,7 @@ impl<'a> MessageFormatter<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_reactions(&mut self, counts: Vec<(&'a str, usize)>, style: Style, text: &mut Text<'a>) {
|
fn push_reactions(&mut self, counts: Vec<(&'a str, usize)>, style: Style, text: &mut Text<'a>) {
|
||||||
let mut emojis = printer::TextPrinter::new(self.width(), style, false, false);
|
let mut emojis = printer::TextPrinter::new(self.width(), style, false, self.settings);
|
||||||
let mut reactions = 0;
|
let mut reactions = 0;
|
||||||
|
|
||||||
for (key, count) in counts {
|
for (key, count) in counts {
|
||||||
|
@ -809,7 +817,7 @@ impl<'a> MessageFormatter<'a> {
|
||||||
let plural = len != 1;
|
let plural = len != 1;
|
||||||
let style = Style::default();
|
let style = Style::default();
|
||||||
let mut threaded =
|
let mut threaded =
|
||||||
printer::TextPrinter::new(self.width(), style, false, false).literal(true);
|
printer::TextPrinter::new(self.width(), style, false, self.settings).literal(true);
|
||||||
let len = Span::styled(len.to_string(), style.add_modifier(StyleModifier::BOLD));
|
let len = Span::styled(len.to_string(), style.add_modifier(StyleModifier::BOLD));
|
||||||
threaded.push_str(" \u{2937} ", style);
|
threaded.push_str(" \u{2937} ", style);
|
||||||
threaded.push_span_nobreak(len);
|
threaded.push_span_nobreak(len);
|
||||||
|
@ -861,6 +869,7 @@ impl Message {
|
||||||
MessageEvent::Local(_, content) => content,
|
MessageEvent::Local(_, content) => content,
|
||||||
MessageEvent::Original(ev) => &ev.content,
|
MessageEvent::Original(ev) => &ev.content,
|
||||||
MessageEvent::Redacted(_) => return None,
|
MessageEvent::Redacted(_) => return None,
|
||||||
|
MessageEvent::State(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
match &content.relates_to {
|
match &content.relates_to {
|
||||||
|
@ -881,6 +890,7 @@ impl Message {
|
||||||
MessageEvent::Local(_, content) => content,
|
MessageEvent::Local(_, content) => content,
|
||||||
MessageEvent::Original(ev) => &ev.content,
|
MessageEvent::Original(ev) => &ev.content,
|
||||||
MessageEvent::Redacted(_) => return None,
|
MessageEvent::Redacted(_) => return None,
|
||||||
|
MessageEvent::State(_) => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
match &content.relates_to {
|
match &content.relates_to {
|
||||||
|
@ -993,12 +1003,7 @@ impl Message {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Now show the message contents, and the inlined reply if we couldn't find it above.
|
// Now show the message contents, and the inlined reply if we couldn't find it above.
|
||||||
let (msg, proto) = self.show_msg(
|
let (msg, proto) = self.show_msg(width, style, reply.is_some(), settings);
|
||||||
width,
|
|
||||||
style,
|
|
||||||
reply.is_some(),
|
|
||||||
settings.tunables.message_shortcode_display,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Given our text so far, determine the image offset.
|
// Given our text so far, determine the image offset.
|
||||||
let proto_main = proto.map(|p| {
|
let proto_main = proto.map(|p| {
|
||||||
|
@ -1040,18 +1045,18 @@ impl Message {
|
||||||
self.show_with_preview(prev, selected, vwctx, info, settings).0
|
self.show_with_preview(prev, selected, vwctx, info, settings).0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn show_msg(
|
fn show_msg<'a>(
|
||||||
&self,
|
&'a self,
|
||||||
width: usize,
|
width: usize,
|
||||||
style: Style,
|
style: Style,
|
||||||
hide_reply: bool,
|
hide_reply: bool,
|
||||||
emoji_shortcodes: bool,
|
settings: &'a ApplicationSettings,
|
||||||
) -> (Text, Option<&Protocol>) {
|
) -> (Text<'a>, Option<&'a Protocol>) {
|
||||||
if let Some(html) = &self.html {
|
if let Some(html) = &self.html {
|
||||||
(html.to_text(width, style, hide_reply, emoji_shortcodes), None)
|
(html.to_text(width, style, hide_reply, settings), None)
|
||||||
} else {
|
} else {
|
||||||
let mut msg = self.event.body();
|
let mut msg = self.event.body();
|
||||||
if emoji_shortcodes {
|
if settings.tunables.message_shortcode_display {
|
||||||
msg = Cow::Owned(replace_emojis_in_str(msg.as_ref()));
|
msg = Cow::Owned(replace_emojis_in_str(msg.as_ref()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1166,6 +1171,16 @@ impl From<RoomMessageEvent> for Message {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<AnySyncStateEvent> for Message {
|
||||||
|
fn from(event: AnySyncStateEvent) -> Self {
|
||||||
|
let timestamp = event.origin_server_ts().into();
|
||||||
|
let user_id = event.sender().to_owned();
|
||||||
|
let event = MessageEvent::State(event.into());
|
||||||
|
|
||||||
|
Message::new(event, user_id, timestamp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Message {
|
impl Display for Message {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.event.body())
|
write!(f, "{}", self.event.body())
|
||||||
|
|
|
@ -11,6 +11,7 @@ use ratatui::text::{Line, Span, Text};
|
||||||
use unicode_segmentation::UnicodeSegmentation;
|
use unicode_segmentation::UnicodeSegmentation;
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
use crate::config::{ApplicationSettings, TunableValues};
|
||||||
use crate::util::{
|
use crate::util::{
|
||||||
replace_emojis_in_line,
|
replace_emojis_in_line,
|
||||||
replace_emojis_in_span,
|
replace_emojis_in_span,
|
||||||
|
@ -25,28 +26,34 @@ pub struct TextPrinter<'a> {
|
||||||
width: usize,
|
width: usize,
|
||||||
base_style: Style,
|
base_style: Style,
|
||||||
hide_reply: bool,
|
hide_reply: bool,
|
||||||
emoji_shortcodes: bool,
|
|
||||||
|
|
||||||
alignment: Alignment,
|
alignment: Alignment,
|
||||||
curr_spans: Vec<Span<'a>>,
|
curr_spans: Vec<Span<'a>>,
|
||||||
curr_width: usize,
|
curr_width: usize,
|
||||||
literal: bool,
|
literal: bool,
|
||||||
|
|
||||||
|
pub(super) settings: &'a ApplicationSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> TextPrinter<'a> {
|
impl<'a> TextPrinter<'a> {
|
||||||
/// Create a new printer.
|
/// Create a new printer.
|
||||||
pub fn new(width: usize, base_style: Style, hide_reply: bool, emoji_shortcodes: bool) -> Self {
|
pub fn new(
|
||||||
|
width: usize,
|
||||||
|
base_style: Style,
|
||||||
|
hide_reply: bool,
|
||||||
|
settings: &'a ApplicationSettings,
|
||||||
|
) -> Self {
|
||||||
TextPrinter {
|
TextPrinter {
|
||||||
text: Text::default(),
|
text: Text::default(),
|
||||||
width,
|
width,
|
||||||
base_style,
|
base_style,
|
||||||
hide_reply,
|
hide_reply,
|
||||||
emoji_shortcodes,
|
|
||||||
|
|
||||||
alignment: Alignment::Left,
|
alignment: Alignment::Left,
|
||||||
curr_spans: vec![],
|
curr_spans: vec![],
|
||||||
curr_width: 0,
|
curr_width: 0,
|
||||||
literal: false,
|
literal: false,
|
||||||
|
settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +76,15 @@ impl<'a> TextPrinter<'a> {
|
||||||
|
|
||||||
/// Indicates whether emojis should be replaced by shortcodes
|
/// Indicates whether emojis should be replaced by shortcodes
|
||||||
pub fn emoji_shortcodes(&self) -> bool {
|
pub fn emoji_shortcodes(&self) -> bool {
|
||||||
self.emoji_shortcodes
|
self.tunables().message_shortcode_display
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn settings(&self) -> &ApplicationSettings {
|
||||||
|
self.settings
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn tunables(&self) -> &TunableValues {
|
||||||
|
&self.settings.tunables
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Indicates the current printer's width.
|
/// Indicates the current printer's width.
|
||||||
|
@ -84,12 +99,12 @@ impl<'a> TextPrinter<'a> {
|
||||||
width: self.width.saturating_sub(indent),
|
width: self.width.saturating_sub(indent),
|
||||||
base_style: self.base_style,
|
base_style: self.base_style,
|
||||||
hide_reply: self.hide_reply,
|
hide_reply: self.hide_reply,
|
||||||
emoji_shortcodes: self.emoji_shortcodes,
|
|
||||||
|
|
||||||
alignment: self.alignment,
|
alignment: self.alignment,
|
||||||
curr_spans: vec![],
|
curr_spans: vec![],
|
||||||
curr_width: 0,
|
curr_width: 0,
|
||||||
literal: self.literal,
|
literal: self.literal,
|
||||||
|
settings: self.settings,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -179,7 +194,7 @@ impl<'a> TextPrinter<'a> {
|
||||||
|
|
||||||
/// Push a [Span] that isn't allowed to break across lines.
|
/// Push a [Span] that isn't allowed to break across lines.
|
||||||
pub fn push_span_nobreak(&mut self, mut span: Span<'a>) {
|
pub fn push_span_nobreak(&mut self, mut span: Span<'a>) {
|
||||||
if self.emoji_shortcodes {
|
if self.emoji_shortcodes() {
|
||||||
replace_emojis_in_span(&mut span);
|
replace_emojis_in_span(&mut span);
|
||||||
}
|
}
|
||||||
let sw = UnicodeWidthStr::width(span.content.as_ref());
|
let sw = UnicodeWidthStr::width(span.content.as_ref());
|
||||||
|
@ -217,7 +232,7 @@ impl<'a> TextPrinter<'a> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cow = if self.emoji_shortcodes {
|
let cow = if self.emoji_shortcodes() {
|
||||||
Cow::Owned(replace_emojis_in_str(word))
|
Cow::Owned(replace_emojis_in_str(word))
|
||||||
} else {
|
} else {
|
||||||
Cow::Borrowed(word)
|
Cow::Borrowed(word)
|
||||||
|
@ -253,7 +268,7 @@ impl<'a> TextPrinter<'a> {
|
||||||
/// Push a [Line] into the printer.
|
/// Push a [Line] into the printer.
|
||||||
pub fn push_line(&mut self, mut line: Line<'a>) {
|
pub fn push_line(&mut self, mut line: Line<'a>) {
|
||||||
self.commit();
|
self.commit();
|
||||||
if self.emoji_shortcodes {
|
if self.emoji_shortcodes() {
|
||||||
replace_emojis_in_line(&mut line);
|
replace_emojis_in_line(&mut line);
|
||||||
}
|
}
|
||||||
self.text.lines.push(line);
|
self.text.lines.push(line);
|
||||||
|
@ -262,7 +277,7 @@ impl<'a> TextPrinter<'a> {
|
||||||
/// Push multiline [Text] into the printer.
|
/// Push multiline [Text] into the printer.
|
||||||
pub fn push_text(&mut self, mut text: Text<'a>) {
|
pub fn push_text(&mut self, mut text: Text<'a>) {
|
||||||
self.commit();
|
self.commit();
|
||||||
if self.emoji_shortcodes {
|
if self.emoji_shortcodes() {
|
||||||
for line in &mut text.lines {
|
for line in &mut text.lines {
|
||||||
replace_emojis_in_line(line);
|
replace_emojis_in_line(line);
|
||||||
}
|
}
|
||||||
|
@ -280,10 +295,12 @@ impl<'a> TextPrinter<'a> {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::tests::mock_settings;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_push_nobreak() {
|
fn test_push_nobreak() {
|
||||||
let mut printer = TextPrinter::new(5, Style::default(), false, false);
|
let settings = mock_settings();
|
||||||
|
let mut printer = TextPrinter::new(5, Style::default(), false, &settings);
|
||||||
printer.push_span_nobreak("hello world".into());
|
printer.push_span_nobreak("hello world".into());
|
||||||
let text = printer.finish();
|
let text = printer.finish();
|
||||||
assert_eq!(text.lines.len(), 1);
|
assert_eq!(text.lines.len(), 1);
|
||||||
|
|
944
src/message/state.rs
Normal file
944
src/message/state.rs
Normal file
|
@ -0,0 +1,944 @@
|
||||||
|
//! Code for displaying state events.
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use matrix_sdk::ruma::{
|
||||||
|
events::{
|
||||||
|
room::member::MembershipChange,
|
||||||
|
AnyFullStateEventContent,
|
||||||
|
AnySyncStateEvent,
|
||||||
|
FullStateEventContent,
|
||||||
|
},
|
||||||
|
OwnedRoomId,
|
||||||
|
UserId,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::html::{StyleTree, StyleTreeNode};
|
||||||
|
use ratatui::style::{Modifier as StyleModifier, Style};
|
||||||
|
|
||||||
|
fn bold(s: impl Into<Cow<'static, str>>) -> StyleTreeNode {
|
||||||
|
let bold = Style::default().add_modifier(StyleModifier::BOLD);
|
||||||
|
let text = StyleTreeNode::Text(s.into());
|
||||||
|
StyleTreeNode::Style(Box::new(text), bold)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn body_cow_state(ev: &AnySyncStateEvent) -> Cow<'static, str> {
|
||||||
|
let event = match ev.content() {
|
||||||
|
AnyFullStateEventContent::PolicyRuleRoom(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let mut m = format!(
|
||||||
|
"* updated the room policy rule for {:?} to {:?}",
|
||||||
|
content.0.entity,
|
||||||
|
content.0.recommendation.as_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
if !content.0.reason.is_empty() {
|
||||||
|
m.push_str(" (reason: ");
|
||||||
|
m.push_str(&content.0.reason);
|
||||||
|
m.push(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleServer(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let mut m = format!(
|
||||||
|
"* updated the server policy rule for {:?} to {:?}",
|
||||||
|
content.0.entity,
|
||||||
|
content.0.recommendation.as_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
if !content.0.reason.is_empty() {
|
||||||
|
m.push_str(" (reason: ");
|
||||||
|
m.push_str(&content.0.reason);
|
||||||
|
m.push(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleUser(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let mut m = format!(
|
||||||
|
"* updated the user policy rule for {:?} to {:?}",
|
||||||
|
content.0.entity,
|
||||||
|
content.0.recommendation.as_str()
|
||||||
|
);
|
||||||
|
|
||||||
|
if !content.0.reason.is_empty() {
|
||||||
|
m.push_str(" (reason: ");
|
||||||
|
m.push_str(&content.0.reason);
|
||||||
|
m.push(')');
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAliases(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
let mut m = String::from("* set the room aliases to: ");
|
||||||
|
|
||||||
|
for (i, alias) in content.aliases.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
m.push_str(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
m.push_str(alias.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAvatar(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
prev_content,
|
||||||
|
}) => {
|
||||||
|
let prev_url = prev_content.as_ref().and_then(|p| p.url.as_ref());
|
||||||
|
|
||||||
|
match (prev_url, content.url) {
|
||||||
|
(None, Some(_)) => return Cow::Borrowed("* added a room avatar"),
|
||||||
|
(Some(old), Some(new)) => {
|
||||||
|
if old != &new {
|
||||||
|
return Cow::Borrowed("* replaced the room avatar");
|
||||||
|
}
|
||||||
|
|
||||||
|
return Cow::Borrowed("* updated the room avatar state");
|
||||||
|
},
|
||||||
|
(Some(_), None) => return Cow::Borrowed("* removed the room avatar"),
|
||||||
|
(None, None) => return Cow::Borrowed("* updated the room avatar state"),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCanonicalAlias(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
prev_content,
|
||||||
|
}) => {
|
||||||
|
let old_canon = prev_content.as_ref().and_then(|p| p.alias.as_ref());
|
||||||
|
let new_canon = content.alias.as_ref();
|
||||||
|
|
||||||
|
match (old_canon, new_canon) {
|
||||||
|
(None, Some(canon)) => {
|
||||||
|
format!("* updated the canonical alias for the room to: {}", canon)
|
||||||
|
},
|
||||||
|
(Some(old), Some(new)) => {
|
||||||
|
if old != new {
|
||||||
|
format!("* updated the canonical alias for the room to: {}", new)
|
||||||
|
} else {
|
||||||
|
return Cow::Borrowed("* removed the canonical alias for the room");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Some(_), None) => {
|
||||||
|
return Cow::Borrowed("* removed the canonical alias for the room");
|
||||||
|
},
|
||||||
|
(None, None) => {
|
||||||
|
return Cow::Borrowed("* did not change the canonical alias");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCreate(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
if content.federate {
|
||||||
|
return Cow::Borrowed("* created a federated room");
|
||||||
|
} else {
|
||||||
|
return Cow::Borrowed("* created a non-federated room");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomEncryption(FullStateEventContent::Original { .. }) => {
|
||||||
|
return Cow::Borrowed("* updated the encryption settings for the room");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomGuestAccess(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
format!("* set guest access for the room to {:?}", content.guest_access.as_str())
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomHistoryVisibility(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
format!(
|
||||||
|
"* updated history visibility for the room to {:?}",
|
||||||
|
content.history_visibility.as_str()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomJoinRules(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
format!("* update the join rules for the room to {:?}", content.join_rule.as_str())
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomMember(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
prev_content,
|
||||||
|
}) => {
|
||||||
|
let Ok(state_key) = UserId::parse(ev.state_key()) else {
|
||||||
|
return Cow::Owned(format!(
|
||||||
|
"* failed to calculate membership change for {:?}",
|
||||||
|
ev.state_key()
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|
||||||
|
let prev_details = prev_content.as_ref().map(|p| p.details());
|
||||||
|
let change = content.membership_change(prev_details, ev.sender(), &state_key);
|
||||||
|
|
||||||
|
match change {
|
||||||
|
MembershipChange::None => {
|
||||||
|
format!("* did nothing to {}", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::Error => {
|
||||||
|
format!("* failed to calculate membership change to {}", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::Joined => {
|
||||||
|
return Cow::Borrowed("* joined the room");
|
||||||
|
},
|
||||||
|
MembershipChange::Left => {
|
||||||
|
return Cow::Borrowed("* left the room");
|
||||||
|
},
|
||||||
|
MembershipChange::Banned => {
|
||||||
|
format!("* banned {} from the room", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::Unbanned => {
|
||||||
|
format!("* unbanned {} from the room", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::Kicked => {
|
||||||
|
format!("* kicked {} from the room", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::Invited => {
|
||||||
|
format!("* invited {} to the room", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::KickedAndBanned => {
|
||||||
|
format!("* kicked and banned {} from the room", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::InvitationAccepted => {
|
||||||
|
return Cow::Borrowed("* accepted an invitation to join the room");
|
||||||
|
},
|
||||||
|
MembershipChange::InvitationRejected => {
|
||||||
|
return Cow::Borrowed("* rejected an invitation to join the room");
|
||||||
|
},
|
||||||
|
MembershipChange::InvitationRevoked => {
|
||||||
|
format!("* revoked an invitation for {} to join the room", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::Knocked => {
|
||||||
|
return Cow::Borrowed("* would like to join the room");
|
||||||
|
},
|
||||||
|
MembershipChange::KnockAccepted => {
|
||||||
|
format!("* accepted the room knock from {}", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::KnockRetracted => {
|
||||||
|
return Cow::Borrowed("* retracted their room knock");
|
||||||
|
},
|
||||||
|
MembershipChange::KnockDenied => {
|
||||||
|
format!("* rejected the room knock from {}", state_key)
|
||||||
|
},
|
||||||
|
MembershipChange::ProfileChanged { displayname_change, avatar_url_change } => {
|
||||||
|
match (displayname_change, avatar_url_change) {
|
||||||
|
(Some(change), avatar_change) => {
|
||||||
|
let mut m = match (change.old, change.new) {
|
||||||
|
(None, Some(new)) => {
|
||||||
|
format!("* set their display name to {:?}", new)
|
||||||
|
},
|
||||||
|
(Some(old), Some(new)) => {
|
||||||
|
format!(
|
||||||
|
"* changed their display name from {:?} to {:?}",
|
||||||
|
old, new
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(Some(_), None) => "* unset their display name".to_string(),
|
||||||
|
(None, None) => {
|
||||||
|
"* made an unknown change to their display name".to_string()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if avatar_change.is_some() {
|
||||||
|
m.push_str(" and changed their user avatar");
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
},
|
||||||
|
(None, Some(change)) => {
|
||||||
|
match (change.old, change.new) {
|
||||||
|
(None, Some(_)) => {
|
||||||
|
return Cow::Borrowed("* added a user avatar");
|
||||||
|
},
|
||||||
|
(Some(_), Some(_)) => {
|
||||||
|
return Cow::Borrowed("* changed their user avatar");
|
||||||
|
},
|
||||||
|
(Some(_), None) => {
|
||||||
|
return Cow::Borrowed("* removed their user avatar");
|
||||||
|
},
|
||||||
|
(None, None) => {
|
||||||
|
return Cow::Borrowed(
|
||||||
|
"* made an unknown change to their user avatar",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(None, None) => {
|
||||||
|
return Cow::Borrowed("* changed their user profile");
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ev => {
|
||||||
|
format!("* made an unknown membership change to {}: {:?}", state_key, ev)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomName(FullStateEventContent::Original { content, .. }) => {
|
||||||
|
format!("* updated the room name to {:?}", content.name)
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPinnedEvents(FullStateEventContent::Original { .. }) => {
|
||||||
|
return Cow::Borrowed("* updated the pinned events for the room");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPowerLevels(FullStateEventContent::Original { .. }) => {
|
||||||
|
return Cow::Borrowed("* updated the power levels for the room");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomServerAcl(FullStateEventContent::Original { .. }) => {
|
||||||
|
return Cow::Borrowed("* updated the room's server ACLs");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomThirdPartyInvite(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
format!("* sent a third-party invite to {:?}", content.display_name)
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTombstone(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
format!(
|
||||||
|
"* upgraded the room; replacement room is {}",
|
||||||
|
content.replacement_room.as_str()
|
||||||
|
)
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTopic(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
format!("* set the room topic to {:?}", content.topic)
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceChild(FullStateEventContent::Original { .. }) => {
|
||||||
|
format!("* added a space child: {}", ev.state_key())
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceParent(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
if content.canonical {
|
||||||
|
format!("* added a canonical parent space: {}", ev.state_key())
|
||||||
|
} else {
|
||||||
|
format!("* added a parent space: {}", ev.state_key())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::BeaconInfo(FullStateEventContent::Original { .. }) => {
|
||||||
|
return Cow::Borrowed("* shared beacon information");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::CallMember(FullStateEventContent::Original { .. }) => {
|
||||||
|
return Cow::Borrowed("* updated membership for room call");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::MemberHints(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
let mut m = String::from("* updated the list of service members in the room hints: ");
|
||||||
|
|
||||||
|
for (i, member) in content.service_members.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
m.push_str(", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
m.push_str(member.as_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
m
|
||||||
|
},
|
||||||
|
|
||||||
|
// Redacted variants of state events:
|
||||||
|
AnyFullStateEventContent::PolicyRuleRoom(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated a room policy rule (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleServer(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated a server policy rule (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleUser(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated a user policy rule (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAliases(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the room aliases for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAvatar(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the room avatar (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCanonicalAlias(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the canonical alias for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCreate(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* created the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomEncryption(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the encryption settings for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomGuestAccess(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed(
|
||||||
|
"* updated the guest access configuration for the room (redacted)",
|
||||||
|
);
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomHistoryVisibility(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated history visilibity for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomJoinRules(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the join rules for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomMember(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the room membership (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomName(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the room name (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPinnedEvents(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the pinned events for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPowerLevels(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the power levels for the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomServerAcl(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the room's server ACLs (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomThirdPartyInvite(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* sent a third-party invite (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTombstone(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* upgraded the room (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTopic(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* updated the room topic (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceChild(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* added a space child (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceParent(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* added a parent space (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::BeaconInfo(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("* shared beacon information (redacted)");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::CallMember(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("Call membership changed");
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::MemberHints(FullStateEventContent::Redacted(_)) => {
|
||||||
|
return Cow::Borrowed("Member hints changed");
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle unknown events:
|
||||||
|
e => {
|
||||||
|
format!("* sent an unknown state event: {:?}", e.event_type())
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return Cow::Owned(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn html_state(ev: &AnySyncStateEvent) -> StyleTree {
|
||||||
|
let children = match ev.content() {
|
||||||
|
AnyFullStateEventContent::PolicyRuleRoom(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* updated the room policy rule for ".into());
|
||||||
|
let entity = bold(format!("{:?}", content.0.entity));
|
||||||
|
let middle = StyleTreeNode::Text(" to ".into());
|
||||||
|
let rec =
|
||||||
|
StyleTreeNode::Text(format!("{:?}", content.0.recommendation.as_str()).into());
|
||||||
|
let mut cs = vec![prefix, entity, middle, rec];
|
||||||
|
|
||||||
|
if !content.0.reason.is_empty() {
|
||||||
|
let reason = format!(" (reason: {})", content.0.reason);
|
||||||
|
cs.push(StyleTreeNode::Text(reason.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleServer(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* updated the server policy rule for ".into());
|
||||||
|
let entity = bold(format!("{:?}", content.0.entity));
|
||||||
|
let middle = StyleTreeNode::Text(" to ".into());
|
||||||
|
let rec =
|
||||||
|
StyleTreeNode::Text(format!("{:?}", content.0.recommendation.as_str()).into());
|
||||||
|
let mut cs = vec![prefix, entity, middle, rec];
|
||||||
|
|
||||||
|
if !content.0.reason.is_empty() {
|
||||||
|
let reason = format!(" (reason: {})", content.0.reason);
|
||||||
|
cs.push(StyleTreeNode::Text(reason.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleUser(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* updated the user policy rule for ".into());
|
||||||
|
let entity = bold(format!("{:?}", content.0.entity));
|
||||||
|
let middle = StyleTreeNode::Text(" to ".into());
|
||||||
|
let rec =
|
||||||
|
StyleTreeNode::Text(format!("{:?}", content.0.recommendation.as_str()).into());
|
||||||
|
let mut cs = vec![prefix, entity, middle, rec];
|
||||||
|
|
||||||
|
if !content.0.reason.is_empty() {
|
||||||
|
let reason = format!(" (reason: {})", content.0.reason);
|
||||||
|
cs.push(StyleTreeNode::Text(reason.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAliases(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* set the room aliases to: ".into());
|
||||||
|
let mut cs = vec![prefix];
|
||||||
|
|
||||||
|
for (i, alias) in content.aliases.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
cs.push(StyleTreeNode::Text(", ".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.push(StyleTreeNode::RoomAlias(alias.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAvatar(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
prev_content,
|
||||||
|
}) => {
|
||||||
|
let prev_url = prev_content.as_ref().and_then(|p| p.url.as_ref());
|
||||||
|
|
||||||
|
let node = match (prev_url, content.url) {
|
||||||
|
(None, Some(_)) => StyleTreeNode::Text("* added a room avatar".into()),
|
||||||
|
(Some(old), Some(new)) => {
|
||||||
|
if old != &new {
|
||||||
|
StyleTreeNode::Text("* replaced the room avatar".into())
|
||||||
|
} else {
|
||||||
|
StyleTreeNode::Text("* updated the room avatar state".into())
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(Some(_), None) => StyleTreeNode::Text("* removed the room avatar".into()),
|
||||||
|
(None, None) => StyleTreeNode::Text("* updated the room avatar state".into()),
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![node]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCanonicalAlias(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
if let Some(canon) = content.alias.as_ref() {
|
||||||
|
let canon = bold(canon.to_string());
|
||||||
|
let prefix =
|
||||||
|
StyleTreeNode::Text("* updated the canonical alias for the room to: ".into());
|
||||||
|
vec![prefix, canon]
|
||||||
|
} else {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* removed the canonical alias for the room".into(),
|
||||||
|
)]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCreate(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
if content.federate {
|
||||||
|
vec![StyleTreeNode::Text("* created a federated room".into())]
|
||||||
|
} else {
|
||||||
|
vec![StyleTreeNode::Text("* created a non-federated room".into())]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomEncryption(FullStateEventContent::Original { .. }) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the encryption settings for the room".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomGuestAccess(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let access = bold(format!("{:?}", content.guest_access.as_str()));
|
||||||
|
let prefix = StyleTreeNode::Text("* set guest access for the room to ".into());
|
||||||
|
vec![prefix, access]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomHistoryVisibility(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix =
|
||||||
|
StyleTreeNode::Text("* updated history visibility for the room to ".into());
|
||||||
|
let vis = bold(format!("{:?}", content.history_visibility.as_str()));
|
||||||
|
vec![prefix, vis]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomJoinRules(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* update the join rules for the room to ".into());
|
||||||
|
let rule = bold(format!("{:?}", content.join_rule.as_str()));
|
||||||
|
vec![prefix, rule]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomMember(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
prev_content,
|
||||||
|
}) => {
|
||||||
|
let Ok(state_key) = UserId::parse(ev.state_key()) else {
|
||||||
|
let prefix =
|
||||||
|
StyleTreeNode::Text("* failed to calculate membership change for ".into());
|
||||||
|
let user_id = bold(format!("{:?}", ev.state_key()));
|
||||||
|
let children = vec![prefix, user_id];
|
||||||
|
|
||||||
|
return StyleTree { children };
|
||||||
|
};
|
||||||
|
|
||||||
|
let prev_details = prev_content.as_ref().map(|p| p.details());
|
||||||
|
let change = content.membership_change(prev_details, ev.sender(), &state_key);
|
||||||
|
let user_id = StyleTreeNode::UserId(state_key);
|
||||||
|
|
||||||
|
match change {
|
||||||
|
MembershipChange::None => {
|
||||||
|
let prefix = StyleTreeNode::Text("* did nothing to ".into());
|
||||||
|
vec![prefix, user_id]
|
||||||
|
},
|
||||||
|
MembershipChange::Error => {
|
||||||
|
let prefix =
|
||||||
|
StyleTreeNode::Text("* failed to calculate membership change to ".into());
|
||||||
|
vec![prefix, user_id]
|
||||||
|
},
|
||||||
|
MembershipChange::Joined => {
|
||||||
|
vec![StyleTreeNode::Text("* joined the room".into())]
|
||||||
|
},
|
||||||
|
MembershipChange::Left => {
|
||||||
|
vec![StyleTreeNode::Text("* left the room".into())]
|
||||||
|
},
|
||||||
|
MembershipChange::Banned => {
|
||||||
|
let prefix = StyleTreeNode::Text("* banned ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(" from the room".into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
MembershipChange::Unbanned => {
|
||||||
|
let prefix = StyleTreeNode::Text("* unbanned ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(" from the room".into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
MembershipChange::Kicked => {
|
||||||
|
let prefix = StyleTreeNode::Text("* kicked ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(" from the room".into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
MembershipChange::Invited => {
|
||||||
|
let prefix = StyleTreeNode::Text("* invited ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(" to the room".into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
MembershipChange::KickedAndBanned => {
|
||||||
|
let prefix = StyleTreeNode::Text("* kicked and banned ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(" from the room".into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
MembershipChange::InvitationAccepted => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* accepted an invitation to join the room".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
MembershipChange::InvitationRejected => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* rejected an invitation to join the room".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
MembershipChange::InvitationRevoked => {
|
||||||
|
let prefix = StyleTreeNode::Text("* revoked an invitation for ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(" to join the room".into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
MembershipChange::Knocked => {
|
||||||
|
vec![StyleTreeNode::Text("* would like to join the room".into())]
|
||||||
|
},
|
||||||
|
MembershipChange::KnockAccepted => {
|
||||||
|
let prefix = StyleTreeNode::Text("* accepted the room knock from ".into());
|
||||||
|
vec![prefix, user_id]
|
||||||
|
},
|
||||||
|
MembershipChange::KnockRetracted => {
|
||||||
|
vec![StyleTreeNode::Text("* retracted their room knock".into())]
|
||||||
|
},
|
||||||
|
MembershipChange::KnockDenied => {
|
||||||
|
let prefix = StyleTreeNode::Text("* rejected the room knock from ".into());
|
||||||
|
vec![prefix, user_id]
|
||||||
|
},
|
||||||
|
MembershipChange::ProfileChanged { displayname_change, avatar_url_change } => {
|
||||||
|
let m = match (displayname_change, avatar_url_change) {
|
||||||
|
(Some(change), avatar_change) => {
|
||||||
|
let mut m = match (change.old, change.new) {
|
||||||
|
(None, Some(new)) => {
|
||||||
|
format!("* set their display name to {:?}", new)
|
||||||
|
},
|
||||||
|
(Some(old), Some(new)) => {
|
||||||
|
format!(
|
||||||
|
"* changed their display name from {:?} to {:?}",
|
||||||
|
old, new
|
||||||
|
)
|
||||||
|
},
|
||||||
|
(Some(_), None) => "* unset their display name".to_string(),
|
||||||
|
(None, None) => {
|
||||||
|
"* made an unknown change to their display name".to_string()
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if avatar_change.is_some() {
|
||||||
|
m.push_str(" and changed their user avatar");
|
||||||
|
}
|
||||||
|
|
||||||
|
Cow::Owned(m)
|
||||||
|
},
|
||||||
|
(None, Some(change)) => {
|
||||||
|
match (change.old, change.new) {
|
||||||
|
(None, Some(_)) => Cow::Borrowed("* added a user avatar"),
|
||||||
|
(Some(_), Some(_)) => Cow::Borrowed("* changed their user avatar"),
|
||||||
|
(Some(_), None) => Cow::Borrowed("* removed their user avatar"),
|
||||||
|
(None, None) => {
|
||||||
|
Cow::Borrowed("* made an unknown change to their user avatar")
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(None, None) => Cow::Borrowed("* changed their user profile"),
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![StyleTreeNode::Text(m)]
|
||||||
|
},
|
||||||
|
ev => {
|
||||||
|
let prefix =
|
||||||
|
StyleTreeNode::Text("* made an unknown membership change to ".into());
|
||||||
|
let suffix = StyleTreeNode::Text(format!(": {:?}", ev).into());
|
||||||
|
vec![prefix, user_id, suffix]
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomName(FullStateEventContent::Original { content, .. }) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* updated the room name to ".into());
|
||||||
|
let name = bold(format!("{:?}", content.name));
|
||||||
|
vec![prefix, name]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPinnedEvents(FullStateEventContent::Original { .. }) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the pinned events for the room".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPowerLevels(FullStateEventContent::Original { .. }) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the power levels for the room".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomServerAcl(FullStateEventContent::Original { .. }) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room's server ACLs".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomThirdPartyInvite(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* sent a third-party invite to ".into());
|
||||||
|
let name = bold(format!("{:?}", content.display_name));
|
||||||
|
vec![prefix, name]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTombstone(FullStateEventContent::Original {
|
||||||
|
content,
|
||||||
|
..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* upgraded the room; replacement room is ".into());
|
||||||
|
let room = StyleTreeNode::RoomId(content.replacement_room.clone());
|
||||||
|
vec![prefix, room]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTopic(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* set the room topic to ".into());
|
||||||
|
let topic = bold(format!("{:?}", content.topic));
|
||||||
|
vec![prefix, topic]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceChild(FullStateEventContent::Original { .. }) => {
|
||||||
|
let prefix = StyleTreeNode::Text("* added a space child: ".into());
|
||||||
|
|
||||||
|
let room_id = if let Ok(room_id) = OwnedRoomId::from_str(ev.state_key()) {
|
||||||
|
StyleTreeNode::RoomId(room_id)
|
||||||
|
} else {
|
||||||
|
bold(ev.state_key().to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![prefix, room_id]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceParent(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
let prefix = if content.canonical {
|
||||||
|
StyleTreeNode::Text("* added a canonical parent space: ".into())
|
||||||
|
} else {
|
||||||
|
StyleTreeNode::Text("* added a parent space: ".into())
|
||||||
|
};
|
||||||
|
|
||||||
|
let room_id = if let Ok(room_id) = OwnedRoomId::from_str(ev.state_key()) {
|
||||||
|
StyleTreeNode::RoomId(room_id)
|
||||||
|
} else {
|
||||||
|
bold(ev.state_key().to_string())
|
||||||
|
};
|
||||||
|
|
||||||
|
vec![prefix, room_id]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::BeaconInfo(FullStateEventContent::Original { .. }) => {
|
||||||
|
vec![StyleTreeNode::Text("* shared beacon information".into())]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::CallMember(FullStateEventContent::Original { .. }) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated membership for room call".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::MemberHints(FullStateEventContent::Original {
|
||||||
|
content, ..
|
||||||
|
}) => {
|
||||||
|
let prefix = StyleTreeNode::Text(
|
||||||
|
"* updated the list of service members in the room hints: ".into(),
|
||||||
|
);
|
||||||
|
let mut cs = vec![prefix];
|
||||||
|
|
||||||
|
for (i, member) in content.service_members.iter().enumerate() {
|
||||||
|
if i != 0 {
|
||||||
|
cs.push(StyleTreeNode::Text(", ".into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs.push(StyleTreeNode::UserId(member.clone()));
|
||||||
|
}
|
||||||
|
|
||||||
|
cs
|
||||||
|
},
|
||||||
|
|
||||||
|
// Redacted variants of state events:
|
||||||
|
AnyFullStateEventContent::PolicyRuleRoom(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated a room policy rule (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleServer(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated a server policy rule (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::PolicyRuleUser(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated a user policy rule (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAliases(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room aliases for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomAvatar(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room avatar (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCanonicalAlias(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the canonical alias for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomCreate(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text("* created the room (redacted)".into())]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomEncryption(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the encryption settings for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomGuestAccess(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the guest access configuration for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomHistoryVisibility(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated history visilibity for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomJoinRules(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the join rules for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomMember(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room membership (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomName(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room name (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPinnedEvents(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the pinned events for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomPowerLevels(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the power levels for the room (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomServerAcl(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room's server ACLs (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomThirdPartyInvite(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* sent a third-party invite (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTombstone(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text("* upgraded the room (redacted)".into())]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::RoomTopic(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* updated the room topic (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceChild(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* added a space child (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::SpaceParent(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* added a parent space (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::BeaconInfo(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text(
|
||||||
|
"* shared beacon information (redacted)".into(),
|
||||||
|
)]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::CallMember(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text("Call membership changed".into())]
|
||||||
|
},
|
||||||
|
AnyFullStateEventContent::MemberHints(FullStateEventContent::Redacted(_)) => {
|
||||||
|
vec![StyleTreeNode::Text("Member hints changed".into())]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Handle unknown events:
|
||||||
|
e => {
|
||||||
|
let prefix = StyleTreeNode::Text("* sent an unknown state event: ".into());
|
||||||
|
let event = bold(format!("{:?}", e.event_type()));
|
||||||
|
vec![prefix, event]
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
StyleTree { children }
|
||||||
|
}
|
|
@ -380,6 +380,7 @@ impl ChatState {
|
||||||
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
||||||
MessageEvent::Original(ev) => ev.event_id.clone(),
|
MessageEvent::Original(ev) => ev.event_id.clone(),
|
||||||
MessageEvent::Local(event_id, _) => event_id.clone(),
|
MessageEvent::Local(event_id, _) => event_id.clone(),
|
||||||
|
MessageEvent::State(ev) => ev.event_id().to_owned(),
|
||||||
MessageEvent::Redacted(_) => {
|
MessageEvent::Redacted(_) => {
|
||||||
let msg = "Cannot react to a redacted message";
|
let msg = "Cannot react to a redacted message";
|
||||||
let err = UIError::Failure(msg.into());
|
let err = UIError::Failure(msg.into());
|
||||||
|
@ -417,6 +418,7 @@ impl ChatState {
|
||||||
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
||||||
MessageEvent::Original(ev) => ev.event_id.clone(),
|
MessageEvent::Original(ev) => ev.event_id.clone(),
|
||||||
MessageEvent::Local(event_id, _) => event_id.clone(),
|
MessageEvent::Local(event_id, _) => event_id.clone(),
|
||||||
|
MessageEvent::State(ev) => ev.event_id().to_owned(),
|
||||||
MessageEvent::Redacted(_) => {
|
MessageEvent::Redacted(_) => {
|
||||||
let msg = "Cannot redact already redacted message";
|
let msg = "Cannot redact already redacted message";
|
||||||
let err = UIError::Failure(msg.into());
|
let err = UIError::Failure(msg.into());
|
||||||
|
@ -464,6 +466,7 @@ impl ChatState {
|
||||||
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
MessageEvent::EncryptedRedacted(ev) => ev.event_id.clone(),
|
||||||
MessageEvent::Original(ev) => ev.event_id.clone(),
|
MessageEvent::Original(ev) => ev.event_id.clone(),
|
||||||
MessageEvent::Local(event_id, _) => event_id.clone(),
|
MessageEvent::Local(event_id, _) => event_id.clone(),
|
||||||
|
MessageEvent::State(ev) => ev.event_id().to_owned(),
|
||||||
MessageEvent::Redacted(_) => {
|
MessageEvent::Redacted(_) => {
|
||||||
let msg = "Cannot unreact to a redacted message";
|
let msg = "Cannot unreact to a redacted message";
|
||||||
let err = UIError::Failure(msg.into());
|
let err = UIError::Failure(msg.into());
|
||||||
|
|
|
@ -13,7 +13,6 @@ use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use futures::{stream::FuturesUnordered, StreamExt};
|
use futures::{stream::FuturesUnordered, StreamExt};
|
||||||
use gethostname::gethostname;
|
use gethostname::gethostname;
|
||||||
use matrix_sdk::ruma::events::AnySyncTimelineEvent;
|
|
||||||
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
|
||||||
use tokio::sync::Semaphore;
|
use tokio::sync::Semaphore;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::task::JoinHandle;
|
||||||
|
@ -60,6 +59,8 @@ use matrix_sdk::{
|
||||||
typing::SyncTypingEvent,
|
typing::SyncTypingEvent,
|
||||||
AnyInitialStateEvent,
|
AnyInitialStateEvent,
|
||||||
AnyMessageLikeEvent,
|
AnyMessageLikeEvent,
|
||||||
|
AnySyncStateEvent,
|
||||||
|
AnyTimelineEvent,
|
||||||
EmptyStateKey,
|
EmptyStateKey,
|
||||||
InitialStateEvent,
|
InitialStateEvent,
|
||||||
SyncEphemeralRoomEvent,
|
SyncEphemeralRoomEvent,
|
||||||
|
@ -115,8 +116,7 @@ const IAMB_DEVICE_NAME: &str = "iamb";
|
||||||
const IAMB_USER_AGENT: &str = "iamb";
|
const IAMB_USER_AGENT: &str = "iamb";
|
||||||
const MIN_MSG_LOAD: u32 = 50;
|
const MIN_MSG_LOAD: u32 = 50;
|
||||||
|
|
||||||
type MessageFetchResult =
|
type MessageFetchResult = IambResult<(Option<String>, Vec<(AnyTimelineEvent, Vec<OwnedUserId>)>)>;
|
||||||
IambResult<(Option<String>, Vec<(AnyMessageLikeEvent, Vec<OwnedUserId>)>)>;
|
|
||||||
|
|
||||||
fn initial_devname() -> String {
|
fn initial_devname() -> String {
|
||||||
format!("{} on {}", IAMB_DEVICE_NAME, gethostname().to_string_lossy())
|
format!("{} on {}", IAMB_DEVICE_NAME, gethostname().to_string_lossy())
|
||||||
|
@ -294,10 +294,8 @@ async fn load_older_one(
|
||||||
let mut msgs = vec![];
|
let mut msgs = vec![];
|
||||||
|
|
||||||
for ev in chunk.into_iter() {
|
for ev in chunk.into_iter() {
|
||||||
let deserialized = ev.into_raw().deserialize().map_err(IambError::Serde)?;
|
let Ok(msg) = ev.into_raw().deserialize() else {
|
||||||
let msg: AnyMessageLikeEvent = match deserialized {
|
continue;
|
||||||
AnySyncTimelineEvent::MessageLike(e) => e.into_full_event(room_id.to_owned()),
|
|
||||||
AnySyncTimelineEvent::State(_) => continue,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let event_id = msg.event_id();
|
let event_id = msg.event_id();
|
||||||
|
@ -312,6 +310,7 @@ async fn load_older_one(
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let msg = msg.into_full_event(room_id.to_owned());
|
||||||
msgs.push((msg, receipts));
|
msgs.push((msg, receipts));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -343,10 +342,10 @@ fn load_insert(
|
||||||
}
|
}
|
||||||
|
|
||||||
match msg {
|
match msg {
|
||||||
AnyMessageLikeEvent::RoomEncrypted(msg) => {
|
AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomEncrypted(msg)) => {
|
||||||
info.insert_encrypted(msg);
|
info.insert_encrypted(msg);
|
||||||
},
|
},
|
||||||
AnyMessageLikeEvent::RoomMessage(msg) => {
|
AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::RoomMessage(msg)) => {
|
||||||
info.insert_with_preview(
|
info.insert_with_preview(
|
||||||
room_id.clone(),
|
room_id.clone(),
|
||||||
store.clone(),
|
store.clone(),
|
||||||
|
@ -356,10 +355,15 @@ fn load_insert(
|
||||||
client.media(),
|
client.media(),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
AnyMessageLikeEvent::Reaction(ev) => {
|
AnyTimelineEvent::MessageLike(AnyMessageLikeEvent::Reaction(ev)) => {
|
||||||
info.insert_reaction(ev);
|
info.insert_reaction(ev);
|
||||||
},
|
},
|
||||||
_ => continue,
|
AnyTimelineEvent::MessageLike(_) => {
|
||||||
|
continue;
|
||||||
|
},
|
||||||
|
AnyTimelineEvent::State(msg) => {
|
||||||
|
info.insert_any_state(msg.into());
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1055,6 +1059,18 @@ impl ClientWorker {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let _ = self.client.add_event_handler(
|
||||||
|
|ev: AnySyncStateEvent, room: MatrixRoom, store: Ctx<AsyncProgramStore>| {
|
||||||
|
async move {
|
||||||
|
let room_id = room.room_id();
|
||||||
|
let mut locked = store.lock().await;
|
||||||
|
|
||||||
|
let info = locked.application.get_room_info(room_id.to_owned());
|
||||||
|
info.insert_any_state(ev);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
let _ = self.client.add_event_handler(
|
let _ = self.client.add_event_handler(
|
||||||
|ev: OriginalSyncRoomRedactionEvent,
|
|ev: OriginalSyncRoomRedactionEvent,
|
||||||
room: MatrixRoom,
|
room: MatrixRoom,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue