1060 lines
39 KiB
JavaScript
1060 lines
39 KiB
JavaScript
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 };
|
|
}
|
|
}
|
|
|
|
var Store = {
|
|
layout: 'message-on-right',
|
|
listeners: {},
|
|
on: function(ev, cb)
|
|
{
|
|
this.listeners[ev] = this.listeners[ev] || [];
|
|
this.listeners[ev].push(cb);
|
|
},
|
|
setLayout: function(l)
|
|
{
|
|
this.layout = l;
|
|
(this.listeners['setLayout'] || []).map(i => i());
|
|
}
|
|
};
|
|
|
|
var DropDownBase = {
|
|
instances: {},
|
|
componentDidMount: function()
|
|
{
|
|
if (!DropDownBase.setBodyListener)
|
|
{
|
|
window.addEventListener('click', DropDownBase.hideAll);
|
|
DropDownBase.setBodyListener = true;
|
|
}
|
|
DropDownBase.instances[this.props.id] = this;
|
|
},
|
|
hideAll: function()
|
|
{
|
|
for (var i in DropDownBase.instances)
|
|
DropDownBase.instances[i].hide();
|
|
},
|
|
componentWillUnmount: function()
|
|
{
|
|
delete DropDownBase.instances[this.props.id];
|
|
},
|
|
getInitialState: function()
|
|
{
|
|
return { visible: false, top: 0, left: 0, calloutLeft: null, selectedItem: -1 };
|
|
},
|
|
onClick: function(ev)
|
|
{
|
|
ev.stopPropagation();
|
|
},
|
|
hide: function()
|
|
{
|
|
this.setState({ visible: false });
|
|
if (this.onClose)
|
|
{
|
|
this.onClose();
|
|
delete this.onClose;
|
|
}
|
|
},
|
|
showAt: function(el, onClose)
|
|
{
|
|
if (this.onClose)
|
|
{
|
|
this.onClose();
|
|
delete this.onClose;
|
|
}
|
|
// TODO: move on window resize
|
|
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.setState({ visible: true, top: top, left: left, calloutLeft: calloutLeft });
|
|
this.refs.dd.focus();
|
|
this.onClose = onClose;
|
|
}
|
|
};
|
|
|
|
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
|
|
? <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' : ''))}>
|
|
{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({
|
|
statics: {
|
|
instances: [],
|
|
setBodyListener: false,
|
|
hideAll: function(ev)
|
|
{
|
|
for (var i = 0; i < DropDownButton.instances.length; i++)
|
|
DropDownButton.instances[i].setState({ pressed: false });
|
|
}
|
|
},
|
|
componentDidMount: function()
|
|
{
|
|
if (!DropDownButton.setBodyListener)
|
|
{
|
|
window.addEventListener('click', DropDownButton.hideAll);
|
|
DropDownButton.setBodyListener = true;
|
|
}
|
|
DropDownButton.instances.push(this);
|
|
},
|
|
componentWillUnmount: function()
|
|
{
|
|
for (var i = DropDownButton.instances.length-1; i >= 0; i--)
|
|
if (DropDownButton.instances[i] == this)
|
|
DropDownButton.instances.splice(i, 1);
|
|
},
|
|
render: function()
|
|
{
|
|
return <a ref="btn" title={(this.state.checked ? this.props.checkedTitle : null) || this.props.title} onClick={this.onClickButton}
|
|
className={'button '+(this.props.dropdownId ? 'show-dropdown ' : '')+(this.state.checked ? 'checked ' : '')+
|
|
(this.state.pressed ? 'pressed ' : '')+(this.props.className || '')}>
|
|
{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}
|
|
{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)
|
|
{
|
|
DropDownBase.hideAll();
|
|
DropDownBase.instances[this.props.dropdownId].showAt(this.refs.btn);
|
|
}
|
|
else
|
|
DropDownBase.instances[this.props.dropdownId].hide();
|
|
this.setState({ pressed: !this.state.pressed });
|
|
},
|
|
onClickButton: function(ev)
|
|
{
|
|
if (this.props.whole || this.props.checkable && this.state.pressed)
|
|
this.toggle();
|
|
if (this.props.checkable)
|
|
this.setState({ checked: !this.state.checked });
|
|
ev.stopPropagation();
|
|
},
|
|
onClickDown: function(ev)
|
|
{
|
|
this.toggle();
|
|
ev.stopPropagation();
|
|
}
|
|
});
|
|
|
|
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...'
|
|
} ]
|
|
}
|
|
);
|
|
|
|
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'
|
|
} ]
|
|
}
|
|
);
|
|
|
|
var dropdown_forward = React.createElement(
|
|
DropDownMenu, {
|
|
id: 'forward',
|
|
items: [ {
|
|
hotkey: 'F',
|
|
icon: 'mail_forward',
|
|
text: 'Reply'
|
|
}, {
|
|
hotkey: 'D',
|
|
text: 'Redirect'
|
|
} ]
|
|
}
|
|
);
|
|
|
|
var dropdown_delete = React.createElement(
|
|
DropDownMenu, {
|
|
id: 'delete',
|
|
items: [ {
|
|
text: 'Move to Trash'
|
|
}, {
|
|
icon: 'delete',
|
|
text: 'Delete Permanently'
|
|
} ]
|
|
}
|
|
);
|
|
|
|
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'
|
|
} ]
|
|
}
|
|
);
|
|
|
|
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'
|
|
} ]
|
|
}
|
|
);
|
|
|
|
var ListSortSettings = React.createClass({
|
|
render: function()
|
|
{
|
|
var self = this;
|
|
return <div className={this.props.className}>
|
|
<select className="sortby">
|
|
{['sent date', 'status', 'label', 'size', 'subject'].map(function(i) {
|
|
return <option key={'s'+i} selected={self.props.sort.sortby == i ? 'selected' : null}>Sort by {i}</option>
|
|
})}
|
|
</select>
|
|
<select className="group">
|
|
<option selected={!this.props.sort.group ? 'selected' : null}>Do not group</option>
|
|
<option selected={this.props.sort.group == 'read' ? 'selected' : null}>Group by read status</option>
|
|
<option selected={this.props.sort.group == 'pinned' ? 'selected' : null}>Group by pinned status</option>
|
|
<option selected={this.props.sort.group == 'date' ? 'selected' : null}>Group by date</option>
|
|
</select>
|
|
<label><input type="checkbox" checked={this.props.sort.ascending ? "checked" : null} /> Sort ascending</label>
|
|
<label><input type="checkbox" checked={this.props.sort.threaded ? "checked" : null} /> Threaded</label>
|
|
</div>
|
|
}
|
|
});
|
|
|
|
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>
|
|
<div className="title">Sorting for {this.props.folder}</div>
|
|
<label><input type="checkbox" checked={this.props.override ? "checked" : null} /> Override default sorting</label>
|
|
<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>
|
|
<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 });
|
|
}
|
|
});
|
|
|
|
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>
|
|
<div className="text">Mail Layout</div>
|
|
<div className="layouts">
|
|
<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>
|
|
<div className="split"><i></i></div>
|
|
<div className="text">Default List Sorting</div>
|
|
<ListSortSettings className="fields" sort={this.props.defaultSorting} />
|
|
<div className="fields">
|
|
<label><input type="checkbox" checked={this.state.showQuickReply} onClick={this.showQuickReply} /> Show Quick Reply</label>
|
|
</div>
|
|
<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>
|
|
},
|
|
componentDidMount: function()
|
|
{
|
|
Store.on('setLayout', this.setLayout);
|
|
},
|
|
setLayout: function()
|
|
{
|
|
this.setState({ layout: Store.layout });
|
|
},
|
|
switchLayout: function(ev)
|
|
{
|
|
var t = ev.target.nodeName == 'A' ? ev.target : ev.target.parentNode;
|
|
var l = / mail-(\S+)/.exec(t.className)[1];
|
|
Store.setLayout(l);
|
|
},
|
|
getInitialState: function()
|
|
{
|
|
return { showQuickReply: this.props.showQuickReply && true, layout: Store.layout };
|
|
},
|
|
showQuickReply: function()
|
|
{
|
|
this.setState({ showQuickReply: !this.state.showQuickReply });
|
|
}
|
|
});
|
|
|
|
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
|
|
}
|
|
}
|
|
);
|
|
|
|
var dropdown_settings = React.createElement(
|
|
MailSettingsWindow, {
|
|
id: 'settings',
|
|
window: true,
|
|
showQuickReply: true,
|
|
markDelay: -1,
|
|
defaultSorting: {
|
|
sort: {
|
|
sortby: 'sent date',
|
|
group: 'date',
|
|
ascending: false,
|
|
threaded: false
|
|
}
|
|
}
|
|
}
|
|
);
|
|
|
|
var AccountFolders = React.createClass({
|
|
render: function()
|
|
{
|
|
return <div className="account">
|
|
<div className={"account-header"+(this.state.collapsed ? ' collapsed' : '')} onClick={this.onClick}>
|
|
{this.props.name}
|
|
{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>
|
|
] : null}
|
|
<div key="n" className="msg-count">{this.props.unreadCount}</div>
|
|
</div>
|
|
<div className={"account-folders"+(this.state.collapsed ? ' collapsed' : '')} style={{ height: this.state.h }}>
|
|
<div ref="vis" className={'visible-part'+(this.state.animating ? ' animating' : '')}>
|
|
{this.props.folders.map((f, i) =>
|
|
<div key={'f'+i} className={'folder'+(f.unreadCount > 0 ? ' with-unread' : '')+(f.selected ? ' selected' : '')}>
|
|
{f.icon ? <img src={'icons/'+f.icon+'.png'} /> : null}
|
|
{' '}
|
|
<span>{f.name}</span>
|
|
{f.unreadCount > 0 ? <div className="msg-count">{f.unreadCount}</div> : null}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
},
|
|
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 };
|
|
},
|
|
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);
|
|
}
|
|
});
|
|
|
|
var FolderList = React.createClass({
|
|
render: function()
|
|
{
|
|
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>
|
|
<div className="listview">
|
|
{this.props.accounts.map(function(account) {
|
|
return <AccountFolders key={'a'+account.accountId} {...account} />
|
|
})}
|
|
</div>
|
|
<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>
|
|
},
|
|
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);
|
|
}
|
|
});
|
|
|
|
var TabPanel = React.createClass({
|
|
render: function()
|
|
{
|
|
var bar = [];
|
|
var body = [];
|
|
for (var i = 0; i < this.state.tabs.length; i++)
|
|
{
|
|
var t = this.state.tabs[i];
|
|
bar.push(
|
|
<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(
|
|
<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>
|
|
);
|
|
}
|
|
return <div className="tab-panel">
|
|
<div className="tab-bar">{bar}</div>
|
|
{body}
|
|
</div>
|
|
},
|
|
componentWillReceiveProps: function(nextProps, nextContent)
|
|
{
|
|
this.setState({ selected: this.state.selected % nextProps.tabs.length, tabs: nextProps.tabs });
|
|
},
|
|
getInitialState: function()
|
|
{
|
|
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();
|
|
}
|
|
});
|
|
|
|
function formatBytes(s)
|
|
{
|
|
if (!s) return '';
|
|
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);
|
|
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)
|
|
{
|
|
var d = dt.getDate();
|
|
var m = dt.getMonth()+1;
|
|
return (d < 10 ? '0' : '')+d+' '+(m < 10 ? '0' : '')+m+' '.dt.getFullYear();
|
|
}
|
|
else if (dt.getTime() < tod.getTime())
|
|
{
|
|
var wd = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
|
|
var mon = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
|
|
return wd[dt.getDay()]+' '+dt.getDate()+' '+mon[dt.getMonth()];
|
|
}
|
|
var h = dt.getHours();
|
|
var m = dt.getMinutes();
|
|
return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m;
|
|
}
|
|
|
|
var ComposeWindow = React.createClass({
|
|
getInitialState: function()
|
|
{
|
|
return {
|
|
attachments: [],
|
|
text: '',
|
|
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 });
|
|
},
|
|
changeText: function(ev)
|
|
{
|
|
this.setState({ text: ev.target.value });
|
|
},
|
|
render: function()
|
|
{
|
|
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>
|
|
<div className="flex">
|
|
<div className="headers">
|
|
<div className="headers-table">
|
|
<div className="header">
|
|
<div className="field">From</div>
|
|
<div className="value">
|
|
<select>
|
|
{this.props.accounts.map((a, i) =>
|
|
<option key={'a'+i}>{'"'+a.name+'" <'+a.email+'>'}</option>
|
|
)}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="header">
|
|
<div className="field">To</div>
|
|
<div className="value"><input /></div>
|
|
</div>
|
|
<div className="header">
|
|
<div className="field">Cc</div>
|
|
<div className="value"><input /></div>
|
|
</div>
|
|
<div className="header">
|
|
<div className="field">Bcc</div>
|
|
<div className="value"><input /></div>
|
|
</div>
|
|
<div className="header">
|
|
<div className="field">Subject</div>
|
|
<div className="value"><input /></div>
|
|
</div>
|
|
</div>
|
|
<div className="attach">
|
|
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
|
|
<input type="file" multiple="multiple" onChange={this.addAttachments} />
|
|
</div>
|
|
<div className="attach-list" tabIndex="1" onScroll={this.scrollAttachList}
|
|
style={this.state.attachments.length ? null : { display: 'none' }}>
|
|
<div 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 key={'a'+i} className={'attachment'+(a.selected ? ' selected' : '')}>
|
|
<div className="name">{a.name}</div>
|
|
<div className="size">{formatBytes(a.size)}</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="text">
|
|
<textarea onChange={this.changeText} defaultValue={this.state.text}></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
}
|
|
});
|
|
|
|
var MessageInList = React.createClass({
|
|
msgClasses: [ 'unread', 'unseen', 'replied', 'pinned', 'sent', 'unloaded', 'selected' ],
|
|
render: function()
|
|
{
|
|
var msg = this.props.msg;
|
|
return <div className={'message'+(this.msgClasses.map(c => (msg[c] ? ' '+c : '')).join(''))+(msg.thread ? ' thread0' : '')}>
|
|
<div className="icon" style={{ width: (20+10*(msg.level||0)), backgroundPosition: (10*(msg.level||0))+'px 7px' }}></div>
|
|
<div className="subject">{msg.subject}</div>
|
|
{msg.thread ? <div className={'expand'+(msg.collapsed ? '' : ' collapse')}></div> : null}
|
|
<div className="bullet"></div>
|
|
<div className="from" style={{ left: (21+10*(msg.level||0)) }}>{(msg.sent || msg.outgoing ? 'To '+msg.to : msg.from)}</div>
|
|
<div className="size">{formatBytes(msg.size)}</div>
|
|
<div className="attach" style={msg.attach ? null : { display: 'none' }}></div>
|
|
<div className="time">{formatDate(msg.date)}</div>
|
|
</div>
|
|
}
|
|
});
|
|
|
|
var MessageList = React.createClass({
|
|
getInitialState: function()
|
|
{
|
|
return { firstDayTop: 0 };
|
|
},
|
|
changeFirstDay: function(ev)
|
|
{
|
|
this.setState({ firstDayTop: ev.target.scrollTop });
|
|
},
|
|
render: function()
|
|
{
|
|
return <div className="message-list">
|
|
<div className="top-border-gradient"></div>
|
|
<div className="bottom-border-gradient"></div>
|
|
<div className="actions">
|
|
<div className="searchbox">
|
|
<input type="text" placeholder="Search mail" />
|
|
</div>
|
|
<DropDownButton dropdownId="threads" className="threads"
|
|
title="Show Message Thread" checkedTitle="Hide Message Thread" icon="thread" checkedIcon="thread_selected" checkable="1" />
|
|
<DropDownButton 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" />
|
|
<div className="clear"></div>
|
|
</div>
|
|
<div className="listview" tabIndex="1" onScroll={this.changeFirstDay}>
|
|
<div className="day first-day" style={{ top: this.state.firstDayTop }}>{this.props.groups[0].name}</div>
|
|
{this.props.groups.map((grp, i) => [
|
|
i > 0 ? <div className="day">{grp.name}</div> : null,
|
|
<div className="day-list">
|
|
{grp.messages.map(msg => [
|
|
<MessageInList msg={msg} />,
|
|
(msg.thread ?
|
|
<div className="thread">
|
|
{msg.thread.map(reply => <MessageInList msg={reply} />)}
|
|
</div>
|
|
: null)
|
|
])}
|
|
</div>
|
|
])}
|
|
</div>
|
|
</div>
|
|
}
|
|
});
|
|
|
|
var MessageView = React.createClass({
|
|
formatLongTime: function(d)
|
|
{
|
|
//Чт 09 июн 2016 21:10:50
|
|
},
|
|
render: function()
|
|
{
|
|
var msg = this.props.msg;
|
|
return <div className={'message-view'+(!msg ? ' no-mail-shown' : '')}>
|
|
<div className="actions">
|
|
<DropDownButton dropdownId="reply" icon="mail_reply" text="Reply" />
|
|
<a className="button"><img src="icons/mail_reply_all.png" />Reply All</a>
|
|
<DropDownButton dropdownId="forward" icon="mail_forward" text="Forward" />
|
|
<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" />
|
|
<a className="button show-dropdown"><img src="icons/label.png" />Label <span className="down"></span></a>
|
|
<DropDownButton dropdownId="settings" whole="1" icon="config" text="Settings" />
|
|
</div>
|
|
<div className="nomail">
|
|
<div className="flex1"></div>
|
|
<div className="middle">
|
|
<img src="icons/no_mail.png" />
|
|
<div className="txt">No message selected</div>
|
|
</div>
|
|
<div className="flex1"></div>
|
|
</div>
|
|
{msg ? [
|
|
<div className="headers">
|
|
<div className="top">
|
|
<div className="pin"></div>
|
|
<div className="time">{this.formatLongTime(msg.time)}</div>
|
|
<div className="subject">{msg.subject}</div>
|
|
</div>
|
|
<div className="header-table">
|
|
<div className="header">
|
|
<div className="field">From</div>
|
|
<div className="value"><a className="button">{msg.from}</a></div>
|
|
</div>
|
|
<div className="header">
|
|
<div className="field">To</div>
|
|
<div className="value"><a className="button">{msg.to}</a></div>
|
|
</div>
|
|
{msg.cc ?
|
|
<div className="header">
|
|
<div className="field">Cc</div>
|
|
<div className="value">
|
|
{msg.cc.map(cc => <a className="button">{cc}</a>)}
|
|
</div>
|
|
</div>
|
|
: null}
|
|
{msg.replyto ?
|
|
<div className="header">
|
|
<div className="field">Reply-to</div>
|
|
<div className="value"><a className="button">{msg.replyto}</a></div>
|
|
</div>
|
|
: null}
|
|
</div>
|
|
</div>,
|
|
(msg.blockedImages ?
|
|
<div className="blocked-images">
|
|
<img src="icons/block.png" /> This message contains blocked images.
|
|
<a className="button raised">Load Images</a>
|
|
<label><input type="checkbox" /> Always load from {msg.from}</label>
|
|
</div>
|
|
: null),
|
|
<div className="text" dangerouslySetInnerHTML={msg.body}></div>,
|
|
<div className="quick-reply">
|
|
<textarea></textarea>
|
|
<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>
|
|
</div>
|
|
</div>
|
|
] : null}
|
|
</div>
|
|
}
|
|
});
|
|
|
|
var accounts = [
|
|
{
|
|
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 },
|
|
],
|
|
},
|
|
{
|
|
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' },
|
|
],
|
|
},
|
|
{
|
|
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' },
|
|
],
|
|
}
|
|
];
|
|
|
|
var composeAccounts = [
|
|
{ name: 'Виталий Филиппов', email: 'vitalif@mail.ru' },
|
|
{ name: 'Vitaliy Filippov', email: 'vitalif@yourcmc.ru' }
|
|
];
|
|
|
|
var listGroups = [ {
|
|
name: 'TODAY',
|
|
messages: [ {
|
|
subject: 'кошку хочешь?))',
|
|
sent: true,
|
|
to: 'Margarita Philippova',
|
|
date: '2016-06-14 21:24',
|
|
thread: [ {
|
|
subject: 'Re: кошку хочешь?))',
|
|
from: 'Margarita Philippova',
|
|
date: '2016-06-14 22:31',
|
|
level: 1
|
|
} ]
|
|
} ]
|
|
} ];
|
|
|
|
var AllTabs = React.createClass({
|
|
componentDidMount: function()
|
|
{
|
|
Store.on('setLayout', this.setLayout);
|
|
},
|
|
setLayout: function()
|
|
{
|
|
this.setState({ layout: Store.layout });
|
|
},
|
|
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 groups={listGroups} />, <MessageView /> ]
|
|
},
|
|
{
|
|
icon: 'mail_drafts',
|
|
i16: true,
|
|
title: 'Compose Message',
|
|
children: <ComposeWindow accounts={composeAccounts} />
|
|
}
|
|
] });
|
|
}
|
|
});
|
|
|
|
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" />
|
|
<AllTabs />
|
|
</div>,
|
|
document.body
|
|
);
|