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

View File

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

View File

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

View File

@ -1,63 +1,63 @@
const React = require('react');
const AttachList = require('./AttachList.js');
const StoreListener = require('./StoreListener.js');
import React from 'react';
import AttachList from './AttachList.js';
import StoreListener from './StoreListener.js';
var ComposeWindow = React.createClass({
getInitialState: function()
{
return {
text: ''
};
},
changeText: function(ev)
class ComposeWindow extends React.PureComponent
{
state = {
text: ''
}
changeText = (ev) =>
{
this.setState({ text: ev.target.value });
},
render: function()
}
render()
{
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.filter(a => a.accountId).map((a, i) =>
<option key={'a'+a.accountId}>{'"'+a.name+'" <'+a.email+'>'}</option>
)}
</select>
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.filter(a => a.accountId).map((a, i) =>
<option key={'a'+a.accountId}>{'"'+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="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>
<AttachList />
</div>
<div className="text">
<textarea onChange={this.changeText} defaultValue={this.state.text}></textarea>
</div>
<AttachList />
</div>
<div className="text">
<textarea onChange={this.changeText} defaultValue={this.state.text}></textarea>
</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)
{
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 };
}
}
import React from 'react';
var DropDownBase = module.exports = {
instances: {},
currentVisible: null,
componentDidMount: function()
export default class DropDownBase extends React.PureComponent
{
static instances = {};
static currentVisible = null;
state = { visible: false, top: 0, left: 0, calloutLeft: null, selectedItem: -1 };
componentDidMount()
{
if (!DropDownBase.setBodyListener)
{
@ -38,36 +16,38 @@ var DropDownBase = module.exports = {
DropDownBase.setBodyListener = true;
}
DropDownBase.instances[this.props.id] = this;
},
hideAll: function()
}
static hideAll()
{
for (var i in DropDownBase.instances)
DropDownBase.instances[i].hide();
},
repositionCurrent: function()
}
static repositionCurrent()
{
if (DropDownBase.currentVisible)
DropDownBase.currentVisible[0].showAt(DropDownBase.currentVisible[1], DropDownBase.currentVisible[0].onClose);
},
componentWillUnmount: function()
}
componentWillUnmount()
{
delete DropDownBase.instances[this.props.id];
if (DropDownBase.currentVisible[0] == this)
DropDownBase.currentVisible = null;
},
getInitialState: function()
{
return { visible: false, top: 0, left: 0, calloutLeft: null, selectedItem: -1 };
},
onClick: function(ev)
}
onClick = (ev) =>
{
ev.stopPropagation();
},
isVisible: function()
}
isVisible = () =>
{
return this.state.visible;
},
hide: function()
}
hide = () =>
{
this.setState({ visible: false });
DropDownBase.currentVisible = null;
@ -76,8 +56,9 @@ var DropDownBase = module.exports = {
this.onClose();
delete this.onClose;
}
},
showAt: function(el, onClose)
}
showAt = (el, onClose) =>
{
if (this.onClose && this.onClose != onClose)
{
@ -108,4 +89,32 @@ var DropDownBase = module.exports = {
this.refs.dd.focus();
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');
const DropDownBase = require('./DropDownBase.js');
import React from 'react';
var DropDownButton = module.exports = React.createClass({
componentDidUpdate: function(prevProps, prevState)
import DropDownBase from './DropDownBase.js';
export default class DropDownButton extends React.PureComponent
{
state = { pressed: false, checked: false }
componentDidUpdate(prevProps, prevState)
{
if (prevProps.hidden && !this.props.hidden &&
DropDownBase.instances[this.props.dropdownId].isVisible())
{
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}
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.props.dropdownId ? <span className="down" onClick={this.onClickDown}></span> : null}
</a>
},
getInitialState: function()
{
return { pressed: false, checked: false };
},
toggle: function()
}
toggle = () =>
{
if (!this.state.pressed)
{
@ -35,12 +37,14 @@ var DropDownButton = module.exports = React.createClass({
else
DropDownBase.instances[this.props.dropdownId].hide();
this.setState({ pressed: !this.state.pressed });
},
unpress: function()
}
unpress = () =>
{
this.setState({ pressed: false });
},
onClickButton: function(ev)
}
onClickButton = (ev) =>
{
if (this.props.whole || this.props.checkable && this.state.pressed)
this.toggle();
@ -53,10 +57,11 @@ var DropDownButton = module.exports = React.createClass({
else if (this.props.onClick)
this.props.onClick();
ev.stopPropagation();
},
onClickDown: function(ev)
}
onClickDown = (ev) =>
{
this.toggle();
ev.stopPropagation();
}
});
}

View File

@ -1,13 +1,12 @@
const React = require('react');
const DropDownBase = require('./DropDownBase.js');
import React from 'react';
var DropDownMenu = module.exports = React.createClass({
mixins: [ DropDownBase ],
getInitialState: function()
{
return { items: this.props.items };
},
render: function()
import DropDownBase from './DropDownBase.js';
export default class DropDownMenu extends DropDownBase
{
state = { items: this.props.items }
render()
{
var sel = this.state.selectedItem;
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>
},
onMouseOver: function(ev)
}
onMouseOver = (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)
}
onKeyDown = (ev) =>
{
if (ev.keyCode == 40 || ev.keyCode == 38)
{
@ -50,14 +51,16 @@ var DropDownMenu = module.exports = React.createClass({
this.clickItem();
ev.preventDefault();
ev.stopPropagation();
},
clickItem: function(ev)
}
clickItem = (ev) =>
{
},
myOnClick: function(ev)
}
myOnClick = (ev) =>
{
if (ev.target.getAttribute('data-index'))
this.clickItem();
this.onClick(ev);
}
});
}

View File

@ -1,15 +1,18 @@
const React = require('react');
const AccountFolders = require('./AccountFolders.js');
const DropDownButton = require('./DropDownButton.js');
const Store = require('./Store.js');
const StoreListener = require('./StoreListenerClass.js');
const MailProgress = require('./MailProgress.js');
import React from 'react';
import AccountFolders from './AccountFolders.js';
import DropDownButton from './DropDownButton.js';
import Store from './Store.js';
import StoreListener from './StoreListener.js';
import MailProgress from './MailProgress.js';
var FolderList = React.createClass({
render: function()
class FolderList extends React.PureComponent
{
state = { selectedAccount: -1, selectedFolder: -1 }
render()
{
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="bottom-border-gradient"></div>
<div className="actions">
@ -24,13 +27,15 @@ var FolderList = React.createClass({
})}
</div>
<MailProgress />
</div>
},
onClickCheckSend: function()
</div>)
}
onClickCheckSend = () =>
{
Store.startResync();
},
onSelectFolder: function(accIndex, folderIndex)
}
onSelectFolder = (accIndex, folderIndex) =>
{
var acc = this.props.accounts[accIndex];
var folder = this.props.accounts[accIndex].folders[folderIndex];
@ -42,11 +47,7 @@ var FolderList = React.createClass({
Store.loadFolder({ accountId: acc.accountId, folderType: folder.type });
}
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;
}

View File

@ -1,7 +1,8 @@
const React = require('react');
import React from 'react';
var ListSortSettings = module.exports = React.createClass({
render: function()
export default class ListSortSettings extends React.PureComponent
{
render()
{
return <div className={this.props.className} value={this.props.sort.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>
</div>
}
});
}

View File

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

View File

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

View File

@ -1,7 +1,7 @@
const StoreListener = require('./StoreListenerClass.js');
const ProgressBar = require('./ProgressBar.js');
import StoreListener from './StoreListener.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 };
});

View File

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

View File

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

View File

@ -1,11 +1,14 @@
const React = require('react');
const DropDownButton = require('./DropDownButton.js');
const Store = require('./Store.js');
const StoreListener = require('./StoreListener.js');
const Util = require('./Util.js');
import React from 'react';
import DropDownButton from './DropDownButton.js';
import Store from './Store.js';
import StoreListener from './StoreListener.js';
import Util from './Util.js';
var MessageView = React.createClass({
formatLongDate: function(dt)
class MessageView extends React.PureComponent
{
state = { showImages: false }
formatLongDate(dt)
{
if (!(dt instanceof Date))
dt = new Date(dt.replace(' ', 'T'));
@ -14,8 +17,9 @@ var MessageView = React.createClass({
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 dt.toLocaleString();
},
componentWillReceiveProps: function(nextProps)
}
componentWillReceiveProps(nextProps)
{
if (nextProps.msg != this.props.msg)
{
@ -26,16 +30,14 @@ var MessageView = React.createClass({
}
this.setState(ns);
}
},
showImages: function()
}
showImages = () =>
{
this.setState({ showImages: true });
},
getInitialState: function()
{
return { showImages: false };
},
render: function()
}
render()
{
var showImages = this.state.showImages;
var msg = this.props.msg;
@ -148,6 +150,6 @@ var MessageView = React.createClass({
] : null}
</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({
render: function()
export default class ProgressBar extends React.PureComponent
{
state = { width: '' }
render()
{
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>
@ -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>
</div>
},
componentDidUpdate: function(prevProps, prevState)
}
componentDidUpdate(prevProps, prevState)
{
if (!prevState.width)
{
setTimeout(this.onResize, 50);
}
},
getInitialState: function()
{
return { width: '' };
},
onResize: function()
}
onResize = () =>
{
this.setState({ width: this.refs.pbar.offsetWidth });
},
componentDidMount: function()
}
componentDidMount()
{
window.addEventListener('resize', this.onResize);
this.onResize();
},
componentWillUnmount: function()
}
componentWillUnmount()
{
window.removeEventListener('resize', this.onResize);
}
});
}

View File

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

View File

<
@ -1,19 +1,23 @@
const React = require('react');
const Store = require('./Store.js');
import React from 'react';
import Store from './Store.js';
// "react-redux connect()"-like example
var StoreListener = React.createClass({
componentDidMount: function()
class StoreListener extends React.PureComponent