likeopera-frontend/mail.js

1485 lines
55 KiB
JavaScript
Raw Normal View History

window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame;
var WeekDays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
var Months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
function getOffset(elem)
{
if (elem.getBoundingClientRect)
{
var box = elem.getBoundingClientRect();
var body = document.body;
var docElem = document.documentElement;
var scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft;
var clientTop = docElem.clientTop || body.clientTop || 0;
var clientLeft = docElem.clientLeft || body.clientLeft || 0;
var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;
return { top: Math.round(top), left: Math.round(left) };
}
else
{
var top = 0, left = 0;
while(elem)
{
top = top + parseInt(elem.offsetTop);
left = left + parseInt(elem.offsetLeft);
elem = elem.offsetParent;
}
return { top: top, left: left };
}
}
2016-06-22 00:14:07 +03:00
var Store = {
layout: 'message-on-right',
quickReply: true,
msg: null,
2016-06-23 01:17:22 +03:00
threads: false,
2016-06-22 00:14:07 +03:00
listeners: {},
on: function(ev, cb)
{
this.listeners[ev] = this.listeners[ev] || [];
this.listeners[ev].push(cb);
},
un: function(ev, cb)
{
if (!this.listeners[ev])
return;
for (var i = this.listeners[ev].length; i >= 0; i--)
if (this.listeners[ev] == cb)
this.listeners[ev].splice(i, 1);
},
set: function(k, v)
2016-06-22 00:14:07 +03:00
{
this[k] = v;
(this.listeners[k] || []).map(i => i());
2016-06-22 00:14:07 +03:00
}
};
var DropDownBase = {
instances: {},
2016-06-22 01:01:37 +03:00
currentVisible: null,
componentDidMount: function()
{
if (!DropDownBase.setBodyListener)
{
window.addEventListener('click', DropDownBase.hideAll);
2016-06-22 01:01:37 +03:00
window.addEventListener('resize', DropDownBase.repositionCurrent);
DropDownBase.setBodyListener = true;
}
DropDownBase.instances[this.props.id] = this;
},
hideAll: function()
{
for (var i in DropDownBase.instances)
DropDownBase.instances[i].hide();
},
2016-06-22 01:01:37 +03:00
repositionCurrent: function()
{
if (DropDownBase.currentVisible)
DropDownBase.currentVisible[0].showAt(DropDownBase.currentVisible[1], DropDownBase.currentVisible[0].onClose);
},
componentWillUnmount: function()
{
delete DropDownBase.instances[this.props.id];
2016-06-22 01:01:37 +03:00
if (DropDownBase.currentVisible[0] == this)
DropDownBase.currentVisible = null;
},
getInitialState: function()
{
return { visible: false, top: 0, left: 0, calloutLeft: null, selectedItem: -1 };
},
onClick: function(ev)
{
ev.stopPropagation();
},
hide: function()
{
this.setState({ visible: false });
2016-06-22 01:01:37 +03:00
DropDownBase.currentVisible = null;
if (this.onClose)
{
this.onClose();
delete this.onClose;
}
},
showAt: function(el, onClose)
{
2016-06-22 01:01:37 +03:00
if (this.onClose && this.onClose != onClose)
{
this.onClose();
delete this.onClose;
}
2016-06-22 01:01:37 +03:00
DropDownBase.currentVisible = [ this, el ];
var p = getOffset(el);
var left = p.left, top = p.top+el.offsetHeight, calloutLeft = null;
this.setState({ visible: true, top: top, left: left, selectedItem: -1 });
this.refs.dd.style.display = 'block';
if (this.props.window)
{
left = Math.round(p.left+el.offsetWidth/2-this.refs.dd.offsetWidth/2);
top = p.top+el.offsetHeight+3;
}
var ww = window.innerWidth || de.clientWidth || db.clientWidth;
var wh = window.innerHeight || de.clientHeight || db.clientHeight;
if (left + this.refs.dd.offsetWidth > ww)
{
left = ww-this.refs.dd.offsetWidth;
calloutLeft = Math.round(p.left+el.offsetWidth/2-left);
}
if (top + this.refs.dd.offsetHeight > wh)
top = wh-this.refs.dd.offsetHeight;
this.refs.dd.style.display = '';
this.setState({ visible: true, top: top, left: left, calloutLeft: calloutLeft });
this.refs.dd.focus();
this.onClose = onClose;
}
};
2016-06-20 23:11:04 +03:00
var DropDownMenu = React.createClass({
mixins: [ DropDownBase ],
getInitialState: function()
{
return { items: this.props.items };
},
render: function()
{
var sel = this.state.selectedItem;
return <div ref="dd" className={'dropdown'+(this.state.visible ? ' visible' : '')} id={'dropdown-'+this.props.id}
tabIndex="1" style={{ top: this.state.top, left: this.state.left }} onClick={this.myOnClick} onKeyDown={this.onKeyDown}
onMouseOver={this.onMouseOver}>
{this.state.items.map(function(i, index) {
return (i.split
2016-06-20 23:11:04 +03:00
? <div key={index} className="split"><i></i></div> :
<div key={index} data-index={index} className={'item'+(i.i16 ? ' i16' : '')+(i.disabled ? ' disabled' : (sel == index ? ' over' : ''))}>
2016-06-20 23:11:04 +03:00
{i.hotkey ? <div className="hotkey">{i.hotkey}</div> : null}
{i.icon ? <img src={'icons/'+i.icon+'.png'} /> : null}
<span>{i.text}</span>
</div>
);
})}
</div>
},
onMouseOver: function(ev)
{
var t = ev.target;
while ((t && t != this.refs.dd) && (!t.className || t.className.substr(0, 4) != 'item'))
t = t.parentNode;
if (t && t != this.refs.dd)
this.setState({ selectedItem: parseInt(t.getAttribute('data-index')) });
},
onKeyDown: function(ev)
{
if (ev.keyCode == 40 || ev.keyCode == 38)
{
var a = ev.keyCode == 40 ? 1 : this.state.items.length-1;
var sel = this.state.selectedItem;
do
{
sel = ((sel+a) % this.state.items.length);
}
while (this.state.items[sel].split && sel != this.state.selectedItem);
this.setState({ selectedItem: sel });
}
else if (ev.keyCode == 10 || ev.keyCode == 13)
this.clickItem();
ev.preventDefault();
ev.stopPropagation();
},
clickItem: function(ev)
{
},
myOnClick: function(ev)
{
if (ev.target.getAttribute('data-index'))
this.clickItem();
this.onClick(ev);
}
});
var DropDownButton = React.createClass({
render: function()
{
return <a ref="btn" title={(this.state.checked ? this.props.checkedTitle : null) || this.props.title} onClick={this.onClickButton}
2016-06-21 21:38:21 +03:00
className={'button '+(this.props.dropdownId ? 'show-dropdown ' : '')+(this.state.checked ? 'checked ' : '')+
(this.state.pressed ? 'pressed ' : '')+(this.props.className || '')}>
2016-06-21 21:47:41 +03:00
{this.props.icon ? <img src={'icons/'+(this.state.checked && this.props.checkedIcon || this.props.icon)+'.png'} /> : null}
{this.state.checked && this.props.checkedText || this.props.text || null}
2016-06-21 21:38:21 +03:00
{this.props.dropdownId ? <span className="down" onClick={this.onClickDown}></span> : null}
</a>
},
getInitialState: function()
{
return { pressed: false, checked: false };
},
toggle: function()
{
if (!this.state.pressed)
2016-06-21 21:38:21 +03:00
{
DropDownBase.hideAll();
2016-06-22 20:24:06 +03:00
DropDownBase.instances[this.props.dropdownId].showAt(this.refs.btn, this.unpress);
2016-06-21 21:38:21 +03:00
}
else
DropDownBase.instances[this.props.dropdownId].hide();
this.setState({ pressed: !this.state.pressed });
2016-06-22 20:24:06 +03:00
},
unpress: function()
{
this.setState({ pressed: false });
},
onClickButton: function(ev)
{
2016-06-21 21:38:21 +03:00
if (this.props.whole || this.props.checkable && this.state.pressed)
this.toggle();
2016-06-21 21:38:21 +03:00
if (this.props.checkable)
this.setState({ checked: !this.state.checked });
ev.stopPropagation();
},
onClickDown: function(ev)
{
this.toggle();
ev.stopPropagation();
}
});
2016-06-20 23:11:04 +03:00
var dropdown_account = React.createElement(
DropDownMenu, {
id: 'account',
items: [ {
icon: 'mail_unread',
i16: true,
text: 'Read vitalif@mail.ru'
}, {
icon: 'folder',
text: 'IMAP Folders',
}, {
icon: 'properties',
text: 'Properties...'
} ]
}
);
2016-06-20 23:11:04 +03:00
var dropdown_reply = React.createElement(
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'
} ]
}
);
2016-06-20 23:11:04 +03:00
var dropdown_forward = React.createElement(
DropDownMenu, {
id: 'forward',
items: [ {
hotkey: 'F',
icon: 'mail_forward',
text: 'Reply'
}, {
hotkey: 'D',
text: 'Redirect'
} ]
}
);
2016-06-20 23:11:04 +03:00
var dropdown_delete = React.createElement(
DropDownMenu, {
id: 'delete',
items: [ {
text: 'Move to Trash'
}, {
icon: 'delete',
text: 'Delete Permanently'
} ]
}
);
2016-06-20 23:11:04 +03:00
var dropdown_check_send = React.createElement(
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'
} ]
}
);
2016-06-20 23:11:04 +03:00
var dropdown_threads = React.createElement(
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'
} ]
}
);
2016-06-20 23:11:04 +03:00
var ListSortSettings = React.createClass({
render: function()
{
2016-06-23 01:17:22 +03:00
return <div className={this.props.className} value={this.props.sort.sortby}>
2016-06-20 23:11:04 +03:00
<select className="sortby">
{['sent date', 'status', 'label', 'size', 'subject'].map(function(i) {
2016-06-23 01:17:22 +03:00
return <option key={'s'+i} value={i}>Sort by {i}</option>
})}
</select>
2016-06-23 01:17:22 +03:00
<select className="group" value={this.props.sort.group}>
<option value="">Do not group</option>
<option value="read">Group by read status</option>
<option value="pinned">Group by pinned status</option>
<option value="date">Group by date</option>
</select>
2016-06-23 01:17:22 +03:00
<label><input type="checkbox" checked={this.props.sort.ascending} /> Sort ascending</label>
<label><input type="checkbox" checked={this.props.sort.threaded} /> Threaded</label>
</div>
}
2016-06-20 23:11:04 +03:00
});
2016-06-20 23:11:04 +03:00
var ListSortSettingsWindow = React.createClass({
mixins: [ DropDownBase ],
render: function()
{
var sort = this.props.override ? this.props.sorting : this.props.defaultSorting;
return <div ref="dd" onClick={this.onClick} className={'dropdown window list-sort'+(this.state.visible ? ' visible' : '')}
id={'dropdown-'+this.props.id} tabIndex="1" style={{ top: this.state.top, left: this.state.left }}>
<div ref="callout" className="callout-top" style={{ left: this.state.calloutLeft }}></div>
2016-06-20 23:11:04 +03:00
<div className="title">Sorting for {this.props.folder}</div>
<label><input type="checkbox" checked={this.props.override ? "checked" : null} /> Override default sorting</label>
2016-06-20 23:11:04 +03:00
<ListSortSettings className="sorting" sort={sort} />
<div className="show">
<a className="button" onClick={this.expandChecks}><span className={this.state.checksVisible ? 'collapse' : 'expand'}></span> Show</a>
</div>
2016-06-20 23:11:04 +03:00
<div className="show-checks" style={{ display: this.state.checksVisible ? null : 'none' }}>
<label><input type="checkbox" checked={this.props.show.read ? "checked" : null} /> Show Read</label>
<label><input type="checkbox" checked={this.props.show.trash ? "checked" : null} /> Show Trash</label>
<label><input type="checkbox" checked={this.props.show.spam ? "checked" : null} /> Show Spam</label>
<label><input type="checkbox" checked={this.props.show.lists ? "checked" : null} /> Show Mailing Lists</label>
<label><input type="checkbox" checked={this.props.show.sent ? "checked" : null} /> Show Sent</label>
<label><input type="checkbox" checked={this.props.show.dups ? "checked" : null} /> Show Duplicates</label>
</div>
</div>
},
getInitialState: function()
{
return { checksVisible: false };
},
expandChecks: function()
{
this.setState({ checksVisible: !this.state.checksVisible });
}
});
2016-06-20 23:11:04 +03:00
var MailSettingsWindow = React.createClass({
mixins: [ DropDownBase ],
render: function()
{
return <div ref="dd" onClick={this.onClick} className={'dropdown window'+(this.state.visible ? ' visible' : '')}
id={'dropdown-'+this.props.id} tabIndex="1" style={{ top: this.state.top, left: this.state.left }}>
<div ref="callout" className="callout-top" style={{ left: this.state.calloutLeft }}></div>
2016-06-20 23:11:04 +03:00
<div className="text">Mail Layout</div>
<div className="layouts">
2016-06-22 00:14:07 +03:00
<a onClick={this.switchLayout} className={'button mail-message-on-right'+(this.state.layout == 'message-on-right' ? ' selected' : '')} title="List and Message on Right"><span></span></a>
<a onClick={this.switchLayout} className={'button mail-message-on-bottom'+(this.state.layout == 'message-on-bottom' ? ' selected' : '')} title="List and Message Below"><span></span></a>
<a onClick={this.switchLayout} className={'button mail-message-invisible'+(this.state.layout == 'message-invisible' ? ' selected' : '')} title="List Only"><span></span></a>
</div>
2016-06-20 23:11:04 +03:00
<div className="split"><i></i></div>
<div className="text">Default List Sorting</div>
<ListSortSettings className="fields" sort={this.props.defaultSorting} />
2016-06-22 00:14:07 +03:00
<div className="fields">
<label><input type="checkbox" checked={this.state.showQuickReply} onClick={this.showQuickReply} /> Show Quick Reply</label>
</div>
2016-06-20 23:11:04 +03:00
<div className="split"><i></i></div>
<div className="text">Mark as Read</div>
<div className="fields">
<select className="sortby" defaultValue={this.props.markDelay}>
<option value="-1">Manually</option>
<option value="0">On Select</option>
<option value="1">After 1 second</option>
<option value="3">After 3 seconds</option>
<option value="5">After 5 seconds</option>
</select>
</div>
</div>
},
2016-06-22 00:14:07 +03:00
componentDidMount: function()
{
Store.on('layout', this.setLayout);
Store.on('quickReply', this.setQuickReply);
},
componentWillUnmount: function()
{
Store.un('layout', this.setLayout);
Store.un('quickReply', this.setQuickReply);
2016-06-22 00:14:07 +03:00
},
setLayout: function()
{
this.setState({ layout: Store.layout });
},
setQuickReply: function()
{
this.setState({ showQuickReply: Store.quickReply });
},
2016-06-22 00:14:07 +03:00
switchLayout: function(ev)
{
var t = ev.target.nodeName == 'A' ? ev.target : ev.target.parentNode;
var l = / mail-(\S+)/.exec(t.className)[1];
Store.set('layout', l);
2016-06-22 00:14:07 +03:00
},
getInitialState: function()
{
return { showQuickReply: Store.quickReply, layout: Store.layout };
},
showQuickReply: function()
{
Store.set('quickReply', !this.state.showQuickReply);
}
});
2016-06-20 23:11:04 +03:00
var dropdown_list_sort = React.createElement(
ListSortSettingsWindow, {
id: 'list-sort',
window: true,
folder: 'INBOX',
override: false,
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
}
}
2016-06-20 23:11:04 +03:00
);
2016-06-20 23:11:04 +03:00
var dropdown_settings = React.createElement(
MailSettingsWindow, {
id: 'settings',
window: true,
markDelay: -1,
defaultSorting: {
sort: {
sortby: 'sent date',
group: 'date',
ascending: false,
threaded: false
}
}
}
);
2016-06-20 23:11:04 +03:00
var AccountFolders = React.createClass({
render: function()
{
2016-06-20 23:11:04 +03:00
return <div className="account">
<div className={"account-header"+(this.state.collapsed ? ' collapsed' : '')} onClick={this.onClick}>
{this.props.name}
2016-06-22 01:01:37 +03:00
<div key="n" className="msg-count">{this.props.unreadCount}</div>
2016-06-20 23:11:04 +03:00
{this.props.accountId ? [
<div key="load" className="loading icon" style={{display: this.props.loading ? '' : 'none'}}></div>,
<div key="warn" className="warning icon" style={{display: this.props.warning ? '' : 'none'}}></div>,
<div key="cfg" className={'cfg'+(this.state.cfgPressed ? ' pressed' : '')} onClick={this.showCfg}></div>
2016-06-20 23:11:04 +03:00
] : null}
</div>
<div className={"account-folders"+(this.state.collapsed ? ' collapsed' : '')} style={{ height: this.state.h }}>
<div ref="vis" className={'visible-part'+(this.state.animating ? ' animating' : '')}>
2016-06-20 23:11:04 +03:00
{this.props.folders.map((f, i) =>
<div key={'f'+i} data-i={i} className={'folder'+(f.unreadCount > 0 ? ' with-unread' : '')+
(this.state.selected == i ? ' selected' : '')} onClick={this.selectFolder}>
{f.icon ? <img src={'icons/'+f.icon+'.png'} /> : null}
2016-06-20 23:11:04 +03:00
{' '}
<span>{f.name}</span>
2016-06-20 23:11:04 +03:00
{f.unreadCount > 0 ? <div className="msg-count">{f.unreadCount}</div> : null}
</div>
2016-06-20 23:11:04 +03:00
)}
</div>
</div>
</div>
},
selectFolder: function(ev)
{
var t = ev.target;
while (t && !t.getAttribute('data-i') && t != this.refs.vis)
t = t.parentNode;
if (t && t != this.refs.vis)
{
var i = t.getAttribute('data-i');
this.setState({ selected: i });
this.props.onSelect(this, i);
}
// FIXME: send select event + switch focus to message list if folder changed
},
showCfg: function(ev)
{
var self = this;
var i = DropDownBase.instances.account.state.items;
i[0].text = 'Read '+this.props.name;
DropDownBase.instances.account.setState({ items: i });
DropDownBase.instances.account.showAt(ev.target, function()
{
self.setState({ cfgPressed: false });
});
self.setState({ cfgPressed: true });
ev.stopPropagation();
},
getInitialState: function()
{
return { collapsed: this.props.collapsed, animating: false, h: null, cfgPressed: false, selected: -1 };
},
onClick: function()
{
var self = this;
if (this.state.animating)
return;
this.setState({ animating: true, h: this.refs.vis.offsetHeight });
if (!this.state.collapsed)
{
setTimeout(function()
{
self.setState({ h: 0 });
}, 50);
}
setTimeout(function()
{
self.setState({ collapsed: !self.state.collapsed, animating: false, h: null });
}, this.state.collapsed ? 200 : 250);
}
});
2016-06-20 23:11:04 +03:00
var FolderList = React.createClass({
render: function()
{
var self = this;
2016-06-20 23:11:04 +03:00
return <div className={"folder-list"+(this.props.progress !== undefined ? ' progress-visible' : '')}>
<div className="top-border-gradient"></div>
<div className="bottom-border-gradient"></div>
<div className="actions">
<a className="button"><img src="icons/compose.png" /> Compose</a>
<DropDownButton dropdownId="check-send" className="check-send" icon="mail_check_send" />
</div>
// TODO: keyboard navigation
<div className="listview" tabIndex="1">
{this.props.accounts.map(function(account) {
return <AccountFolders key={'a'+account.accountId} onSelect={self.onSelectFolder} {...account} />
})}
</div>
2016-06-20 23:11:04 +03:00
<div className="progress-bar" ref="pbar">
<div className="pending" style={{ width: this.state.pbarWidth }}>Loading database ({this.props.progress||0}%)</div>
<div className="clip" style={{ width: (this.props.progress||0)+'%', overflow: 'hidden' }}>
<div className="done" ref="pdone" style={{ width: this.state.pbarWidth }}>Loading database ({this.props.progress||0}%)</div>
</div>
</div>
</div>
2016-06-20 23:11:04 +03:00
},
onSelectFolder: function(folders, i)
{
if (this.prevSelected && this.prevSelected != folders)
this.prevSelected.setState({ selected: -1 });
this.prevSelected = folders;
},
2016-06-20 23:11:04 +03:00
getInitialState: function()
{
return { pbarWidth: '' };
},
onResize: function()
{
this.setState({ pbarWidth: this.refs.pbar.offsetWidth });
},
componentDidMount: function()
{
window.addEventListener('resize', this.onResize);
this.onResize();
},
componentWillUnmount: function()
{
window.removeEventListener('resize', this.onResize);
}
});
2016-06-20 23:11:04 +03:00
var TabPanel = React.createClass({
render: function()
{
var bar = [];
var body = [];
2016-06-20 23:11:04 +03:00
for (var i = 0; i < this.state.tabs.length; i++)
{
2016-06-22 00:14:07 +03:00
var t = this.state.tabs[i];
bar.push(
2016-06-20 23:11:04 +03:00
<div key={'t'+i} className={'tab'+(i == this.state.selected ? ' selected' : '')+(t.noclose ? ' noclose' : '')}
id={'t-tab'+i} onClick={this.switchTab} style={i == this.state.closing ? { width: '1px', padding: '0', opacity: '0' } : null}>
{t.noclose ? null : <a className="close" onClick={this.closeTab}></a>}
<img src={'icons/'+t.icon+'.png'} className={t.i16 ? 'i16' : null} /> {t.title}
</div>
);
body.push(
2016-06-20 23:11:04 +03:00
<div key={'c'+i} className={'tab-content'+(t.className ? ' '+t.className : '')}
id={'tab'+i} style={{ display: i == this.state.selected ? null : 'none' }}>
{t.children}
</div>
);
}
2016-06-22 00:14:07 +03:00
return <div className="tab-panel">
2016-06-20 23:11:04 +03:00
<div className="tab-bar">{bar}</div>
{body}
2016-06-22 00:14:07 +03:00
</div>
},
componentWillReceiveProps: function(nextProps, nextContent)
{
// FIXME: Do not own tabs?
2016-06-22 00:14:07 +03:00
this.setState({ selected: this.state.selected % nextProps.tabs.length, tabs: nextProps.tabs });
},
getInitialState: function()
{
2016-06-22 00:14:07 +03:00
return { selected: 0, tabs: this.props.tabs };
},
switchTab: function(ev)
{
this.setState({ selected: ev.target.id.substr(5) });
},
closeTab: function(ev)
{
var self = this;
var tab = ev.target.parentNode;
//this.setState({ closing: tab.id.substr(5) });
tab.style.width = '1px';
tab.style.padding = '0';
tab.style.opacity = '0';
setTimeout(function()
{
var t = self.state.tabs;
t.splice(tab.id.substr(5), 1);
var s = self.state.selected;
if ('t-tab'+s == tab.id)
s = self.state.selected-1;
this.setState({ tabs: t, selected: s });
}, 200);
ev.stopPropagation();
}
});
2016-06-20 20:15:06 +03:00
function formatBytes(s)
{
if (!s) return '';
2016-06-20 20:15:06 +03:00
if (s < 1024) return s+' B';
else if (s < 1024*1024) return (Math.round(s*10/1024)/10)+' KB';
else if (s < 1024*1024*1024) return (Math.round(s*10/1024/1024)/10)+' MB';
return (Math.round(s*10/1024/1024/1024)/10)+' GB';
}
function formatDate(dt)
{
if (!(dt instanceof Date))
dt = new Date(dt.replace(' ', 'T'));
2016-06-20 20:15:06 +03:00
var tod = new Date();
tod.setHours(0);
tod.setMinutes(0);
tod.setSeconds(0);
tod.setMilliseconds(0);
var prevweek = tod;
prevweek = prevweek.getTime() - (7 + (prevweek.getDay()+6)%7)*86400000;
if (dt.getTime() < prevweek)
{
2016-06-20 20:15:06 +03:00
var d = dt.getDate();
var m = dt.getMonth()+1;
2016-06-28 14:17:20 +03:00
return (d < 10 ? '0' : '')+d+' '+(m < 10 ? '0' : '')+m+' '+dt.getFullYear();
2016-06-20 20:15:06 +03:00
}
else if (dt.getTime() < tod.getTime())
{
return WeekDays[dt.getDay()]+' '+dt.getDate()+' '+Months[dt.getMonth()];
2016-06-20 20:15:06 +03:00
}
var h = dt.getHours();
var m = dt.getMinutes();
2016-06-20 20:15:06 +03:00
return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m;
}
2016-06-23 17:27:12 +03:00
// Common selection mixin
var ListWithSelection = {
// requires to override methods: this.deleteSelected(), this.getPageSize(), this.getItemOffset(index), this.getTotalItems()
getInitialState: function()
{
return {
2016-06-23 17:27:12 +03:00
selected: {}
};
},
isSelected: function(i)
{
return this.state.selected[i] || this.state.selected.begin !== undefined &&
this.state.selected.begin <= i && this.state.selected.end >= i;
},
2016-06-23 17:27:12 +03:00
onListKeyDown: function(ev)
{
2016-06-23 17:27:12 +03:00
if (!this.getTotalItems())
2016-06-22 21:23:50 +03:00
return;
if (ev.keyCode == 46) // delete
{
2016-06-23 17:27:12 +03:00
this.deleteSelected();
this.setState({ selected: {} });
ev.stopPropagation();
}
2016-06-22 21:23:50 +03:00
else if (ev.keyCode == 38 || ev.keyCode == 40 || ev.keyCode == 33 || ev.keyCode == 34) // up, down, pgup, pgdown
{
2016-06-22 21:23:50 +03:00
var sel = this.curSel, dir;
if (ev.keyCode < 35)
2016-06-23 17:27:12 +03:00
dir = (ev.keyCode == 34 ? 1 : -1) * this.getPageSize();
2016-06-22 21:23:50 +03:00
else
dir = (ev.keyCode == 40 ? 1 : -1);
if (sel !== null)
{
2016-06-23 22:36:21 +03:00
var nsel = sel+dir, n = this.getTotalItems();
2016-06-22 21:23:50 +03:00
if (nsel < 0)
nsel = 0;
2016-06-23 22:36:21 +03:00
if (nsel >= n)
nsel = n-1;
2016-06-22 21:23:50 +03:00
if (sel != nsel)
{
2016-06-22 21:23:50 +03:00
if (ev.shiftKey)
this.selectTo(nsel);
else
this.selectOne(nsel);
2016-06-23 17:27:12 +03:00
var pos = this.getItemOffset(nsel);
if (pos[0] + pos[1] > this.refs.scroll.scrollTop + this.refs.scroll.offsetHeight)
this.refs.scroll.scrollTop = pos[0] + pos[1] - this.refs.scroll.offsetHeight;
else if (pos[0] < this.refs.scroll.scrollTop + this.getScrollPaddingTop())
this.refs.scroll.scrollTop = pos[0] - this.getScrollPaddingTop();
2016-06-22 21:23:50 +03:00
ev.stopPropagation();
}
2016-06-23 17:27:12 +03:00
ev.preventDefault(); // prevent scroll
}
2016-06-22 21:23:50 +03:00
}
else if (ev.keyCode == 36) // home
{
if (ev.shiftKey)
2016-06-23 17:27:12 +03:00
{
2016-06-22 21:23:50 +03:00
this.selectTo(0);
2016-06-23 17:27:12 +03:00
this.refs.scroll.scrollTop = pos[0] - this.getScrollPaddingTop();
}
2016-06-22 21:23:50 +03:00
else
this.selectOne(0);
}
else if (ev.keyCode == 35) // end
{
2016-06-23 17:27:12 +03:00
var nsel = this.getTotalItems()-1;
2016-06-22 21:23:50 +03:00
if (ev.shiftKey)
2016-06-23 17:27:12 +03:00
{
this.selectTo(nsel);
var pos = this.getItemOffset(nsel);
this.refs.scroll.scrollTop = pos[0] + pos[1] - this.refs.scroll.offsetHeight;
}
2016-06-22 21:23:50 +03:00
else
2016-06-23 17:27:12 +03:00
this.selectOne(nsel);
}
},
2016-06-22 21:23:50 +03:00
selectTo: function(ns)
{
if (this.lastSel === undefined)
return this.selectOne(ns);
var sel = {};
2016-06-23 22:36:21 +03:00
var n = this.getTotalItems();
if (this.lastSel >= n)
this.lastSel = n-1;
2016-06-22 21:23:50 +03:00
if (ns < this.lastSel)
sel = { begin: ns, end: this.lastSel };
2016-06-22 21:23:50 +03:00
else if (ns > this.lastSel)
sel = { begin: this.lastSel, end: ns };
else
sel[ns] = true;
2016-06-23 17:27:12 +03:00
this.setState({ selected: sel });
2016-06-22 21:23:50 +03:00
this.curSel = ns;
2016-06-23 23:31:20 +03:00
if (this.onSelectCurrent)
this.onSelectCurrent(ns);
2016-06-22 21:23:50 +03:00
},
selectOne: function(ns)
{
2016-06-23 17:27:12 +03:00
var sel = {};
sel[ns] = true;
this.setState({ selected: sel });
2016-06-22 21:23:50 +03:00
this.lastSel = ns;
this.curSel = ns;
2016-06-23 23:31:20 +03:00
if (this.onSelectCurrent)
this.onSelectCurrent(ns);
2016-06-22 21:23:50 +03:00
},
2016-06-23 17:27:12 +03:00
onListItemClick: function(ev)
{
var t = ev.target;
while (t && !t.getAttribute('data-i'))
t = t.parentNode;
if (t)
{
var ns = parseInt(t.getAttribute('data-i'));
2016-06-22 21:23:50 +03:00
if (ev.shiftKey)
this.selectTo(ns);
else if (ev.ctrlKey)
{
2016-06-23 17:27:12 +03:00
this.state.selected[ns] = true;
2016-06-22 21:23:50 +03:00
this.curSel = ns;
2016-06-23 23:31:20 +03:00
if (this.onSelectCurrent)
this.onSelectCurrent(ns);
2016-06-22 21:23:50 +03:00
this.lastSel = this.lastSel === undefined ? ns : this.lastSel;
2016-06-23 17:27:12 +03:00
this.setState({ selected: this.state.selected });
}
else
2016-06-22 21:23:50 +03:00
this.selectOne(ns);
}
2016-06-23 17:27:12 +03:00
}
};
var AttachList = React.createClass({
mixins: [ ListWithSelection ],
getInitialState: function()
{
return {
attachments: [],
attachScroll: 0
};
},
addAttachments: function(ev)
{
var a = this.state.attachments;
if (ev.target.files)
for (var i = 0; i < ev.target.files.length; i++)
a.push(ev.target.files[i]);
this.setState({ attachments: a });
// reset file input
ev.target.innerHTML = ev.target.innerHTML;
},
scrollAttachList: function(ev)
{
this.setState({ attachScroll: ev.target.scrollTop });
},
deleteSelected: function()
{
for (var i = this.state.attachments.length-1; i >= 0; i--)
if (this.state.selected[i])
this.state.attachments.splice(i, 1);
this.setState({ attachments: this.state.attachments });
},
getTotalItems: function()
{
return this.state.attachments.length;
},
getPageSize: function()
{
return this.refs.a0 ? Math.round(this.refs.scroll.offsetHeight / this.refs.a0.offsetHeight) : 1;
},
getItemOffset: function(index)
{
var item = this.refs['a'+index];
return [ item.offsetTop, item.offsetHeight ];
},
getScrollPaddingTop: function()
{
return this.refs.title.offsetHeight;
},
render: function()
{
return <div className="attach">
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
<input type="file" multiple="multiple" onChange={this.addAttachments} />
</div>
2016-06-23 17:27:12 +03:00
<div ref="scroll" className="attach-list" tabIndex="1" onScroll={this.scrollAttachList} onKeyDown={this.onListKeyDown}
style={this.state.attachments.length ? null : { display: 'none' }}>
<div ref="title" className="title" style={{ top: this.state.attachScroll }}>
<div className="name">Attachment <a className="button">
<img src="icons/attach.png" />
<input type="file" multiple="multiple" onChange={this.addAttachments} />
</a></div>
<div className="size">Size</div>
</div>
{this.state.attachments.map((a, i) =>
<div ref={'a'+i} title={a.name+' ('+formatBytes(a.size)+')'} key={'a'+i} data-i={i}
className={'attachment'+(this.isSelected(i) ? ' selected' : '')} onMouseDown={this.onListItemClick}>
<div className="name">{a.name}</div>
<div className="size">{formatBytes(a.size)}</div>
</div>
)}
</div>
</div>
}
});
var ComposeWindow = React.createClass({
getInitialState: function()
{
return {
text: ''
};
},
changeText: function(ev)
{
this.setState({ text: ev.target.value });
},
render: function()
{
2016-06-20 23:11:04 +03:00
return <div className="compose">
<div className="actions">
<a className="button"><img src="icons/mail_send.png" />Send</a>
<a className="button"><img src="icons/delete.png" /></a>
</div>
2016-06-20 23:11:04 +03:00
<div className="flex">
<div className="headers">
<div className="headers-table">
<div className="header">
<div className="field">From</div>
<div className="value">
<select>
2016-06-20 23:11:04 +03:00
{this.props.accounts.map((a, i) =>
<option key={'a'+i}>{'"'+a.name+'" <'+a.email+'>'}</option>
)}
</select>
</div>
</div>
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">To</div>
<div className="value"><input /></div>
</div>
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">Cc</div>
<div className="value"><input /></div>
</div>
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">Bcc</div>
<div className="value"><input /></div>
</div>
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">Subject</div>
<div className="value"><input /></div>
</div>
</div>
<AttachList />
</div>
2016-06-20 23:11:04 +03:00
<div className="text">
<textarea onChange={this.changeText} defaultValue={this.state.text}></textarea>
</div>
</div>
</div>
}
});
2016-06-20 23:11:04 +03:00
var MessageInList = React.createClass({
msgClasses: [ 'unread', 'unseen', 'replied', 'pinned', 'sent', 'unloaded' ],
2016-06-20 20:15:06 +03:00
render: function()
{
var msg = this.props.msg;
2016-06-23 22:36:21 +03:00
return <div data-i={this.props.i} className={'message'+(this.msgClasses.map(c => (msg[c] ? ' '+c : '')).join(''))+
2016-06-23 23:31:20 +03:00
(this.props.selected ? ' selected' : '')+(msg.thread && Store.threads ? ' thread0' : '')} onMouseDown={this.props.onClick}>
2016-06-22 00:14:07 +03:00
<div className="icon" style={{ width: (20+10*(msg.level||0)), backgroundPosition: (10*(msg.level||0))+'px 7px' }}></div>
<div className="subject" style={{ paddingLeft: (20+10*(msg.level||0)) }}>{msg.subject}</div>
2016-06-23 01:17:22 +03:00
{msg.thread && Store.threads ? <div className={'expand'+(msg.collapsed ? '' : ' collapse')}></div> : null}
2016-06-20 23:11:04 +03:00
<div className="bullet"></div>
2016-06-22 00:14:07 +03:00
<div className="from" style={{ left: (21+10*(msg.level||0)) }}>{(msg.sent || msg.outgoing ? 'To '+msg.to : msg.from)}</div>
2016-06-20 23:11:04 +03:00
<div className="size">{formatBytes(msg.size)}</div>
<div className="attach" style={msg.attach ? null : { display: 'none' }}></div>
<div className="time">{formatDate(msg.time)}</div>
2016-06-20 20:15:06 +03:00
</div>
}
});
2016-07-06 18:50:12 +03:00
// TODO: expand/collapse days
2016-06-20 23:11:04 +03:00
var MessageList = React.createClass({
2016-06-23 22:36:21 +03:00
mixins: [ ListWithSelection ],
2016-06-20 23:11:04 +03:00
getInitialState: function()
{
2016-06-24 00:37:16 +03:00
return { firstDayTop: 0, firstDay: this.props.groups[0].name, groups: this.props.groups /*FIXME*/ };
2016-06-20 23:11:04 +03:00
},
changeFirstDay: function(ev)
{
2016-06-24 00:37:16 +03:00
var scrollTop = ev.target.scrollTop, scrollSize = ev.target.offsetHeight - this.getScrollPaddingTop();
var top = 0, p, firstVisibleGrp, firstVisible, lastVisibleGrp, lastVisible;
var itemH = (Store.layout == 'message-on-right' ? 60 : 30);
var i;
for (i = 0; i < this.state.groups.length; i++)
{
p = top;
top += (i > 0 ? 30 : 0) + itemH*this.state.groups[i].messageCount;
if (firstVisibleGrp === undefined && scrollTop < top)
{
firstVisibleGrp = i;
if (i > 0 && scrollTop < p+30)
firstVisible = 0;
else
2016-06-24 15:43:44 +03:00
firstVisible = Math.floor((scrollTop - p - (i > 0 ? 30 : 0))/itemH);
2016-06-24 00:37:16 +03:00
}
if (lastVisibleGrp === undefined && scrollTop+scrollSize < top)
{
lastVisibleGrp = i;
2016-06-24 15:43:44 +03:00
if (i > 0 && scrollTop+scrollSize < p+30)
2016-06-24 00:37:16 +03:00
lastVisible = 0;
else
2016-06-24 15:43:44 +03:00
lastVisible = Math.floor((scrollTop+scrollSize - p - (i > 0 ? 30 : 0))/itemH);
if (lastVisible >= this.state.groups[i].messageCount)
lastVisible = this.state.groups[i].messageCount-1;
2016-06-24 00:37:16 +03:00
}
if (firstVisibleGrp !== undefined && lastVisibleGrp !== undefined)
break;
}
2016-06-24 15:43:44 +03:00
if (lastVisibleGrp === undefined)
{
lastVisibleGrp = this.state.groups.length-1;
lastVisible = this.state.groups[lastVisibleGrp].messageCount-1;
}
this.setState({
firstDayTop: scrollTop,
firstDay: this.state.groups[firstVisibleGrp].name,
firstGrp: firstVisibleGrp,
firstMsg: firstVisible,
lastGrp: lastVisibleGrp,
lastMsg: lastVisible
});
},
2016-06-23 22:36:21 +03:00
deleteSelected: function()
{
2016-06-23 23:31:20 +03:00
},
onSelectCurrent: function(index)
{
var total = 0, p;
for (var i = 0; i < this.state.groups.length; i++)
{
p = total;
2016-06-24 00:37:16 +03:00
total += (i > 0 ? 1 : 0)+this.state.groups[i].messageCount;
2016-06-23 23:31:20 +03:00
if (index < total)
{
Store.set('msg', this.state.groups[i].messages[index-p-(i > 0 ? 1 : 0)]);
break;
}
}
2016-06-23 22:36:21 +03:00
},
getTotalItems: function()
{
2016-06-23 23:31:20 +03:00
var total = -1; // do not count first-day as item
2016-06-23 22:36:21 +03:00
for (var i = 0; i < this.state.groups.length; i++)
{
2016-06-24 00:37:16 +03:00
total += 1+this.state.groups[i].messageCount;
2016-06-23 22:36:21 +03:00
}
return total;
},
getPageSize: function()
{
return Math.round(this.refs.scroll.offsetHeight / (Store.layout == 'message-on-right' ? 60 : 30));
},
getItemOffset: function(index)
{
/*var c, gmin = 0, gmax = this.state.groups.length;
while (gmin != gmax-1)
{
c = Math.floor((gmax+gmin)/2);
if (this.state.groups[c]
}*/
2016-06-24 23:45:10 +03:00
var n = 0, top = this.refs.title.offsetHeight, p;
2016-06-23 22:36:21 +03:00
for (var i = 0; i < this.state.groups.length; i++)
{
p = n;
2016-06-24 00:37:16 +03:00
n += (i > 0 ? 1 : 0)+this.state.groups[i].messageCount;
2016-06-23 22:36:21 +03:00
if (index < n)
{
if (index > p)
2016-06-23 23:31:20 +03:00
top += (i > 0 ? 30 : 0) + (Store.layout == 'message-on-right' ? 60 : 30)*(index-p-1);
2016-06-23 22:36:21 +03:00
break;
}
2016-06-24 00:37:16 +03:00
top += (i > 0 ? 30 : 0) + (Store.layout == 'message-on-right' ? 60 : 30)*this.state.groups[i].messageCount;
2016-06-23 22:36:21 +03:00
}
2016-06-23 23:31:20 +03:00
return [ top, (Store.layout == 'message-on-right' && (index == 0 || index != p) ? 60 : 30) ];
2016-06-23 22:36:21 +03:00
},
getScrollPaddingTop: function()
{
return this.refs.title.offsetHeight;
},
2016-06-24 15:43:44 +03:00
getMessages: function(grp, start, end)
{
var a = grp.messages.slice(start, end);
for (var i = 0; i < end-start; i++)
if (!a[i])
a[i] = null;
return a;
},
render: function()
{
2016-06-23 22:36:21 +03:00
var self = this;
var total = 0;
2016-06-24 15:43:44 +03:00
var itemH = (Store.layout == 'message-on-right' ? 60 : 30);
2016-06-20 23:11:04 +03:00
return <div className="message-list">
<div className="top-border-gradient"></div>
<div className="bottom-border-gradient"></div>
<div className="actions">
<div className="searchbox">
2016-06-20 20:15:06 +03:00
<input type="text" placeholder="Search mail" />
</div>
<DropDownButton dropdownId="threads" className="threads"
2016-06-21 21:47:41 +03:00
title="Show Message Thread" checkedTitle="Hide Message Thread" icon="thread" checkedIcon="thread_selected" checkable="1" />
<DropDownButton ref="setBtn" dropdownId="settings" className="settings" whole="1"
title="Settings for this folder" icon="config" />
<DropDownButton dropdownId="list-sort" className="list-sort" whole="1"
title="Sorting settings" icon="list_sort" />
2016-06-20 23:11:04 +03:00
<div className="clear"></div>
2016-06-20 20:15:06 +03:00
</div>
2016-06-23 22:36:21 +03:00
<div ref="scroll" className="listview" tabIndex="1" onScroll={this.changeFirstDay} onKeyDown={this.onListKeyDown}>
2016-06-24 00:37:16 +03:00
<div ref="title" className="day first-day" style={{ top: this.state.firstDayTop }}>{this.state.firstDay}</div>
2016-06-23 22:36:21 +03:00
{this.props.groups.map(function(grp, i) {
2016-06-23 23:31:20 +03:00
if (i > 0)
total++;
2016-06-24 15:43:44 +03:00
var start = total+(self.state.firstGrp == i ? self.state.firstMsg : 0);
2016-06-23 22:36:21 +03:00
var r = [
i > 0 ? <div className="day" data-i={total-1}>{grp.name}</div> : null,
<div className="day-list">
2016-06-24 15:43:44 +03:00
{(self.state.firstGrp > i || self.state.lastGrp < i
? <div className="placeholder" style={{ height: itemH*grp.messageCount }}></div>
: [
(self.state.firstGrp == i
? <div className="placeholder" style={{ height: itemH*self.state.firstMsg }}></div>
: null),
self.getMessages(grp,
self.state.firstGrp == i ? self.state.firstMsg : 0,
self.state.lastGrp == i ? self.state.lastMsg+1 : grp.messageCount
).map((msg, j) => (msg
? [
<MessageInList msg={msg} i={start+j} selected={self.isSelected(start+j)} onClick={self.onListItemClick} />,
/*(msg.thread && Store.threads ?
<div className="thread">
{msg.thread.map(reply => <MessageInList msg={reply} i={0} onClick={self.onListItemClick} />)}
</div>
: null)*/
]
: <div data-i={start+j} className={'message'+(self.isSelected(start+j) ? ' selected' : '')} onMouseDown={self.onListItemClick}></div>
)),
(self.state.lastGrp == i
? <div className="placeholder" style={{ height: itemH*(grp.messageCount-self.state.lastMsg-1) }}></div>
: null)
]
)}
2016-06-23 22:36:21 +03:00
</div>
];
2016-06-24 15:43:44 +03:00
total += grp.messageCount;
2016-06-23 22:36:21 +03:00
return r;
})}
</div>
</div>
2016-06-20 20:15:06 +03:00
}
});
2016-06-20 23:11:04 +03:00
var MessageView = React.createClass({
formatLongDate: function(dt)
{
if (!(dt instanceof Date))
dt = new Date(dt.replace(' ', 'T'));
var h = dt.getHours();
var m = dt.getMinutes();
var s = dt.getSeconds();
return WeekDays[dt.getDay()]+' '+dt.getDate()+' '+Months[dt.getMonth()]+' '+dt.getFullYear()+' '+(h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m+':'+(s < 10 ? '0' : '')+s
//return dt.toLocaleString();
},
componentDidMount: function()
{
Store.on('quickReply', this.setQuickReply);
Store.on('msg', this.setMsg);
},
componentWillUnmount: function()
{
Store.un('quickReply', this.setQuickReply);
Store.un('msg', this.setMsg);
},
setQuickReply: function()
{
this.setState({ quickReply: Store.quickReply });
},
setMsg: function()
2016-06-20 20:15:06 +03:00
{
this.setState({ msg: Store.msg });
},
getInitialState: function()
{
return { quickReply: Store.quickReply };
2016-06-20 20:15:06 +03:00
},
render: function()
{
var msg = this.state.msg;
return <div className={'message-view'+(!msg ? ' no-mail-shown' : '')+(!this.state.quickReply ? ' no-quick' : '')}>
2016-06-20 23:11:04 +03:00
<div className="actions">
<DropDownButton dropdownId="reply" icon="mail_reply" text="Reply" />
2016-06-20 23:11:04 +03:00
<a className="button"><img src="icons/mail_reply_all.png" />Reply All</a>
<DropDownButton dropdownId="forward" icon="mail_forward" text="Forward" />
2016-06-21 21:47:41 +03:00
<DropDownButton className="action-read" icon="read" checkedIcon="read_selected" checkable="1" text="Read" checkedText="Unread" />
<DropDownButton className="action-spam" icon="spam" checkedIcon="spam_selected" checkable="1" text="Spam" checkedText="Not Spam" />
<DropDownButton dropdownId="delete" className="action-delete" icon="delete" checkedIcon="delete_selected" checkable="1" text="Delete" checkedText="Undelete" />
2016-06-20 23:11:04 +03:00
<a className="button show-dropdown"><img src="icons/label.png" />Label <span className="down"></span></a>
<DropDownButton ref="setBtn" dropdownId="settings" whole="1" icon="config" text="Settings" />
2016-06-20 20:15:06 +03:00
</div>
2016-06-20 23:11:04 +03:00
<div className="nomail">
<div className="flex1"></div>
<div className="middle">
<img src="icons/no_mail.png" />
<div className="txt">No message selected</div>
2016-06-20 20:15:06 +03:00
</div>
<div className="flex1"></div>
</div>
2016-06-20 23:11:04 +03:00
{msg ? [
<div className="headers">
<div className="top">
<div className="pin"></div>
<div className="time">{this.formatLongDate(msg.time)}</div>
2016-06-20 23:11:04 +03:00
<div className="subject">{msg.subject}</div>
2016-06-20 20:15:06 +03:00
</div>
2016-06-20 23:11:04 +03:00
<div className="header-table">
<div className="header">
<div className="field">From</div>
<div className="value"><a className="button">{msg.from+' <'+msg.fromEmail+'>'}</a></div>
2016-06-20 20:15:06 +03:00
</div>
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">To</div>
<div className="value"><a className="button">{msg.to}</a></div>
2016-06-20 20:15:06 +03:00
</div>
{msg.cc ?
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">Cc</div>
<div className="value">
{msg.cc.map(cc => <a className="button">{cc}</a>)}
2016-06-20 20:15:06 +03:00
</div>
</div>
: null}
{msg.replyto ?
2016-06-20 23:11:04 +03:00
<div className="header">
<div className="field">Reply-to</div>
<div className="value"><a className="button">{msg.replyto}</a></div>
2016-06-20 20:15:06 +03:00
</div>
: null}
</div>
2016-06-20 23:11:04 +03:00
</div>,
(msg.blockedImages ?
<div className="blocked-images">
2016-06-20 20:15:06 +03:00
<img src="icons/block.png" /> This message contains blocked images.
2016-06-20 23:11:04 +03:00
<a className="button raised">Load Images</a>
2016-06-20 20:15:06 +03:00
<label><input type="checkbox" /> Always load from {msg.from}</label>
</div>
2016-06-20 23:11:04 +03:00
: null),
<div className="text" dangerouslySetInnerHTML={{ __html: msg.body }}></div>,
this.state.quickReply ?
2016-06-20 23:11:04 +03:00
<div className="quick-reply">
2016-06-20 20:15:06 +03:00
<textarea></textarea>
2016-06-20 23:11:04 +03:00
<div className="btns">
<a className="button"><img src="icons/mail_send.png" /> Quick Reply</a>
<a className="button"><img src="icons/mail_reply_all.png" /> Quick Reply All</a>
2016-06-20 20:15:06 +03:00
</div>
</div>
: null
2016-06-20 23:11:04 +03:00
] : null}
</div>
}
});
2016-06-20 23:11:04 +03:00
var accounts = [
{
2016-06-20 23:11:04 +03:00
name: 'All Messages',
accountId: null,
unreadCount: 65,
folders: [
{ name: 'Unread', icon: 'mail_unread', unreadCount: 65 },
{ name: 'Received', icon: 'mail_received', unreadCount: 65 },
{ name: 'Pinned', icon: 'mail_pinned' },
{ name: 'Outbox', icon: 'mail_outbox' },
{ name: 'Sent', icon: 'mail_sent' },
{ name: 'Drafts', icon: 'mail_drafts', unreadCount: 507 },
{ name: 'Spam', icon: 'mail_spam' },
{ name: 'Trash', icon: 'mail_trash', unreadCount: 423 },
],
},
{
2016-06-20 23:11:04 +03:00
name: 'vitalif@mail.ru',
accountId: 1,
unreadCount: 48,
warning: true,
folders: [
{ name: 'Unread', icon: 'mail_unread', unreadCount: 48 },
{ name: 'INBOX', icon: 'folder', unreadCount: 48 },
{ name: 'TODO', icon: 'folder' },
{ name: 'Архив', icon: 'folder' },
{ name: 'Корзина', icon: 'mail_trash' },
{ name: 'Отправленные', icon: 'mail_sent' },
{ name: 'Спам', icon: 'mail_spam' },
{ name: 'Черновики', icon: 'mail_drafts' },
{ name: 'Pinned', icon: 'mail_pinned' },
],
},
{
2016-06-20 23:11:04 +03:00
name: 'vitalif@yourcmc.ru',
accountId: 2,
unreadCount: 16,
loading: true,
folders: [
{ name: 'Unread', icon: 'mail_unread', unreadCount: 16 },
{ name: 'Drafts', icon: 'mail_drafts' },
{ name: 'HAM', icon: 'folder' },
{ name: 'INBOX', icon: 'folder', unreadCount: 16 },
{ name: 'intermedia_su', icon: 'folder' },
{ name: 'Sent', icon: 'mail_sent' },
{ name: 'SPAM', icon: 'mail_spam' },
{ name: 'TRASH', icon: 'mail_trash' },
{ name: 'Pinned', icon: 'mail_pinned' },
],
}
2016-06-20 23:11:04 +03:00
];
2016-06-20 23:11:04 +03:00
var composeAccounts = [
{ name: 'Виталий Филиппов', email: 'vitalif@mail.ru' },
{ name: 'Vitaliy Filippov', email: 'vitalif@yourcmc.ru' }
];
2016-06-24 15:43:44 +03:00
var msg2 = [];
msg2[5] = {
subject: 'кошку хочешь?))',
sent: true,
from: 'Виталий Филиппов',
fromEmail: 'vitalif@mail.ru',
to: 'Margarita Philippova',
time: '2016-06-14 21:24',
body: 'к нам тут пришла какая-то и к нам в дом рвётся, симпатишная) потеряшка какая-то, но явно домашняя, не боится людей))',
thread: [ {
subject: 'Re: кошку хочешь?))',
from: 'Margarita Philippova',
fromEmail: 'philippova.marga@gmail.com',
to: 'Виталий Филиппов',
time: '2016-06-14 22:31',
level: 1,
body: 'Нет, конечно, не хочу. Я уеду в Баку, а кошка как? Уеду в Астану - а кошка как? Наташа меня Сухорукова приглашает в Барселону - а кошку куда? Я только испытала радость облегчения от того, что у меня больше нет горшочных цветов - и кошку мне? НЕт! Папику предложи - у него уже много зверья, одной кошкой больше - одной меньше - какая разница?'
} ]
};
2016-06-20 23:11:04 +03:00
var listGroups = [ {
name: 'TODAY',
2016-06-24 15:43:44 +03:00
messageCount: 10,
2016-06-23 01:17:22 +03:00
messages: [ {
subject: 'The Glossary of Happiness By Emily Anthes , May 12, 2016',
from: 'Margarita Philippova',
fromEmail: 'philippova.marga@gmail.com',
to: 'Виталий Филиппов',
time: '2016-06-20 00:11:18',
unread: true,
body: '<pre><a href="http://www.newyorker.com/tech/elements/the-glossary-of-happiness">http://www.newyorker.com/tech/elements/the-glossary-of-happiness</a><br />\
<br />\
--<br />\
Best regards,<br />\
Margarita Philippova</pre>'
} ]
}, {
name: 'LAST WEEK',
2016-06-24 15:43:44 +03:00
messageCount: 15,
messages: msg2
2016-06-20 23:11:04 +03:00
} ];
2016-06-22 00:14:07 +03:00
var AllTabs = React.createClass({
componentDidMount: function()
{
Store.on('layout', this.setLayout);
},
componentWillUnmount: function()
{
Store.un('layout', this.setLayout);
2016-06-22 00:14:07 +03:00
},
setLayout: function()
{
var oldLayout = this.state.layout;
2016-06-22 00:14:07 +03:00
this.setState({ layout: Store.layout });
window.scrollBy(0, 0);
var self = this;
requestAnimationFrame(function()
{
var btn;
if (oldLayout == 'message-on-right' &&
Store.layout != 'message-on-right')
{
btn = self.refs.list.refs.setBtn;
}
else if (oldLayout != 'message-on-right' &&
Store.layout == 'message-on-right')
{
btn = self.refs.view.refs.setBtn;
}
if (btn)
{
btn.setState({ pressed: false });
btn.toggle();
}
});
2016-06-22 00:14:07 +03:00
},
getInitialState: function()
{
return { layout: Store.layout };
},
render: function()
{
return React.createElement(TabPanel, { tabs: [
{
className: this.state.layout,
noclose: true,
icon: 'mail_unread',
title: 'Unread (64)',
children: [ <MessageList ref="list" groups={listGroups} />, <MessageView ref="view" /> ]
2016-06-22 00:14:07 +03:00
},
{
icon: 'mail_drafts',
i16: true,
title: 'Compose Message',
children: <ComposeWindow accounts={composeAccounts} />
}
] });
}
});
2016-06-20 23:11:04 +03:00
ReactDOM.render(
<div>
{dropdown_account}
{dropdown_reply}
{dropdown_forward}
{dropdown_delete}
{dropdown_check_send}
{dropdown_threads}
{dropdown_list_sort}
{dropdown_settings}
<FolderList accounts={accounts} progress="33" />
2016-06-22 00:14:07 +03:00
<AllTabs />
2016-06-20 23:11:04 +03:00
</div>,
document.body
);