ui: add set_seen shortcut in {Compact,Conversation}
Shortcut sets an entire thread as seen.embed
parent
fada0ffce1
commit
fb7b038ee1
|
@ -417,10 +417,25 @@ impl ThreadNode {
|
||||||
self.has_unseen
|
self.has_unseen
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_has_unseen(&mut self, new_val: bool) {
|
||||||
|
self.has_unseen = new_val;
|
||||||
|
}
|
||||||
|
|
||||||
pub fn len(&self) -> usize {
|
pub fn len(&self) -> usize {
|
||||||
self.len
|
self.len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn date(&self) -> UnixTimestamp {
|
||||||
|
self.date
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn datetime(&self) -> chrono::DateTime<chrono::Utc> {
|
||||||
|
use chrono::{TimeZone, Utc};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
Utc.timestamp(self.date.try_into().unwrap_or(0), 0)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.parent.is_none() && self.message.is_none() && self.children.is_empty()
|
self.parent.is_none() && self.message.is_none() && self.children.is_empty()
|
||||||
}
|
}
|
||||||
|
@ -575,12 +590,12 @@ impl<'a> Iterator for RootIterator<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_ref(buf: &FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
|
pub fn find_thread_group(buf: &FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
|
||||||
if buf[&h].thread_group == h {
|
if buf[&h].thread_group == h {
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
let p = buf[&h].thread_group;
|
let p = buf[&h].thread_group;
|
||||||
find_ref(buf, p)
|
find_thread_group(buf, p)
|
||||||
}
|
}
|
||||||
fn find(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
|
fn find(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, h: ThreadHash) -> ThreadHash {
|
||||||
if buf[&h].thread_group == h {
|
if buf[&h].thread_group == h {
|
||||||
|
@ -617,7 +632,7 @@ fn union(buf: &mut FnvHashMap<ThreadHash, ThreadNode>, x: ThreadHash, y: ThreadH
|
||||||
|
|
||||||
impl Threads {
|
impl Threads {
|
||||||
pub fn is_snoozed(&self, h: ThreadHash) -> bool {
|
pub fn is_snoozed(&self, h: ThreadHash) -> bool {
|
||||||
let root = find_ref(&self.thread_nodes, h);
|
let root = find_thread_group(&self.thread_nodes, h);
|
||||||
self.thread_nodes[&root].snoozed()
|
self.thread_nodes[&root].snoozed()
|
||||||
}
|
}
|
||||||
pub fn find(&mut self, i: ThreadHash) -> ThreadHash {
|
pub fn find(&mut self, i: ThreadHash) -> ThreadHash {
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::components::utilities::PageMovement;
|
use crate::components::utilities::PageMovement;
|
||||||
|
use std::iter::FromIterator;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
const MAX_COLS: usize = 500;
|
const MAX_COLS: usize = 500;
|
||||||
|
@ -892,6 +893,71 @@ impl ConversationsListing {
|
||||||
self.filtered_selection[cursor]
|
self.filtered_selection[cursor]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn perform_action(
|
||||||
|
&mut self,
|
||||||
|
context: &mut Context,
|
||||||
|
thread_hash: ThreadHash,
|
||||||
|
a: &ListingAction,
|
||||||
|
) {
|
||||||
|
let account = &mut context.accounts[self.cursor_pos.0];
|
||||||
|
let mut envs_to_set: StackVec<EnvelopeHash> = StackVec::new();
|
||||||
|
{
|
||||||
|
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
|
||||||
|
let mut stack = StackVec::new();
|
||||||
|
stack.push(thread_hash);
|
||||||
|
while let Some(thread_iter) = stack.pop() {
|
||||||
|
{
|
||||||
|
let threads = account.collection.threads.get_mut(&folder_hash).unwrap();
|
||||||
|
threads
|
||||||
|
.thread_nodes
|
||||||
|
.entry(thread_iter)
|
||||||
|
.and_modify(|t| t.set_has_unseen(false));
|
||||||
|
}
|
||||||
|
let threads = &account.collection.threads[&folder_hash];
|
||||||
|
if let Some(env_hash) = threads[&thread_iter].message() {
|
||||||
|
if !account.contains_key(env_hash) {
|
||||||
|
/* The envelope has been renamed or removed, so wait for the appropriate event to
|
||||||
|
* arrive */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
envs_to_set.push(env_hash);
|
||||||
|
}
|
||||||
|
for c in 0..threads[&thread_iter].children().len() {
|
||||||
|
let c = threads[&thread_iter].children()[c];
|
||||||
|
stack.push(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for env_hash in envs_to_set {
|
||||||
|
match a {
|
||||||
|
ListingAction::SetSeen => {
|
||||||
|
let hash = account.get_env(&env_hash).hash();
|
||||||
|
let op = account.operation(hash);
|
||||||
|
let envelope: &mut Envelope = &mut account.get_env_mut(&env_hash);
|
||||||
|
if let Err(e) = envelope.set_seen(op) {
|
||||||
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::DisplayMessage(e.to_string()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.row_updates.push(thread_hash);
|
||||||
|
}
|
||||||
|
ListingAction::SetUnseen => {
|
||||||
|
let hash = account.get_env(&env_hash).hash();
|
||||||
|
let op = account.operation(hash);
|
||||||
|
let envelope: &mut Envelope = &mut account.get_env_mut(&env_hash);
|
||||||
|
if let Err(e) = envelope.set_unseen(op) {
|
||||||
|
context.replies.push_back(UIEvent::StatusEvent(
|
||||||
|
StatusEvent::DisplayMessage(e.to_string()),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
self.row_updates.push(thread_hash);
|
||||||
|
}
|
||||||
|
ListingAction::Delete => { /* do nothing */ }
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for ConversationsListing {
|
impl Component for ConversationsListing {
|
||||||
|
@ -1074,6 +1140,12 @@ impl Component for ConversationsListing {
|
||||||
self.movement = Some(PageMovement::End);
|
self.movement = Some(PageMovement::End);
|
||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
}
|
}
|
||||||
|
UIEvent::Input(ref key) if *key == shortcuts["set_seen"] => {
|
||||||
|
let thread_hash = self.get_thread_under_cursor(self.cursor_pos.2, context);
|
||||||
|
self.perform_action(context, thread_hash, &ListingAction::SetSeen);
|
||||||
|
self.row_updates.push(thread_hash);
|
||||||
|
self.set_dirty();
|
||||||
|
}
|
||||||
UIEvent::Input(ref k) if self.unfocused && *k == shortcuts["exit_thread"] => {
|
UIEvent::Input(ref k) if self.unfocused && *k == shortcuts["exit_thread"] => {
|
||||||
self.unfocused = false;
|
self.unfocused = false;
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
@ -1116,14 +1188,35 @@ impl Component for ConversationsListing {
|
||||||
if !threads.thread_nodes.contains_key(&new_env_thread_hash) {
|
if !threads.thread_nodes.contains_key(&new_env_thread_hash) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
let thread_group = threads.thread_nodes[&new_env_thread_hash].thread_group();
|
let thread_group = melib::find_thread_group(
|
||||||
let (&thread_hash, _): (&ThreadHash, &usize) = self
|
&threads.thread_nodes,
|
||||||
|
threads.thread_nodes[&new_env_thread_hash].thread_group(),
|
||||||
|
);
|
||||||
|
let (&thread_hash, &row): (&ThreadHash, &usize) = self
|
||||||
.order
|
.order
|
||||||
.iter()
|
.iter()
|
||||||
.find(|(n, _)| threads.thread_nodes[&n].thread_group() == thread_group)
|
.find(|(n, _)| {
|
||||||
|
melib::find_thread_group(
|
||||||
|
&threads.thread_nodes,
|
||||||
|
threads.thread_nodes[&n].thread_group(),
|
||||||
|
) == thread_group
|
||||||
|
})
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
self.row_updates.push(thread_hash);
|
let new_thread_hash = threads.root_set(row);
|
||||||
|
self.row_updates.push(new_thread_hash);
|
||||||
|
if let Some(row) = self.order.remove(&thread_hash) {
|
||||||
|
self.order.insert(new_thread_hash, row);
|
||||||
|
let selection_status = self.selection.remove(&thread_hash).unwrap();
|
||||||
|
self.selection.insert(new_thread_hash, selection_status);
|
||||||
|
for h in self.filtered_selection.iter_mut() {
|
||||||
|
if *h == thread_hash {
|
||||||
|
*h = new_thread_hash;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
|
|
||||||
self.view
|
self.view
|
||||||
|
@ -1182,10 +1275,6 @@ impl Component for ConversationsListing {
|
||||||
| Action::Listing(a @ ListingAction::Delete)
|
| Action::Listing(a @ ListingAction::Delete)
|
||||||
if !self.unfocused =>
|
if !self.unfocused =>
|
||||||
{
|
{
|
||||||
/* Iterate over selection if exists, else only over the envelope under the
|
|
||||||
* cursor. Using two iterators allows chaining them which results into a Chain
|
|
||||||
* type. We can't conditonally select either a slice iterator or a Map iterator
|
|
||||||
* because of the type system */
|
|
||||||
let is_selection_empty =
|
let is_selection_empty =
|
||||||
self.selection.values().cloned().any(std::convert::identity);
|
self.selection.values().cloned().any(std::convert::identity);
|
||||||
let i = [self.get_thread_under_cursor(self.cursor_pos.2, context)];
|
let i = [self.get_thread_under_cursor(self.cursor_pos.2, context)];
|
||||||
|
@ -1200,60 +1289,11 @@ impl Component for ConversationsListing {
|
||||||
let iter = sel_iter
|
let iter = sel_iter
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
.chain(cursor_iter.into_iter().flatten());
|
.chain(cursor_iter.into_iter().flatten())
|
||||||
for &i in iter {
|
.cloned();
|
||||||
let account = &mut context.accounts[self.cursor_pos.0];
|
let stack = StackVec::from_iter(iter.into_iter());
|
||||||
let mut envs_to_set: StackVec<EnvelopeHash> = StackVec::new();
|
for i in stack {
|
||||||
{
|
self.perform_action(context, i, a);
|
||||||
let folder_hash = account[self.cursor_pos.1].unwrap().folder.hash();
|
|
||||||
let threads = &account.collection.threads[&folder_hash];
|
|
||||||
let mut stack = StackVec::new();
|
|
||||||
stack.push(i);
|
|
||||||
while let Some(thread_iter) = stack.pop() {
|
|
||||||
if let Some(env_hash) = threads[&thread_iter].message() {
|
|
||||||
if !account.contains_key(env_hash) {
|
|
||||||
/* The envelope has been renamed or removed, so wait for the appropriate event to
|
|
||||||
* arrive */
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
envs_to_set.push(env_hash);
|
|
||||||
}
|
|
||||||
for c in 0..threads[&thread_iter].children().len() {
|
|
||||||
let c = threads[&thread_iter].children()[c];
|
|
||||||
stack.push(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for env_hash in envs_to_set {
|
|
||||||
match a {
|
|
||||||
ListingAction::SetSeen => {
|
|
||||||
let hash = account.get_env(&env_hash).hash();
|
|
||||||
let op = account.operation(hash);
|
|
||||||
let envelope: &mut Envelope =
|
|
||||||
&mut account.get_env_mut(&env_hash);
|
|
||||||
if let Err(e) = envelope.set_seen(op) {
|
|
||||||
context.replies.push_back(UIEvent::StatusEvent(
|
|
||||||
StatusEvent::DisplayMessage(e.to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.row_updates.push(i);
|
|
||||||
}
|
|
||||||
ListingAction::SetUnseen => {
|
|
||||||
let hash = account.get_env(&env_hash).hash();
|
|
||||||
let op = account.operation(hash);
|
|
||||||
let envelope: &mut Envelope =
|
|
||||||
&mut account.get_env_mut(&env_hash);
|
|
||||||
if let Err(e) = envelope.set_unseen(op) {
|
|
||||||
context.replies.push_back(UIEvent::StatusEvent(
|
|
||||||
StatusEvent::DisplayMessage(e.to_string()),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
self.row_updates.push(i);
|
|
||||||
}
|
|
||||||
ListingAction::Delete => { /* do nothing */ }
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
for v in self.selection.values_mut() {
|
for v in self.selection.values_mut() {
|
||||||
|
@ -1344,6 +1384,14 @@ impl Component for ConversationsListing {
|
||||||
Key::Char('v')
|
Key::Char('v')
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"set_seen",
|
||||||
|
if let Some(key) = config_map.get("set_seen") {
|
||||||
|
(*key).clone()
|
||||||
|
} else {
|
||||||
|
Key::Char('n')
|
||||||
|
},
|
||||||
|
),
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
|
|
|
@ -51,7 +51,8 @@ shortcut_key_values! { "compact_listing",
|
||||||
/// Shortcut listing for a mail listing in compact mode.
|
/// Shortcut listing for a mail listing in compact mode.
|
||||||
pub struct CompactListingShortcuts {
|
pub struct CompactListingShortcuts {
|
||||||
open_thread: Key |> "Open thread.",
|
open_thread: Key |> "Open thread.",
|
||||||
exit_thread: Key |> "Exit thread view."
|
exit_thread: Key |> "Exit thread view.",
|
||||||
|
set_seen: Key |> "Set thread as seen."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,6 +61,7 @@ impl Default for CompactListingShortcuts {
|
||||||
CompactListingShortcuts {
|
CompactListingShortcuts {
|
||||||
open_thread: Key::Char('\n'),
|
open_thread: Key::Char('\n'),
|
||||||
exit_thread: Key::Char('i'),
|
exit_thread: Key::Char('i'),
|
||||||
|
set_seen: Key::Char('n'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue