Add commands for viewing and clearing unreads (#332)

This commit is contained in:
Ulyssa 2024-08-20 19:33:46 -07:00 committed by GitHub
parent 4fc05c7b40
commit 480888a1fc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 135 additions and 0 deletions

View file

@ -61,6 +61,8 @@ Log out of
View a list of joined rooms.
.It Sy ":spaces"
View a list of joined spaces.
.It Sy ":unreads"
View a list of unread rooms.
.It Sy ":welcome"
View the startup Welcome window.
.El
@ -95,6 +97,8 @@ React to the selected message with an Emoji.
Redact the selected message.
.It Sy ":reply"
Reply to the selected message.
.It Sy ":unreads clear"
Mark all unread rooms as read.
.It Sy ":unreact [shortcode]"
Remove your reaction from the selected message.
When no arguments are given, remove all of your reactions from the message.

View file

@ -510,6 +510,9 @@ pub enum IambAction {
/// Toggle the focus within the focused room.
ToggleScrollbackFocus,
/// Clear all unread messages.
ClearUnreads,
}
impl IambAction {
@ -546,6 +549,7 @@ impl From<SendAction> for IambAction {
impl ApplicationAction for IambAction {
fn is_edit_sequence(&self, _: &EditContext) -> SequenceStatus {
match self {
IambAction::ClearUnreads => SequenceStatus::Break,
IambAction::Homeserver(..) => SequenceStatus::Break,
IambAction::Keys(..) => SequenceStatus::Break,
IambAction::Message(..) => SequenceStatus::Break,
@ -560,6 +564,7 @@ impl ApplicationAction for IambAction {
fn is_last_action(&self, _: &EditContext) -> SequenceStatus {
match self {
IambAction::ClearUnreads => SequenceStatus::Atom,
IambAction::Homeserver(..) => SequenceStatus::Atom,
IambAction::Keys(..) => SequenceStatus::Atom,
IambAction::Message(..) => SequenceStatus::Atom,
@ -574,6 +579,7 @@ impl ApplicationAction for IambAction {
fn is_last_selection(&self, _: &EditContext) -> SequenceStatus {
match self {
IambAction::ClearUnreads => SequenceStatus::Ignore,
IambAction::Homeserver(..) => SequenceStatus::Ignore,
IambAction::Keys(..) => SequenceStatus::Ignore,
IambAction::Message(..) => SequenceStatus::Ignore,
@ -588,6 +594,7 @@ impl ApplicationAction for IambAction {
fn is_switchable(&self, _: &EditContext) -> bool {
match self {
IambAction::ClearUnreads => false,
IambAction::Homeserver(..) => false,
IambAction::Message(..) => false,
IambAction::Room(..) => false,
@ -1148,6 +1155,14 @@ impl RoomInfo {
self.user_receipts.insert(user_id, event_id);
}
pub fn fully_read(&mut self, user_id: OwnedUserId) {
let Some(((_, event_id), _)) = self.messages.last_key_value() else {
return;
};
self.set_receipt(user_id, event_id.clone());
}
pub fn get_receipt(&self, user_id: &UserId) -> Option<&OwnedEventId> {
self.user_receipts.get(user_id)
}
@ -1309,6 +1324,20 @@ pub struct SyncInfo {
pub dms: Vec<Arc<(MatrixRoom, Option<Tags>)>>,
}
impl SyncInfo {
pub fn rooms(&self) -> impl Iterator<Item = &RoomId> {
self.rooms.iter().map(|r| r.0.room_id())
}
pub fn dms(&self) -> impl Iterator<Item = &RoomId> {
self.dms.iter().map(|r| r.0.room_id())
}
pub fn chats(&self) -> impl Iterator<Item = &RoomId> {
self.rooms().chain(self.dms())
}
}
bitflags::bitflags! {
/// Load-needs
#[derive(Debug, Default, PartialEq)]
@ -1480,6 +1509,9 @@ pub enum IambId {
/// The `:chats` window.
ChatList,
/// The `:unreads` window.
UnreadList,
}
impl Display for IambId {
@ -1500,6 +1532,7 @@ impl Display for IambId {
IambId::VerifyList => f.write_str("iamb://verify"),
IambId::Welcome => f.write_str("iamb://welcome"),
IambId::ChatList => f.write_str("iamb://chats"),
IambId::UnreadList => f.write_str("iamb://unreads"),
}
}
}
@ -1631,6 +1664,13 @@ impl<'de> Visitor<'de> for IambIdVisitor {
Ok(IambId::ChatList)
},
Some("unreads") => {
if url.path() != "" {
return Err(E::custom("iamb://unreads takes no path"));
}
Ok(IambId::UnreadList)
},
Some(s) => Err(E::custom(format!("{s:?} is not a valid window"))),
None => Err(E::custom("Invalid iamb window URL")),
}
@ -1691,6 +1731,9 @@ pub enum IambBufferId {
/// The `:chats` window.
ChatList,
/// The `:unreads` window.
UnreadList,
}
impl IambBufferId {
@ -1706,6 +1749,7 @@ impl IambBufferId {
IambBufferId::VerifyList => IambId::VerifyList,
IambBufferId::Welcome => IambId::Welcome,
IambBufferId::ChatList => IambId::ChatList,
IambBufferId::UnreadList => IambId::UnreadList,
};
Some(id)
@ -1740,6 +1784,7 @@ impl ApplicationInfo for IambInfo {
IambBufferId::VerifyList => vec![],
IambBufferId::Welcome => vec![],
IambBufferId::ChatList => vec![],
IambBufferId::UnreadList => vec![],
}
}

View file

@ -307,6 +307,30 @@ fn iamb_chats(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
return Ok(step);
}
fn iamb_unreads(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
let mut args = desc.arg.strings()?;
if args.len() > 1 {
return Result::Err(CommandError::InvalidArgument);
}
match args.pop().as_deref() {
Some("clear") => {
let clear = IambAction::ClearUnreads;
let step = CommandStep::Continue(clear.into(), ctx.context.clone());
return Ok(step);
},
Some(_) => return Result::Err(CommandError::InvalidArgument),
None => {
let open = ctx.switch(OpenTarget::Application(IambId::UnreadList));
let step = CommandStep::Continue(open, ctx.context.clone());
return Ok(step);
},
}
}
fn iamb_spaces(desc: CommandDescription, ctx: &mut ProgContext) -> ProgResult {
if !desc.arg.text.is_empty() {
return Result::Err(CommandError::InvalidArgument);
@ -648,6 +672,11 @@ fn add_iamb_commands(cmds: &mut ProgramCommands) {
aliases: vec![],
f: iamb_spaces,
});
cmds.add_command(ProgramCommand {
name: "unreads".into(),
aliases: vec![],
f: iamb_unreads,
});
cmds.add_command(ProgramCommand {
name: "unreact".into(),
aliases: vec![],

View file

@ -529,6 +529,18 @@ impl Application {
}
let info = match action {
IambAction::ClearUnreads => {
let user_id = &store.application.settings.profile.user_id;
for room_id in store.application.sync_info.chats() {
if let Some(room) = store.application.rooms.get_mut(room_id) {
room.fully_read(user_id.clone());
}
}
None
},
IambAction::ToggleScrollbackFocus => {
self.screen.current_window_mut()?.focus_toggle();

View file

@ -315,6 +315,7 @@ macro_rules! delegate {
IambWindow::VerifyList($id) => $e,
IambWindow::Welcome($id) => $e,
IambWindow::ChatList($id) => $e,
IambWindow::UnreadList($id) => $e,
}
};
}
@ -328,6 +329,7 @@ pub enum IambWindow {
SpaceList(SpaceListState),
Welcome(WelcomeState),
ChatList(ChatListState),
UnreadList(UnreadListState),
}
impl IambWindow {
@ -383,6 +385,7 @@ pub type DirectListState = ListState<DirectItem, IambInfo>;
pub type MemberListState = ListState<MemberItem, IambInfo>;
pub type RoomListState = ListState<RoomItem, IambInfo>;
pub type ChatListState = ListState<GenericChatItem, IambInfo>;
pub type UnreadListState = ListState<GenericChatItem, IambInfo>;
pub type SpaceListState = ListState<SpaceItem, IambInfo>;
pub type VerifyListState = ListState<VerifyItem, IambInfo>;
@ -579,6 +582,39 @@ impl WindowOps<IambInfo> for IambWindow {
.focus(focused)
.render(area, buf, state);
},
IambWindow::UnreadList(state) => {
let mut items = store
.application
.sync_info
.rooms
.clone()
.into_iter()
.map(|room_info| GenericChatItem::new(room_info, store, false))
.filter(RoomLikeItem::is_unread)
.collect::<Vec<_>>();
let dms = store
.application
.sync_info
.dms
.clone()
.into_iter()
.map(|room_info| GenericChatItem::new(room_info, store, true))
.filter(RoomLikeItem::is_unread);
items.extend(dms);
let fields = &store.application.settings.tunables.sort.chats;
items.sort_by(|a, b| room_fields_cmp(a, b, fields));
state.set(items);
List::new(store)
.empty_message("You do not have rooms or dms yet")
.empty_alignment(Alignment::Center)
.focus(focused)
.render(area, buf, state);
},
IambWindow::SpaceList(state) => {
let mut items = store
.application
@ -630,6 +666,7 @@ impl WindowOps<IambInfo> for IambWindow {
IambWindow::VerifyList(w) => w.dup(store).into(),
IambWindow::Welcome(w) => w.dup(store).into(),
IambWindow::ChatList(w) => w.dup(store).into(),
IambWindow::UnreadList(w) => w.dup(store).into(),
}
}
@ -670,6 +707,7 @@ impl Window<IambInfo> for IambWindow {
IambWindow::VerifyList(_) => IambId::VerifyList,
IambWindow::Welcome(_) => IambId::Welcome,
IambWindow::ChatList(_) => IambId::ChatList,
IambWindow::UnreadList(_) => IambId::UnreadList,
}
}
@ -681,6 +719,7 @@ impl Window<IambInfo> for IambWindow {
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
IambWindow::UnreadList(_) => bold_spans("Unread Messages"),
IambWindow::Room(w) => {
let title = store.application.get_room_title(w.id());
@ -708,6 +747,7 @@ impl Window<IambInfo> for IambWindow {
IambWindow::VerifyList(_) => bold_spans("Verifications"),
IambWindow::Welcome(_) => bold_spans("Welcome to iamb"),
IambWindow::ChatList(_) => bold_spans("DMs & Rooms"),
IambWindow::UnreadList(_) => bold_spans("Unread Messages"),
IambWindow::Room(w) => w.get_title(store),
IambWindow::MemberList(state, room_id, _) => {
@ -769,6 +809,11 @@ impl Window<IambInfo> for IambWindow {
Ok(list.into())
},
IambId::UnreadList => {
let list = UnreadListState::new(IambBufferId::UnreadList, vec![]);
Ok(IambWindow::UnreadList(list))
},
}
}