Update to ES6 and Webpack

master
Vitaliy Filippov 2018-12-02 19:27:22 +03:00
parent dbe8af8a69
commit 16ec9ec90b
30 changed files with 667 additions and 593 deletions

View File

@ -1,15 +1,4 @@
{ {
"plugins": [ "presets": [ "env", "stage-1", "react" ],
"check-es2015-constants",
"transform-es2015-arrow-functions",
"transform-es2015-block-scoping",
"transform-es2015-classes",
"transform-es2015-for-of",
"transform-es2015-computed-properties",
"transform-es2015-destructuring",
"transform-es2015-shorthand-properties",
"transform-object-rest-spread",
"transform-react-jsx",
],
"retainLines": true "retainLines": true
} }

45
.eslintrc.js Normal file
View File

@ -0,0 +1,45 @@
module.exports = {
"parser": "babel-eslint",
"env": {
"es6": true,
"browser": true
},
"globals": {
"APPCFG": false
},
"extends": [
"eslint:recommended",
"plugin:react/recommended"
],
"parserOptions": {
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
}
},
"plugins": [
"react"
],
"rules": {
"indent": [
"error",
4
],
"linebreak-style": [
"error",
"unix"
],
"semi": [
"error",
"always"
],
"no-control-regex": [
"off"
],
"no-empty": [
"off"
]
}
};

View File

@ -1,8 +1,16 @@
const React = require('react'); import React from 'react';
const DropDownBase = require('./DropDownBase.js'); import DropDownBase from './DropDownBase.js';
var AccountFolders = module.exports = React.createClass({ export default class AccountFolders extends React.PureComponent
render: function() {
state = {
collapsed: this.props.collapsed,
animating: false,
h: null,
cfgPressed: false,
}
render()
{ {
return <div className="account"> return <div className="account">
<div className={"account-header"+(this.state.collapsed ? ' collapsed' : '')} onClick={this.onClick}> <div className={"account-header"+(this.state.collapsed ? ' collapsed' : '')} onClick={this.onClick}>
@ -28,8 +36,9 @@ var AccountFolders = module.exports = React.createClass({
</div> </div>
</div> </div>
</div> </div>
}, }
selectFolder: function(ev)
selectFolder = (ev) =>
{ {
var t = ev.target; var t = ev.target;
while (t && !t.getAttribute('data-i') && t != this.refs.vis) while (t && !t.getAttribute('data-i') && t != this.refs.vis)
@ -40,8 +49,9 @@ var AccountFolders = module.exports = React.createClass({
this.props.onSelect(this.props.accountIndex, i); this.props.onSelect(this.props.accountIndex, i);
} }
// FIXME: send select event + switch focus to message list if folder changed // FIXME: send select event + switch focus to message list if folder changed
}, }
showCfg: function(ev)
showCfg = (ev) =>
{ {
var self = this; var self = this;
var i = DropDownBase.instances.account.state.items; var i = DropDownBase.instances.account.state.items;
@ -53,12 +63,9 @@ var AccountFolders = module.exports = React.createClass({
}); });
self.setState({ cfgPressed: true }); self.setState({ cfgPressed: true });
ev.stopPropagation(); ev.stopPropagation();
}, }
getInitialState: function()
{ onClick = () =>
return { collapsed: this.props.collapsed, animating: false, h: null, cfgPressed: false };
},
onClick: function()
{ {
var self = this; var self = this;
if (this.state.animating) if (this.state.animating)
@ -76,4 +83,4 @@ var AccountFolders = module.exports = React.createClass({
self.setState({ collapsed: !self.state.collapsed, animating: false, h: null }); self.setState({ collapsed: !self.state.collapsed, animating: false, h: null });
}, this.state.collapsed ? 200 : 250); }, this.state.collapsed ? 200 : 250);
} }
}); }

View File

@ -1,11 +1,12 @@
const React = require('react'); import React from 'react';
const DropDownMenu = require('./DropDownMenu.js'); import DropDownMenu from './DropDownMenu.js';
const ListSortSettingsWindow = require('./ListSortSettingsWindow.js'); import ListSortSettingsWindow from './ListSortSettingsWindow.js';
const MailSettingsWindow = require('./MailSettingsWindow.js'); import MailSettingsWindow from './MailSettingsWindow.js';
var dropdown_account = React.createElement( var dropdown_account = React.createElement(
DropDownMenu, { DropDownMenu, {
id: 'account', id: 'account',
key: 'account',
items: [ { items: [ {
icon: 'mail_unread', icon: 'mail_unread',
i16: true, i16: true,
@ -23,6 +24,7 @@ var dropdown_account = React.createElement(
var dropdown_reply = React.createElement( var dropdown_reply = React.createElement(
DropDownMenu, { DropDownMenu, {
id: 'reply', id: 'reply',
key: 'reply',
items: [ { items: [ {
hotkey: 'R', hotkey: 'R',
icon: 'mail_reply', icon: 'mail_reply',
@ -41,6 +43,7 @@ var dropdown_reply = React.createElement(
var dropdown_forward = React.createElement( var dropdown_forward = React.createElement(
DropDownMenu, { DropDownMenu, {
id: 'forward', id: 'forward',
key: 'forward',
items: [ { items: [ {
hotkey: 'F', hotkey: 'F',
icon: 'mail_forward', icon: 'mail_forward',
@ -55,6 +58,7 @@ var dropdown_forward = React.createElement(
var dropdown_delete = React.createElement( var dropdown_delete = React.createElement(
DropDownMenu, { DropDownMenu, {
id: 'delete', id: 'delete',
key: 'delete',
items: [ { items: [ {
text: 'Move to Trash' text: 'Move to Trash'
}, { }, {
@ -67,6 +71,7 @@ var dropdown_delete = React.createElement(
var dropdown_check_send = React.createElement( var dropdown_check_send = React.createElement(
DropDownMenu, { DropDownMenu, {
id: 'check-send', id: 'check-send',
key: 'check-send',
items: [ { items: [ {
hotkey: 'Ctrl-K', hotkey: 'Ctrl-K',
icon: 'mail_check', icon: 'mail_check',
@ -90,6 +95,7 @@ var dropdown_check_send = React.createElement(
var dropdown_threads = React.createElement( var dropdown_threads = React.createElement(
DropDownMenu, { DropDownMenu, {
id: 'threads', id: 'threads',
key: 'threads',
items: [ { items: [ {
icon: 'thread', icon: 'thread',
text: 'Show Message Thread' text: 'Show Message Thread'
@ -111,6 +117,7 @@ var dropdown_threads = React.createElement(
var dropdown_list_sort = React.createElement( var dropdown_list_sort = React.createElement(
ListSortSettingsWindow, { ListSortSettingsWindow, {
id: 'list-sort', id: 'list-sort',
key: 'list-sort',
window: true, window: true,
folder: 'INBOX', folder: 'INBOX',
override: false, override: false,
@ -134,9 +141,7 @@ var dropdown_list_sort = React.createElement(
} }
); );
var dropdown_settings = MailSettingsWindow; export default function()
module.exports = function()
{ {
return [ return [
dropdown_account, dropdown_account,
@ -146,6 +151,6 @@ module.exports = function()
dropdown_check_send, dropdown_check_send,
dropdown_threads, dropdown_threads,
dropdown_list_sort, dropdown_list_sort,
dropdown_settings <MailSettingsWindow key="mail-settings" />,
]; ];
} }

View File

@ -1,17 +1,17 @@
const React = require('react'); import React from 'react';
const ListWithSelection = require('./ListWithSelection.js');
const Util = require('./Util.js');
var AttachList = module.exports = React.createClass({ import ListWithSelection from './ListWithSelection.js';
mixins: [ ListWithSelection ], import Util from './Util.js';
getInitialState: function()
export default class AttachList extends ListWithSelection
{ {
return { state = {
...this.state,
attachments: [], attachments: [],
attachScroll: 0 attachScroll: 0
}; }
},
addAttachments: function(ev) addAttachments = (ev) =>
{ {
var a = this.state.attachments; var a = this.state.attachments;
if (ev.target.files) if (ev.target.files)
@ -20,36 +20,43 @@ var AttachList = module.exports = React.createClass({
this.setState({ attachments: a }); this.setState({ attachments: a });
// reset file input // reset file input
ev.target.innerHTML = ev.target.innerHTML; ev.target.innerHTML = ev.target.innerHTML;
}, }
scrollAttachList: function(ev)
scrollAttachList = (ev) =>
{ {
this.setState({ attachScroll: ev.target.scrollTop }); this.setState({ attachScroll: ev.target.scrollTop });
}, }
deleteSelected: function()
deleteSelected = () =>
{ {
for (var i = this.state.attachments.length-1; i >= 0; i--) for (var i = this.state.attachments.length-1; i >= 0; i--)
if (this.state.selected[i]) if (this.state.selected[i])
this.state.attachments.splice(i, 1); this.state.attachments.splice(i, 1);
this.setState({ attachments: this.state.attachments }); this.setState({ attachments: this.state.attachments });
}, }
getTotalItems: function()
getTotalItems = () =>
{ {
return this.state.attachments.length; return this.state.attachments.length;
}, }
getPageSize: function()
getPageSize = () =>
{ {
return this.refs.a0 ? Math.round(this.refs.scroll.offsetHeight / this.refs.a0.offsetHeight) : 1; return this.refs.a0 ? Math.round(this.refs.scroll.offsetHeight / this.refs.a0.offsetHeight) : 1;
}, }
getItemOffset: function(index)
getItemOffset = (index) =>
{ {
var item = this.refs['a'+index]; var item = this.refs['a'+index];
return [ item.offsetTop, item.offsetHeight ]; return [ item.offsetTop, item.offsetHeight ];
}, }
getScrollPaddingTop: function()
getScrollPaddingTop = () =>
{ {
return this.refs.title.offsetHeight; return this.refs.title.offsetHeight;
}, }
render: function()
render()
{ {
return <div className="attach"> return <div className="attach">
<div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}> <div className="no-attach" style={this.state.attachments.length ? { display: 'none' } : null}>
@ -74,4 +81,4 @@ var AttachList = module.exports = React.createClass({
</div> </div>
</div> </div>
} }
}); }

View File

@ -1,21 +1,21 @@
const React = require('react'); import React from 'react';
const AttachList = require('./AttachList.js'); import AttachList from './AttachList.js';
const StoreListener = require('./StoreListener.js'); import StoreListener from './StoreListener.js';
var ComposeWindow = React.createClass({ class ComposeWindow extends React.PureComponent
getInitialState: function()
{ {
return { state = {
text: '' text: ''
}; }
},
changeText: function(ev) changeText = (ev) =>
{ {
this.setState({ text: ev.target.value }); this.setState({ text: ev.target.value });
}, }
render: function()
render()
{ {
return <div className="compose"> return (<div className="compose">
<div className="actions"> <div className="actions">
<a className="button"><img src="icons/mail_send.png" />Send</a> <a className="button"><img src="icons/mail_send.png" />Send</a>
<a className="button"><img src="icons/delete.png" /></a> <a className="button"><img src="icons/delete.png" /></a>
@ -56,8 +56,8 @@ var ComposeWindow = React.createClass({
<textarea onChange={this.changeText} defaultValue={this.state.text}></textarea> <textarea onChange={this.changeText} defaultValue={this.state.text}></textarea>
</div> </div>
</div> </div>
</div> </div>);
}
} }
});
module.exports = StoreListener(ComposeWindow, (data) => { return { accounts: data.accounts }; }); export default StoreListener(ComposeWindow, (data) => { return { accounts: data.accounts }; });

View File

@ -1,35 +1,13 @@
function getOffset(elem) import React from 'react';
{
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 DropDownBase = module.exports = { export default class DropDownBase extends React.PureComponent
instances: {}, {
currentVisible: null, static instances = {};
componentDidMount: function() static currentVisible = null;
state = { visible: false, top: 0, left: 0, calloutLeft: null, selectedItem: -1 };
componentDidMount()
{ {
if (!DropDownBase.setBodyListener) if (!DropDownBase.setBodyListener)
{ {
@ -38,36 +16,38 @@ var DropDownBase = module.exports = {
DropDownBase.setBodyListener = true; DropDownBase.setBodyListener = true;
} }
DropDownBase.instances[this.props.id] = this; DropDownBase.instances[this.props.id] = this;
}, }
hideAll: function()
static hideAll()
{ {
for (var i in DropDownBase.instances) for (var i in DropDownBase.instances)
DropDownBase.instances[i].hide(); DropDownBase.instances[i].hide();
}, }
repositionCurrent: function()
static repositionCurrent()
{ {
if (DropDownBase.currentVisible) if (DropDownBase.currentVisible)
DropDownBase.currentVisible[0].showAt(DropDownBase.currentVisible[1], DropDownBase.currentVisible[0].onClose); DropDownBase.currentVisible[0].showAt(DropDownBase.currentVisible[1], DropDownBase.currentVisible[0].onClose);
}, }
componentWillUnmount: function()
componentWillUnmount()
{ {
delete DropDownBase.instances[this.props.id]; delete DropDownBase.instances[this.props.id];
if (DropDownBase.currentVisible[0] == this) if (DropDownBase.currentVisible[0] == this)
DropDownBase.currentVisible = null; DropDownBase.currentVisible = null;
}, }
getInitialState: function()
{ onClick = (ev) =>
return { visible: false, top: 0, left: 0, calloutLeft: null, selectedItem: -1 };
},
onClick: function(ev)
{ {
ev.stopPropagation(); ev.stopPropagation();
}, }
isVisible: function()
isVisible = () =>
{ {
return this.state.visible; return this.state.visible;
}, }
hide: function()
hide = () =>
{ {
this.setState({ visible: false }); this.setState({ visible: false });
DropDownBase.currentVisible = null; DropDownBase.currentVisible = null;
@ -76,8 +56,9 @@ var DropDownBase = module.exports = {
this.onClose(); this.onClose();
delete this.onClose; delete this.onClose;
} }
}, }
showAt: function(el, onClose)
showAt = (el, onClose) =>
{ {
if (this.onClose && this.onClose != onClose) if (this.onClose && this.onClose != onClose)
{ {
@ -108,4 +89,32 @@ var DropDownBase = module.exports = {
this.refs.dd.focus(); this.refs.dd.focus();
this.onClose = onClose; this.onClose = onClose;
} }
}; }
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 };
}
}

View File

@ -1,16 +1,21 @@
const React = require('react'); import React from 'react';
const DropDownBase = require('./DropDownBase.js');
var DropDownButton = module.exports = React.createClass({ import DropDownBase from './DropDownBase.js';
componentDidUpdate: function(prevProps, prevState)
export default class DropDownButton extends React.PureComponent
{
state = { pressed: false, checked: false }
componentDidUpdate(prevProps, prevState)
{ {
if (prevProps.hidden && !this.props.hidden && if (prevProps.hidden && !this.props.hidden &&
DropDownBase.instances[this.props.dropdownId].isVisible()) DropDownBase.instances[this.props.dropdownId].isVisible())
{ {
DropDownBase.instances[this.props.dropdownId].showAt(this.refs.btn, this.unpress); DropDownBase.instances[this.props.dropdownId].showAt(this.refs.btn, this.unpress);
} }
}, }
render: function()
render()
{ {
return <a ref="btn" title={(this.state.checked ? this.props.checkedTitle : null) || this.props.title} onClick={this.onClickButton} 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 ' : '')+ className={'button '+(this.props.dropdownId ? 'show-dropdown ' : '')+(this.state.checked ? 'checked ' : '')+
@ -20,12 +25,9 @@ var DropDownButton = module.exports = React.createClass({
{this.state.checked && this.props.checkedText || this.props.text || null} {this.state.checked && this.props.checkedText || this.props.text || null}
{this.props.dropdownId ? <span className="down" onClick={this.onClickDown}></span> : null} {this.props.dropdownId ? <span className="down" onClick={this.onClickDown}></span> : null}
</a> </a>
}, }
getInitialState: function()
{ toggle = () =>
return { pressed: false, checked: false };
},
toggle: function()
{ {
if (!this.state.pressed) if (!this.state.pressed)
{ {
@ -35,12 +37,14 @@ var DropDownButton = module.exports = React.createClass({
else else
DropDownBase.instances[this.props.dropdownId].hide(); DropDownBase.instances[this.props.dropdownId].hide();
this.setState({ pressed: !this.state.pressed }); this.setState({ pressed: !this.state.pressed });
}, }
unpress: function()
unpress = () =>
{ {
this.setState({ pressed: false }); this.setState({ pressed: false });
}, }
onClickButton: function(ev)
onClickButton = (ev) =>
{ {
if (this.props.whole || this.props.checkable && this.state.pressed) if (this.props.whole || this.props.checkable && this.state.pressed)
this.toggle(); this.toggle();
@ -53,10 +57,11 @@ var DropDownButton = module.exports = React.createClass({
else if (this.props.onClick) else if (this.props.onClick)
this.props.onClick(); this.props.onClick();
ev.stopPropagation(); ev.stopPropagation();
}, }
onClickDown: function(ev)
onClickDown = (ev) =>
{ {
this.toggle(); this.toggle();
ev.stopPropagation(); ev.stopPropagation();
} }
}); }

View File

@ -1,13 +1,12 @@
const React = require('react'); import React from 'react';
const DropDownBase = require('./DropDownBase.js');
var DropDownMenu = module.exports = React.createClass({ import DropDownBase from './DropDownBase.js';
mixins: [ DropDownBase ],
getInitialState: function() export default class DropDownMenu extends DropDownBase
{ {
return { items: this.props.items }; state = { items: this.props.items }
},
render: function() render()
{ {
var sel = this.state.selectedItem; var sel = this.state.selectedItem;
return <div ref="dd" className={'dropdown'+(this.state.visible ? ' visible' : '')} id={'dropdown-'+this.props.id} return <div ref="dd" className={'dropdown'+(this.state.visible ? ' visible' : '')} id={'dropdown-'+this.props.id}
@ -24,16 +23,18 @@ var DropDownMenu = module.exports = React.createClass({
); );
})} })}
</div> </div>
}, }
onMouseOver: function(ev)
onMouseOver = (ev) =>
{ {
var t = ev.target; var t = ev.target;
while ((t && t != this.refs.dd) && (!t.className || t.className.substr(0, 4) != 'item')) while ((t && t != this.refs.dd) && (!t.className || t.className.substr(0, 4) != 'item'))
t = t.parentNode; t = t.parentNode;
if (t && t != this.refs.dd) if (t && t != this.refs.dd)
this.setState({ selectedItem: parseInt(t.getAttribute('data-index')) }); this.setState({ selectedItem: parseInt(t.getAttribute('data-index')) });
}, }
onKeyDown: function(ev)
onKeyDown = (ev) =>
{ {
if (ev.keyCode == 40 || ev.keyCode == 38) if (ev.keyCode == 40 || ev.keyCode == 38)
{ {
@ -50,14 +51,16 @@ var DropDownMenu = module.exports = React.createClass({
this.clickItem(); this.clickItem();
ev.preventDefault(); ev.preventDefault();
ev.stopPropagation(); ev.stopPropagation();
}, }
clickItem: function(ev)
clickItem = (ev) =>
{ {
}, }
myOnClick: function(ev)
myOnClick = (ev) =>
{ {
if (ev.target.getAttribute('data-index')) if (ev.target.getAttribute('data-index'))
this.clickItem(); this.clickItem();
this.onClick(ev); this.onClick(ev);
} }
}); }

View File

@ -1,15 +1,18 @@
const React = require('react'); import React from 'react';
const AccountFolders = require('./AccountFolders.js'); import AccountFolders from './AccountFolders.js';
const DropDownButton = require('./DropDownButton.js'); import DropDownButton from './DropDownButton.js';
const Store = require('./Store.js'); import Store from './Store.js';
const StoreListener = require('./StoreListenerClass.js'); import StoreListener from './StoreListener.js';
const MailProgress = require('./MailProgress.js'); import MailProgress from './MailProgress.js';
var FolderList = React.createClass({ class FolderList extends React.PureComponent
render: function() {
state = { selectedAccount: -1, selectedFolder: -1 }
render()
{ {
var self = this; var self = this;
return <div className={"folder-list"+(self.props.progressText ? ' progress-visible' : '')}> return (<div className={"folder-list"+(self.props.progressText ? ' progress-visible' : '')}>
<div className="top-border-gradient"></div> <div className="top-border-gradient"></div>
<div className="bottom-border-gradient"></div> <div className="bottom-border-gradient"></div>
<div className="actions"> <div className="actions">
@ -24,13 +27,15 @@ var FolderList = React.createClass({
})} })}
</div> </div>
<MailProgress /> <MailProgress />
</div> </div>)
}, }
onClickCheckSend: function()
onClickCheckSend = () =>
{ {
Store.startResync(); Store.startResync();
}, }
onSelectFolder: function(accIndex, folderIndex)
onSelectFolder = (accIndex, folderIndex) =>
{ {
var acc = this.props.accounts[accIndex]; var acc = this.props.accounts[accIndex];
var folder = this.props.accounts[accIndex].folders[folderIndex]; var folder = this.props.accounts[accIndex].folders[folderIndex];
@ -42,11 +47,7 @@ var FolderList = React.createClass({
Store.loadFolder({ accountId: acc.accountId, folderType: folder.type }); Store.loadFolder({ accountId: acc.accountId, folderType: folder.type });
} }
this.setState({ selectedAccount: accIndex, selectedFolder: folderIndex }); this.setState({ selectedAccount: accIndex, selectedFolder: folderIndex });
},
getInitialState: function()
{
return { selectedAccount: -1, selectedFolder: -1 };
} }
}); }
module.exports = StoreListener(FolderList, (data) => { return { accounts: data.accounts, progressText: data.progressText }; }); export default StoreListener(FolderList, (data) => { return { accounts: data.accounts, progressText: data.progressText }; });

View File

@ -1,4 +1,4 @@
module.exports = function(msg) export default function(msg)
{ {
return msg; return msg;
} }

View File

@ -1,7 +1,8 @@
const React = require('react'); import React from 'react';
var ListSortSettings = module.exports = React.createClass({ export default class ListSortSettings extends React.PureComponent
render: function() {
render()
{ {
return <div className={this.props.className} value={this.props.sort.sortby}> return <div className={this.props.className} value={this.props.sort.sortby}>
<select className="sortby"> <select className="sortby">
@ -19,4 +20,4 @@ var ListSortSettings = module.exports = React.createClass({
<label><input type="checkbox" checked={this.props.sort.threaded} /> Threaded</label> <label><input type="checkbox" checked={this.props.sort.threaded} /> Threaded</label>
</div> </div>
} }
}); }

View File

@ -1,10 +1,13 @@
const React = require('react'); import React from 'react';
const DropDownBase = require('./DropDownBase.js');
const ListSortSettings = require('./ListSortSettings.js');
var ListSortSettingsWindow = module.exports = React.createClass({ import DropDownBase from './DropDownBase.js';
mixins: [ DropDownBase ], import ListSortSettings from './ListSortSettings.js';
render: function()
export default class ListSortSettingsWindow extends DropDownBase
{
state = { checksVisible: false }
render()
{ {
var sort = this.props.override ? this.props.sorting : this.props.defaultSorting; 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' : '')} return <div ref="dd" onClick={this.onClick} className={'dropdown window list-sort'+(this.state.visible ? ' visible' : '')}
@ -25,13 +28,10 @@ var ListSortSettingsWindow = module.exports = React.createClass({
<label><input type="checkbox" checked={this.props.show.dups ? "checked" : null} /> Show Duplicates</label> <label><input type="checkbox" checked={this.props.show.dups ? "checked" : null} /> Show Duplicates</label>
</div> </div>
</div> </div>
}, }
getInitialState: function()
{ expandChecks = () =>
return { checksVisible: false };
},
expandChecks: function()
{ {
this.setState({ checksVisible: !this.state.checksVisible }); this.setState({ checksVisible: !this.state.checksVisible });
} }
}); }

View File

@ -1,18 +1,24 @@
// Common selection mixin import React from 'react';
var ListWithSelection = module.exports = {
// requires to override methods: this.deleteSelected(), this.getPageSize(), this.getItemOffset(index), this.getTotalItems() // Common "list with selection" component
getInitialState: function()
export default class ListWithSelection extends React.PureComponent
{ {
return { // requires to override methods: this.deleteSelected(), this.getPageSize(), this.getItemOffset(index), this.getTotalItems()
selected: {} constructor(props)
}; {
}, super(props);
isSelected: function(i) this.state = this.state||{};
this.state.selected = {};
}
isSelected(i)
{ {
return this.state.selected[i] || this.state.selected.begin !== undefined && return this.state.selected[i] || this.state.selected.begin !== undefined &&
this.state.selected.begin <= i && this.state.selected.end >= i; this.state.selected.begin <= i && this.state.selected.end >= i;
}, }
onListKeyDown: function(ev)
onListKeyDown = (ev) =>
{ {
if (!this.getTotalItems()) if (!this.getTotalItems())
return; return;
@ -74,8 +80,9 @@ var ListWithSelection = module.exports = {
else else
this.selectOne(nsel); this.selectOne(nsel);
} }
}, }
selectTo: function(ns)
selectTo(ns)
{ {
if (this.lastSel === undefined) if (this.lastSel === undefined)
return this.selectOne(ns); return this.selectOne(ns);
@ -93,8 +100,9 @@ var ListWithSelection = module.exports = {
this.curSel = ns; this.curSel = ns;
if (this.onSelectCurrent) if (this.onSelectCurrent)
this.onSelectCurrent(ns); this.onSelectCurrent(ns);
}, }
selectOne: function(ns)
selectOne(ns)
{ {
var sel = {}; var sel = {};
sel[ns] = true; sel[ns] = true;
@ -103,8 +111,9 @@ var ListWithSelection = module.exports = {
this.curSel = ns; this.curSel = ns;
if (this.onSelectCurrent) if (this.onSelectCurrent)
this.onSelectCurrent(ns); this.onSelectCurrent(ns);
}, }
onListItemClick: function(ev)
onListItemClick = (ev) =>
{ {
var t = ev.target; var t = ev.target;
while (t && !t.getAttribute('data-i')) while (t && !t.getAttribute('data-i'))
@ -127,4 +136,4 @@ var ListWithSelection = module.exports = {
this.selectOne(ns); this.selectOne(ns);
} }
} }
}; }

View File

@ -1,7 +1,7 @@
const StoreListener = require('./StoreListenerClass.js'); import StoreListener from './StoreListener.js';
const ProgressBar = require('./ProgressBar.js'); import ProgressBar from './ProgressBar.js';
module.exports = StoreListener(ProgressBar, function(data) export default StoreListener(ProgressBar, function(data)
{ {
return { text: data.progressText, progress: data.progressPercent }; return { text: data.progressText, progress: data.progressPercent };
}); });

View File

@ -1,12 +1,12 @@
const React = require('react'); import React from 'react';
const DropDownBase = require('./DropDownBase.js'); import DropDownBase from './DropDownBase.js';
const ListSortSettings = require('./ListSortSettings.js'); import ListSortSettings from './ListSortSettings.js';
const Store = require('./Store.js'); import Store from './Store.js';
const StoreListener = require('./StoreListener.js'); import StoreListener from './StoreListener.js';
var MailSettingsWindow = React.createClass({ class MailSettingsWindow extends DropDownBase
mixins: [ DropDownBase ], {
render: function() render()
{ {
return <div ref="dd" onClick={this.onClick} className={'dropdown window'+(this.state.visible ? ' visible' : '')} 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 }}> id={'dropdown-'+this.props.id} tabIndex="1" style={{ top: this.state.top, left: this.state.left }}>
@ -35,20 +35,22 @@ var MailSettingsWindow = React.createClass({
</select> </select>
</div> </div>
</div> </div>
}, }
switchLayout: function(ev)
switchLayout = (ev) =>
{ {
var t = ev.target.nodeName == 'A' ? ev.target : ev.target.parentNode; var t = ev.target.nodeName == 'A' ? ev.target : ev.target.parentNode;
var l = / mail-(\S+)/.exec(t.className)[1]; var l = / mail-(\S+)/.exec(t.className)[1];
Store.set('layout', l); Store.set('layout', l);
}, }
showQuickReply: function()
showQuickReply = () =>
{ {
Store.set('quickReply', !this.props.quickReply); Store.set('quickReply', !this.props.quickReply);
} }
}); }
module.exports = StoreListener( export default StoreListener(
MailSettingsWindow, MailSettingsWindow,
(data) => { return { layout: data.layout, quickReply: data.quickReply }; }, (data) => { return { layout: data.layout, quickReply: data.quickReply }; },
{ {

View File

@ -1,13 +1,15 @@
const React = require('react'); import React from 'react';
const DropDownButton = require('./DropDownButton.js'); import DropDownButton from './DropDownButton.js';
const ListWithSelection = require('./ListWithSelection.js'); import ListWithSelection from './ListWithSelection.js';
const Store = require('./Store.js'); import Store from './Store.js';
const StoreListener = require('./StoreListener.js'); import StoreListener from './StoreListener.js';
const Util = require('./Util.js'); import Util from './Util.js';
var MessageInList = React.createClass({ class MessageInList extends React.PureComponent
msgClasses: { unread: 'unread', unseen: 'unseen', answered: 'replied', flagged: 'pinned', sent: 'sent' }, {
render: function() msgClasses = { unread: 'unread', unseen: 'unseen', answered: 'replied', flagged: 'pinned', sent: 'sent' }
render()
{ {
var msg = this.props.msg; var msg = this.props.msg;
return <div data-i={this.props.i} className={'message'+ return <div data-i={this.props.i} className={'message'+
@ -29,26 +31,26 @@ var MessageInList = React.createClass({
</div> </div>
} }
}); }
// TODO: expand/collapse days // TODO: expand/collapse days
var MessageList = React.createClass({ class MessageList extends ListWithSelection
mixins: [ ListWithSelection ],
_preloadSize: 20,
_pageSize: 50,
getInitialState: function()
{ {
return { _preloadSize = 20
_pageSize = 50
state = {
...this.state,
firstDayTop: 0, firstDayTop: 0,
firstDay: this.props.groups && this.props.groups[0] && this.props.groups[0].name || null firstDay: this.props.groups && this.props.groups[0] && this.props.groups[0].name || null
}; }
},
componentWillReceiveProps: function(nextProps) componentWillReceiveProps(nextProps)
{ {
this.setFirstDayFromProps(nextProps); this.setFirstDayFromProps(nextProps);
}, }
// Main virtual scroll detector method // Main virtual scroll detector method
setFirstDayFromProps: function(props) setFirstDayFromProps(props)
{ {
var groups = props.groups; var groups = props.groups;
var messages = props.messages; var messages = props.messages;
@ -118,16 +120,19 @@ var MessageList = React.createClass({
Store.loadMessages(loadFirst, loadFirstEnd-loadFirst); Store.loadMessages(loadFirst, loadFirstEnd-loadFirst);
if (loadFirstEnd < loadLastStart && loadLastStart < loadLast) if (loadFirstEnd < loadLastStart && loadLastStart < loadLast)
Store.loadMessages(loadLastStart, loadLast-loadLastStart); Store.loadMessages(loadLastStart, loadLast-loadLastStart);
}, }
changeFirstDay: function(ev)
changeFirstDay = (ev) =>
{ {
this.setFirstDayFromProps(this.props); this.setFirstDayFromProps(this.props);
}, }
deleteSelected: function()
deleteSelected = () =>
{ {
}, }
onSelectCurrent: function(index)
onSelectCurrent = (index) =>
{ {
var self = this; var self = this;
var total = 0, p, msg, idx; var total = 0, p, msg, idx;
@ -155,8 +160,9 @@ var MessageList = React.createClass({
break; break;
} }
} }
}, }
getTotalItems: function()
getTotalItems = () =>
{ {
var total = -1; // do not count first-day as item var total = -1; // do not count first-day as item
for (var i = 0; i < (this.props.groups||[]).length; i++) for (var i = 0; i < (this.props.groups||[]).length; i++)
@ -164,12 +170,14 @@ var MessageList = React.createClass({
total += 1+this.props.groups[i].messageCount; total += 1+this.props.groups[i].messageCount;
} }
return total; return total;
}, }
getPageSize: function()
getPageSize = () =>
{ {
return Math.floor(this.refs.scroll.offsetHeight / (this.props.layout == 'message-on-right' ? 60 : 30)); return Math.floor(this.refs.scroll.offsetHeight / (this.props.layout == 'message-on-right' ? 60 : 30));
}, }
getItemOffset: function(index)
getItemOffset = (index) =>
{ {
var n = 0, top = 0, p; var n = 0, top = 0, p;
var h = (this.props.layout == 'message-on-right' ? 60 : 30); var h = (this.props.layout == 'message-on-right' ? 60 : 30);
@ -185,28 +193,32 @@ var MessageList = React.createClass({
top += (i > 0 ? 30 : 0) + h*this.props.groups[i].messageCount; top += (i > 0 ? 30 : 0) + h*this.props.groups[i].messageCount;
} }
return [ top, index == p && i > 0 ? 30 : h ]; return [ top, index == p && i > 0 ? 30 : h ];
}, }
getScrollPaddingTop: function()
getScrollPaddingTop = () =>
{ {
return this.refs.title.offsetHeight; return this.refs.title.offsetHeight;
}, }
getMessages: function(grp, start, end)
getMessages = (grp, start, end) =>
{ {
var a = this.props.messages.slice(grp.start+start, grp.start+end); var a = this.props.messages.slice(grp.start+start, grp.start+end);
for (var i = 0; i < end-start; i++) for (var i = 0; i < end-start; i++)
if (!a[i]) if (!a[i])
a[i] = null; a[i] = null;
return a; return a;
}, }
onSearchTextChange: function(event)
onSearchTextChange = (event) =>
{ {
var s = event.target.value; var s = event.target.value;
this.setState({ searchText: s }); this.setState({ searchText: s });
if (this._searchTimeout) if (this._searchTimeout)
clearTimeout(this._searchTimeout); clearTimeout(this._searchTimeout);
this._searchTimeout = setTimeout(function() { Store.search(s) }, 300); this._searchTimeout = setTimeout(function() { Store.search(s) }, 300);
}, }
render: function()
render()
{ {
var self = this; var self = this;
var total = 0; var total = 0;
@ -270,18 +282,20 @@ var MessageList = React.createClass({
})} })}
</div> </div>
</div> </div>
}, }
componentDidMount: function()
componentDidMount()
{ {
window.addEventListener('resize', this.changeFirstDay); window.addEventListener('resize', this.changeFirstDay);
}, }
componentWillUnmount: function()
componentWillUnmount()
{ {
window.removeEventListener('resize', this.changeFirstDay); window.removeEventListener('resize', this.changeFirstDay);
} }
}); }
module.exports = StoreListener(MessageList, function(data) export default StoreListener(MessageList, function(data)
{ {
return { return {
threads: data.threads, threads: data.threads,

View File

@ -1,11 +1,14 @@
const React = require('react'); import React from 'react';
const DropDownButton = require('./DropDownButton.js'); import DropDownButton from './DropDownButton.js';
const Store = require('./Store.js'); import Store from './Store.js';
const StoreListener = require('./StoreListener.js'); import StoreListener from './StoreListener.js';
const Util = require('./Util.js'); import Util from './Util.js';
var MessageView = React.createClass({ class MessageView extends React.PureComponent
formatLongDate: function(dt) {
state = { showImages: false }
formatLongDate(dt)
{ {
if (!(dt instanceof Date)) if (!(dt instanceof Date))
dt = new Date(dt.replace(' ', 'T')); dt = new Date(dt.replace(' ', 'T'));
@ -14,8 +17,9 @@ var MessageView = React.createClass({
var s = dt.getSeconds(); var s = dt.getSeconds();
return Util.WeekDays[dt.getDay()]+' '+dt.getDate()+' '+Util.Months[dt.getMonth()]+' '+dt.getFullYear()+' '+(h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m+':'+(s < 10 ? '0' : '')+s return Util.WeekDays[dt.getDay()]+' '+dt.getDate()+' '+Util.Months[dt.getMonth()]+' '+dt.getFullYear()+' '+(h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m+':'+(s < 10 ? '0' : '')+s
//return dt.toLocaleString(); //return dt.toLocaleString();
}, }
componentWillReceiveProps: function(nextProps)
componentWillReceiveProps(nextProps)
{ {
if (nextProps.msg != this.props.msg) if (nextProps.msg != this.props.msg)
{ {
@ -26,16 +30,14 @@ var MessageView = React.createClass({
} }
this.setState(ns); this.setState(ns);
} }
}, }
showImages: function()
showImages = () =>
{ {
this.setState({ showImages: true }); this.setState({ showImages: true });
}, }
getInitialState: function()
{ render()
return { showImages: false };
},
render: function()
{ {
var showImages = this.state.showImages; var showImages = this.state.showImages;
var msg = this.props.msg; var msg = this.props.msg;
@ -148,6 +150,6 @@ var MessageView = React.createClass({
] : null} ] : null}
</div> </div>
} }
}); }
module.exports = StoreListener(MessageView, (data) => { return { layout: data.layout, quickReply: data.quickReply, msg: data.msg }; }); export default StoreListener(MessageView, (data) => { return { layout: data.layout, quickReply: data.quickReply, msg: data.msg }; });

View File

@ -1,7 +1,10 @@
const React = require('react'); import React from 'react';
var ProgressBar = module.exports = React.createClass({ export default class ProgressBar extends React.PureComponent
render: function() {
state = { width: '' }
render()
{ {
return <div className="progress-bar" ref="pbar" style={{ display: this.props.text ? '' : 'none' }}> return <div className="progress-bar" ref="pbar" style={{ display: this.props.text ? '' : 'none' }}>
<div className="pending" style={{ width: this.state.width }}>{this.props.text} ({this.props.progress||0}%)</div> <div className="pending" style={{ width: this.state.width }}>{this.props.text} ({this.props.progress||0}%)</div>
@ -9,29 +12,29 @@ var ProgressBar = module.exports = React.createClass({
<div className="done" ref="pdone" style={{ width: this.state.width }}>{this.props.text} ({this.props.progress||0}%)</div> <div className="done" ref="pdone" style={{ width: this.state.width }}>{this.props.text} ({this.props.progress||0}%)</div>
</div> </div>
</div> </div>
}, }
componentDidUpdate: function(prevProps, prevState)
componentDidUpdate(prevProps, prevState)
{ {
if (!prevState.width) if (!prevState.width)
{ {
setTimeout(this.onResize, 50); setTimeout(this.onResize, 50);
} }
}, }
getInitialState: function()
{ onResize = () =>
return { width: '' };
},
onResize: function()
{ {
this.setState({ width: this.refs.pbar.offsetWidth }); this.setState({ width: this.refs.pbar.offsetWidth });
}, }
componentDidMount: function()
componentDidMount()
{ {
window.addEventListener('resize', this.onResize); window.addEventListener('resize', this.onResize);
this.onResize(); this.onResize();
}, }
componentWillUnmount: function()
componentWillUnmount()
{ {
window.removeEventListener('resize', this.onResize); window.removeEventListener('resize', this.onResize);
} }
}); }

View File

@ -1,10 +1,10 @@
const superagent = require('superagent'); import superagent from 'superagent';
const socket_io = require('socket.io-client'); import socket_io from 'socket.io-client';
const _ = require('./I18n.js'); import _ from './I18n.js';
const Util = require('./Util.js'); import Util from './Util.js';
var Store = module.exports = { const Store = {
data: { data: {
layout: 'message-on-right', layout: 'message-on-right',
quickReply: true, quickReply: true,
@ -195,3 +195,5 @@ var Store = module.exports = {
}; };
Store.startIo(); Store.startIo();
export default Store;

View File

@ -1,19 +1,23 @@
const React = require('react'); import React from 'react';
const Store = require('./Store.js');
import Store from './Store.js';
// "react-redux connect()"-like example // "react-redux connect()"-like example
var StoreListener = React.createClass({ class StoreListener extends React.PureComponent
componentDidMount: function() {
componentDidMount()
{ {
Store.on(this.update); Store.on(this.update);
}, }
componentWillUnmount: function()
componentWillUnmount()
{ {
Store.un(this.update); Store.un(this.update);
}, }
update: function()
update = () =>
{ {
var newState = this.props.mapStateToProps(Store.data); var newState = this.mapStateToProps(Store.data);
for (var i in newState) for (var i in newState)
{ {
if (this.state[i] != newState[i]) if (this.state[i] != newState[i])
@ -22,18 +26,28 @@ var StoreListener = React.createClass({
return; return;
} }
} }
},
getInitialState: function()
{
return { ...this.props.initial, ...this.props.mapStateToProps(Store.data) };
},
render: function()
{
return React.createElement(this.props.wrappedComponent, this.state);
} }
});
module.exports = function(component, map, initial) render()
{ {
return React.createElement(StoreListener, { wrappedComponent: component, mapStateToProps: map, initial: initial||{} }); var props = { ...this.initial, ...this.props, ...this.state };
return React.createElement(this.wrappedComponent, props);
}
}
export default function(component, map, initial)
{
var cl = class extends StoreListener
{
constructor(props, context, updater)
{
super(props, context, updater);
this.wrappedComponent = component;
this.mapStateToProps = map;
this.initial = initial;
this.state = map(Store.data);
this.update = this.update.bind(this);
}
};
return cl;
}; };

View File

@ -1,52 +0,0 @@
const React = require('react');
const Store = require('./Store.js');
// "react-redux connect()"-like example
class StoreListener extends React.Component
{
componentDidMount()
{
Store.on(this.update);
}
componentWillUnmount()
{
Store.un(this.update);
}
update()
{
var newState = this.mapStateToProps(Store.data);
for (var i in newState)
{
if (this.state[i] != newState[i])
{
this.setState(newState);
return;
}
}
}
render()
{
var props = { ...this.initial, ...this.props, ...this.state };
return React.createElement(this.wrappedComponent, props);
}
}
module.exports = function(component, map, initial)
{
var cl = class extends StoreListener
{
constructor(props, context, updater)
{
super(props, context, updater);
this.wrappedComponent = component;
this.mapStateToProps = map;
this.initial = initial;
this.state = map(Store.data);
this.update = this.update.bind(this);
}
};
return cl;
};

View File

@ -1,50 +0,0 @@
// НЕ РАБОТАЕТ!
const React = require('react');
const Store = require('./Store.js');
// "react-redux connect()"-like example
var StoreListener = React.createClass({
componentDidMount: function()
{
Store.on(this.update);
},
componentWillUnmount: function()
{
Store.un(this.update);
},
update: function()
{
var newState = this.mapStateToProps(Store.data);
for (var i in newState)
{
if (this.state[i] != newState[i])
{
this.setState(newState);
return;
}
}
},
getInitialState: function()
{
return { ...this.initial, ...this.mapStateToProps(Store.data) };
},
render: function()
{
var props = { ...this.initial, ...this.props, ...this.state };
return React.createElement(this.wrappedComponent, props);
}
});
module.exports = function(component, map, initial)
{
var fn = function(props, context, updater)
{
StoreListener.call(this, props, context, updater);
this.wrappedComponent = component;
this.mapStateToProps = map;
this.initial = initial||{};
};
fn.prototype = Object.create(StoreListener);
fn.prototype.constructor = fn;
return fn;
};

View File

@ -1,7 +1,10 @@
const React = require('react'); import React from 'react';
var TabPanel = module.exports = React.createClass({ export default class TabPanel extends React.PureComponent
render: function() {
state = { selected: 0, tabs: this.props.tabs }
render()
{ {
var bar = []; var bar = [];
var body = []; var body = [];
@ -26,21 +29,20 @@ var TabPanel = module.exports = React.createClass({
<div className="tab-bar">{bar}</div> <div className="tab-bar">{bar}</div>
{body} {body}
</div> </div>
}, }
componentWillReceiveProps: function(nextProps, nextContent)
componentWillReceiveProps(nextProps, nextContent)
{ {
// FIXME: Do not own tabs? // FIXME: Do not own tabs?
this.setState({ selected: this.state.selected % nextProps.tabs.length, tabs: nextProps.tabs }); this.setState({ selected: this.state.selected % nextProps.tabs.length, tabs: nextProps.tabs });
}, }
getInitialState: function()
{ switchTab = (ev) =>
return { selected: 0, tabs: this.props.tabs };
},
switchTab: function(ev)
{ {
this.setState({ selected: ev.target.id.substr(5) }); this.setState({ selected: ev.target.id.substr(5) });
}, }
closeTab: function(ev)
closeTab = (ev) =>
{ {
var self = this; var self = this;
var tab = ev.target.parentNode; var tab = ev.target.parentNode;
@ -59,4 +61,4 @@ var TabPanel = module.exports = React.createClass({
}, 200); }, 200);
ev.stopPropagation(); ev.stopPropagation();
} }
}); }

13
Util.js
View File

@ -1,7 +1,9 @@
var WeekDays = module.exports.WeekDays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ]; export default class Util
var Months = module.exports.Months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; {
static WeekDays = [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ];
static Months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
module.exports.formatBytes = function(s) static formatBytes(s)
{ {
if (!s) return ''; if (!s) return '';
if (s < 1024) return s+' B'; if (s < 1024) return s+' B';
@ -10,7 +12,7 @@ module.exports.formatBytes = function(s)
return (Math.round(s*10/1024/1024/1024)/10)+' GB'; return (Math.round(s*10/1024/1024/1024)/10)+' GB';
} }
module.exports.formatDate = function(dt) static formatDate(dt)
{ {
if (!(dt instanceof Date)) if (!(dt instanceof Date))
dt = new Date(dt.replace(' ', 'T')); dt = new Date(dt.replace(' ', 'T'));
@ -36,7 +38,7 @@ module.exports.formatDate = function(dt)
return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m; return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m;
} }
module.exports.getGroupName = function(k) static getGroupName(k)
{ {
if (k == 't') if (k == 't')
{ {
@ -56,3 +58,4 @@ module.exports.getGroupName = function(k)
} }
return k; return k;
} }
}

View File

@ -17,6 +17,15 @@ html, body
user-select: none; user-select: none;
} }
#app
{
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
.clear .clear
{ {
clear: both; clear: both;

28
mail.js
View File

@ -1,13 +1,13 @@
const React = require('react'); import React from 'react';
const ReactDOM = require('react-dom'); import ReactDOM from 'react-dom';
const ComposeWindow = require('./ComposeWindow.js'); import ComposeWindow from './ComposeWindow.js';
const FolderList = require('./FolderList.js'); import FolderList from './FolderList.js';
const MessageList = require('./MessageList.js'); import MessageList from './MessageList.js';
const MessageView = require('./MessageView.js'); import MessageView from './MessageView.js';
const TabPanel = require('./TabPanel.js'); import TabPanel from './TabPanel.js';
const Store = require('./Store.js'); import Store from './Store.js';
const StoreListener = require('./StoreListener.js'); import StoreListener from './StoreListener.js';
const AllDropdowns = require('./AllDropdowns.js'); import AllDropdowns from './AllDropdowns.js';
window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame; window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame;
@ -19,13 +19,13 @@ var AllTabs = StoreListener(TabPanel, function(data)
noclose: true, noclose: true,
icon: 'mail_unread', icon: 'mail_unread',
title: 'Unread (64)', title: 'Unread (64)',
children: [ MessageList, MessageView ] children: [ <MessageList key="1" />, <MessageView key="2" /> ]
}, },
{ {
icon: 'mail_drafts', icon: 'mail_drafts',
i16: true, i16: true,
title: 'Compose Message', title: 'Compose Message',
children: [ ComposeWindow ] children: [ <ComposeWindow key="1" /> ]
} }
] } ] }
}); });
@ -34,9 +34,9 @@ ReactDOM.render(
<div> <div>
{AllDropdowns()} {AllDropdowns()}
<FolderList /> <FolderList />
{AllTabs} <AllTabs />
</div>, </div>,
document.body document.getElementById('app')
); );
Store.loadAccounts(); Store.loadAccounts();

View File

@ -5,13 +5,7 @@
<link rel="stylesheet" type="text/css" href="mail.css" /> <link rel="stylesheet" type="text/css" href="mail.css" />
</head> </head>
<body> <body>
<!-- <script src="https://fb.me/react-15.0.1.js"></script> <div id="app"></div>
<script src="https://fb.me/react-dom-15.1.0.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.34/browser.js"></script>-->
<!-- <script src="react-15.0.1.js"></script>
<script src="react-dom-15.1.0.js"></script>
<script src="browser.js"></script>
<script type="text/babel" src="mail.js"></script>-->
<script type="text/javascript" src="mail.c.js"></script> <script type="text/javascript" src="mail.c.js"></script>
</body> </body>
</html> </html>

View File

@ -6,33 +6,29 @@
"url": "http://yourcmc.ru/wiki/" "url": "http://yourcmc.ru/wiki/"
}, },
"description": "LikeOperaMail", "description": "LikeOperaMail",
"dependencies": { "dependencies": {},
},
"devDependencies": { "devDependencies": {
"browserify": "latest", "babel-core": "^6.26.0",
"babelify": "latest", "babel-eslint": "^8.2.2",
"watchify": "latest", "babel-loader": "^7.1.5",
"babel-plugin-check-es2015-constants": "latest", "babel-polyfill": "^6.26.0",
"babel-plugin-transform-es2015-arrow-functions": "latest", "babel-preset-env": "^1.6.1",
"babel-plugin-transform-es2015-block-scoping": "latest", "babel-preset-react": "^6.24.1",
"babel-plugin-transform-es2015-classes": "latest", "babel-preset-stage-1": "^6.24.1",
"babel-plugin-transform-es2015-computed-properties": "latest",
"babel-plugin-transform-es2015-for-of": "latest",
"babel-plugin-transform-es2015-destructuring": "latest",
"babel-plugin-transform-es2015-shorthand-properties": "latest",
"babel-plugin-transform-object-rest-spread": "latest",
"babel-plugin-transform-react-jsx": "latest",
"react": "latest",
"react-dom": "latest",
"uglifyjs": "latest",
"uglifyify": "latest",
"eslint": "latest", "eslint": "latest",
"eslint-plugin-react": "^7.7.0",
"react": "^16.2.0",
"react-dom": "^16.2.0",
"superagent": "latest", "superagent": "latest",
"socket.io-client": "latest" "socket.io-client": "latest",
"webpack": "^3.12.0",
"webpack-bundle-analyzer": "^2.13.1"
}, },
"scripts": { "scripts": {
"compile": "browserify -t babelify -t uglifyify mail.js | uglifyjs -cm > mail.c.js", "lint": "eslint *.js",
"watch-dev": "watchify -t babelify mail.js -o mail.c.js", "compile": "webpack --optimize-minimize",
"watch": "watchify -t babelify -t uglifyify mail.js -o 'uglifyjs -cm > mail.c.js'" "stats": "NODE_ENV=production webpack --optimize-minimize --profile --json > stats.json; webpack-bundle-analyzer stats.json -h 0.0.0.0",
"watch-dev": "NODE_ENV=development webpack -w",
"watch": "NODE_ENV=production webpack -w --optimize-minimize"
} }
} }

54
webpack.config.js Normal file
View File

@ -0,0 +1,54 @@
const webpack = require('webpack');
const path = require('path');
module.exports = {
entry: {
main: [ "babel-polyfill", './mail.js' ]
},
context: __dirname,
output: {
path: __dirname,
filename: './mail.c.js'
},
devtool: 'cheap-module-source-map',
module: {
rules: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/
},
{
test: /\.css$/,
use: [
{
loader: "style-loader",
options: {
singleton: true
}
},
{
loader: "css-loader",
options: {
modules: true, // default is false
sourceMap: true,
importLoaders: 1,
localIdentName: "[name]--[local]--[hash:base64:8]"
}
}
]
}
]
},
plugins: [
new webpack.DefinePlugin({
"process.env": {
NODE_ENV: JSON.stringify(process.env.NODE_ENV || "production")
}
})
],
performance: {
maxEntrypointSize: 5000000,
maxAssetSize: 5000000
}
};