Add contact view page, edit headers in compose, index style in conf
parent
1883bb46dd
commit
62168e9183
|
@ -22,6 +22,7 @@ use chrono::{DateTime, Local};
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
|
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
|
||||||
pub struct AddressBook {
|
pub struct AddressBook {
|
||||||
|
@ -71,6 +72,14 @@ impl AddressBook {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Deref for AddressBook {
|
||||||
|
type Target = FnvHashMap<Uuid, Card>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &FnvHashMap<Uuid, Card> {
|
||||||
|
&self.cards
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
impl Card {
|
impl Card {
|
||||||
pub fn new() -> Card {
|
pub fn new() -> Card {
|
||||||
|
@ -94,6 +103,10 @@ impl Card {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn uuid(&self) -> &Uuid {
|
||||||
|
&self.uuid
|
||||||
|
}
|
||||||
|
|
||||||
pub fn title(&self) -> &str {
|
pub fn title(&self) -> &str {
|
||||||
self.title.as_str()
|
self.title.as_str()
|
||||||
}
|
}
|
||||||
|
@ -121,6 +134,9 @@ impl Card {
|
||||||
pub fn key(&self) -> &str {
|
pub fn key(&self) -> &str {
|
||||||
self.key.as_str()
|
self.key.as_str()
|
||||||
}
|
}
|
||||||
|
pub fn last_edited(&self) -> String {
|
||||||
|
self.last_edited.to_rfc2822()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_title(&mut self, new: &str) {
|
pub fn set_title(&mut self, new: &str) {
|
||||||
self.title = new.to_string();()
|
self.title = new.to_string();()
|
||||||
|
|
|
@ -64,9 +64,9 @@ fn main() {
|
||||||
|
|
||||||
/* Register some reasonably useful interfaces */
|
/* Register some reasonably useful interfaces */
|
||||||
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
||||||
let listing = listing::Listing::default();
|
let listing = listing::Listing::from(IndexStyle::Compact);
|
||||||
let b = Entity::from(Box::new(listing));
|
let b = Entity::from(Box::new(listing));
|
||||||
let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context))]));
|
let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context)), Box::new(ContactManager::default())]));
|
||||||
let window = Entity::from(tabs);
|
let window = Entity::from(tabs);
|
||||||
|
|
||||||
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
|
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
|
||||||
|
|
|
@ -21,8 +21,31 @@
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
macro_rules! write_field {
|
||||||
|
($title:expr, $value:expr, $target_grid:expr, $fg_color:expr, $bg_color:expr, $width:expr, $y:expr) => {{
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
$title,
|
||||||
|
&mut $target_grid,
|
||||||
|
$fg_color,
|
||||||
|
$bg_color,
|
||||||
|
((1, $y + 2), ($width - 1, $y + 2)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
write_string_to_grid(
|
||||||
|
&$value,
|
||||||
|
&mut $target_grid,
|
||||||
|
Color::Default,
|
||||||
|
Color::Default,
|
||||||
|
((x, y), ($width - 1, y)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
y
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ContactManager {
|
pub struct ContactManager {
|
||||||
|
pub card: Card,
|
||||||
content: CellBuffer,
|
content: CellBuffer,
|
||||||
dirty: bool,
|
dirty: bool,
|
||||||
initialized: bool,
|
initialized: bool,
|
||||||
|
@ -31,7 +54,8 @@ pub struct ContactManager {
|
||||||
impl Default for ContactManager {
|
impl Default for ContactManager {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
ContactManager {
|
ContactManager {
|
||||||
content: CellBuffer::default(),
|
card: Card::new(),
|
||||||
|
content: CellBuffer::new(200, 100, Cell::with_char(' ')),
|
||||||
dirty: true,
|
dirty: true,
|
||||||
initialized: false,
|
initialized: false,
|
||||||
}
|
}
|
||||||
|
@ -45,14 +69,66 @@ impl fmt::Display for ContactManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ContactManager {
|
impl ContactManager {
|
||||||
|
fn initialize(&mut self) {
|
||||||
|
let (width, height) = self.content.size();
|
||||||
|
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
"Contact Name ",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Byte(33),
|
||||||
|
Color::Default,
|
||||||
|
((0, 0), (width, 0)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
"Last edited: ",
|
||||||
|
&mut self.content,
|
||||||
|
Color::Byte(250),
|
||||||
|
Color::Default,
|
||||||
|
((x, 0), (width, 0)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let (x, y) = write_string_to_grid(
|
||||||
|
&self.card.last_edited(),
|
||||||
|
&mut self.content,
|
||||||
|
Color::Byte(250),
|
||||||
|
Color::Default,
|
||||||
|
((x, 0), (width, 0)),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
for x in 0..width {
|
||||||
|
set_and_join_box(&mut self.content, (x, 2), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 4), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 6), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 8), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 10), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 12), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 14), HORZ_BOUNDARY);
|
||||||
|
set_and_join_box(&mut self.content, (x, 16), HORZ_BOUNDARY);
|
||||||
|
}
|
||||||
|
for y in 0..height {
|
||||||
|
set_and_join_box(&mut self.content, (width - 1, y), VERT_BOUNDARY);
|
||||||
|
}
|
||||||
|
let mut y = write_field!("First Name: ", self.card.firstname(), self.content, Color::Byte(250), Color::Default, width, 1);
|
||||||
|
y = write_field!("Last Name: ", self.card.lastname(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
y = write_field!("Additional Name: ", self.card.additionalname(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
y = write_field!("Name Prefix: ", self.card.name_prefix(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
y = write_field!("Name Suffix: ", self.card.name_suffix(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
y = write_field!("E-mail: ", self.card.email(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
y = write_field!("url: ", self.card.url(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
y = write_field!("key: ", self.card.key(), self.content, Color::Byte(250), Color::Default, width, y);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for ContactManager {
|
impl Component for ContactManager {
|
||||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||||
if !self.initialized {
|
if !self.initialized {
|
||||||
clear_area(grid, area);
|
self.initialize();
|
||||||
self.initialized = true;
|
self.initialized = true;
|
||||||
}
|
}
|
||||||
|
clear_area(grid, area);
|
||||||
|
let (width, height) = self.content.size();
|
||||||
|
copy_area(grid, &self.content, area, ((0, 0), (width - 1, height -1)));
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,15 +20,29 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::dbg;
|
||||||
|
|
||||||
use melib::Draft;
|
use melib::Draft;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq)]
|
||||||
|
enum Cursor {
|
||||||
|
From,
|
||||||
|
To,
|
||||||
|
Cc,
|
||||||
|
Bcc,
|
||||||
|
Body,
|
||||||
|
Attachments,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Composer {
|
pub struct Composer {
|
||||||
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
||||||
account_cursor: usize,
|
account_cursor: usize,
|
||||||
|
|
||||||
|
cursor: Cursor,
|
||||||
|
|
||||||
pager: Pager,
|
pager: Pager,
|
||||||
draft: Draft,
|
draft: Draft,
|
||||||
|
|
||||||
|
@ -43,6 +57,8 @@ impl Default for Composer {
|
||||||
reply_context: None,
|
reply_context: None,
|
||||||
account_cursor: 0,
|
account_cursor: 0,
|
||||||
|
|
||||||
|
cursor: Cursor::To,
|
||||||
|
|
||||||
pager: Pager::default(),
|
pager: Pager::default(),
|
||||||
draft: Draft::default(),
|
draft: Draft::default(),
|
||||||
|
|
||||||
|
@ -57,6 +73,7 @@ impl Default for Composer {
|
||||||
enum ViewMode {
|
enum ViewMode {
|
||||||
Discard(Uuid),
|
Discard(Uuid),
|
||||||
Pager,
|
Pager,
|
||||||
|
Selector(Selector),
|
||||||
Overview,
|
Overview,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,6 +101,14 @@ impl ViewMode {
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_selector(&self) -> bool {
|
||||||
|
if let ViewMode::Selector(_) = self {
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for Composer {
|
impl fmt::Display for Composer {
|
||||||
|
@ -145,13 +170,21 @@ impl Composer {
|
||||||
let headers = self.draft.headers();
|
let headers = self.draft.headers();
|
||||||
{
|
{
|
||||||
let (mut x, mut y) = upper_left;
|
let (mut x, mut y) = upper_left;
|
||||||
for k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
|
for &k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
|
||||||
|
let bg_color = match self.cursor {
|
||||||
|
Cursor::Cc if k == "Cc" => Color::Byte(240),
|
||||||
|
Cursor::Bcc if k == "Bcc" => Color::Byte(240),
|
||||||
|
Cursor::To if k == "To" => Color::Byte(240),
|
||||||
|
_ => Color::Default,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
let update = {
|
let update = {
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
k,
|
k,
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
bg_color,
|
||||||
((x, y), set_y(bottom_right, y)),
|
((x, y), set_y(bottom_right, y)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
@ -159,11 +192,11 @@ impl Composer {
|
||||||
": ",
|
": ",
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
bg_color,
|
||||||
((x, y), set_y(bottom_right, y)),
|
((x, y), set_y(bottom_right, y)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
let (x, y) = if k == &"From" {
|
let (x, y) = if k == "From" {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
"◀ ",
|
"◀ ",
|
||||||
grid,
|
grid,
|
||||||
|
@ -176,14 +209,14 @@ impl Composer {
|
||||||
(x, y)
|
(x, y)
|
||||||
};
|
};
|
||||||
let (x, y) = write_string_to_grid(
|
let (x, y) = write_string_to_grid(
|
||||||
&headers[*k],
|
&headers[k],
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
bg_color,
|
||||||
((x, y), set_y(bottom_right, y)),
|
((x, y), set_y(bottom_right, y)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
if k == &"From" {
|
if k == "From" {
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
" ▶",
|
" ▶",
|
||||||
grid,
|
grid,
|
||||||
|
@ -284,64 +317,72 @@ impl Component for Composer {
|
||||||
clear_area(grid, header_area);
|
clear_area(grid, header_area);
|
||||||
clear_area(grid, body_area);
|
clear_area(grid, body_area);
|
||||||
self.draw_header_table(grid, header_area);
|
self.draw_header_table(grid, header_area);
|
||||||
self.pager.draw(grid, body_area, context);
|
|
||||||
|
|
||||||
/* Let user choose whether to quit with/without saving or cancel */
|
match self.mode {
|
||||||
if let ViewMode::Discard(_) = self.mode {
|
ViewMode::Overview | ViewMode::Pager => {
|
||||||
let mid_x = width!(area) / 2;
|
self.pager.draw(grid, body_area, context);
|
||||||
let mid_y = height!(area) / 2;
|
},
|
||||||
for x in mid_x - 40..=mid_x + 40 {
|
ViewMode::Selector(ref mut s) => {
|
||||||
for y in mid_y - 11..=mid_y + 11 {
|
s.draw(grid, body_area, context);
|
||||||
grid[(x, y)] = Cell::default();
|
},
|
||||||
|
ViewMode::Discard(_) => {
|
||||||
|
/* Let user choose whether to quit with/without saving or cancel */
|
||||||
|
let mid_x = width!(area) / 2;
|
||||||
|
let mid_y = height!(area) / 2;
|
||||||
|
for x in mid_x - 40..=mid_x + 40 {
|
||||||
|
for y in mid_y - 11..=mid_y + 11 {
|
||||||
|
grid[(x, y)] = Cell::default();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for i in mid_x - 40..=mid_x + 40 {
|
for i in mid_x - 40..=mid_x + 40 {
|
||||||
set_and_join_box(grid, (i, mid_y - 11), HORZ_BOUNDARY);
|
set_and_join_box(grid, (i, mid_y - 11), HORZ_BOUNDARY);
|
||||||
|
|
||||||
set_and_join_box(grid, (i, mid_y + 11), HORZ_BOUNDARY);
|
set_and_join_box(grid, (i, mid_y + 11), HORZ_BOUNDARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in mid_y - 11..=mid_y + 11 {
|
for i in mid_y - 11..=mid_y + 11 {
|
||||||
set_and_join_box(grid, (mid_x - 40, i), VERT_BOUNDARY);
|
set_and_join_box(grid, (mid_x - 40, i), VERT_BOUNDARY);
|
||||||
|
|
||||||
set_and_join_box(grid, (mid_x + 40, i), VERT_BOUNDARY);
|
set_and_join_box(grid, (mid_x + 40, i), VERT_BOUNDARY);
|
||||||
}
|
}
|
||||||
|
|
||||||
let area = ((mid_x - 20, mid_y - 7), (mid_x + 39, mid_y + 10));
|
let area = ((mid_x - 20, mid_y - 7), (mid_x + 39, mid_y + 10));
|
||||||
|
|
||||||
let (_, y) = write_string_to_grid(
|
let (_, y) = write_string_to_grid(
|
||||||
&format!("Draft \"{:10}\"", self.draft.headers()["Subject"]),
|
&format!("Draft \"{:10}\"", self.draft.headers()["Subject"]),
|
||||||
grid,
|
grid,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
Color::Default,
|
Color::Default,
|
||||||
area,
|
area,
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
let (_, y) = write_string_to_grid(
|
let (_, y) = write_string_to_grid(
|
||||||
"[x] quit without saving",
|
"[x] quit without saving",
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(124),
|
Color::Byte(124),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left!(area), y + 2), bottom_right!(area)),
|
(set_y(upper_left!(area), y + 2), bottom_right!(area)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
let (_, y) = write_string_to_grid(
|
let (_, y) = write_string_to_grid(
|
||||||
"[y] save draft and quit",
|
"[y] save draft and quit",
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(124),
|
Color::Byte(124),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
|
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
write_string_to_grid(
|
write_string_to_grid(
|
||||||
"[n] cancel",
|
"[n] cancel",
|
||||||
grid,
|
grid,
|
||||||
Color::Byte(124),
|
Color::Byte(124),
|
||||||
Color::Default,
|
Color::Default,
|
||||||
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
|
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
context.dirty_areas.push_back(area);
|
context.dirty_areas.push_back(area);
|
||||||
|
@ -360,16 +401,81 @@ impl Component for Composer {
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
(ViewMode::Selector(ref mut s), _) => {
|
||||||
|
if s.process_event(event, context) {
|
||||||
|
self.dirty = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
},
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
match event.event_type {
|
match event.event_type {
|
||||||
|
UIEventType::Input(Key::Up) if self.mode.is_overview() => {
|
||||||
|
match self.cursor {
|
||||||
|
Cursor::From => {},
|
||||||
|
Cursor::To => {
|
||||||
|
self.cursor = Cursor::From;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::Cc => {
|
||||||
|
self.cursor = Cursor::To;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::Bcc => {
|
||||||
|
self.cursor = Cursor::Cc;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::Body => {
|
||||||
|
self.cursor = Cursor::Bcc;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Down) if self.mode.is_overview() => {
|
||||||
|
match self.cursor {
|
||||||
|
Cursor::From => {
|
||||||
|
self.cursor = Cursor::To;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::To => {
|
||||||
|
self.cursor = Cursor::Cc;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::Cc => {
|
||||||
|
self.cursor = Cursor::Bcc;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::Bcc => {
|
||||||
|
self.cursor = Cursor::Body;
|
||||||
|
self.dirty = true;
|
||||||
|
},
|
||||||
|
Cursor::Body => {},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Esc) if self.mode.is_selector() => {
|
||||||
|
self.mode = ViewMode::Overview;
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
UIEventType::Input(Key::Char('\n')) if self.mode.is_selector() => {
|
||||||
|
let mut old_mode = std::mem::replace(&mut self.mode, ViewMode::Overview);
|
||||||
|
if let ViewMode::Selector(s) = old_mode {
|
||||||
|
eprintln!("collected {:?}", s.collect());
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
UIEventType::Resize => {
|
UIEventType::Resize => {
|
||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
}
|
},
|
||||||
/* Switch e-mail From: field to the `left` configured account. */
|
/* Switch e-mail From: field to the `left` configured account. */
|
||||||
UIEventType::Input(Key::Left) => {
|
UIEventType::Input(Key::Left) if self.cursor == Cursor::From => {
|
||||||
self.account_cursor = self.account_cursor.saturating_sub(1);
|
self.account_cursor = self.account_cursor.saturating_sub(1);
|
||||||
self.draft.headers_mut().insert(
|
self.draft.headers_mut().insert(
|
||||||
"From".into(),
|
"From".into(),
|
||||||
|
@ -379,7 +485,7 @@ impl Component for Composer {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/* Switch e-mail From: field to the `right` configured account. */
|
/* Switch e-mail From: field to the `right` configured account. */
|
||||||
UIEventType::Input(Key::Right) => {
|
UIEventType::Input(Key::Right) if self.cursor == Cursor::From => {
|
||||||
if self.account_cursor + 1 < context.accounts.len() {
|
if self.account_cursor + 1 < context.accounts.len() {
|
||||||
self.account_cursor += 1;
|
self.account_cursor += 1;
|
||||||
self.draft.headers_mut().insert(
|
self.draft.headers_mut().insert(
|
||||||
|
@ -432,33 +538,48 @@ impl Component for Composer {
|
||||||
self.set_dirty();
|
self.set_dirty();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/* Edit draft in $EDITOR */
|
|
||||||
UIEventType::Input(Key::Char('e')) => {
|
UIEventType::Input(Key::Char('e')) => {
|
||||||
use std::process::{Command, Stdio};
|
match self.cursor {
|
||||||
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
Cursor::Body => {
|
||||||
{
|
/* Edit draft in $EDITOR */
|
||||||
context.input_kill();
|
use std::process::{Command, Stdio};
|
||||||
}
|
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||||
let mut f =
|
{
|
||||||
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
|
context.input_kill();
|
||||||
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
}
|
||||||
|
let mut f =
|
||||||
|
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
|
||||||
|
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
|
||||||
|
|
||||||
// TODO: check exit status
|
// TODO: check exit status
|
||||||
Command::new("vim")
|
Command::new("vim")
|
||||||
.arg("+/^$")
|
.arg("+/^$")
|
||||||
.arg(&f.path())
|
.arg(&f.path())
|
||||||
.stdin(Stdio::inherit())
|
.stdin(Stdio::inherit())
|
||||||
.stdout(Stdio::inherit())
|
.stdout(Stdio::inherit())
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to execute process");
|
.expect("failed to execute process");
|
||||||
let result = f.read_to_string();
|
let result = f.read_to_string();
|
||||||
self.draft = Draft::from_str(result.as_str()).unwrap();
|
self.draft = Draft::from_str(result.as_str()).unwrap();
|
||||||
self.pager.update_from_str(self.draft.body());
|
self.pager.update_from_str(self.draft.body());
|
||||||
context.replies.push_back(UIEvent {
|
context.replies.push_back(UIEvent {
|
||||||
id: 0,
|
id: 0,
|
||||||
event_type: UIEventType::Fork(ForkType::Finished),
|
event_type: UIEventType::Fork(ForkType::Finished),
|
||||||
});
|
});
|
||||||
context.restore_input();
|
context.restore_input();
|
||||||
|
},
|
||||||
|
Cursor::To | Cursor::Cc | Cursor::Bcc => {
|
||||||
|
let account = &context.accounts[self.account_cursor];
|
||||||
|
let mut entries = account.address_book.values().map(|v| (v.uuid().as_bytes().to_vec(), v.email().to_string())).collect();
|
||||||
|
self.mode = ViewMode::Selector(Selector::new(entries, true));
|
||||||
|
},
|
||||||
|
Cursor::Attachments => {
|
||||||
|
unimplemented!()
|
||||||
|
},
|
||||||
|
Cursor::From => {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
self.dirty = true;
|
self.dirty = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
@ -116,3 +116,14 @@ impl Component for Listing {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<IndexStyle> for Listing {
|
||||||
|
fn from(index_style: IndexStyle) -> Self {
|
||||||
|
match index_style {
|
||||||
|
IndexStyle::Plain => Listing::Plain(Default::default()),
|
||||||
|
IndexStyle::Threaded => Listing::Threaded(Default::default()),
|
||||||
|
IndexStyle::Compact => Listing::Compact(Default::default()),
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use std::dbg;
|
||||||
|
|
||||||
const MAX_COLS: usize = 500;
|
const MAX_COLS: usize = 500;
|
||||||
|
|
||||||
|
@ -434,6 +435,7 @@ impl Component for ThreadListing {
|
||||||
/* Draw the entire list */
|
/* Draw the entire list */
|
||||||
self.draw_list(grid, area, context);
|
self.draw_list(grid, area, context);
|
||||||
} else {
|
} else {
|
||||||
|
self.cursor_pos = self.new_cursor_pos;
|
||||||
let upper_left = upper_left!(area);
|
let upper_left = upper_left!(area);
|
||||||
let bottom_right = bottom_right!(area);
|
let bottom_right = bottom_right!(area);
|
||||||
if self.length == 0 && self.dirty {
|
if self.length == 0 && self.dirty {
|
||||||
|
@ -469,8 +471,9 @@ impl Component for ThreadListing {
|
||||||
let account = &mut context.accounts[self.cursor_pos.0];
|
let account = &mut context.accounts[self.cursor_pos.0];
|
||||||
let (hash, is_seen) = {
|
let (hash, is_seen) = {
|
||||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||||
|
eprintln!("key is {}", self.locations[dbg!(self.cursor_pos).2]);
|
||||||
let envelope: &Envelope =
|
let envelope: &Envelope =
|
||||||
&mailbox.collection[&self.locations[self.new_cursor_pos.2]];
|
&mailbox.collection[&self.locations[self.cursor_pos.2]];
|
||||||
(envelope.hash(), envelope.is_seen())
|
(envelope.hash(), envelope.is_seen())
|
||||||
};
|
};
|
||||||
if !is_seen {
|
if !is_seen {
|
||||||
|
@ -484,7 +487,7 @@ impl Component for ThreadListing {
|
||||||
};
|
};
|
||||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||||
let envelope: &mut Envelope =
|
let envelope: &mut Envelope =
|
||||||
mailbox.collection.get_mut(&self.locations[self.new_cursor_pos.2]).unwrap();
|
mailbox.collection.get_mut(&self.locations[self.cursor_pos.2]).unwrap();
|
||||||
envelope.set_seen(op).unwrap();
|
envelope.set_seen(op).unwrap();
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -395,6 +395,13 @@ impl Component for MailView {
|
||||||
match self.mode {
|
match self.mode {
|
||||||
ViewMode::ContactSelector(_) => {
|
ViewMode::ContactSelector(_) => {
|
||||||
if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) {
|
if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) {
|
||||||
|
for c in s.collect() {
|
||||||
|
let mut new_card: Card = Card::new();
|
||||||
|
let email = String::from_utf8(c).unwrap();
|
||||||
|
new_card.set_email(&email);
|
||||||
|
new_card.set_firstname("");
|
||||||
|
context.accounts[self.coordinates.0].address_book.add_card(new_card);
|
||||||
|
}
|
||||||
//eprintln!("{:?}", s.collect());
|
//eprintln!("{:?}", s.collect());
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -899,7 +899,7 @@ impl Component for Selector {
|
||||||
self.cursor -= 1;
|
self.cursor -= 1;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
UIEventType::Input(Key::Down) if self.cursor < height - 1=> {
|
UIEventType::Input(Key::Down) if self.cursor < height.saturating_sub(1) => {
|
||||||
self.cursor += 1;
|
self.cursor += 1;
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,11 +28,13 @@ pub mod pager;
|
||||||
|
|
||||||
pub mod accounts;
|
pub mod accounts;
|
||||||
pub use self::accounts::Account;
|
pub use self::accounts::Account;
|
||||||
|
use self::config::{Config, File, FileFormat};
|
||||||
|
|
||||||
use melib::conf::AccountSettings;
|
use melib::conf::AccountSettings;
|
||||||
use melib::error::*;
|
use melib::error::*;
|
||||||
use pager::PagerSettings;
|
use pager::PagerSettings;
|
||||||
|
|
||||||
|
use self::serde::{de, Deserialize, Deserializer};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::env;
|
use std::env;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
@ -63,7 +65,8 @@ pub struct FileAccount {
|
||||||
draft_folder: String,
|
draft_folder: String,
|
||||||
identity: String,
|
identity: String,
|
||||||
display_name: Option<String>,
|
display_name: Option<String>,
|
||||||
threaded: bool,
|
#[serde(deserialize_with = "index_from_str")]
|
||||||
|
index: IndexStyle,
|
||||||
folders: Option<HashMap<String, FolderConf>>,
|
folders: Option<HashMap<String, FolderConf>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,11 +101,8 @@ impl FileAccount {
|
||||||
pub fn folder(&self) -> &str {
|
pub fn folder(&self) -> &str {
|
||||||
&self.root_folder
|
&self.root_folder
|
||||||
}
|
}
|
||||||
pub fn threaded(&self) -> bool {
|
pub fn index(&self) -> IndexStyle {
|
||||||
self.threaded
|
self.index
|
||||||
}
|
|
||||||
pub fn toggle_threaded(&mut self) {
|
|
||||||
self.threaded = !self.threaded;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,6 @@ pub struct Settings {
|
||||||
pub pager: PagerSettings,
|
pub pager: PagerSettings,
|
||||||
}
|
}
|
||||||
|
|
||||||
use self::config::{Config, File, FileFormat};
|
|
||||||
impl FileSettings {
|
impl FileSettings {
|
||||||
pub fn new() -> Result<FileSettings> {
|
pub fn new() -> Result<FileSettings> {
|
||||||
let config_path = match env::var("MELI_CONFIG") {
|
let config_path = match env::var("MELI_CONFIG") {
|
||||||
|
@ -183,3 +182,29 @@ impl Settings {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[derive(Copy, Debug, Clone, Deserialize)]
|
||||||
|
pub enum IndexStyle {
|
||||||
|
Plain,
|
||||||
|
Threaded,
|
||||||
|
Compact,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for IndexStyle {
|
||||||
|
fn default() -> Self {
|
||||||
|
IndexStyle::Compact
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn index_from_str<'de, D>(deserializer: D) -> std::result::Result<IndexStyle, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
let s = <String>::deserialize(deserializer)?;
|
||||||
|
match s.as_str() {
|
||||||
|
"Plain" | "plain" => Ok(IndexStyle::Plain),
|
||||||
|
"Threaded" | "threaded" => Ok(IndexStyle::Threaded),
|
||||||
|
"Compact" | "compact" => Ok(IndexStyle::Compact),
|
||||||
|
_ => Err(de::Error::custom("invalid `index` value")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -170,6 +170,10 @@ impl CellBuffer {
|
||||||
}
|
}
|
||||||
content
|
content
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn is_empty(&self) -> bool {
|
||||||
|
self.buf.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HasSize for CellBuffer {
|
impl HasSize for CellBuffer {
|
||||||
|
@ -566,6 +570,11 @@ pub fn copy_area_with_break(
|
||||||
);
|
);
|
||||||
return upper_left!(dest);
|
return upper_left!(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if grid_src.is_empty() || grid_dest.is_empty() {
|
||||||
|
return upper_left!(dest);
|
||||||
|
}
|
||||||
|
|
||||||
let mut ret = bottom_right!(dest);
|
let mut ret = bottom_right!(dest);
|
||||||
let mut src_x = get_x(upper_left!(src));
|
let mut src_x = get_x(upper_left!(src));
|
||||||
let mut src_y = get_y(upper_left!(src));
|
let mut src_y = get_y(upper_left!(src));
|
||||||
|
@ -609,6 +618,10 @@ pub fn copy_area(grid_dest: &mut CellBuffer, grid_src: &CellBuffer, dest: Area,
|
||||||
return upper_left!(dest);
|
return upper_left!(dest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if grid_src.is_empty() || grid_dest.is_empty() {
|
||||||
|
return upper_left!(dest);
|
||||||
|
}
|
||||||
|
|
||||||
let mut ret = bottom_right!(dest);
|
let mut ret = bottom_right!(dest);
|
||||||
let mut src_x = get_x(upper_left!(src));
|
let mut src_x = get_x(upper_left!(src));
|
||||||
let mut src_y = get_y(upper_left!(src));
|
let mut src_y = get_y(upper_left!(src));
|
||||||
|
|
Loading…
Reference in New Issue