mirror of
https://github.com/flectar/mail.git
synced 2026-11-11 17:57:54 +00:00
feat: move and copy mail to other accounts from the folder picker
This commit is contained in:
1 parent
3ef3ccf5df
commit
f60df67dfb
13 files changed
+1295
-696
No files matched your search
+521
-460
File diff suppressed because it is too large.
Load diff
+32
@@ -1784,6 +1784,38 @@ impl CoreMailSource {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
/// Copies or moves mail to a folder of another account. The original of
|
||||
/// a move goes once the copy is in place.
|
||||
pub async fn transfer_to_folder(
|
||||
&self,
|
||||
target: MailTarget,
|
||||
folder_id: i64,
|
||||
is_move: bool,
|
||||
) -> Result<(), String> {
|
||||
self.core
|
||||
.transfer_mail(target.action_target(), folder_id, is_move)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
/// Files mail in one more folder of its JMAP account.
|
||||
pub async fn copy_to_folder(&self, target: MailTarget, folder_id: i64) -> Result<(), String> {
|
||||
self.core
|
||||
.perform_action(PerformActionArgs {
|
||||
kind: ActionKind::Copy,
|
||||
target: target.action_target(),
|
||||
params: Some(ActionParams {
|
||||
wake_at: None,
|
||||
target_folder_id: Some(folder_id),
|
||||
label_id: None,
|
||||
}),
|
||||
})
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
/// Inbox tabs route conversations, so a message's routes its conversation.
|
||||
pub async fn route_to_tab(&self, target: MailTarget, tab: String) -> Result<(), String> {
|
||||
self.core
|
||||
|
||||
@@ -139,4 +139,26 @@ fn drops_resolve_by_scope_not_by_mailbox_name() {
|
||||
Ok(MailDropDestination::Action("trash"))
|
||||
);
|
||||
assert!(drop(Scope::Folder(12), 1, 12).is_err());
|
||||
|
||||
// Another account's folder gets a copy, and the original goes once it
|
||||
// is there; its Sent and Starred take no mail.
|
||||
let other = |scope, folder_id, label| MailboxEntry {
|
||||
account_id: 2,
|
||||
..mailbox(scope, folder_id, label)
|
||||
};
|
||||
let other_sent = Scope::Account {
|
||||
account_id: 2,
|
||||
mailbox: StandardMailbox::Sent,
|
||||
};
|
||||
state.mailboxes.push(other(Scope::Folder(30), 30, "Travel"));
|
||||
state.mailboxes.push(other(other_sent, 31, "Sent"));
|
||||
let drop = |scope, account_id, folder_id| {
|
||||
resolve_single_mail_drop(&state, &item, scope, account_id, folder_id)
|
||||
.map(|(_, destination)| destination)
|
||||
};
|
||||
assert_eq!(
|
||||
drop(Scope::Folder(30), 2, 30),
|
||||
Ok(MailDropDestination::Transfer(30))
|
||||
);
|
||||
assert!(drop(other_sent, 2, 31).is_err());
|
||||
}
|
||||
@@ -44,8 +44,27 @@ pub(super) enum Operation {
|
||||
EmptyTrash(Option<i64>),
|
||||
Drop(MailDropDestination),
|
||||
Label(i64, bool),
|
||||
/// Files mail in one more folder: another of its own account's, where
|
||||
/// a message can be in several (JMAP), or a copy in another account's.
|
||||
Copy {
|
||||
folder_id: i64,
|
||||
other_account: bool,
|
||||
},
|
||||
}
|
||||
impl Operation {
|
||||
/// Whether it starts moving (`true`) or copying mail to another
|
||||
/// account, which goes on after it returns.
|
||||
fn starts_transfer(&self) -> Option<bool> {
|
||||
match self {
|
||||
Self::Drop(MailDropDestination::Transfer(_)) => Some(true),
|
||||
Self::Copy {
|
||||
other_account: true,
|
||||
..
|
||||
} => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn queues_permanent_deletion(&self) -> bool {
|
||||
match self {
|
||||
Self::EmptyTrash(_) => true,
|
||||
@@ -75,6 +94,17 @@ impl Operation {
|
||||
core.perform_message_action(target, a).await
|
||||
}
|
||||
Self::Drop(MailDropDestination::Folder(id)) => core.move_to_folder(target, *id).await,
|
||||
Self::Drop(MailDropDestination::Transfer(id)) => {
|
||||
core.transfer_to_folder(target, *id, true).await
|
||||
}
|
||||
Self::Copy {
|
||||
folder_id,
|
||||
other_account: true,
|
||||
} => core.transfer_to_folder(target, *folder_id, false).await,
|
||||
Self::Copy {
|
||||
folder_id,
|
||||
other_account: false,
|
||||
} => core.copy_to_folder(target, *folder_id).await,
|
||||
Self::Drop(MailDropDestination::Label(id)) => {
|
||||
core.perform_label_action(target, *id, true).await
|
||||
}
|
||||
@@ -96,6 +126,9 @@ struct ActionResult {
|
||||
moved: Vec<i32>,
|
||||
error: Option<String>,
|
||||
deletion_queued: bool,
|
||||
/// Mail started on its way to another account, moving (`true`) or
|
||||
/// being copied.
|
||||
transfer_started: Option<bool>,
|
||||
}
|
||||
|
||||
pub(super) fn register(
|
||||
@@ -279,6 +312,13 @@ pub(super) fn register(
|
||||
Some(error) => {
|
||||
app.set_render_status(UiMessage::detail("Message action failed: {}", error))
|
||||
}
|
||||
// What became of it is told once the other account has it.
|
||||
None if result.transfer_started == Some(true) => {
|
||||
app.set_render_status(UiMessage::plain("Moving mail to the other account…"))
|
||||
}
|
||||
None if result.transfer_started == Some(false) => {
|
||||
app.set_render_status(UiMessage::plain("Copying mail to the other account…"))
|
||||
}
|
||||
None if result.deletion_queued => app.set_render_status(UiMessage::plain(
|
||||
"Permanent deletion queued. Mail will disappear after the server confirms.",
|
||||
)),
|
||||
@@ -447,14 +487,17 @@ where
|
||||
moved: vec![],
|
||||
error: None,
|
||||
deletion_queued: false,
|
||||
transfer_started: None,
|
||||
};
|
||||
for (id, target, operation) in operations {
|
||||
let deletion = operation.queues_permanent_deletion();
|
||||
let transfer = operation.starts_transfer();
|
||||
result.ids.push(id);
|
||||
let moves = operation.moves();
|
||||
match perform(target, operation).await {
|
||||
Ok(()) => {
|
||||
result.deletion_queued |= deletion;
|
||||
result.transfer_started = result.transfer_started.or(transfer);
|
||||
result.completed.push(id);
|
||||
if moves {
|
||||
result.moved.push(id);
|
||||
|
||||
+28
-9
@@ -706,6 +706,8 @@ struct MailDragItem {
|
||||
enum MailDropDestination {
|
||||
Action(&'static str),
|
||||
Folder(i64),
|
||||
/// A folder of another account.
|
||||
Transfer(i64),
|
||||
Label(i64),
|
||||
Route(String),
|
||||
}
|
||||
@@ -832,15 +834,12 @@ fn resolve_single_mail_drop(
|
||||
return Err("this unified mailbox is not a drop destination".to_owned());
|
||||
}
|
||||
Scope::Folder(_) | Scope::Account { .. } => {
|
||||
if i64::from(target_account_id) != message.account_id {
|
||||
return Err("messages cannot be moved between accounts".to_owned());
|
||||
}
|
||||
let target = state
|
||||
.mailboxes
|
||||
.iter()
|
||||
.find(|mailbox| {
|
||||
!mailbox.is_account
|
||||
&& mailbox.account_id == message.account_id
|
||||
&& mailbox.account_id == i64::from(target_account_id)
|
||||
&& mailbox.scope == Some(target_scope)
|
||||
&& mailbox.folder_id == i64::from(target_folder_id)
|
||||
})
|
||||
@@ -848,17 +847,32 @@ fn resolve_single_mail_drop(
|
||||
if !target.is_selectable {
|
||||
return Err("this mailbox is a hierarchy container".to_owned());
|
||||
}
|
||||
// Folders are told apart by scope: another folder can have the
|
||||
// name of the one shown.
|
||||
if state.scope.shows_mail_in(target_scope) {
|
||||
return Err("message is already in this destination".to_owned());
|
||||
}
|
||||
// Custom folders are plain moves even when named like a
|
||||
// standard mailbox.
|
||||
let standard = match target_scope {
|
||||
Scope::Account { mailbox, .. } => Some(mailbox),
|
||||
_ => None,
|
||||
};
|
||||
// Mail moves to another account as Thunderbird moves it: a copy
|
||||
// goes there, and the original goes once the copy is in place.
|
||||
if target.account_id != message.account_id {
|
||||
return match standard {
|
||||
Some(
|
||||
StandardMailbox::Sent | StandardMailbox::Drafts | StandardMailbox::Starred,
|
||||
) => Err("this mailbox is not a drop destination".to_owned()),
|
||||
_ if target.folder_id >= 0 => Ok((
|
||||
MailTarget::of(message)
|
||||
.ok_or_else(|| "message thread is unavailable".to_owned())?,
|
||||
MailDropDestination::Transfer(target.folder_id),
|
||||
)),
|
||||
_ => Err("mailbox destination is unavailable".to_owned()),
|
||||
};
|
||||
}
|
||||
// Folders are told apart by scope: another folder can have the
|
||||
// name of the one shown.
|
||||
if state.scope.shows_mail_in(target_scope) {
|
||||
return Err("message is already in this destination".to_owned());
|
||||
}
|
||||
match standard {
|
||||
Some(StandardMailbox::Starred) if !message.starred => {
|
||||
MailDropDestination::Action("star")
|
||||
@@ -3869,6 +3883,11 @@ pub fn run(platform: PlatformContext) -> Result<(), Box<dyn std::error::Error>>
|
||||
app.set_render_status(message.clone());
|
||||
app.set_sync_status(message);
|
||||
}
|
||||
if let Some(report) = pending.transfers
|
||||
&& let Some(app) = mail_update_app.upgrade()
|
||||
{
|
||||
app.set_render_status(move_picker::transfer_message(&report));
|
||||
}
|
||||
if pending.calendar_changed
|
||||
&& let Some(app) = mail_update_app.upgrade()
|
||||
&& app.get_settings_open()
|
||||
|
||||
+567
-205
File diff suppressed because it is too large.
Load diff
+10
-1
@@ -18,7 +18,7 @@ use crate::{
|
||||
};
|
||||
use flectar_mail_core::{
|
||||
config::Paths,
|
||||
events::CoreEvent,
|
||||
events::{CoreEvent, TransferReport},
|
||||
models::{
|
||||
Account, AccountConfig, CalendarConnection, CardDavConnection, MailGrouping, MessageCursor,
|
||||
Settings, ThreadCursor, normalized_workspace_list_pane_width,
|
||||
@@ -584,6 +584,8 @@ pub(crate) struct PendingCoreUpdates {
|
||||
pub(crate) all_mail_changed: bool,
|
||||
pub(crate) account_states: HashMap<i64, (String, Option<String>)>,
|
||||
pub(crate) action_error: Option<String>,
|
||||
/// What became of the latest mail copied or moved to other accounts.
|
||||
pub(crate) transfers: Option<TransferReport>,
|
||||
pub(crate) calendar_changed: bool,
|
||||
pub(crate) contacts_changed: bool,
|
||||
}
|
||||
@@ -738,6 +740,13 @@ pub(crate) fn spawn_core_event_listener(
|
||||
.action_error = Some(error);
|
||||
true
|
||||
}
|
||||
Ok(CoreEvent::TransfersFinished(report)) => {
|
||||
pending
|
||||
.lock()
|
||||
.unwrap_or_else(|poisoned| poisoned.into_inner())
|
||||
.transfers = Some(report);
|
||||
true
|
||||
}
|
||||
Ok(CoreEvent::CalendarUpdated { .. })
|
||||
| Ok(CoreEvent::CalendarEventsAdded { .. }) => {
|
||||
pending
|
||||
|
||||
@@ -496,6 +496,15 @@ export global I18n {
|
||||
if (key == "Label deleted.") { return @tr("Label deleted."); }
|
||||
if (key == "No other folder to move this mail to.") { return @tr("No other folder to move this mail to."); }
|
||||
if (key == "Could not move mail: {}") { return @tr("Could not move mail: {}", argument); }
|
||||
if (key == "No folder to copy this mail to.") { return @tr("No folder to copy this mail to."); }
|
||||
if (key == "Could not copy mail: {}") { return @tr("Could not copy mail: {}", argument); }
|
||||
if (key == "Moving mail to the other account…") { return @tr("Moving mail to the other account…"); }
|
||||
if (key == "Copying mail to the other account…") { return @tr("Copying mail to the other account…"); }
|
||||
if (key == "Moved {} messages to the other account.") { return @tr("Moved one message to the other account." | "Moved {0} messages to the other account." % round(argument.to-float()), argument); }
|
||||
if (key == "Copied {} messages to the other account.") { return @tr("Copied one message to the other account." | "Copied {0} messages to the other account." % round(argument.to-float()), argument); }
|
||||
if (key == "Moved or copied {} messages to the other account.") { return @tr("Moved or copied one message to the other account." | "Moved or copied {0} messages to the other account." % round(argument.to-float()), argument); }
|
||||
if (key == "The other account had {} messages already.") { return @tr("The other account had that message already." | "The other account had those {0} messages already." % round(argument.to-float()), argument); }
|
||||
if (key == "Could not move or copy {} messages: {}") { return @tr("Could not move or copy one message: {1}" | "Could not move or copy {0} messages: {1}" % round(argument.to-float()), argument, second-argument); }
|
||||
if (key == "Could not update label: {}") { return @tr("Could not update label: {}", argument); }
|
||||
if (key == "Could not save label: {}") { return @tr("Could not save label: {}", argument); }
|
||||
if (key == "Could not delete label: {}") { return @tr("Could not delete label: {}", argument); }
|
||||
|
||||
@@ -7,7 +7,8 @@ import { LineEdit, ToolbarIconButton } from "../components/controls.slint";
|
||||
export struct MoveTargetRow {
|
||||
// What `MovePickerUi.choose` takes; -1 on headings.
|
||||
key: int,
|
||||
// inbox, archive, spam, trash or folder; on headings, recent or folders.
|
||||
// inbox, archive, spam, trash or folder; on headings, recent, folders,
|
||||
// or the account of the rows below: account, device or unified.
|
||||
kind: string,
|
||||
// The name shown, which for standard mailboxes is translated; empty on
|
||||
// headings, which their kind names.
|
||||
@@ -18,18 +19,22 @@ export struct MoveTargetRow {
|
||||
label_has_emoji: bool,
|
||||
}
|
||||
|
||||
// The non-drag way to move mail into a folder (WCAG 2.5.7). Rust lists the
|
||||
// destinations when the picker opens and as the search changes; choosing
|
||||
// one does what dropping the mail on that sidebar row does.
|
||||
// The non-drag way to move mail into a folder (WCAG 2.5.7), and to copy it.
|
||||
// Rust lists the destinations when the picker opens and as the search
|
||||
// changes; choosing one for a move does what dropping the mail on that
|
||||
// sidebar row does.
|
||||
export global MovePickerUi {
|
||||
in-out property <bool> open: false;
|
||||
// move or copy.
|
||||
in property <string> mode: "move";
|
||||
in property <[MoveTargetRow]> rows: [];
|
||||
in-out property <string> query: "";
|
||||
// Where focus goes back to once the picker closes: list or reader.
|
||||
in-out property <string> return-to: "list";
|
||||
// Opens the picker for "row" (a list row, with the rows checked along
|
||||
// with it), "checked" (the checked rows) or "open" (the open mail).
|
||||
callback request(/* source */ string, /* email id */ int);
|
||||
// Opens the picker to move or copy mail of "row" (a list row, with the
|
||||
// rows checked along with it), "checked" (the checked rows) or "open"
|
||||
// (the open mail).
|
||||
callback request(/* mode */ string, /* source */ string, /* email id */ int);
|
||||
callback search(string);
|
||||
callback choose(/* key */ int);
|
||||
|
||||
@@ -42,6 +47,8 @@ export global MovePickerUi {
|
||||
if kind == "trash" { return @tr("Trash"); }
|
||||
if kind == "recent" { return @tr("Recent"); }
|
||||
if kind == "folders" { return @tr("All folders"); }
|
||||
if kind == "device" { return @tr("On this device"); }
|
||||
if kind == "unified" { return @tr("All accounts"); }
|
||||
label
|
||||
}
|
||||
}
|
||||
@@ -64,7 +71,7 @@ export component MoveDialog inherits Rectangle {
|
||||
background: #00000066;
|
||||
forward-focus: search-field;
|
||||
accessible-role: groupbox;
|
||||
accessible-label: @tr("Move to");
|
||||
accessible-label: MovePickerUi.mode == "copy" ? @tr("Copy to") : @tr("Move to");
|
||||
|
||||
// A heading is always followed by a destination, so one step past it
|
||||
// reaches one.
|
||||
@@ -188,7 +195,7 @@ export component MoveDialog inherits Rectangle {
|
||||
spacing: 8px;
|
||||
|
||||
Text {
|
||||
text: @tr("Move to");
|
||||
text: MovePickerUi.mode == "copy" ? @tr("Copy to") : @tr("Move to");
|
||||
color: AppTheme.title;
|
||||
font-size: TypeScale.heading;
|
||||
font-weight: 700;
|
||||
@@ -243,10 +250,11 @@ export component MoveDialog inherits Rectangle {
|
||||
x: 10px;
|
||||
width: parent.width - 20px;
|
||||
height: parent.height - 4px;
|
||||
text: MovePickerUi.display-name(row.kind, "");
|
||||
text: MovePickerUi.display-name(row.kind, row.label);
|
||||
color: AppTheme.muted;
|
||||
font-size: TypeScale.caption;
|
||||
font-weight: 600;
|
||||
font-family: row.label_has_emoji ? "Noto Emoji" : "";
|
||||
vertical-alignment: bottom;
|
||||
overflow: elide;
|
||||
accessible-role: none;
|
||||
|
||||
@@ -2396,6 +2396,7 @@ export component SettingsDialog inherits Rectangle {
|
||||
ShortcutRow { title: @tr("Search mail"); keys: [Shortcuts.search, Shortcuts.search-alternate]; title-color: root.body-text; separator-color: root.faint-text; }
|
||||
ShortcutRow { title: @tr("Archive"); keys: [Shortcuts.archive]; title-color: root.body-text; separator-color: root.faint-text; }
|
||||
ShortcutRow { title: @tr("Move to a folder"); keys: [Shortcuts.move-to]; title-color: root.body-text; separator-color: root.faint-text; }
|
||||
ShortcutRow { title: @tr("Copy to a folder"); keys: [Shortcuts.copy-to]; title-color: root.body-text; separator-color: root.faint-text; }
|
||||
ShortcutRow { title: @tr("Move to spam"); keys: [Shortcuts.spam]; title-color: root.body-text; separator-color: root.faint-text; }
|
||||
ShortcutRow { title: @tr("Move to trash"); keys: [Shortcuts.trash]; title-color: root.body-text; separator-color: root.faint-text; }
|
||||
}
|
||||
|
||||
+3
-1
@@ -11,8 +11,10 @@ export global Shortcuts {
|
||||
out property <keys> spam: @keys(J);
|
||||
// Mac keyboards label Backspace "delete", and Apple Mail trashes with it.
|
||||
out property <keys> trash: KeyboardKeys.apple ? @keys(Backspace) : @keys(Delete);
|
||||
// Outlook's and Evolution's key for moving mail to a folder.
|
||||
// Outlook's and Evolution's keys for moving and copying mail to a
|
||||
// folder.
|
||||
out property <keys> move-to: @keys(Control + Shift + V);
|
||||
out property <keys> copy-to: @keys(Control + Shift + Y);
|
||||
|
||||
// Thunderbird's keys: F5 checks the accounts in view, Shift+F5 every
|
||||
// account. Apple Mail checks every account with Shift-Command-N.
|
||||
|
||||
@@ -456,14 +456,19 @@ export component MailListPane inherits FocusScope {
|
||||
keys: Shortcuts.move-to;
|
||||
// Not while typing: text fields elsewhere paste plain text with it.
|
||||
enabled: !search-toolbar.search-focused;
|
||||
activated => { root.move-messages(); }
|
||||
activated => { root.pick-folder("move"); }
|
||||
}
|
||||
KeyBinding {
|
||||
keys: Shortcuts.copy-to;
|
||||
enabled: !search-toolbar.search-focused;
|
||||
activated => { root.pick-folder("copy"); }
|
||||
}
|
||||
|
||||
// Move to opens for what the other shortcuts act on.
|
||||
function move-messages() {
|
||||
// Move to and Copy to open for what the other shortcuts act on.
|
||||
function pick-folder(mode: string) {
|
||||
if root.selection-mode {
|
||||
MovePickerUi.return-to = "list";
|
||||
MovePickerUi.request("checked", -1);
|
||||
MovePickerUi.request(mode, "checked", -1);
|
||||
return;
|
||||
}
|
||||
let target = MailListNavigation.target(root.list_entries, root.keyboard-email-id, "current", 1);
|
||||
@@ -471,7 +476,7 @@ export component MailListPane inherits FocusScope {
|
||||
let at-cursor = list-keyboard.has-focus && target.id == root.keyboard-email-id;
|
||||
if !at-cursor && !root.list_entries[target.entry_index].email.selected { return; }
|
||||
MovePickerUi.return-to = "list";
|
||||
MovePickerUi.request("row", target.id);
|
||||
MovePickerUi.request(mode, "row", target.id);
|
||||
}
|
||||
|
||||
function pointer-focus(id: int) {
|
||||
@@ -781,7 +786,14 @@ export component MailListPane inherits FocusScope {
|
||||
title: @tr("Move to…");
|
||||
activated => {
|
||||
MovePickerUi.return-to = "list";
|
||||
MovePickerUi.request("checked", -1);
|
||||
MovePickerUi.request("move", "checked", -1);
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy to…");
|
||||
activated => {
|
||||
MovePickerUi.return-to = "list";
|
||||
MovePickerUi.request("copy", "checked", -1);
|
||||
}
|
||||
}
|
||||
MenuItem { title: @tr("Mark as read"); activated => { root.bulk_message_action("mark_read"); } }
|
||||
@@ -1379,7 +1391,14 @@ export component MailListPane inherits FocusScope {
|
||||
title: @tr("Move to…");
|
||||
activated => {
|
||||
MovePickerUi.return-to = "list";
|
||||
MovePickerUi.request("row", mail-data.id);
|
||||
MovePickerUi.request("move", "row", mail-data.id);
|
||||
}
|
||||
}
|
||||
MenuItem {
|
||||
title: @tr("Copy to…");
|
||||
activated => {
|
||||
MovePickerUi.return-to = "list";
|
||||
MovePickerUi.request("copy", "row", mail-data.id);
|
||||
}
|
||||
}
|
||||
if !mail-row.local-mail : MenuItem { title: @tr("Move to spam"); activated => { root.message_action_for_email(mail-data.id, "spam"); } }
|
||||
|
||||
@@ -348,6 +348,7 @@ export component MailReadingPane inherits FocusScope {
|
||||
{ id: "read", title: root.selected_unread ? @tr("Mark as read") : @tr("Mark as unread"), icon: root.selected_unread ? @image-url("../icons/lucide/outline/mail-open.svg") : @image-url("../icons/lucide/outline/mail.svg") },
|
||||
{ id: "archive", title: @tr("Archive"), icon: @image-url("../icons/lucide/outline/archive.svg") },
|
||||
{ id: "move", title: @tr("Move to…"), icon: @image-url("../icons/lucide/outline/folder-move.svg") },
|
||||
{ id: "copy", title: @tr("Copy to…"), icon: @image-url("../icons/lucide/outline/copy.svg") },
|
||||
{ id: "spam", title: @tr("Move to spam"), icon: @image-url("../icons/lucide/outline/alert.svg") },
|
||||
{ id: root.trash_view ? "delete_permanently" : "trash", title: root.trash_view ? @tr("Delete permanently…") : @tr("Move to trash"), icon: @image-url("../icons/lucide/outline/trash.svg"), destructive: true },
|
||||
];
|
||||
@@ -366,6 +367,7 @@ export component MailReadingPane inherits FocusScope {
|
||||
{ id: "read", title: root.selected_unread ? @tr("Mark as read") : @tr("Mark as unread"), icon: root.selected_unread ? @image-url("../icons/lucide/outline/mail-open.svg") : @image-url("../icons/lucide/outline/mail.svg") },
|
||||
{ id: "archive", title: @tr("Archive"), icon: @image-url("../icons/lucide/outline/archive.svg") },
|
||||
{ id: "move", title: @tr("Move to…"), icon: @image-url("../icons/lucide/outline/folder-move.svg") },
|
||||
{ id: "copy", title: @tr("Copy to…"), icon: @image-url("../icons/lucide/outline/copy.svg") },
|
||||
{ id: "spam", title: @tr("Move to spam"), icon: @image-url("../icons/lucide/outline/alert.svg") },
|
||||
{ id: root.trash_view ? "delete_permanently" : "trash", title: root.trash_view ? @tr("Delete permanently…") : @tr("Move to trash"), icon: @image-url("../icons/lucide/outline/trash.svg"), destructive: true },
|
||||
];
|
||||
@@ -385,6 +387,7 @@ export component MailReadingPane inherits FocusScope {
|
||||
{ id: "original_colors", title: MessageColors.original ? @tr("Use message appearance") : @tr("Show original colors"), icon: @image-url("../icons/lucide/outline/contrast.svg") },
|
||||
{ id: "read", title: root.selected_unread ? @tr("Mark as read") : @tr("Mark as unread"), icon: root.selected_unread ? @image-url("../icons/lucide/outline/mail-open.svg") : @image-url("../icons/lucide/outline/mail.svg") },
|
||||
{ id: "move", title: @tr("Move to…"), icon: @image-url("../icons/lucide/outline/folder-move.svg") },
|
||||
{ id: "copy", title: @tr("Copy to…"), icon: @image-url("../icons/lucide/outline/copy.svg") },
|
||||
{ id: root.trash_view ? "delete_permanently" : "trash", title: root.trash_view ? @tr("Delete permanently…") : @tr("Move to trash"), icon: @image-url("../icons/lucide/outline/trash.svg"), destructive: true },
|
||||
];
|
||||
private property <[ActionMenuEntry]> local-action-menu-no-print: [
|
||||
@@ -401,6 +404,7 @@ export component MailReadingPane inherits FocusScope {
|
||||
{ id: "original_colors", title: MessageColors.original ? @tr("Use message appearance") : @tr("Show original colors"), icon: @image-url("../icons/lucide/outline/contrast.svg") },
|
||||
{ id: "read", title: root.selected_unread ? @tr("Mark as read") : @tr("Mark as unread"), icon: root.selected_unread ? @image-url("../icons/lucide/outline/mail-open.svg") : @image-url("../icons/lucide/outline/mail.svg") },
|
||||
{ id: "move", title: @tr("Move to…"), icon: @image-url("../icons/lucide/outline/folder-move.svg") },
|
||||
{ id: "copy", title: @tr("Copy to…"), icon: @image-url("../icons/lucide/outline/copy.svg") },
|
||||
{ id: root.trash_view ? "delete_permanently" : "trash", title: root.trash_view ? @tr("Delete permanently…") : @tr("Move to trash"), icon: @image-url("../icons/lucide/outline/trash.svg"), destructive: true },
|
||||
];
|
||||
// The reader toolbar drops actions that the More menu also offers when
|
||||
@@ -565,7 +569,15 @@ export component MailReadingPane inherits FocusScope {
|
||||
enabled: root.message-keys && !root.selected_is_draft && !EmailReader.find-open;
|
||||
activated => {
|
||||
MovePickerUi.return-to = "reader";
|
||||
MovePickerUi.request("open", -1);
|
||||
MovePickerUi.request("move", "open", -1);
|
||||
}
|
||||
}
|
||||
KeyBinding {
|
||||
keys: Shortcuts.copy-to;
|
||||
enabled: root.message-keys && !root.selected_is_draft && !EmailReader.find-open;
|
||||
activated => {
|
||||
MovePickerUi.return-to = "reader";
|
||||
MovePickerUi.request("copy", "open", -1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1064,9 +1076,9 @@ export component MailReadingPane inherits FocusScope {
|
||||
MessageColors.original = !MessageColors.original;
|
||||
} else if action == "read" {
|
||||
root.message_action(root.selected_unread ? "mark_read" : "mark_unread");
|
||||
} else if action == "move" {
|
||||
} else if action == "move" || action == "copy" {
|
||||
MovePickerUi.return-to = "reader";
|
||||
MovePickerUi.request("open", -1);
|
||||
MovePickerUi.request(action, "open", -1);
|
||||
} else {
|
||||
root.message_action(action);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user