diff --git a/app/internal_packages/account-sidebar/lib/sidebar-item.ts b/app/internal_packages/account-sidebar/lib/sidebar-item.ts index 6a412eb2c..ddbb219ff 100644 --- a/app/internal_packages/account-sidebar/lib/sidebar-item.ts +++ b/app/internal_packages/account-sidebar/lib/sidebar-item.ts @@ -11,14 +11,22 @@ import { Actions, RegExpUtils, localized, + MessageStore, + GetMessageRFC2822Task, + MutableQuerySubscription, + Thread, + Message, + DatabaseStore, + Folder, } from 'mailspring-exports'; import * as SidebarActions from './sidebar-actions'; import { ISidebarItem } from './types'; +import { dialog } from '@electron/remote'; const idForCategories = categories => _.pluck(categories, 'id').join('-'); -const countForItem = function (perspective) { +const countForItem = function(perspective) { const unreadCountEnabled = AppEnv.config.get('core.workspace.showUnreadForAllCategories'); if (perspective.isInbox() || unreadCountEnabled) { return perspective.unreadCount(); @@ -28,7 +36,7 @@ const countForItem = function (perspective) { const isItemSelected = perspective => FocusedPerspectiveStore.current().isEqual(perspective); -const isItemCollapsed = function (id) { +const isItemCollapsed = function(id) { if (AppEnv.savedState.sidebarKeysCollapsed[id] !== undefined) { return AppEnv.savedState.sidebarKeysCollapsed[id]; } else { @@ -36,14 +44,14 @@ const isItemCollapsed = function (id) { } }; -const toggleItemCollapsed = function (item) { +const toggleItemCollapsed = function(item) { if (!(item.children.length > 0)) { return; } SidebarActions.setKeyCollapsed(item.id, !isItemCollapsed(item.id)); }; -const onDeleteItem = function (item) { +const onDeleteItem = function(item) { if (item.deleted === true) { return; } @@ -74,7 +82,40 @@ const onDeleteItem = function (item) { ); }; -const onEditItem = function (item, value) { +const onExportItem = async function(item) { + const filepath = await dialog.showOpenDialog({ + properties: ['openDirectory', 'createDirectory'], + }); + + if (!filepath.canceled && filepath.filePaths[0]) { + await DatabaseStore.findAll(Message, { remoteFolderId: this.id }).then(messages => { + const tasks = []; + + messages.forEach(message => { + const savePath = `${filepath.filePaths[0]}/${ + message.subject + } - ${message.date.toLocaleString('sv-SE')} - ${message.id.substring(0, 10)}.eml` + .replace(/:/g, ';') + .replace(RegExpUtils.illegalPathCharacters(), '-') + .replace(RegExpUtils.unicodeControlCharacters(), '-'); + + tasks.push( + new GetMessageRFC2822Task({ + messageId: message.id, + accountId: message.accountId, + filepath: savePath, + }) + ); + }); + + if (tasks.length > 0) { + Actions.queueTasks(tasks); + } + }); + } +}; + +const onEditItem = function(item, value) { let newDisplayName; if (!value) { return; @@ -134,6 +175,7 @@ export default class SidebarItem { counterStyle, onDelete: opts.deletable ? onDeleteItem : undefined, onEdited: opts.editable ? onEditItem : undefined, + onExport: opts.exportable ? onExportItem : undefined, onCollapseToggled: toggleItemCollapsed, onDrop(item, event) { @@ -190,6 +232,9 @@ export default class SidebarItem { if (opts.editable == null) { opts.editable = true; } + + opts.exportable = true; + opts.contextMenuLabel = contextMenuLabel; return this.forPerspective(id, perspective, opts); } diff --git a/app/internal_packages/account-sidebar/lib/types.ts b/app/internal_packages/account-sidebar/lib/types.ts index 989ffe17d..c3b01d43c 100644 --- a/app/internal_packages/account-sidebar/lib/types.ts +++ b/app/internal_packages/account-sidebar/lib/types.ts @@ -20,6 +20,7 @@ export interface ISidebarItem { deletable?: boolean; editable?: boolean; + exportable?: boolean; } export interface ISidebarSection { diff --git a/app/internal_packages/message-list/lib/message-controls.tsx b/app/internal_packages/message-list/lib/message-controls.tsx index 68d42208b..931aad53e 100644 --- a/app/internal_packages/message-list/lib/message-controls.tsx +++ b/app/internal_packages/message-list/lib/message-controls.tsx @@ -9,6 +9,7 @@ import { GetMessageRFC2822Task, Thread, Message, + RegExpUtils, } from 'mailspring-exports'; import { RetinaImg, ButtonDropdown, Menu } from 'mailspring-component-kit'; import { ipcRenderer, SaveDialogReturnValue } from 'electron'; @@ -162,10 +163,13 @@ export default class MessageControls extends React.Component { - return this.props.item.onDelete != null || this.props.item.onEdited != null; + return ( + this.props.item.onDelete != null || + this.props.item.onEdited != null || + this.props.item.onExport != null + ); }; _shouldAcceptDrop = event => { @@ -244,6 +248,10 @@ class OutlineViewItem extends Component { + this._runCallback('onExport'); + }; + _onInputFocus = event => { const input = event.target; input.selectionStart = input.selectionEnd = input.value.length; @@ -273,7 +281,7 @@ class OutlineViewItem extends Component any; onDelete?: (...args: any[]) => any; onEdited?: (...args: any[]) => any; + onExport?: (...args: any[]) => any; } interface OutlineViewProps { @@ -184,7 +185,7 @@ export class OutlineView extends Component { _renderHeading(allowCreate, collapsed, collapsible) { const collapseLabel = collapsed ? localized('Show') : localized('Hide'); - let style: CSSProperties = {} + let style: CSSProperties = {}; if (this.props.titleColor) { style = { height: '50%', @@ -192,7 +193,7 @@ export class OutlineView extends Component { borderLeftWidth: '4px', borderLeftColor: this.props.titleColor, borderLeftStyle: 'solid', - } + }; } return ( \\:*|"]/g; + }, + + // Finds Unicode Control codes + // C0 0x00-0x1f & C1 (0x80-0x9f) + // http://en.wikipedia.org/wiki/C0_and_C1_control_codes + unicodeControlCharacters() { + // eslint-disable-next-line no-control-regex + return /[\x00-\x1f\x80-\x9f]/g; + }, }; export default RegExpUtils;