mirror of https://git.meli.delivery/meli/meli
mail/listing.rs: move mail view to listing parent component
Instead of having a different widget to view mail in for each Listing (plain, threaded, compact, etc) use a single widget in the listing's parent type. This will help with making the listing logic more modular in future refactors to allow all combinations of listing/mail view/ thread view positions and layouts.pull/227/head
parent
5c9b3fb044
commit
575509f1ed
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,180 @@
|
||||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use melib::{Envelope, Error, Mail, Result};
|
||||
|
||||
use super::{EnvelopeView, MailView, ViewSettings};
|
||||
use crate::{jobs::JoinHandle, mailbox_settings, Component, Context, ShortcutMaps, UIEvent};
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum PendingReplyAction {
|
||||
Reply,
|
||||
ReplyToAuthor,
|
||||
ReplyToAll,
|
||||
ForwardAttachment,
|
||||
ForwardInline,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MailViewState {
|
||||
Init {
|
||||
pending_action: Option<PendingReplyAction>,
|
||||
},
|
||||
LoadingBody {
|
||||
handle: JoinHandle<Result<Vec<u8>>>,
|
||||
pending_action: Option<PendingReplyAction>,
|
||||
},
|
||||
Error {
|
||||
err: Error,
|
||||
},
|
||||
Loaded {
|
||||
bytes: Vec<u8>,
|
||||
env: Box<Envelope>,
|
||||
env_view: Box<EnvelopeView>,
|
||||
stack: Vec<Box<dyn Component>>,
|
||||
},
|
||||
}
|
||||
|
||||
impl MailViewState {
|
||||
pub fn load_bytes(self_: &mut MailView, bytes: Vec<u8>, context: &mut Context) {
|
||||
let Some(coordinates) = self_.coordinates else { return; };
|
||||
let account = &mut context.accounts[&coordinates.0];
|
||||
if account
|
||||
.collection
|
||||
.get_env(coordinates.2)
|
||||
.other_headers()
|
||||
.is_empty()
|
||||
{
|
||||
let _ = account
|
||||
.collection
|
||||
.get_env_mut(coordinates.2)
|
||||
.populate_headers(&bytes);
|
||||
}
|
||||
let env = Box::new(account.collection.get_env(coordinates.2).clone());
|
||||
let env_view = Box::new(EnvelopeView::new(
|
||||
Mail {
|
||||
envelope: *env.clone(),
|
||||
bytes: bytes.clone(),
|
||||
},
|
||||
None,
|
||||
None,
|
||||
Some(ViewSettings {
|
||||
theme_default: crate::conf::value(context, "theme_default"),
|
||||
body_theme: crate::conf::value(context, "mail.view.body"),
|
||||
env_view_shortcuts: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.shortcuts
|
||||
.envelope_view
|
||||
)
|
||||
.key_values(),
|
||||
pager_filter: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.filter
|
||||
)
|
||||
.clone(),
|
||||
html_filter: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.html_filter
|
||||
)
|
||||
.clone(),
|
||||
url_launcher: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.url_launcher
|
||||
)
|
||||
.clone(),
|
||||
auto_choose_multipart_alternative: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pager
|
||||
.auto_choose_multipart_alternative
|
||||
)
|
||||
.is_true(),
|
||||
expand_headers: false,
|
||||
sticky_headers: *mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pager.sticky_headers
|
||||
),
|
||||
show_date_in_my_timezone: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pager
|
||||
.show_date_in_my_timezone
|
||||
)
|
||||
.is_true(),
|
||||
show_extra_headers: mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pager
|
||||
.show_extra_headers
|
||||
)
|
||||
.clone(),
|
||||
auto_verify_signatures: *mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1]
|
||||
.pgp
|
||||
.auto_verify_signatures
|
||||
),
|
||||
auto_decrypt: *mailbox_settings!(
|
||||
context[coordinates.0][&coordinates.1].pgp.auto_decrypt
|
||||
),
|
||||
}),
|
||||
context.main_loop_handler.clone(),
|
||||
));
|
||||
self_.state = MailViewState::Loaded {
|
||||
env,
|
||||
bytes,
|
||||
env_view,
|
||||
stack: vec![],
|
||||
};
|
||||
}
|
||||
|
||||
pub fn is_dirty(&self) -> bool {
|
||||
matches!(self, Self::Loaded { ref env_view, .. } if env_view.is_dirty())
|
||||
}
|
||||
|
||||
pub fn set_dirty(&mut self, dirty: bool) {
|
||||
if let Self::Loaded {
|
||||
ref mut env_view, ..
|
||||
} = self
|
||||
{
|
||||
env_view.set_dirty(dirty);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shortcuts(&self, context: &Context) -> ShortcutMaps {
|
||||
if let Self::Loaded { ref env_view, .. } = self {
|
||||
env_view.shortcuts(context)
|
||||
} else {
|
||||
ShortcutMaps::default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
|
||||
if let Self::Loaded {
|
||||
ref mut env_view, ..
|
||||
} = self
|
||||
{
|
||||
env_view.process_event(event, context)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for MailViewState {
|
||||
fn default() -> Self {
|
||||
MailViewState::Init {
|
||||
pending_action: None,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,183 @@
|
||||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use melib::{attachment_types::Charset, pgp::DecryptionMetadata, Attachment, Error, Result};
|
||||
|
||||
use crate::{
|
||||
conf::shortcuts::EnvelopeViewShortcuts,
|
||||
jobs::{JobId, JoinHandle},
|
||||
ShortcutMap, ThemeAttribute, UIDialog,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ViewSettings {
|
||||
pub pager_filter: Option<String>,
|
||||
pub html_filter: Option<String>,
|
||||
pub url_launcher: Option<String>,
|
||||
pub expand_headers: bool,
|
||||
pub theme_default: ThemeAttribute,
|
||||
pub env_view_shortcuts: ShortcutMap,
|
||||
/// `"mail.view.body"`
|
||||
pub body_theme: ThemeAttribute,
|
||||
pub auto_choose_multipart_alternative: bool,
|
||||
pub sticky_headers: bool,
|
||||
pub show_date_in_my_timezone: bool,
|
||||
pub show_extra_headers: Vec<String>,
|
||||
pub auto_verify_signatures: bool,
|
||||
pub auto_decrypt: bool,
|
||||
}
|
||||
|
||||
impl Default for ViewSettings {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
theme_default: Default::default(),
|
||||
body_theme: Default::default(),
|
||||
pager_filter: None,
|
||||
html_filter: None,
|
||||
url_launcher: None,
|
||||
env_view_shortcuts: EnvelopeViewShortcuts::default().key_values(),
|
||||
auto_choose_multipart_alternative: true,
|
||||
expand_headers: false,
|
||||
sticky_headers: false,
|
||||
show_date_in_my_timezone: false,
|
||||
show_extra_headers: vec![],
|
||||
auto_verify_signatures: true,
|
||||
auto_decrypt: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub enum LinkKind {
|
||||
Url,
|
||||
Email,
|
||||
}
|
||||
|
||||
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
|
||||
pub struct Link {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub kind: self::LinkKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub enum ForceCharset {
|
||||
#[default]
|
||||
None,
|
||||
Dialog(Box<UIDialog<Option<Charset>>>),
|
||||
Forced(Charset),
|
||||
}
|
||||
|
||||
impl Into<Option<Charset>> for &ForceCharset {
|
||||
fn into(self) -> Option<Charset> {
|
||||
match self {
|
||||
ForceCharset::Forced(val) => Some(*val),
|
||||
ForceCharset::None | ForceCharset::Dialog(_) => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||
pub enum Source {
|
||||
Decoded,
|
||||
Raw,
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Default)]
|
||||
pub enum ViewMode {
|
||||
#[default]
|
||||
Normal,
|
||||
Url,
|
||||
Attachment(usize),
|
||||
Source(Source),
|
||||
Subview,
|
||||
}
|
||||
|
||||
macro_rules! is_variant {
|
||||
($n:ident, $($var:tt)+) => {
|
||||
#[inline]
|
||||
pub fn $n(&self) -> bool {
|
||||
matches!(self, Self::$($var)*)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
impl ViewMode {
|
||||
is_variant! { is_normal, Normal }
|
||||
is_variant! { is_url, Url }
|
||||
is_variant! { is_attachment, Attachment(_) }
|
||||
is_variant! { is_source, Source(_) }
|
||||
is_variant! { is_subview, Subview }
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum AttachmentDisplay {
|
||||
Alternative {
|
||||
inner: Box<Attachment>,
|
||||
shown_display: usize,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
},
|
||||
InlineText {
|
||||
inner: Box<Attachment>,
|
||||
comment: Option<String>,
|
||||
text: String,
|
||||
},
|
||||
InlineOther {
|
||||
inner: Box<Attachment>,
|
||||
},
|
||||
Attachment {
|
||||
inner: Box<Attachment>,
|
||||
},
|
||||
SignedPending {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
handle: JoinHandle<Result<()>>,
|
||||
job_id: JobId,
|
||||
},
|
||||
SignedFailed {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
error: Error,
|
||||
},
|
||||
SignedUnverified {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
},
|
||||
SignedVerified {
|
||||
inner: Box<Attachment>,
|
||||
display: Vec<AttachmentDisplay>,
|
||||
description: String,
|
||||
},
|
||||
EncryptedPending {
|
||||
inner: Box<Attachment>,
|
||||
handle: JoinHandle<Result<(DecryptionMetadata, Vec<u8>)>>,
|
||||
},
|
||||
EncryptedFailed {
|
||||
inner: Box<Attachment>,
|
||||
error: Error,
|
||||
},
|
||||
EncryptedSuccess {
|
||||
inner: Box<Attachment>,
|
||||
plaintext: Box<Attachment>,
|
||||
plaintext_display: Vec<AttachmentDisplay>,
|
||||
description: String,
|
||||
},
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* meli
|
||||
*
|
||||
* Copyright 2017-2018 Manos Pitsidianakis
|
||||
*
|
||||
* This file is part of meli.
|
||||
*
|
||||
* meli is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* meli is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
use std::{fs::File, io::Write, os::unix::fs::PermissionsExt, path::Path};
|
||||
|
||||
use melib::Result;
|
||||
|
||||
pub fn save_attachment(path: &Path, bytes: &[u8]) -> Result<()> {
|
||||
let mut f = File::create(path)?;
|
||||
let mut permissions = f.metadata()?.permissions();
|
||||
permissions.set_mode(0o600); // Read/write for owner only.
|
||||
f.set_permissions(permissions)?;
|
||||
f.write_all(bytes)?;
|
||||
f.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn desktop_exec_to_command(command: &str, path: String, is_url: bool) -> String {
|
||||
/* Purge unused field codes */
|
||||
let command = command
|
||||
.replace("%i", "")
|
||||
.replace("%c", "")
|
||||
.replace("%k", "");
|
||||
if command.contains("%f") {
|
||||
command.replacen("%f", &path.replace(' ', "\\ "), 1)
|
||||
} else if command.contains("%F") {
|
||||
command.replacen("%F", &path.replace(' ', "\\ "), 1)
|
||||
} else if command.contains("%u") || command.contains("%U") {
|
||||
let from_pattern = if command.contains("%u") { "%u" } else { "%U" };
|
||||
if is_url {
|
||||
command.replacen(from_pattern, &path, 1)
|
||||
} else {
|
||||
command.replacen(
|
||||
from_pattern,
|
||||
&format!("file://{}", path).replace(' ', "\\ "),
|
||||
1,
|
||||
)
|
||||
}
|
||||
} else if is_url {
|
||||
format!("{} {}", command, path)
|
||||
} else {
|
||||
format!("{} {}", command, path.replace(' ', "\\ "))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_desktop_exec() {
|
||||
assert_eq!(
|
||||
"ristretto /tmp/file".to_string(),
|
||||
desktop_exec_to_command("ristretto %F", "/tmp/file".to_string(), false)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/lib/firefox-esr/firefox-esr file:///tmp/file".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"/tmp/file".to_string(),
|
||||
false
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/lib/firefox-esr/firefox-esr www.example.com".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/lib/firefox-esr/firefox-esr %u",
|
||||
"www.example.com".to_string(),
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"/usr/bin/vlc --started-from-file www.example.com".to_string(),
|
||||
desktop_exec_to_command(
|
||||
"/usr/bin/vlc --started-from-file %U",
|
||||
"www.example.com".to_string(),
|
||||
true
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
"zathura --fork file:///tmp/file".to_string(),
|
||||
desktop_exec_to_command("zathura --fork %U", "file:///tmp/file".to_string(), true)
|
||||
);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue