423 lines
14 KiB
JavaScript
423 lines
14 KiB
JavaScript
import React from 'react';
|
|
import ReactDOM from 'react-dom';
|
|
|
|
import superagent from 'superagent';
|
|
import socket_io from 'socket.io-client';
|
|
|
|
import ComposeWindow from './ComposeWindow.js';
|
|
import FolderList from './FolderList.js';
|
|
import MessageList from './MessageList.js';
|
|
import MessageView from './MessageView.js';
|
|
import DropDownMenu from './DropDownMenu.js';
|
|
import ListSortSettingsWindow from './ListSortSettingsWindow.js';
|
|
import MailSettingsWindow from './MailSettingsWindow.js';
|
|
import TabPanel from './TabPanel.js';
|
|
import _ from './I18n.js';
|
|
import Util from './Util.js';
|
|
|
|
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame;
|
|
|
|
class MainWindow extends React.PureComponent
|
|
{
|
|
state = {
|
|
layout: 'message-on-right',
|
|
quickReply: true,
|
|
msg: null,
|
|
threads: false,
|
|
accounts: [],
|
|
listGroups: [],
|
|
messages: []
|
|
}
|
|
|
|
startIo = () =>
|
|
{
|
|
this.io = socket_io('', { path: window.location.pathname.replace(/[^\/]+$/, 'backend/socket.io') });
|
|
this.io.on('sync', (params) =>
|
|
{
|
|
if (params.state == 'start')
|
|
{
|
|
this.setState({ progressText: 'Syncing '+params.email+' / '+params.folder, progressPercent: 0 });
|
|
}
|
|
else if (params.state == 'progress')
|
|
{
|
|
this.setState({ progressPercent: Math.round(100*params.done/(params.total||1)) });
|
|
}
|
|
else if (params.state == 'finish-box')
|
|
{
|
|
this.setState({ progressPercent: 100 });
|
|
}
|
|
else if (params.state == 'complete')
|
|
{
|
|
this.setState({ progressText: '', progressPercent: 0 });
|
|
}
|
|
this.setState({ sync: params.progress });
|
|
});
|
|
}
|
|
|
|
loadAccounts()
|
|
{
|
|
superagent.get('backend/folders').end((err, res) =>
|
|
{
|
|
let ixOfAll = {
|
|
received: 1,
|
|
outbox: 3,
|
|
sent: 4,
|
|
drafts: 5,
|
|
spam: 6,
|
|
trash: 7
|
|
};
|
|
let accounts = [ {
|
|
name: _('All Messages'),
|
|
accountId: null,
|
|
unreadCount: 0,
|
|
folders: [
|
|
{ name: _('Unread'), icon: 'mail_unread', unreadCount: 0, type: 'unread' },
|
|
{ name: _('Received'), icon: 'mail_received', unreadCount: 0, type: 'inbox' },
|
|
{ name: _('Pinned'), icon: 'mail_pinned', unreadCount: 0, type: 'pinned' },
|
|
{ name: _('Outbox'), icon: 'mail_outbox', unreadCount: 0, type: 'outbox' }, // FIXME
|
|
{ name: _('Sent'), icon: 'mail_sent', unreadCount: 0, type: 'sent' },
|
|
{ name: _('Drafts'), icon: 'mail_drafts', unreadCount: 0, type: 'drafts' },
|
|
{ name: _('Spam'), icon: 'mail_spam', unreadCount: 0, type: 'spam' },
|
|
{ name: _('Trash'), icon: 'mail_trash', unreadCount: 0, type: 'trash' },
|
|
],
|
|
} ];
|
|
for (let a of res.body.accounts)
|
|
{
|
|
let account = {
|
|
name: a.name,
|
|
email: a.email,
|
|
accountId: a.id,
|
|
unreadCount: 0,
|
|
warning: false,
|
|
folders: [
|
|
{ name: _('Unread'), icon: 'mail_unread', unreadCount: 0, type: 'unread' },
|
|
{ name: _('Pinned'), icon: 'mail_pinned', unreadCount: a.pinned_count-0, type: 'pinned' },
|
|
],
|
|
folderMap: a.foldermap,
|
|
folderTypes: {}
|
|
};
|
|
if (!account.folderMap.received)
|
|
{
|
|
account.folderMap.received = 'INBOX';
|
|
}
|
|
for (let f in account.folderMap)
|
|
{
|
|
account.folderTypes[account.folderMap[f]] = f;
|
|
}
|
|
for (let f of a.folders)
|
|
{
|
|
if (f.kind)
|
|
{
|
|
account.folderTypes[f.name] = f.kind;
|
|
}
|
|
}
|
|
for (let f of a.folders)
|
|
{
|
|
let icon = (account.folderTypes[f.name] ? 'mail_'+account.folderTypes[f.name] : 'folder');
|
|
account.folders.push({ name: f.name, icon: icon, unreadCount: f.unread_count-0, folderId: f.id });
|
|
if (f.kind != 'sent' && f.kind != 'trash' && f.kind != 'drafts')
|
|
{
|
|
account.folders[0].unreadCount += (f.unread_count-0);
|
|
}
|
|
if (account.folderTypes[f.name])
|
|
{
|
|
accounts[0].folders[ixOfAll[account.folderTypes[f.name]]].unreadCount += (f.unread_count-0);
|
|
}
|
|
account.unreadCount += (f.unread_count-0);
|
|
}
|
|
accounts.push(account);
|
|
// unread total
|
|
accounts[0].unreadCount += account.folders[0].unreadCount;
|
|
accounts[0].folders[0].unreadCount += account.folders[0].unreadCount;
|
|
// pinned total (not unread)
|
|
accounts[0].folders[2].unreadCount += account.folders[1].unreadCount;
|
|
}
|
|
this.setState({ accounts });
|
|
});
|
|
}
|
|
|
|
loadFolder = (folderParams) =>
|
|
{
|
|
superagent.get('backend/groups').query(folderParams).end((err, res) =>
|
|
{
|
|
let groups = res.body.groups.map(g => ({ name: Util.getGroupName(g.name), messageCount: g.count-0, start: 0 }));
|
|
let start = 0;
|
|
for (let i = 0; i < groups.length; i++)
|
|
{
|
|
groups[i].start = start;
|
|
start += groups[i].messageCount;
|
|
}
|
|
this.setState({
|
|
folderParams: folderParams,
|
|
listGroups: groups,
|
|
messages: []
|
|
});
|
|
});
|
|
}
|
|
|
|
search = (text) =>
|
|
{
|
|
this.loadFolder({ ...this.state.folderParams, search: text });
|
|
}
|
|
|
|
loadMessages = (start, count) =>
|
|
{
|
|
let p = { ...this.state.folderParams };
|
|
p.offset = start;
|
|
p.limit = count;
|
|
superagent.get('backend/messages').query(p).end((err, res) =>
|
|
{
|
|
let msgs = [ ...this.state.messages ];
|
|
let par = res.body.messages;
|
|
par.unshift(par.length);
|
|
par.unshift(start);
|
|
msgs.splice.apply(msgs, par);
|
|
this.setState({ messages: msgs });
|
|
});
|
|
}
|
|
|
|
loadMessage = (msgId, callback) =>
|
|
{
|
|
superagent.get('backend/message').query({ msgId: msgId }).end((err, res) =>
|
|
{
|
|
callback(res.body.msg);
|
|
});
|
|
}
|
|
|
|
toggleRead = (is_read) =>
|
|
{
|
|
// FIXME: Support marking multiple messages as read
|
|
let msg = this.state.msg;
|
|
if (!msg)
|
|
{
|
|
return;
|
|
}
|
|
let flags = [ ...msg.flags.filter(f => f != 'unread') ];
|
|
if (!is_read)
|
|
{
|
|
flags.push('unread');
|
|
}
|
|
superagent.post('backend/mark').query({ msgId: msg.id, flag: 'seen', del: is_read ? '' : '1' }).end((err, res) =>
|
|
{
|
|
if (this.state.msg == msg)
|
|
{
|
|
let newMsg = { ...msg, flags };
|
|
this.setState({
|
|
msg: newMsg,
|
|
messages: this.state.messages.map(m => m == msg ? newMsg : m),
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
startResync = () =>
|
|
{
|
|
superagent.post('backend/sync').send().end((err, res) =>
|
|
{
|
|
});
|
|
}
|
|
|
|
setLayout = (l) =>
|
|
{
|
|
this.setState({ layout: l });
|
|
}
|
|
|
|
toggleQuickReply = () =>
|
|
{
|
|
this.setState({ quickReply: !this.state.quickReply });
|
|
}
|
|
|
|
setMessage = (msg) =>
|
|
{
|
|
this.setState({ msg });
|
|
}
|
|
|
|
render()
|
|
{
|
|
return (<div>
|
|
<DropDownMenu
|
|
id="account"
|
|
items={[ {
|
|
icon: 'mail_unread',
|
|
i16: true,
|
|
text: 'Read vitalif@mail.ru'
|
|
}, {
|
|
icon: 'folder',
|
|
text: 'IMAP Folders',
|
|
}, {
|
|
icon: 'properties',
|
|
text: 'Properties...'
|
|
} ]}
|
|
/>
|
|
<DropDownMenu
|
|
id="reply"
|
|
items={[ {
|
|
hotkey: 'R',
|
|
icon: 'mail_reply',
|
|
text: 'Reply'
|
|
}, {
|
|
icon: 'mail_reply',
|
|
text: 'Reply to Sender',
|
|
}, {
|
|
disabled: true,
|
|
icon: 'mail_reply_all',
|
|
text: 'Reply to List'
|
|
} ]}
|
|
/>
|
|
<DropDownMenu
|
|
id="forward"
|
|
items={[ {
|
|
hotkey: 'F',
|
|
icon: 'mail_forward',
|
|
text: 'Reply'
|
|
}, {
|
|
hotkey: 'D',
|
|
text: 'Redirect'
|
|
} ]}
|
|
/>
|
|
<DropDownMenu
|
|
id="delete"
|
|
items={[ {
|
|
text: 'Move to Trash'
|
|
}, {
|
|
icon: 'delete',
|
|
text: 'Delete Permanently'
|
|
} ]}
|
|
/>
|
|
<DropDownMenu
|
|
id="check-send"
|
|
items={[ {
|
|
hotkey: 'Ctrl-K',
|
|
icon: 'mail_check',
|
|
text: 'Check All'
|
|
}, {
|
|
hotkey: 'Ctrl-Shift-K',
|
|
icon: 'mail_send',
|
|
text: 'Send Queued'
|
|
}, { split: true }, {
|
|
icon: 'mail_check',
|
|
text: 'vitalif@mail.ru'
|
|
}, {
|
|
icon: 'mail_check',
|
|
text: 'vitalif@yourcmc.ru'
|
|
}, { split: true }, {
|
|
text: 'Resynchronize All Messages'
|
|
} ]}
|
|
/>
|
|
<DropDownMenu
|
|
id="threads"
|
|
items={[ {
|
|
icon: 'thread',
|
|
text: 'Show Message Thread'
|
|
}, {
|
|
text: 'Follow Thread'
|
|
}, {
|
|
text: 'Ignore Thread'
|
|
}, { split: true }, {
|
|
hotkey: 'M',
|
|
icon: 'read',
|
|
text: 'Mark Thread as Read'
|
|
}, { split: true }, {
|
|
hotkey: 'N',
|
|
text: 'Mark Thread and Go to Next Unread'
|
|
} ]}
|
|
/>
|
|
<ListSortSettingsWindow
|
|
id="list-sort"
|
|
window={true}
|
|
folder="INBOX"
|
|
override={false}
|
|
markDelay={-1}
|
|
sorting={{}}
|
|
defaultSorting={{
|
|
sort: {
|
|
sortby: 'sent date',
|
|
group: 'date',
|
|
ascending: false,
|
|
threaded: false
|
|
}
|
|
}}
|
|
show={{
|
|
read: true,
|
|
trash: false,
|
|
spam: false,
|
|
lists: true,
|
|
sent: true,
|
|
dups: true
|
|
}}
|
|
/>
|
|
<MailSettingsWindow
|
|
id="settings"
|
|
defaultSorting={{
|
|
sort: {
|
|
sortby: 'sent date',
|
|
group: 'date',
|
|
ascending: false,
|
|
threaded: false
|
|
}
|
|
}}
|
|
layout={this.state.layout}
|
|
quickReply={this.state.quickReply}
|
|
setLayout={this.setLayout}
|
|
toggleQuickReply={this.toggleQuickReply}
|
|
/>
|
|
<FolderList
|
|
accounts={this.state.accounts}
|
|
progressText={this.state.progressText}
|
|
progressPercent={this.state.progressPercent}
|
|
startResync={this.startResync}
|
|
loadFolder={this.loadFolder}
|
|
/>
|
|
<TabPanel
|
|
tabs={[
|
|
{
|
|
className: this.state.layout,
|
|
noclose: true,
|
|
icon: 'mail_unread',
|
|
title: 'Unread (64)',
|
|
children: [
|
|
<MessageList
|
|
key="1"
|
|
loadMessages={this.loadMessages}
|
|
loadMessage={this.loadMessage}
|
|
setMessage={this.setMessage}
|
|
search={this.search}
|
|
threads={this.state.threads}
|
|
layout={this.state.layout}
|
|
groups={this.state.listGroups}
|
|
messages={this.state.messages}
|
|
/>,
|
|
<MessageView
|
|
key="2"
|
|
layout={this.state.layout}
|
|
quickReply={this.state.quickReply}
|
|
msg={this.state.msg}
|
|
onToggleRead={this.toggleRead}
|
|
/>,
|
|
]
|
|
},
|
|
{
|
|
icon: 'mail_drafts',
|
|
i16: true,
|
|
title: 'Compose Message',
|
|
children: [
|
|
<ComposeWindow
|
|
key="1"
|
|
accounts={this.state.accounts}
|
|
/>
|
|
]
|
|
}
|
|
]}
|
|
/>
|
|
</div>);
|
|
}
|
|
|
|
componentDidMount()
|
|
{
|
|
this.loadAccounts();
|
|
this.startIo();
|
|
}
|
|
}
|
|
|
|
ReactDOM.render(<MainWindow />, document.getElementById('app'));
|