From c2b841d75733695d15a8347239afd40c2c3b1eb1 Mon Sep 17 00:00:00 2001 From: Vitaliy Filippov Date: Tue, 14 May 2019 15:49:20 +0300 Subject: [PATCH] Remove Redux-like superglobal Store, remove all var's and self's --- AccountFolders.js | 22 +-- AllDropdowns.js | 156 --------------- AttachList.js | 8 +- ComposeWindow.js | 5 +- DropDownBase.js | 30 +-- DropDownMenu.js | 8 +- FolderList.js | 39 ++-- ListSortSettings.js | 6 +- ListSortSettingsWindow.js | 2 +- ListWithSelection.js | 20 +- MailProgress.js | 7 - MailSettingsWindow.js | 35 +--- MessageList.js | 135 ++++++------- MessageView.js | 6 +- Store.js | 199 ------------------- StoreListener.js | 53 ----- TabPanel.js | 19 +- Util.js | 12 +- mail.js | 397 +++++++++++++++++++++++++++++++++++--- 19 files changed, 520 insertions(+), 639 deletions(-) delete mode 100644 AllDropdowns.js delete mode 100644 MailProgress.js delete mode 100644 Store.js delete mode 100644 StoreListener.js diff --git a/AccountFolders.js b/AccountFolders.js index f0f8c86..07e1f09 100644 --- a/AccountFolders.js +++ b/AccountFolders.js @@ -40,12 +40,12 @@ export default class AccountFolders extends React.PureComponent selectFolder = (ev) => { - var t = ev.target; + let t = ev.target; while (t && !t.getAttribute('data-i') && t != this.refs.vis) t = t.parentNode; if (t && t != this.refs.vis) { - var i = t.getAttribute('data-i'); + let i = t.getAttribute('data-i'); this.props.onSelect(this.props.accountIndex, i); } // FIXME: send select event + switch focus to message list if folder changed @@ -53,34 +53,32 @@ export default class AccountFolders extends React.PureComponent showCfg = (ev) => { - var self = this; - var i = DropDownBase.instances.account.state.items; + let i = DropDownBase.instances.account.state.items; i[0].text = 'Read '+(this.props.account.email||this.props.account.name); DropDownBase.instances.account.setState({ items: i }); - DropDownBase.instances.account.showAt(ev.target, function() + DropDownBase.instances.account.showAt(ev.target, () => { - self.setState({ cfgPressed: false }); + this.setState({ cfgPressed: false }); }); - self.setState({ cfgPressed: true }); + this.setState({ cfgPressed: true }); ev.stopPropagation(); } onClick = () => { - var self = this; if (this.state.animating) return; this.setState({ animating: true, h: this.refs.vis.offsetHeight }); if (!this.state.collapsed) { - setTimeout(function() + setTimeout(() => { - self.setState({ h: 0 }); + this.setState({ h: 0 }); }, 50); } - setTimeout(function() + setTimeout(() => { - self.setState({ collapsed: !self.state.collapsed, animating: false, h: null }); + this.setState({ collapsed: !this.state.collapsed, animating: false, h: null }); }, this.state.collapsed ? 200 : 250); } } diff --git a/AllDropdowns.js b/AllDropdowns.js deleted file mode 100644 index d37e789..0000000 --- a/AllDropdowns.js +++ /dev/null @@ -1,156 +0,0 @@ -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, - text: 'Read vitalif@mail.ru' - }, { - icon: 'folder', - text: 'IMAP Folders', - }, { - icon: 'properties', - text: 'Properties...' - } ] - } -); - -var dropdown_reply = React.createElement( - DropDownMenu, { - id: 'reply', - key: 'reply', - items: [ { - hotkey: 'R', - icon: 'mail_reply', - text: 'Reply' - }, { - icon: 'mail_reply', - text: 'Reply to Sender', - }, { - disabled: true, - icon: 'mail_reply_all', - text: 'Reply to List' - } ] - } -); - -var dropdown_forward = React.createElement( - DropDownMenu, { - id: 'forward', - key: 'forward', - items: [ { - hotkey: 'F', - icon: 'mail_forward', - text: 'Reply' - }, { - hotkey: 'D', - text: 'Redirect' - } ] - } -); - -var dropdown_delete = React.createElement( - DropDownMenu, { - id: 'delete', - key: 'delete', - items: [ { - text: 'Move to Trash' - }, { - icon: 'delete', - text: 'Delete Permanently' - } ] - } -); - -var dropdown_check_send = React.createElement( - DropDownMenu, { - id: 'check-send', - key: 'check-send', - items: [ { - hotkey: 'Ctrl-K', - icon: 'mail_check', - text: 'Check All' - }, { - hotkey: 'Ctrl-Shift-K', - icon: 'mail_send', - text: 'Send Queued' - }, { split: true }, { - icon: 'mail_check', - text: 'vitalif@mail.ru' - }, { - icon: 'mail_check', - text: 'vitalif@yourcmc.ru' - }, { split: true }, { - text: 'Resynchronize All Messages' - } ] - } -); - -var dropdown_threads = React.createElement( - DropDownMenu, { - id: 'threads', - key: 'threads', - items: [ { - icon: 'thread', - text: 'Show Message Thread' - }, { - text: 'Follow Thread' - }, { - text: 'Ignore Thread' - }, { split: true }, { - hotkey: 'M', - icon: 'read', - text: 'Mark Thread as Read' - }, { split: true }, { - hotkey: 'N', - text: 'Mark Thread and Go to Next Unread' - } ] - } -); - -var dropdown_list_sort = React.createElement( - ListSortSettingsWindow, { - id: 'list-sort', - key: 'list-sort', - window: true, - folder: 'INBOX', - override: false, - sorting: {}, - defaultSorting: { - sort: { - sortby: 'sent date', - group: 'date', - ascending: false, - threaded: false - } - }, - show: { - read: true, - trash: false, - spam: false, - lists: true, - sent: true, - dups: true - } - } -); - -export default function() -{ - return [ - dropdown_account, - dropdown_reply, - dropdown_forward, - dropdown_delete, - dropdown_check_send, - dropdown_threads, - dropdown_list_sort, - , - ]; -} diff --git a/AttachList.js b/AttachList.js index df9c666..d7af379 100644 --- a/AttachList.js +++ b/AttachList.js @@ -13,9 +13,9 @@ export default class AttachList extends ListWithSelection addAttachments = (ev) => { - var a = this.state.attachments; + let a = this.state.attachments; if (ev.target.files) - for (var i = 0; i < ev.target.files.length; i++) + for (let i = 0; i < ev.target.files.length; i++) a.push(ev.target.files[i]); this.setState({ attachments: a }); // reset file input @@ -29,7 +29,7 @@ export default class AttachList extends ListWithSelection deleteSelected = () => { - for (var i = this.state.attachments.length-1; i >= 0; i--) + for (let 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 }); @@ -47,7 +47,7 @@ export default class AttachList extends ListWithSelection getItemOffset = (index) => { - var item = this.refs['a'+index]; + let item = this.refs['a'+index]; return [ item.offsetTop, item.offsetHeight ]; } diff --git a/ComposeWindow.js b/ComposeWindow.js index e0d9fd8..2df3785 100644 --- a/ComposeWindow.js +++ b/ComposeWindow.js @@ -1,8 +1,7 @@ import React from 'react'; import AttachList from './AttachList.js'; -import StoreListener from './StoreListener.js'; -class ComposeWindow extends React.PureComponent +export default class ComposeWindow extends React.PureComponent { state = { text: '' @@ -59,5 +58,3 @@ class ComposeWindow extends React.PureComponent ); } } - -export default StoreListener(ComposeWindow, (data) => { return { accounts: data.accounts }; }); diff --git a/DropDownBase.js b/DropDownBase.js index 6ce8923..3afd678 100644 --- a/DropDownBase.js +++ b/DropDownBase.js @@ -20,7 +20,7 @@ export default class DropDownBase extends React.PureComponent static hideAll() { - for (var i in DropDownBase.instances) + for (let i in DropDownBase.instances) DropDownBase.instances[i].hide(); } @@ -66,8 +66,8 @@ export default class DropDownBase extends React.PureComponent delete this.onClose; } DropDownBase.currentVisible = [ this, el ]; - var p = getOffset(el); - var left = p.left, top = p.top+el.offsetHeight, calloutLeft = null; + let p = getOffset(el); + let left = p.left, top = p.top+el.offsetHeight, calloutLeft = null; this.setState({ visible: true, top: top, left: left, selectedItem: -1 }); this.refs.dd.style.display = 'block'; if (this.props.window) @@ -75,8 +75,8 @@ export default class DropDownBase extends React.PureComponent left = Math.round(p.left+el.offsetWidth/2-this.refs.dd.offsetWidth/2); top = p.top+el.offsetHeight+3; } - var ww = window.innerWidth || de.clientWidth || db.clientWidth; - var wh = window.innerHeight || de.clientHeight || db.clientHeight; + let ww = window.innerWidth || de.clientWidth || db.clientWidth; + let wh = window.innerHeight || de.clientHeight || db.clientHeight; if (left + this.refs.dd.offsetWidth > ww) { left = ww-this.refs.dd.offsetWidth; @@ -95,20 +95,20 @@ 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; + let box = elem.getBoundingClientRect(); + let body = document.body; + let docElem = document.documentElement; + let scrollTop = window.pageYOffset || docElem.scrollTop || body.scrollTop; + let scrollLeft = window.pageXOffset || docElem.scrollLeft || body.scrollLeft; + let clientTop = docElem.clientTop || body.clientTop || 0; + let clientLeft = docElem.clientLeft || body.clientLeft || 0; + let top = box.top + scrollTop - clientTop; + let left = box.left + scrollLeft - clientLeft; return { top: Math.round(top), left: Math.round(left) }; } else { - var top = 0, left = 0; + let top = 0, left = 0; while(elem) { top = top + parseInt(elem.offsetTop); diff --git a/DropDownMenu.js b/DropDownMenu.js index 3018646..6d2c7d9 100644 --- a/DropDownMenu.js +++ b/DropDownMenu.js @@ -8,7 +8,7 @@ export default class DropDownMenu extends DropDownBase render() { - var sel = this.state.selectedItem; + let sel = this.state.selectedItem; return
@@ -27,7 +27,7 @@ export default class DropDownMenu extends DropDownBase onMouseOver = (ev) => { - var t = ev.target; + let 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) @@ -38,8 +38,8 @@ export default class DropDownMenu extends DropDownBase { if (ev.keyCode == 40 || ev.keyCode == 38) { - var a = ev.keyCode == 40 ? 1 : this.state.items.length-1; - var sel = this.state.selectedItem; + let a = ev.keyCode == 40 ? 1 : this.state.items.length-1; + let sel = this.state.selectedItem; do { sel = ((sel+a) % this.state.items.length); diff --git a/FolderList.js b/FolderList.js index c7f05f8..3e3dd7e 100644 --- a/FolderList.js +++ b/FolderList.js @@ -1,53 +1,54 @@ 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'; +import ProgressBar from './ProgressBar.js'; -class FolderList extends React.PureComponent +export default class FolderList extends React.PureComponent { state = { selectedAccount: -1, selectedFolder: -1 } render() { - var self = this; - return (
+ return (
// TODO: keyboard navigation
- {self.props.accounts.map(function(account, i) { - return - })} + {this.props.accounts.map((account, i) => )}
- +
) } onClickCheckSend = () => { - Store.startResync(); + this.props.startResync(); } onSelectFolder = (accIndex, folderIndex) => { - var acc = this.props.accounts[accIndex]; - var folder = this.props.accounts[accIndex].folders[folderIndex]; + let acc = this.props.accounts[accIndex]; + let folder = this.props.accounts[accIndex].folders[folderIndex]; if (this.state.selectedAccount != accIndex || this.state.selectedFolder != folderIndex) { if (folder.folderId) - Store.loadFolder({ folderId: folder.folderId }); + this.props.loadFolder({ folderId: folder.folderId }); else - Store.loadFolder({ accountId: acc.accountId, folderType: folder.type }); + this.props.loadFolder({ accountId: acc.accountId, folderType: folder.type }); } this.setState({ selectedAccount: accIndex, selectedFolder: folderIndex }); } } - -export default StoreListener(FolderList, (data) => { return { accounts: data.accounts, progressText: data.progressText }; }); diff --git a/ListSortSettings.js b/ListSortSettings.js index 0f497ff..fe24b33 100644 --- a/ListSortSettings.js +++ b/ListSortSettings.js @@ -6,9 +6,9 @@ export default class ListSortSettings extends React.PureComponent { return
Show Quick Reply +
Mark as Read
@@ -39,31 +37,8 @@ class MailSettingsWindow extends DropDownBase 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 = () => - { - Store.set('quickReply', !this.props.quickReply); + let t = ev.target.nodeName == 'A' ? ev.target : ev.target.parentNode; + let l = / mail-(\S+)/.exec(t.className)[1]; + this.props.setLayout(l); } } - -export default StoreListener( - MailSettingsWindow, - (data) => { return { layout: data.layout, quickReply: data.quickReply }; }, - { - 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 33cfd47..4395e87 100644 --- a/MessageList.js +++ b/MessageList.js @@ -1,8 +1,6 @@ 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'; class MessageInList extends React.PureComponent @@ -11,7 +9,7 @@ class MessageInList extends React.PureComponent render() { - var msg = this.props.msg; + let msg = this.props.msg; return
(this.msgClasses[c] ? ' '+this.msgClasses[c] : '')).join(''))+ @@ -34,7 +32,7 @@ class MessageInList extends React.PureComponent } // TODO: expand/collapse days -class MessageList extends ListWithSelection +export default class MessageList extends ListWithSelection { _preloadSize = 20 _pageSize = 50 @@ -52,14 +50,14 @@ class MessageList extends ListWithSelection // Main virtual scroll detector method setFirstDayFromProps(props) { - var groups = props.groups; - var messages = props.messages; + let groups = props.groups; + let messages = props.messages; if (!groups || !groups.length) return; - var scrollTop = this.refs.scroll.scrollTop, scrollSize = this.refs.scroll.offsetHeight - this.getScrollPaddingTop(); - var top = 0, p, firstVisibleGrp, firstVisible, lastVisibleGrp, lastVisible; - var itemH = (this.props.layout == 'message-on-right' ? 60 : 30); - var i; + let scrollTop = this.refs.scroll.scrollTop, scrollSize = this.refs.scroll.offsetHeight - this.getScrollPaddingTop(); + let top = 0, p, firstVisibleGrp, firstVisible, lastVisibleGrp, lastVisible; + let itemH = (this.props.layout == 'message-on-right' ? 60 : 30); + let i; for (i = 0; i < groups.length; i++) { p = top; @@ -103,23 +101,23 @@ class MessageList extends ListWithSelection lastGrp: lastVisibleGrp, lastMsg: lastVisible }); - var loadFirst = groups[firstVisibleGrp].start+firstVisible-this._preloadSize; - var loadLast = groups[lastVisibleGrp].start+lastVisible+1+this._preloadSize; - var total = groups[groups.length-1].messageCount+groups[groups.length-1].start; + let loadFirst = groups[firstVisibleGrp].start+firstVisible-this._preloadSize; + let loadLast = groups[lastVisibleGrp].start+lastVisible+1+this._preloadSize; + let total = groups[groups.length-1].messageCount+groups[groups.length-1].start; loadLast = Math.floor((loadLast + this._pageSize - 1) / this._pageSize) * this._pageSize; loadLast = loadLast < total ? loadLast : total; loadFirst = loadFirst < 0 ? 0 : loadFirst; loadFirst = loadFirst - (loadFirst % this._pageSize); - var loadFirstEnd; + let loadFirstEnd; for (loadFirstEnd = loadFirst; loadFirstEnd < loadLast && messages[loadFirstEnd] === undefined; loadFirstEnd++) messages[loadFirstEnd] = null; - var loadLastStart; + let loadLastStart; for (loadLastStart = loadLast; loadLastStart > loadFirst && messages[loadLastStart-1] === undefined; loadLastStart--) messages[loadLastStart-1] = null; if (loadFirstEnd > loadFirst) - Store.loadMessages(loadFirst, loadFirstEnd-loadFirst); + this.props.loadMessages(loadFirst, loadFirstEnd-loadFirst); if (loadFirstEnd < loadLastStart && loadLastStart < loadLast) - Store.loadMessages(loadLastStart, loadLast-loadLastStart); + this.props.loadMessages(loadLastStart, loadLast-loadLastStart); } changeFirstDay = (ev) => @@ -134,29 +132,30 @@ class MessageList extends ListWithSelection onSelectCurrent = (index) => { - var self = this; - var total = 0, p, msg, idx; - for (var i = 0; i < (self.props.groups||[]).length; i++) + let total = 0, p, msg, idx; + for (let i = 0; i < (this.props.groups||[]).length; i++) { p = total; - total += (i > 0 ? 1 : 0)+self.props.groups[i].messageCount; + total += (i > 0 ? 1 : 0)+this.props.groups[i].messageCount; if (index < total) { - idx = self.props.groups[i].start+index-p-(i > 0 ? 1 : 0); - msg = self.props.messages[idx]; + idx = this.props.groups[i].start+index-p-(i > 0 ? 1 : 0); + msg = this.props.messages[idx]; if (msg && !msg.body_text && !msg.body_html) { - Store.loadMessage(msg.id, function(newMsg) + this.props.loadMessage(msg.id, (newMsg) => { - Store.set('msg', newMsg); - if (self.props.messages[idx] == msg) + this.props.setMessage(newMsg); + if (this.props.messages[idx] == msg) { - self.props.messages[idx] = newMsg; + this.props.messages[idx] = newMsg; } }); } else - Store.set('msg', msg); + { + this.props.setMessage(msg); + } break; } } @@ -164,8 +163,8 @@ class MessageList extends ListWithSelection getTotalItems = () => { - var total = -1; // do not count first-day as item - for (var i = 0; i < (this.props.groups||[]).length; i++) + let total = -1; // do not count first-day as item + for (let i = 0; i < (this.props.groups||[]).length; i++) { total += 1+this.props.groups[i].messageCount; } @@ -179,9 +178,9 @@ class MessageList extends ListWithSelection getItemOffset = (index) => { - var n = 0, top = 0, p; - var h = (this.props.layout == 'message-on-right' ? 60 : 30); - for (var i = 0; i < (this.props.groups||[]).length; i++) + let n = 0, top = 0, p, i; + let h = (this.props.layout == 'message-on-right' ? 60 : 30); + for (i = 0; i < (this.props.groups||[]).length; i++) { p = n; n += (i > 0 ? 1 : 0)+this.props.groups[i].messageCount; @@ -202,8 +201,8 @@ class MessageList extends ListWithSelection getMessages = (grp, start, end) => { - var a = this.props.messages.slice(grp.start+start, grp.start+end); - for (var i = 0; i < end-start; i++) + let a = this.props.messages.slice(grp.start+start, grp.start+end); + for (let i = 0; i < end-start; i++) if (!a[i]) a[i] = null; return a; @@ -211,18 +210,19 @@ class MessageList extends ListWithSelection onSearchTextChange = (event) => { - var s = event.target.value; + let s = event.target.value; this.setState({ searchText: s }); if (this._searchTimeout) + { clearTimeout(this._searchTimeout); - this._searchTimeout = setTimeout(function() { Store.search(s) }, 300); + } + this._searchTimeout = setTimeout(() => this.props.search(s), 300); } render() { - var self = this; - var total = 0; - var itemH = (this.props.layout == 'message-on-right' ? 60 : 30); + let total = 0; + let itemH = (this.props.layout == 'message-on-right' ? 60 : 30); return
@@ -232,46 +232,47 @@ class MessageList extends ListWithSelection
-
-
+
- {(self.state.firstDay||'').toUpperCase()} + style={{ top: this.state.firstDayTop, display: this.state.firstDay ? '' : 'none' }}> + {(this.state.firstDay||'').toUpperCase()}
- {(self.props.groups||[]).map(function(grp, i) { + {(this.props.groups||[]).map((grp, i) => + { if (i > 0) total++; - var start = total+(self.state.firstGrp == i ? self.state.firstMsg : 0); - var r = [ + let start = total+(this.state.firstGrp == i ? this.state.firstMsg : 0); + let r = [ i > 0 ?
{grp.name.toUpperCase()}
: null,
- {(self.state.firstGrp > i || self.state.lastGrp < i + {(this.state.firstGrp > i || this.state.lastGrp < i ?
: [ - (self.state.firstGrp == i - ?
+ (this.state.firstGrp == i + ?
: null), - self.getMessages(grp, - self.state.firstGrp == i ? self.state.firstMsg : 0, - self.state.lastGrp == i ? self.state.lastMsg+1 : grp.messageCount - ).map(function(msg, j) { return (msg + this.getMessages(grp, + this.state.firstGrp == i ? this.state.firstMsg : 0, + this.state.lastGrp == i ? this.state.lastMsg+1 : grp.messageCount + ).map((msg, j) => (msg ? [ - , - /*(msg.thread && Store.threads ? + , + /*(msg.thread && this.props.threads ?
- {msg.thread.map(reply => )} + {msg.thread.map(reply => )}
: null)*/ ] - :
- ); }), - (self.state.lastGrp == i - ?
+ :
+ )), + (this.state.lastGrp == i + ?
: null) ] )} @@ -294,13 +295,3 @@ class MessageList extends ListWithSelection window.removeEventListener('resize', this.changeFirstDay); } } - -export default StoreListener(MessageList, function(data) -{ - return { - threads: data.threads, - layout: data.layout, - groups: data.listGroups, - messages: data.messages - }; -}); diff --git a/MessageView.js b/MessageView.js index 36a0dda..3beac5e 100644 --- a/MessageView.js +++ b/MessageView.js @@ -1,10 +1,8 @@ import React from 'react'; import DropDownButton from './DropDownButton.js'; -import Store from './Store.js'; -import StoreListener from './StoreListener.js'; import Util from './Util.js'; -class MessageView extends React.PureComponent +export default class MessageView extends React.PureComponent { state = { showImages: false } @@ -151,5 +149,3 @@ class MessageView extends React.PureComponent
} } - -export default StoreListener(MessageView, (data) => { return { layout: data.layout, quickReply: data.quickReply, msg: data.msg }; }); diff --git a/Store.js b/Store.js deleted file mode 100644 index d61d6b7..0000000 --- a/Store.js +++ /dev/null @@ -1,199 +0,0 @@ -import superagent from 'superagent'; -import socket_io from 'socket.io-client'; - -import _ from './I18n.js'; -import Util from './Util.js'; - -const Store = { - data: { - layout: 'message-on-right', - quickReply: true, - msg: null, - threads: false, - accounts: [], - listGroups: [], - messages: [] - }, - - listeners: [], - on: function(cb) - { - 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.data[k] = v; - (this.listeners || []).map(i => i()); - }, - setAll: function(obj) - { - for (var k in obj) - this.data[k] = obj[k]; - (this.listeners || []).map(i => i()); - }, - - startIo: function() - { - var self = this; - this.io = socket_io('', { path: window.location.pathname.replace(/[^\/]+$/, 'backend/socket.io') }); - this.io.on('sync', function(params) - { - if (params.state == 'start') - { - self.setAll({ progressText: 'Syncing '+params.email+' / '+params.folder, progressPercent: 0 }); - } - else if (params.state == 'progress') - { - self.setAll({ progressPercent: Math.round(100*params.done/(params.total||1)) }); - } - else if (params.state == 'finish-box') - { - self.setAll({ progressPercent: 100 }); - } - else if (params.state == 'complete') - { - self.setAll({ progressText: '', progressPercent: 0 }); - } - self.set('sync', params.progress); - }); - }, - - 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-0); - if (account.folderTypes[f.name]) - { - accounts[0].folders[ixOfAll[account.folderTypes[f.name]]].unreadCount += (f.unread_count-0); - } - account.unreadCount += (f.unread_count-0); - } - 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); - }); - }, - - loadFolder: function(folderParams) - { - superagent.get('backend/groups').query(folderParams).end(function(err, res) - { - var groups = res.body.groups.map(g => { return { name: Util.getGroupName(g.name), messageCount: g.count-0, start: 0 } }); - var start = 0; - for (var i = 0; i < groups.length; i++) - { - groups[i].start = start; - start += groups[i].messageCount; - } - Store.setAll({ - folderParams: folderParams, - listGroups: groups, - messages: [] - }); - }); - }, - - search: function(text) - { - Store.loadFolder({ ...Store.get('folderParams'), search: text }); - }, - - loadMessages: function(start, count) - { - var p = { ...Store.get('folderParams') }; - p.offset = start; - p.limit = count; - superagent.get('backend/messages').query(p).end(function(err, res) - { - var msgs = Store.get('messages').slice(0); - var par = res.body.messages; - par.unshift(par.length); - par.unshift(start); - msgs.splice.apply(msgs, par); - Store.set('messages', msgs); - }); - }, - - loadMessage: function(msgId, callback) - { - superagent.get('backend/message').query({ msgId: msgId }).end(function(err, res) - { - callback(res.body.msg); - }); - }, - - startResync: function() - { - superagent.post('backend/sync').send().end(function(err, res) - { - }); - } -}; - -Store.startIo(); - -export default Store; diff --git a/StoreListener.js b/StoreListener.js deleted file mode 100644 index 990b943..0000000 --- a/StoreListener.js +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; - -import Store from './Store.js'; - -// "react-redux connect()"-like example -class StoreListener extends React.PureComponent -{ - 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); - } -} - -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; -}; diff --git a/TabPanel.js b/TabPanel.js index dc8b833..24dce2b 100644 --- a/TabPanel.js +++ b/TabPanel.js @@ -6,11 +6,11 @@ export default class TabPanel extends React.PureComponent render() { - var bar = []; - var body = []; - for (var i = 0; i < this.state.tabs.length; i++) + let bar = []; + let body = []; + for (let i = 0; i < this.state.tabs.length; i++) { - var t = this.state.tabs[i]; + let t = this.state.tabs[i]; bar.push(
@@ -44,19 +44,18 @@ export default class TabPanel extends React.PureComponent closeTab = (ev) => { - var self = this; - var tab = ev.target.parentNode; + let tab = ev.target.parentNode; //this.setState({ closing: tab.id.substr(5) }); tab.style.width = '1px'; tab.style.padding = '0'; tab.style.opacity = '0'; - setTimeout(function() + setTimeout(() => { - var t = self.state.tabs; + let t = this.state.tabs; t.splice(tab.id.substr(5), 1); - var s = self.state.selected; + let s = this.state.selected; if ('t-tab'+s == tab.id) - s = self.state.selected-1; + s = this.state.selected-1; this.setState({ tabs: t, selected: s }); }, 200); ev.stopPropagation(); diff --git a/Util.js b/Util.js index 10fd67c..2926ed3 100644 --- a/Util.js +++ b/Util.js @@ -16,25 +16,25 @@ export default class Util { if (!(dt instanceof Date)) dt = new Date(dt.replace(' ', 'T')); - var tod = new Date(); + let tod = new Date(); tod.setHours(0); tod.setMinutes(0); tod.setSeconds(0); tod.setMilliseconds(0); - var prevweek = tod; + let prevweek = tod; prevweek = prevweek.getTime() - (7 + (prevweek.getDay()+6)%7)*86400000; if (dt.getTime() < prevweek) { - var d = dt.getDate(); - var m = dt.getMonth()+1; + let d = dt.getDate(); + let m = dt.getMonth()+1; return (d < 10 ? '0' : '')+d+'.'+(m < 10 ? '0' : '')+m+'.'+dt.getFullYear(); } else if (dt.getTime() < tod.getTime()) { return Util.WeekDays[dt.getDay()]+' '+dt.getDate()+' '+Util.Months[dt.getMonth()]; } - var h = dt.getHours(); - var m = dt.getMinutes(); + let h = dt.getHours(); + let m = dt.getMinutes(); return (h < 10 ? '0' : '')+h+':'+(m < 10 ? '0' : '')+m; } diff --git a/mail.js b/mail.js index db1c8fb..75c8bdb 100644 --- a/mail.js +++ b/mail.js @@ -1,42 +1,381 @@ import React from 'react'; import ReactDOM from 'react-dom'; + +import superagent from 'superagent'; +import socket_io from 'socket.io-client'; + import ComposeWindow from './ComposeWindow.js'; import FolderList from './FolderList.js'; import MessageList from './MessageList.js'; import MessageView from './MessageView.js'; +import DropDownMenu from './DropDownMenu.js'; +import ListSortSettingsWindow from './ListSortSettingsWindow.js'; +import MailSettingsWindow from './MailSettingsWindow.js'; import TabPanel from './TabPanel.js'; -import Store from './Store.js'; -import StoreListener from './StoreListener.js'; -import AllDropdowns from './AllDropdowns.js'; +import _ from './I18n.js'; +import Util from './Util.js'; window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame; -var AllTabs = StoreListener(TabPanel, function(data) +class MainWindow extends React.PureComponent { - return { tabs: [ - { - className: data.layout, - noclose: true, - icon: 'mail_unread', - title: 'Unread (64)', - children: [ , ] - }, - { - icon: 'mail_drafts', - i16: true, - title: 'Compose Message', - children: [ ] - } - ] } -}); + state = { + layout: 'message-on-right', + quickReply: true, + msg: null, + threads: false, + accounts: [], + listGroups: [], + messages: [] + } -ReactDOM.render( -
- {AllDropdowns()} - - -
, - document.getElementById('app') -); + startIo = () => + { + this.io = socket_io('', { path: window.location.pathname.replace(/[^\/]+$/, 'backend/socket.io') }); + this.io.on('sync', (params) => + { + if (params.state == 'start') + { + this.setState({ progressText: 'Syncing '+params.email+' / '+params.folder, progressPercent: 0 }); + } + else if (params.state == 'progress') + { + this.setState({ progressPercent: Math.round(100*params.done/(params.total||1)) }); + } + else if (params.state == 'finish-box') + { + this.setState({ progressPercent: 100 }); + } + else if (params.state == 'complete') + { + this.setState({ progressText: '', progressPercent: 0 }); + } + this.setState({ sync: params.progress }); + }); + } -Store.loadAccounts(); + loadAccounts() + { + superagent.get('backend/folders').end((err, res) => + { + let ixOfAll = { + received: 1, + outbox: 3, + sent: 4, + drafts: 5, + spam: 6, + trash: 7 + }; + let 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-0); + if (account.folderTypes[f.name]) + { + accounts[0].folders[ixOfAll[account.folderTypes[f.name]]].unreadCount += (f.unread_count-0); + } + account.unreadCount += (f.unread_count-0); + } + accounts.push(account); + accounts[0].unreadCount += account.unreadCount; + accounts[0].folders[0].unreadCount += account.unreadCount; + accounts[0].folders[2].unreadCount += account.folders[1].unreadCount; + } + this.setState({ accounts }); + }); + } + + loadFolder = (folderParams) => + { + superagent.get('backend/groups').query(folderParams).end((err, res) => + { + let groups = res.body.groups.map(g => ({ name: Util.getGroupName(g.name), messageCount: g.count-0, start: 0 })); + let start = 0; + for (let i = 0; i < groups.length; i++) + { + groups[i].start = start; + start += groups[i].messageCount; + } + this.setState({ + folderParams: folderParams, + listGroups: groups, + messages: [] + }); + }); + } + + search = (text) => + { + this.loadFolder({ ...this.state.folderParams, search: text }); + } + + loadMessages = (start, count) => + { + let p = { ...this.state.folderParams }; + p.offset = start; + p.limit = count; + superagent.get('backend/messages').query(p).end((err, res) => + { + let msgs = [ ...this.state.messages ]; + let par = res.body.messages; + par.unshift(par.length); + par.unshift(start); + msgs.splice.apply(msgs, par); + this.setState({ messages: msgs }); + }); + } + + loadMessage = (msgId, callback) => + { + superagent.get('backend/message').query({ msgId: msgId }).end((err, res) => + { + callback(res.body.msg); + }); + } + + startResync = () => + { + superagent.post('backend/sync').send().end((err, res) => + { + }); + } + + setLayout = (l) => + { + this.setState({ layout: l }); + } + + toggleQuickReply = () => + { + this.setState({ quickReply: !this.state.quickReply }); + } + + setMessage = (msg) => + { + this.setState({ msg }); + } + + render() + { + return (
+ + + + + + + + + + , + , + ] + }, + { + icon: 'mail_drafts', + i16: true, + title: 'Compose Message', + children: [ + + ] + } + ]} + /> +
); + } + + componentDidMount() + { + this.loadAccounts(); + this.startIo(); + } +} + +ReactDOM.render(, document.getElementById('app'));