diff --git a/.babelrc b/.babelrc index b770454..3a3201e 100644 --- a/.babelrc +++ b/.babelrc @@ -4,6 +4,7 @@ "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", diff --git a/AccountFolders.js b/AccountFolders.js index e653eb6..48c2450 100644 --- a/AccountFolders.js +++ b/AccountFolders.js @@ -6,7 +6,7 @@ var AccountFolders = module.exports = React.createClass({ { return
- {this.props.name} + {this.props.email || this.props.name}
{this.props.unreadCount}
{this.props.accountId ? [
, @@ -46,7 +46,7 @@ var AccountFolders = module.exports = React.createClass({ { var self = this; var i = DropDownBase.instances.account.state.items; - i[0].text = 'Read '+this.props.name; + i[0].text = 'Read '+(this.props.email||this.props.name); DropDownBase.instances.account.setState({ items: i }); DropDownBase.instances.account.showAt(ev.target, function() { diff --git a/AllDropdowns.js b/AllDropdowns.js index 2e40edc..891e36a 100644 --- a/AllDropdowns.js +++ b/AllDropdowns.js @@ -134,21 +134,7 @@ var dropdown_list_sort = React.createElement( } ); -var dropdown_settings = React.createElement( - MailSettingsWindow, { - id: 'settings', - window: true, - markDelay: -1, - defaultSorting: { - sort: { - sortby: 'sent date', - group: 'date', - ascending: false, - threaded: false - } - } - } -); +var dropdown_settings = MailSettingsWindow; module.exports = function() { diff --git a/ComposeWindow.js b/ComposeWindow.js index 64cde34..44cee41 100644 --- a/ComposeWindow.js +++ b/ComposeWindow.js @@ -1,7 +1,8 @@ const React = require('react'); const AttachList = require('./AttachList.js'); +const StoreListener = require('./StoreListener.js'); -var ComposeWindow = module.exports = React.createClass({ +var ComposeWindow = React.createClass({ getInitialState: function() { return { @@ -58,3 +59,5 @@ var ComposeWindow = module.exports = React.createClass({
} }); + +module.exports = StoreListener(ComposeWindow, (data) => { return { accounts: data.accounts }; }); diff --git a/I18n.js b/I18n.js new file mode 100644 index 0000000..c51757e --- /dev/null +++ b/I18n.js @@ -0,0 +1,4 @@ +module.exports = function(msg) +{ + return msg; +} diff --git a/MailSettingsWindow.js b/MailSettingsWindow.js index 2ae2ade..a08b866 100644 --- a/MailSettingsWindow.js +++ b/MailSettingsWindow.js @@ -2,8 +2,9 @@ const React = require('react'); const DropDownBase = require('./DropDownBase.js'); const ListSortSettings = require('./ListSortSettings.js'); const Store = require('./Store.js'); +const StoreListener = require('./StoreListener.js'); -var MailSettingsWindow = module.exports = React.createClass({ +var MailSettingsWindow = React.createClass({ mixins: [ DropDownBase ], render: function() { @@ -12,15 +13,15 @@ var MailSettingsWindow = module.exports = React.createClass({
Mail Layout
- - - + + +
Default List Sorting
- +
Mark as Read
@@ -35,36 +36,32 @@ var MailSettingsWindow = module.exports = React.createClass({
}, - componentDidMount: function() - { - Store.on('layout', this.setLayout); - Store.on('quickReply', this.setQuickReply); - }, - componentWillUnmount: function() - { - Store.un('layout', this.setLayout); - Store.un('quickReply', this.setQuickReply); - }, - setLayout: function() - { - this.setState({ layout: Store.layout }); - }, - setQuickReply: function() - { - this.setState({ showQuickReply: Store.quickReply }); - }, switchLayout: function(ev) { var t = ev.target.nodeName == 'A' ? ev.target : ev.target.parentNode; var l = / mail-(\S+)/.exec(t.className)[1]; Store.set('layout', l); }, - getInitialState: function() - { - return { showQuickReply: Store.quickReply, layout: Store.layout }; - }, showQuickReply: function() { Store.set('quickReply', !this.state.showQuickReply); } }); + +module.exports = StoreListener( + MailSettingsWindow, + (data) => { return { layout: data.layout, showQuickReply: data.showQuickReply }; }, + { + id: 'settings', + window: true, + markDelay: -1, + defaultSorting: { + sort: { + sortby: 'sent date', + group: 'date', + ascending: false, + threaded: false + } + } + } +); diff --git a/MessageList.js b/MessageList.js index 28d780b..a687d61 100644 --- a/MessageList.js +++ b/MessageList.js @@ -1,7 +1,7 @@ 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'); var MessageInList = React.createClass({ @@ -10,10 +10,10 @@ var MessageInList = React.createClass({ { var msg = this.props.msg; return
(msg[c] ? ' '+c : '')).join(''))+ - (this.props.selected ? ' selected' : '')+(msg.thread && Store.threads ? ' thread0' : '')} onMouseDown={this.props.onClick}> + (this.props.selected ? ' selected' : '')+(msg.thread && this.props.threads ? ' thread0' : '')} onMouseDown={this.props.onClick}>
{msg.subject}
- {msg.thread && Store.threads ?
: null} + {msg.thread && this.props.threads ?
: null}
{(msg.sent || msg.outgoing ? 'To '+msg.to : msg.from)}
{Util.formatBytes(msg.size)}
@@ -24,17 +24,19 @@ var MessageInList = React.createClass({ }); // TODO: expand/collapse days -var MessageList = module.exports = React.createClass({ +var MessageList = React.createClass({ mixins: [ ListWithSelection ], getInitialState: function() { - return { firstDayTop: 0, firstDay: this.props.groups[0].name, groups: this.props.groups /*FIXME*/ }; + return { firstDayTop: 0, firstDay: this.props.groups && this.props.groups[0] && this.props.groups[0].name || '', groups: this.props.groups||[] /*FIXME*/ }; }, changeFirstDay: function(ev) { + if (!this.state.groups.length) + return; var scrollTop = ev.target.scrollTop, scrollSize = ev.target.offsetHeight - this.getScrollPaddingTop(); var top = 0, p, firstVisibleGrp, firstVisible, lastVisibleGrp, lastVisible; - var itemH = (Store.layout == 'message-on-right' ? 60 : 30); + var itemH = (this.props.layout == 'message-on-right' ? 60 : 30); var i; for (i = 0; i < this.state.groups.length; i++) { @@ -104,7 +106,7 @@ var MessageList = module.exports = React.createClass({ }, getPageSize: function() { - return Math.round(this.refs.scroll.offsetHeight / (Store.layout == 'message-on-right' ? 60 : 30)); + return Math.round(this.refs.scroll.offsetHeight / (this.props.layout == 'message-on-right' ? 60 : 30)); }, getItemOffset: function(index) { @@ -122,12 +124,12 @@ var MessageList = module.exports = React.createClass({ if (index < n) { if (index > p) - top += (i > 0 ? 30 : 0) + (Store.layout == 'message-on-right' ? 60 : 30)*(index-p-1); + top += (i > 0 ? 30 : 0) + (this.props.layout == 'message-on-right' ? 60 : 30)*(index-p-1); break; } - top += (i > 0 ? 30 : 0) + (Store.layout == 'message-on-right' ? 60 : 30)*this.state.groups[i].messageCount; + top += (i > 0 ? 30 : 0) + (this.props.layout == 'message-on-right' ? 60 : 30)*this.state.groups[i].messageCount; } - return [ top, (Store.layout == 'message-on-right' && (index == 0 || index != p) ? 60 : 30) ]; + return [ top, (this.props.layout == 'message-on-right' && (index == 0 || index != p) ? 60 : 30) ]; }, getScrollPaddingTop: function() { @@ -145,7 +147,7 @@ var MessageList = module.exports = React.createClass({ { var self = this; var total = 0; - var itemH = (Store.layout == 'message-on-right' ? 60 : 30); + var itemH = (this.props.layout == 'message-on-right' ? 60 : 30); return
@@ -163,7 +165,7 @@ var MessageList = module.exports = React.createClass({
{this.state.firstDay}
- {this.props.groups.map(function(grp, i) { + {(this.props.groups||[]).map(function(grp, i) { if (i > 0) total++; var start = total+(self.state.firstGrp == i ? self.state.firstMsg : 0); @@ -181,7 +183,7 @@ var MessageList = module.exports = React.createClass({ self.state.lastGrp == i ? self.state.lastMsg+1 : grp.messageCount ).map((msg, j) => (msg ? [ - , + , /*(msg.thread && Store.threads ?
{msg.thread.map(reply => )} @@ -204,3 +206,12 @@ var MessageList = module.exports = React.createClass({
} }); + +module.exports = StoreListener(MessageList, function(data) +{ + return { + threads: data.threads, + layout: data.layout, + groups: data.listGroups + }; +}); diff --git a/MessageView.js b/MessageView.js index c71ae05..33ae1c5 100644 --- a/MessageView.js +++ b/MessageView.js @@ -1,9 +1,10 @@ const React = require('react'); const DropDownButton = require('./DropDownButton.js'); const Store = require('./Store.js'); +const StoreListener = require('./StoreListener.js'); const Util = require('./Util.js'); -var MessageView = module.exports = React.createClass({ +var MessageView = React.createClass({ formatLongDate: function(dt) { if (!(dt instanceof Date)) @@ -14,32 +15,10 @@ var MessageView = module.exports = React.createClass({ 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(); }, - componentDidMount: function() - { - Store.on('quickReply', this.setQuickReply); - Store.on('msg', this.setMsg); - }, - componentWillUnmount: function() - { - Store.un('quickReply', this.setQuickReply); - Store.un('msg', this.setMsg); - }, - setQuickReply: function() - { - this.setState({ quickReply: Store.quickReply }); - }, - setMsg: function() - { - this.setState({ msg: Store.msg }); - }, - getInitialState: function() - { - return { quickReply: Store.quickReply }; - }, render: function() { - var msg = this.state.msg; - return
+ var msg = this.props.msg; + return
Reply All @@ -98,7 +77,7 @@ var MessageView = module.exports = React.createClass({
: null),
, - this.state.quickReply ? + this.props.quickReply ?
@@ -111,3 +90,5 @@ var MessageView = module.exports = React.createClass({
} }); + +module.exports = StoreListener(MessageView, (data) => { return { quickReply: data.quickReply, msg: data.msg }; }); diff --git a/Store.js b/Store.js index 48f8b28..eef9bc6 100644 --- a/Store.js +++ b/Store.js @@ -1,26 +1,102 @@ -var Store = module.exports = { - layout: 'message-on-right', - quickReply: true, - msg: null, - threads: false, +const superagent = require('superagent'); +const _ = require('./I18n.js'); - listeners: {}, - on: function(ev, cb) - { - this.listeners[ev] = this.listeners[ev] || []; - this.listeners[ev].push(cb); +var Store = module.exports = { + data: { + layout: 'message-on-right', + quickReply: true, + msg: null, + threads: false, + accounts: [], }, - un: function(ev, cb) + + listeners: [], + on: function(cb) { - if (!this.listeners[ev]) - return; - for (var i = this.listeners[ev].length; i >= 0; i--) - if (this.listeners[ev] == cb) - this.listeners[ev].splice(i, 1); + this.listeners.push(cb); + }, + un: function(cb) + { + for (var i = this.listeners.length; i >= 0; i--) + if (this.listeners[i] == cb) + this.listeners.splice(i, 1); + }, + get: function(k) + { + return this.data[k]; }, set: function(k, v) { - this[k] = v; - (this.listeners[k] || []).map(i => i()); + this.data[k] = v; + (this.listeners || []).map(i => i()); + }, + + loadAccounts: function() + { + superagent.get('backend/folders').end(function(err, res) + { + var ixOfAll = { + received: 1, + outbox: 3, + sent: 4, + drafts: 5, + spam: 6, + trash: 7 + }; + var accounts = [ { + name: _('All Messages'), + accountId: null, + unreadCount: 0, + folders: [ + { name: _('Unread'), icon: 'mail_unread', unreadCount: 0, type: 'unread' }, + { name: _('Received'), icon: 'mail_received', unreadCount: 0, type: 'inbox' }, + { name: _('Pinned'), icon: 'mail_pinned', unreadCount: 0, type: 'pinned' }, + { name: _('Outbox'), icon: 'mail_outbox', unreadCount: 0, type: 'outbox' }, + { name: _('Sent'), icon: 'mail_sent', unreadCount: 0, type: 'sent' }, + { name: _('Drafts'), icon: 'mail_drafts', unreadCount: 0, type: 'drafts' }, + { name: _('Spam'), icon: 'mail_spam', unreadCount: 0, type: 'spam' }, + { name: _('Trash'), icon: 'mail_trash', unreadCount: 0, type: 'trash' }, + ], + } ]; + for (let a of res.body.accounts) + { + let account = { + name: a.name, + email: a.email, + accountId: a.id, + unreadCount: 0, + warning: false, + folders: [ + { name: _('Unread'), icon: 'mail_unread', unreadCount: 0, type: 'unread' }, + { name: _('Pinned'), icon: 'mail_pinned', unreadCount: a.pinned_unread_count, type: 'pinned' }, + ], + folderMap: a.foldermap, + folderTypes: {} + }; + if (!account.folderMap.received) + { + account.folderMap.received = 'INBOX'; + } + for (let f in account.folderMap) + { + account.folderTypes[account.folderMap[f]] = f; + } + for (let f of a.folders) + { + let icon = (account.folderTypes[f.name] ? 'mail_'+account.folderTypes[f.name] : 'folder'); + account.folders.push({ name: f.name, icon: icon, unreadCount: f.unread_count-0, folderId: f.id }); + account.folders[0].unreadCount -= -f.unread_count; + if (account.folderTypes[f.name]) + { + accounts[0].folders[ixOfAll[account.folderTypes[f.name]]].unreadCount -= -f.unread_count; + } + } + accounts.push(account); + accounts[0].unreadCount += account.unreadCount; + accounts[0].folders[0].unreadCount += account.unreadCount; + accounts[0].folders[2].unreadCount += account.folders[1].unreadCount; + } + Store.set('accounts', accounts); + }); } }; diff --git a/StoreListener.js b/StoreListener.js new file mode 100644 index 0000000..b34403b --- /dev/null +++ b/StoreListener.js @@ -0,0 +1,39 @@ +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.props.mapStateToProps(Store.data); + for (var i in newState) + { + if (this.state[i] != newState[i]) + { + this.setState(newState); + 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) +{ + return React.createElement(StoreListener, { wrappedComponent: component, mapStateToProps: map, initial: initial||{} }); +}; diff --git a/mail.js b/mail.js index 8f0d413..69b06df 100644 --- a/mail.js +++ b/mail.js @@ -6,67 +6,11 @@ const MessageList = require('./MessageList.js'); const MessageView = require('./MessageView.js'); const TabPanel = require('./TabPanel.js'); const Store = require('./Store.js'); +const StoreListener = require('./StoreListener.js'); const AllDropdowns = require('./AllDropdowns.js'); window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame; -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 msg2 = []; msg2[5] = { subject: 'кошку хочешь?))', @@ -87,7 +31,7 @@ msg2[5] = { } ] }; -var listGroups = [ { +Store.listGroups = [ { name: 'TODAY', messageCount: 10, messages: [ { @@ -109,7 +53,26 @@ Best regards,
\ messages: msg2 } ]; -var AllTabs = React.createClass({ +var AllTabs = StoreListener(TabPanel, function(data) +{ + return { tabs: [ + { + className: data.layout, + noclose: true, + icon: 'mail_unread', + title: 'Unread (64)', + children: [ MessageList, MessageView ] + }, + { + icon: 'mail_drafts', + i16: true, + title: 'Compose Message', + children: [ ComposeWindow ] + } + ] } +}); + +/*React.createClass({ componentDidMount: function() { Store.on('layout', this.setLayout); @@ -150,29 +113,17 @@ var AllTabs = React.createClass({ }, render: function() { - return React.createElement(TabPanel, { tabs: [ - { - className: this.state.layout, - noclose: true, - icon: 'mail_unread', - title: 'Unread (64)', - children: [ , ] - }, - { - icon: 'mail_drafts', - i16: true, - title: 'Compose Message', - children: - } - ] }); + return React.createElement(); } -}); +});*/ ReactDOM.render(
{AllDropdowns()} - - + {StoreListener(FolderList, (data) => { return { accounts: data.accounts }; }, { progress: 33 })} + {AllTabs}
, document.body ); + +Store.loadAccounts(); diff --git a/package.json b/package.json index 9d40b93..65f4665 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "babel-plugin-transform-es2015-block-scoping": "latest", "babel-plugin-transform-es2015-classes": "latest", "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", @@ -25,10 +26,12 @@ "react-dom": "latest", "uglifyjs": "latest", "uglifyify": "latest", - "eslint": "latest" + "eslint": "latest", + "superagent": "latest" }, "scripts": { "compile": "browserify -t babelify -t uglifyify mail.js | uglifyjs -cm > mail.c.js", + "watch-dev": "watchify -t babelify mail.js -o mail.c.js", "watch": "watchify -t babelify -t uglifyify mail.js -o 'uglifyjs -cm > mail.c.js'" } }