From 661cd2aaf9e824bcae01679bd297747cfcc3db9e Mon Sep 17 00:00:00 2001 From: the-djmaze <> Date: Wed, 23 Feb 2022 19:26:52 +0100 Subject: [PATCH] Restructure JavaScript Split list code from MessageUserStore into MessagelistUserStore Move functions out of AppUser --- dev/App/User.js | 326 +---------------- dev/Common/Folders.js | 221 ++++++++++++ dev/Common/Momentor.js | 55 --- dev/Common/Translator.js | 60 +++- dev/Common/UtilsUser.js | 206 +++++++---- dev/External/User/ko.js | 5 +- dev/External/ko.js | 1 + dev/Model/FolderCollection.js | 83 +++-- dev/Model/Message.js | 66 ++-- dev/Remote/User/Fetch.js | 23 +- dev/Screen/User/MailBox.js | 10 +- dev/Settings/User/Folders.js | 7 +- dev/Settings/User/General.js | 7 +- dev/Stores/User/Folder.js | 2 +- dev/Stores/User/Message.js | 517 +-------------------------- dev/Stores/User/Messagelist.js | 435 ++++++++++++++++++++++ dev/View/Popup/AdvancedSearch.js | 4 +- dev/View/Popup/Compose.js | 25 +- dev/View/Popup/Filter.js | 2 +- dev/View/Popup/FolderClear.js | 5 +- dev/View/Popup/FolderCreate.js | 2 +- dev/View/Popup/FolderSystem.js | 2 +- dev/View/User/MailBox/FolderList.js | 7 +- dev/View/User/MailBox/MessageList.js | 219 ++++++------ dev/View/User/MailBox/MessageView.js | 32 +- dev/View/User/SystemDropDown.js | 8 +- 26 files changed, 1144 insertions(+), 1186 deletions(-) create mode 100644 dev/Common/Folders.js delete mode 100644 dev/Common/Momentor.js create mode 100644 dev/Stores/User/Messagelist.js diff --git a/dev/App/User.js b/dev/App/User.js index 3fefd7b79..60811ce21 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -1,17 +1,15 @@ import 'External/User/ko'; -import { isArray, arrayLength, pString, forEachObjectValue } from 'Common/Utils'; +import { isArray, pString } from 'Common/Utils'; import { mailToHelper, setLayoutResizer } from 'Common/UtilsUser'; import { - Notification, Scope } from 'Common/Enums'; import { FolderType, SetSystemFoldersNotification, - MessageSetAction, ClientSideKeyName } from 'Common/EnumsUser'; @@ -34,9 +32,7 @@ import { getFolderFromCacheList } from 'Common/Cache'; -import { mailBox } from 'Common/Links'; - -import { getNotification, i18n } from 'Common/Translator'; +import { i18n, reloadTime } from 'Common/Translator'; import { SettingsUserStore } from 'Stores/User/Settings'; import { NotificationUserStore } from 'Stores/User/Notification'; @@ -45,12 +41,11 @@ import { ContactUserStore } from 'Stores/User/Contact'; import { IdentityUserStore } from 'Stores/User/Identity'; import { FolderUserStore } from 'Stores/User/Folder'; import { PgpUserStore } from 'Stores/User/Pgp'; -import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { ThemeStore } from 'Stores/Theme'; import Remote from 'Remote/User/Fetch'; -import { EmailModel } from 'Model/Email'; import { AccountModel } from 'Model/Account'; import { IdentityModel } from 'Model/Identity'; @@ -66,24 +61,16 @@ import { ComposePopupView } from 'View/Popup/Compose'; import { FolderSystemPopupView } from 'View/Popup/FolderSystem'; import { AskPopupView } from 'View/Popup/Ask'; -import { timeToNode } from 'Common/Momentor'; - -// Every 5 minutes -const refreshFolders = 300000; - -let moveCache = {}; +import { folderInformationMultiply, refreshFoldersInterval, messagesMoveHelper, messagesDeleteHelper } from 'Common/Folders'; +import { loadFolders } from 'Model/FolderCollection'; class AppUser extends AbstractApp { constructor() { super(Remote); - this.moveOrDeleteResponseHelper = this.moveOrDeleteResponseHelper.bind(this); - - this.messagesMoveTrigger = this.messagesMoveTrigger.debounce(500); - // wakeUp const interval = 3600000; // 60m - var lastTime = Date.now(); + let lastTime = Date.now(); setInterval(() => { const currentTime = Date.now(); if (currentTime > (lastTime + interval + 1000)) { @@ -105,126 +92,6 @@ class AppUser extends AbstractApp { (Settings.app('inIframe') ? parent : window).location.reload(); } - /** - * @param {boolean=} bDropPagePosition = false - * @param {boolean=} bDropCurrenFolderCache = false - */ - reloadMessageList(bDropPagePosition = false, bDropCurrenFolderCache = false) { - let iOffset = (MessageUserStore.listPage() - 1) * SettingsUserStore.messagesPerPage(); - - if (bDropCurrenFolderCache) { - setFolderHash(FolderUserStore.currentFolderFullName(), ''); - } - - if (bDropPagePosition) { - MessageUserStore.listPage(1); - MessageUserStore.listPageBeforeThread(1); - iOffset = 0; - - rl.route.setHash( - mailBox( - FolderUserStore.currentFolderFullNameHash(), - MessageUserStore.listPage(), - MessageUserStore.listSearch(), - MessageUserStore.listThreadUid() - ), - true, - true - ); - } - - MessageUserStore.listLoading(true); - MessageUserStore.listError(''); - Remote.messageList( - (iError, oData, bCached) => { - if (iError) { - if (Notification.RequestAborted !== iError) { - MessageUserStore.list([]); - MessageUserStore.listError(getNotification(iError)); - } - } else { - MessageUserStore.setMessageList(oData, bCached); - } - MessageUserStore.listLoading(false); - }, - { - Folder: FolderUserStore.currentFolderFullName(), - Offset: iOffset, - Limit: SettingsUserStore.messagesPerPage(), - Search: MessageUserStore.listSearch(), - ThreadUid: MessageUserStore.listThreadUid() - } - ); - } - - messagesMoveTrigger() { - const sTrashFolder = FolderUserStore.trashFolder(), - sSpamFolder = FolderUserStore.spamFolder(); - - forEachObjectValue(moveCache, item => { - const isSpam = sSpamFolder === item.To, - isTrash = sTrashFolder === item.To, - isHam = !isSpam && sSpamFolder === item.From && getFolderInboxName() === item.To; - - Remote.request('MessageMove', - this.moveOrDeleteResponseHelper, - { - FromFolder: item.From, - ToFolder: item.To, - Uids: item.Uid.join(','), - MarkAsRead: (isSpam || isTrash) ? 1 : 0, - Learning: isSpam ? 'SPAM' : isHam ? 'HAM' : '' - }, - null, - '', - ['MessageList'] - ); - }); - - moveCache = {}; - } - - messagesMoveHelper(fromFolderFullName, toFolderFullName, uidsForMove) { - const hash = '$$' + fromFolderFullName + '$$' + toFolderFullName + '$$'; - if (!moveCache[hash]) { - moveCache[hash] = { - From: fromFolderFullName, - To: toFolderFullName, - Uid: [] - }; - } - - moveCache[hash].Uid = moveCache[hash].Uid.concat(uidsForMove).unique(); - this.messagesMoveTrigger(); - } - - messagesDeleteHelper(sFromFolderFullName, aUidForRemove) { - Remote.request('MessageDelete', - this.moveOrDeleteResponseHelper, - { - Folder: sFromFolderFullName, - Uids: aUidForRemove.join(',') - }, - null, - '', - ['MessageList'] - ); - } - - moveOrDeleteResponseHelper(iError, oData) { - if (iError) { - setFolderHash(FolderUserStore.currentFolderFullName(), ''); - alert(getNotification(iError)); - } else if (FolderUserStore.currentFolder()) { - if (2 === arrayLength(oData.Result)) { - setFolderHash(oData.Result[0], oData.Result[1]); - } else { - setFolderHash(FolderUserStore.currentFolderFullName(), ''); - } - this.reloadMessageList(!MessageUserStore.list.length); - } - } - /** * @param {number} iDeleteType * @param {string} sFromFolderFullName @@ -276,46 +143,16 @@ class AppUser extends AbstractApp { showScreenPopup(AskPopupView, [ i18n('POPUPS_ASK/DESC_WANT_DELETE_MESSAGES'), () => { - this.messagesDeleteHelper(sFromFolderFullName, aUidForRemove); - MessageUserStore.removeMessagesFromList(sFromFolderFullName, aUidForRemove); + messagesDeleteHelper(sFromFolderFullName, aUidForRemove); + MessagelistUserStore.removeMessagesFromList(sFromFolderFullName, aUidForRemove); } ]); } else if (oMoveFolder) { - this.messagesMoveHelper(sFromFolderFullName, oMoveFolder.fullName, aUidForRemove); - MessageUserStore.removeMessagesFromList(sFromFolderFullName, aUidForRemove, oMoveFolder.fullName); + messagesMoveHelper(sFromFolderFullName, oMoveFolder.fullName, aUidForRemove); + MessagelistUserStore.removeMessagesFromList(sFromFolderFullName, aUidForRemove, oMoveFolder.fullName); } } - /** - * @param {string} sFromFolderFullName - * @param {Array} aUidForMove - * @param {string} sToFolderFullName - * @param {boolean=} bCopy = false - */ - moveMessagesToFolder(sFromFolderFullName, aUidForMove, sToFolderFullName, bCopy) { - if (sFromFolderFullName !== sToFolderFullName && arrayLength(aUidForMove)) { - const oFromFolder = getFolderFromCacheList(sFromFolderFullName), - oToFolder = getFolderFromCacheList(sToFolderFullName); - - if (oFromFolder && oToFolder) { - if (undefined === bCopy ? false : !!bCopy) { - Remote.request('MessageCopy', null, { - FromFolder: oFromFolder.fullName, - ToFolder: oToFolder.fullName, - Uids: aUidForMove.join(',') - }); - } else { - this.messagesMoveHelper(oFromFolder.fullName, oToFolder.fullName, aUidForMove); - } - - MessageUserStore.removeMessagesFromList(oFromFolder.fullName, aUidForMove, oToFolder.fullName, bCopy); - return true; - } - } - - return false; - } - accountsAndIdentities() { AccountUserStore.loading(true); IdentityUserStore.loading(true); @@ -396,10 +233,10 @@ class AppUser extends AbstractApp { MessageFlagsCache.setFor(folderFromCache.fullName, message.Uid.toString(), message.Flags) ); - MessageUserStore.reloadFlagsAndCachedMessage(); + MessagelistUserStore.reloadFlagsAndCachedMessage(); } - MessageUserStore.initUidNextAndNewMessages( + MessagelistUserStore.initUidNextAndNewMessages( folderFromCache.fullName, result.UidNext, result.NewMessages @@ -407,7 +244,7 @@ class AppUser extends AbstractApp { if (!hash || unreadCountChange || result.Hash !== hash) { if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) { - this.reloadMessageList(); + MessagelistUserStore.reload(); } else if (getFolderInboxName() === folderFromCache.fullName) { Remote.messageList(null, {Folder: getFolderInboxName()}, true); } @@ -421,126 +258,6 @@ class AppUser extends AbstractApp { } } - /** - * @param {boolean=} boot = false - */ - folderInformationMultiply(boot = false) { - const folders = FolderUserStore.getNextFolderNames(refreshFolders); - if (arrayLength(folders)) { - Remote.request('FolderInformationMultiply', (iError, oData) => { - if (!iError && arrayLength(oData.Result)) { - const utc = Date.now(); - oData.Result.forEach(item => { - const hash = getFolderHash(item.Folder), - folder = getFolderFromCacheList(item.Folder); - - if (folder) { - folder.expires = utc; - - setFolderHash(item.Folder, item.Hash); - - folder.messageCountAll(item.MessageCount); - - let unreadCountChange = folder.messageCountUnread() !== item.MessageUnseenCount; - - folder.messageCountUnread(item.MessageUnseenCount); - - if (unreadCountChange) { - MessageFlagsCache.clearFolder(folder.fullName); - } - - if (!hash || item.Hash !== hash) { - if (folder.fullName === FolderUserStore.currentFolderFullName()) { - this.reloadMessageList(); - } - } else if (unreadCountChange - && folder.fullName === FolderUserStore.currentFolderFullName() - && MessageUserStore.list.length) { - this.folderInformation(folder.fullName, MessageUserStore.list()); - } - } - }); - - if (boot) { - setTimeout(() => this.folderInformationMultiply(true), 2000); - } - } - }, { - Folders: folders - }); - } - } - - /** - * @param {string} sFolderFullName - * @param {number} iSetAction - * @param {Array=} messages = null - */ - messageListAction(sFolderFullName, iSetAction, messages) { - messages = messages || MessageUserStore.listChecked(); - - let folder = null, - alreadyUnread = 0, - rootUids = messages.map(oMessage => oMessage && oMessage.uid ? oMessage.uid : null) - .validUnique(), - length = rootUids.length; - - if (sFolderFullName && length) { - switch (iSetAction) { - case MessageSetAction.SetSeen: - length = 0; - // fallthrough is intentionally - case MessageSetAction.UnsetSeen: - rootUids.forEach(sSubUid => - alreadyUnread += MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction) - ); - - folder = getFolderFromCacheList(sFolderFullName); - if (folder) { - folder.messageCountUnread(folder.messageCountUnread() - alreadyUnread + length); - } - - Remote.request('MessageSetSeen', null, { - Folder: sFolderFullName, - Uids: rootUids.join(','), - SetAction: iSetAction == MessageSetAction.SetSeen ? 1 : 0 - }); - break; - - case MessageSetAction.SetFlag: - case MessageSetAction.UnsetFlag: - rootUids.forEach(sSubUid => - MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction) - ); - Remote.request('MessageSetFlagged', null, { - Folder: sFolderFullName, - Uids: rootUids.join(','), - SetAction: iSetAction == MessageSetAction.SetFlag ? 1 : 0 - }); - break; - // no default - } - - MessageUserStore.reloadFlagsAndCachedMessage(); - } - } - - /** - * @param {string} query - * @param {Function} autocompleteCallback - */ - getAutocomplete(query, autocompleteCallback) { - Remote.suggestions((iError, data) => { - if (!iError && isArray(data.Result)) { - autocompleteCallback( - data.Result.map(item => (item && item[0] ? new EmailModel(item[0], item[1]) : null)).filter(v => v) - ); - } else if (Notification.RequestAborted !== iError) { - autocompleteCallback([]); - } - }, query); - } - logout() { Remote.request('Logout', () => rl.logoutReload()); } @@ -569,7 +286,7 @@ class AppUser extends AbstractApp { SettingsUserStore.init(); ContactUserStore.init(); - Remote.foldersReload(value => { + loadFolders(value => { try { if (value) { startScreens([ @@ -584,8 +301,8 @@ class AppUser extends AbstractApp { if (iF !== cF) { this.folderInformation(cF); } - this.folderInformationMultiply(); - }, refreshFolders); + folderInformationMultiply(); + }, refreshFoldersInterval); ContactUserStore.init(); @@ -596,7 +313,7 @@ class AppUser extends AbstractApp { if (getFolderInboxName() !== cF) { this.folderInformation(cF); } - FolderUserStore.hasCapability('LIST-STATUS') || this.folderInformationMultiply(true); + FolderUserStore.hasCapability('LIST-STATUS') || folderInformationMultiply(true); }, 1000); setTimeout(() => Remote.request('AppDelayStart'), 35000); @@ -639,7 +356,7 @@ class AppUser extends AbstractApp { } }, 1); - setInterval(this.reloadTime(), 60000); + setInterval(reloadTime(), 60000); PgpUserStore.init(); } else { @@ -655,13 +372,6 @@ class AppUser extends AbstractApp { } } - reloadTime() - { - setTimeout(() => - doc.querySelectorAll('time').forEach(element => timeToNode(element)) - , 1) - } - showMessageComposer(params = []) { showScreenPopup(ComposePopupView, params); diff --git a/dev/Common/Folders.js b/dev/Common/Folders.js new file mode 100644 index 000000000..fa59abfe0 --- /dev/null +++ b/dev/Common/Folders.js @@ -0,0 +1,221 @@ +import { isArray, arrayLength } from 'Common/Utils'; +import { + MessageFlagsCache, + setFolderHash, + getFolderHash, + getFolderInboxName, + getFolderFromCacheList +} from 'Common/Cache'; +import { SettingsUserStore } from 'Stores/User/Settings'; +import { FolderUserStore } from 'Stores/User/Folder'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; +import { getNotification } from 'Common/Translator'; + +import Remote from 'Remote/User/Fetch'; + +export const + +sortFolders = folders => { + try { + let collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); + folders.sort((a, b) => + a.isInbox() ? -1 : (b.isInbox() ? 1 : collator.compare(a.fullName, b.fullName)) + ); + } catch (e) { + console.error(e); + } +}, + +/** + * @param {Array=} aDisabled + * @param {Array=} aHeaderLines + * @param {Function=} fDisableCallback + * @param {Function=} fRenameCallback + * @param {boolean=} bNoSelectSelectable Used in FolderCreatePopupView + * @returns {Array} + */ +folderListOptionsBuilder = ( + aDisabled, + aHeaderLines, + fRenameCallback, + fDisableCallback, + bNoSelectSelectable, + aList = FolderUserStore.folderList() +) => { + const + aResult = [], + sDeepPrefix = '\u00A0\u00A0\u00A0', + // FolderSystemPopupView should always be true + showUnsubscribed = fRenameCallback ? !SettingsUserStore.hideUnsubscribed() : true, + + foldersWalk = folders => { + folders.forEach(oItem => { + if (showUnsubscribed || oItem.hasSubscriptions() || !oItem.exists) { + aResult.push({ + id: oItem.fullName, + name: + sDeepPrefix.repeat(oItem.deep) + + fRenameCallback(oItem), + system: false, + disabled: !bNoSelectSelectable && ( + !oItem.selectable() || + aDisabled.includes(oItem.fullName) || + fDisableCallback(oItem)) + }); + } + + if (oItem.subFolders.length) { + foldersWalk(oItem.subFolders()); + } + }); + }; + + + fDisableCallback = fDisableCallback || (() => false); + fRenameCallback = fRenameCallback || (oItem => oItem.name()); + isArray(aDisabled) || (aDisabled = []); + + isArray(aHeaderLines) && aHeaderLines.forEach(line => + aResult.push({ + id: line[0], + name: line[1], + system: false, + disabled: false + }) + ); + + foldersWalk(aList); + + return aResult; +}, + +// Every 5 minutes +refreshFoldersInterval = 300000, + +/** + * @param {boolean=} boot = false + */ +folderInformationMultiply = (boot = false) => { + const folders = FolderUserStore.getNextFolderNames(refreshFoldersInterval); + if (arrayLength(folders)) { + Remote.request('FolderInformationMultiply', (iError, oData) => { + if (!iError && arrayLength(oData.Result)) { + const utc = Date.now(); + oData.Result.forEach(item => { + const hash = getFolderHash(item.Folder), + folder = getFolderFromCacheList(item.Folder); + + if (folder) { + folder.expires = utc; + + setFolderHash(item.Folder, item.Hash); + + folder.messageCountAll(item.MessageCount); + + let unreadCountChange = folder.messageCountUnread() !== item.MessageUnseenCount; + + folder.messageCountUnread(item.MessageUnseenCount); + + if (unreadCountChange) { + MessageFlagsCache.clearFolder(folder.fullName); + } + + if (!hash || item.Hash !== hash) { + if (folder.fullName === FolderUserStore.currentFolderFullName()) { + MessagelistUserStore.reload(); + } + } else if (unreadCountChange + && folder.fullName === FolderUserStore.currentFolderFullName() + && MessagelistUserStore.length) { + rl.app.folderInformation(folder.fullName, MessagelistUserStore()); + } + } + }); + + if (boot) { + setTimeout(() => folderInformationMultiply(true), 2000); + } + } + }, { + Folders: folders + }); + } +}, + +moveOrDeleteResponseHelper = (iError, oData) => { + if (iError) { + setFolderHash(FolderUserStore.currentFolderFullName(), ''); + alert(getNotification(iError)); + } else if (FolderUserStore.currentFolder()) { + if (2 === arrayLength(oData.Result)) { + setFolderHash(oData.Result[0], oData.Result[1]); + } else { + setFolderHash(FolderUserStore.currentFolderFullName(), ''); + } + MessagelistUserStore.reload(!MessagelistUserStore.length); + } +}, + +messagesMoveHelper = (fromFolderFullName, toFolderFullName, uidsForMove) => { + const + sSpamFolder = FolderUserStore.spamFolder(), + isSpam = sSpamFolder === toFolderFullName, + isHam = !isSpam && sSpamFolder === fromFolderFullName && getFolderInboxName() === toFolderFullName; + + Remote.request('MessageMove', + moveOrDeleteResponseHelper, + { + FromFolder: fromFolderFullName, + ToFolder: toFolderFullName, + Uids: uidsForMove.join(','), + MarkAsRead: (isSpam || FolderUserStore.trashFolder() === toFolderFullName) ? 1 : 0, + Learning: isSpam ? 'SPAM' : isHam ? 'HAM' : '' + }, + null, + '', + ['MessageList'] + ); +}, + +messagesDeleteHelper = (sFromFolderFullName, aUidForRemove) => { + Remote.request('MessageDelete', + moveOrDeleteResponseHelper, + { + Folder: sFromFolderFullName, + Uids: aUidForRemove.join(',') + }, + null, + '', + ['MessageList'] + ); +}, + +/** + * @param {string} sFromFolderFullName + * @param {Array} aUidForMove + * @param {string} sToFolderFullName + * @param {boolean=} bCopy = false + */ +moveMessagesToFolder = (sFromFolderFullName, aUidForMove, sToFolderFullName, bCopy) => { + if (sFromFolderFullName !== sToFolderFullName && arrayLength(aUidForMove)) { + const oFromFolder = getFolderFromCacheList(sFromFolderFullName), + oToFolder = getFolderFromCacheList(sToFolderFullName); + + if (oFromFolder && oToFolder) { + if (bCopy) { + Remote.request('MessageCopy', null, { + FromFolder: oFromFolder.fullName, + ToFolder: oToFolder.fullName, + Uids: aUidForMove.join(',') + }); + } else { + messagesMoveHelper(oFromFolder.fullName, oToFolder.fullName, aUidForMove); + } + + MessagelistUserStore.removeMessagesFromList(oFromFolder.fullName, aUidForMove, oToFolder.fullName, bCopy); + return true; + } + } + + return false; +}; diff --git a/dev/Common/Momentor.js b/dev/Common/Momentor.js deleted file mode 100644 index a6c970130..000000000 --- a/dev/Common/Momentor.js +++ /dev/null @@ -1,55 +0,0 @@ -import { i18n } from 'Common/Translator'; - -export function timestampToString(timeStampInUTC, formatStr) { - const now = Date.now(), - time = 0 < timeStampInUTC ? Math.min(now, timeStampInUTC * 1000) : (0 === timeStampInUTC ? now : 0); - - if (31536000000 < time) { - const m = new Date(time); - switch (formatStr) { - case 'FROMNOW': - return m.fromNow(); - case 'SHORT': { - if (4 >= (now - time) / 3600000) - return m.fromNow(); - const mt = m.getTime(), date = new Date, - dt = date.setHours(0,0,0,0); - if (mt > dt) - return i18n('MESSAGE_LIST/TODAY_AT', {TIME: m.format('LT')}); - if (mt > dt - 86400000) - return i18n('MESSAGE_LIST/YESTERDAY_AT', {TIME: m.format('LT')}); - if (date.getFullYear() === m.getFullYear()) - return m.format('d M'); - return m.format('LL'); - } - case 'FULL': - return m.format('LLL'); - default: - return m.format(formatStr); - } - } - - return ''; -} - -export function timeToNode(element, time) { - try { - if (time) { - element.dateTime = (new Date(time * 1000)).format('Y-m-d\\TH:i:s'); - } else { - time = Date.parse(element.dateTime) / 1000; - } - - let key = element.dataset.momentFormat; - if (key) { - element.textContent = timestampToString(time, key); - } - - if ((key = element.dataset.momentFormatTitle)) { - element.title = timestampToString(time, key); - } - } catch (e) { - // prevent knockout crashes - console.error(e); - } -} diff --git a/dev/Common/Translator.js b/dev/Common/Translator.js index d34ac40bd..ffe7c770d 100644 --- a/dev/Common/Translator.js +++ b/dev/Common/Translator.js @@ -82,6 +82,64 @@ export const element.querySelectorAll('[data-i18n]').forEach(item => i18nToNode(item)) , 1), + timestampToString = (timeStampInUTC, formatStr) => { + const now = Date.now(), + time = 0 < timeStampInUTC ? Math.min(now, timeStampInUTC * 1000) : (0 === timeStampInUTC ? now : 0); + + if (31536000000 < time) { + const m = new Date(time); + switch (formatStr) { + case 'FROMNOW': + return m.fromNow(); + case 'SHORT': { + if (4 >= (now - time) / 3600000) + return m.fromNow(); + const mt = m.getTime(), date = new Date, + dt = date.setHours(0,0,0,0); + if (mt > dt) + return i18n('MESSAGE_LIST/TODAY_AT', {TIME: m.format('LT')}); + if (mt > dt - 86400000) + return i18n('MESSAGE_LIST/YESTERDAY_AT', {TIME: m.format('LT')}); + if (date.getFullYear() === m.getFullYear()) + return m.format('d M'); + return m.format('LL'); + } + case 'FULL': + return m.format('LLL'); + default: + return m.format(formatStr); + } + } + + return ''; + }, + + timeToNode = (element, time) => { + try { + if (time) { + element.dateTime = (new Date(time * 1000)).format('Y-m-d\\TH:i:s'); + } else { + time = Date.parse(element.dateTime) / 1000; + } + + let key = element.dataset.momentFormat; + if (key) { + element.textContent = timestampToString(time, key); + } + + if ((key = element.dataset.momentFormatTitle)) { + element.title = timestampToString(time, key); + } + } catch (e) { + // prevent knockout crashes + console.error(e); + } + }, + + reloadTime = () => setTimeout(() => + doc.querySelectorAll('time').forEach(element => timeToNode(element)) + , 1), + /** * @param {Function} startCallback * @param {Function=} langCallback = null @@ -129,7 +187,7 @@ export const // reload the data if (init()) { i18nToNodes(doc); - admin || rl.app.reloadTime(); + admin || reloadTime(); trigger(!trigger()); } script.remove(); diff --git a/dev/Common/UtilsUser.js b/dev/Common/UtilsUser.js index d9fd01e98..b366cd158 100644 --- a/dev/Common/UtilsUser.js +++ b/dev/Common/UtilsUser.js @@ -1,26 +1,18 @@ -import { ComposeType/*, FolderType*/ } from 'Common/EnumsUser'; +import { MessageFlagsCache, addRequestedMessage } from 'Common/Cache'; +import { MessageSetAction, ComposeType/*, FolderType*/ } from 'Common/EnumsUser'; +import { doc, createElement, elementById } from 'Common/Globals'; +import { plainToHtml } from 'Common/Html'; +import { getNotification } from 'Common/Translator'; import { EmailModel } from 'Model/Email'; -import { isArray } from 'Common/Utils'; -import { doc, createElement } from 'Common/Globals'; -import { FolderUserStore } from 'Stores/User/Folder'; +import { MessageModel } from 'Model/Message'; +import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { SettingsUserStore } from 'Stores/User/Settings'; import * as Local from 'Storage/Client'; -import { plainToHtml } from 'Common/Html'; import { ThemeStore } from 'Stores/Theme'; export const -sortFolders = folders => { - try { - let collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'}); - folders.sort((a, b) => - a.isInbox() ? -1 : (b.isInbox() ? 1 : collator.compare(a.fullName, b.fullName)) - ); - } catch (e) { - console.error(e); - } -}, - /** * @param {string} link * @returns {boolean} @@ -39,69 +31,6 @@ download = (link, name = "") => { } }, -/** - * @param {Array=} aDisabled - * @param {Array=} aHeaderLines - * @param {Function=} fDisableCallback - * @param {Function=} fRenameCallback - * @param {boolean=} bNoSelectSelectable Used in FolderCreatePopupView - * @returns {Array} - */ -folderListOptionsBuilder = ( - aDisabled, - aHeaderLines, - fRenameCallback, - fDisableCallback, - bNoSelectSelectable, - aList = FolderUserStore.folderList() -) => { - const - aResult = [], - sDeepPrefix = '\u00A0\u00A0\u00A0', - // FolderSystemPopupView should always be true - showUnsubscribed = fRenameCallback ? !SettingsUserStore.hideUnsubscribed() : true, - - foldersWalk = folders => { - folders.forEach(oItem => { - if (showUnsubscribed || oItem.hasSubscriptions() || !oItem.exists) { - aResult.push({ - id: oItem.fullName, - name: - sDeepPrefix.repeat(oItem.deep) + - fRenameCallback(oItem), - system: false, - disabled: !bNoSelectSelectable && ( - !oItem.selectable() || - aDisabled.includes(oItem.fullName) || - fDisableCallback(oItem)) - }); - } - - if (oItem.subFolders.length) { - foldersWalk(oItem.subFolders()); - } - }); - }; - - - fDisableCallback = fDisableCallback || (() => false); - fRenameCallback = fRenameCallback || (oItem => oItem.name()); - isArray(aDisabled) || (aDisabled = []); - - isArray(aHeaderLines) && aHeaderLines.forEach(line => - aResult.push({ - id: line[0], - name: line[1], - system: false, - disabled: false - }) - ); - - foldersWalk(aList); - - return aResult; -}, - /** * @returns {function} */ @@ -334,4 +263,123 @@ setLayoutResizer = (source, target, sClientSideKeyName, mode) => } else { source.observer && source.observer.disconnect(); } +}, + +populateMessageBody = (oMessage, preload) => { + if (oMessage) { + preload || MessageUserStore.hideMessageBodies(); + preload || MessageUserStore.loading(true); + rl.app.Remote.message((iError, oData/*, bCached*/) => { + if (iError) { + if (Notification.RequestAborted !== iError && !preload) { + MessageUserStore.message(null); + MessageUserStore.error(getNotification(iError)); + } + } else { + oMessage = preload ? oMessage : null; + let + isNew = false, + json = oData && oData.Result, + message = oMessage || MessageUserStore.message(); + + if ( + json && + MessageModel.validJson(json) && + message && + message.folder === json.Folder + ) { + const threads = message.threads(), + messagesDom = MessageUserStore.bodiesDom(); + if (!oMessage && message.uid != json.Uid && threads.includes(json.Uid)) { + message = MessageModel.reviveFromJson(json); + if (message) { + message.threads(threads); + MessageFlagsCache.initMessage(message); + + // Set clone + MessageUserStore.message(MessageModel.fromMessageListItem(message)); + message = MessageUserStore.message(); + + isNew = true; + } + } + + if (message && message.uid == json.Uid) { + oMessage || MessageUserStore.error(''); +/* + if (bCached) { + delete json.Flags; + } +*/ + isNew || message.revivePropertiesFromJson(json); + addRequestedMessage(message.folder, message.uid); + if (messagesDom) { + let id = 'rl-msg-' + message.hash.replace(/[^a-zA-Z0-9]/g, ''), + body = elementById(id); + if (body) { + message.body = body; + message.isHtml(body.classList.contains('html')); + message.hasImages(body.rlHasImages); + } else { + body = Element.fromHTML('
'); + message.body = body; + if (!SettingsUserStore.viewHTML() || !message.viewHtml()) { + message.viewPlain(); + } + + MessageUserStore.purgeMessageBodyCache(); + } + + messagesDom.append(body); + + if (!oMessage) { + MessageUserStore.activeDom(message.body); + MessageUserStore.hideMessageBodies(); + message.body.hidden = false; + } + oMessage && message.viewPopupMessage(); + } + + MessageFlagsCache.initMessage(message); + if (message.isUnseen()) { + MessageUserStore.MessageSeenTimer = setTimeout( + () => MessagelistUserStore.setAction(message.folder, MessageSetAction.SetSeen, [message]), + SettingsUserStore.messageReadDelay() * 1000 // seconds + ); + } + + if (message && isNew) { + let selectedMessage = MessagelistUserStore.selectedMessage(); + if ( + selectedMessage && + (message.folder !== selectedMessage.folder || message.uid != selectedMessage.uid) + ) { + MessagelistUserStore.selectedMessage(null); + if (1 === MessagelistUserStore.length) { + MessagelistUserStore.focusedMessage(null); + } + } else if (!selectedMessage) { + selectedMessage = MessagelistUserStore.find( + subMessage => + subMessage && + subMessage.folder === message.folder && + subMessage.uid == message.uid + ); + + if (selectedMessage) { + MessagelistUserStore.selectedMessage(selectedMessage); + MessagelistUserStore.focusedMessage(selectedMessage); + } + } + } + } + } + } + preload || MessageUserStore.loading(false); + }, oMessage.folder, oMessage.uid); + } }; diff --git a/dev/External/User/ko.js b/dev/External/User/ko.js index 8c6dae0bc..d5897ed7a 100644 --- a/dev/External/User/ko.js +++ b/dev/External/User/ko.js @@ -1,11 +1,12 @@ import 'External/ko'; import ko from 'ko'; import { HtmlEditor } from 'Common/Html'; -import { timeToNode } from 'Common/Momentor'; +import { timeToNode } from 'Common/Translator'; import { elementById } from 'Common/Globals'; import { isArray } from 'Common/Utils'; import { EmailAddressesComponent } from 'Component/EmailAddresses'; import { ThemeStore } from 'Stores/Theme'; +import { moveMessagesToFolder } from 'Common/Folders'; const rlContentType = 'snappymail/action', @@ -145,7 +146,7 @@ ko.bindingHandlers.dropmessages = { if ('messages' === getDragAction(e) && ['move','copy'].includes(e.dataTransfer.effectAllowed)) { let data = dragData.data; if (folder && data && data.folder && isArray(data.uids)) { - rl.app.moveMessagesToFolder(data.folder, data.uids, folder.fullName, data.copy && e.ctrlKey); + moveMessagesToFolder(data.folder, data.uids, folder.fullName, data.copy && e.ctrlKey); } } }); diff --git a/dev/External/ko.js b/dev/External/ko.js index 4a247e7b2..3eba5045c 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -1,3 +1,4 @@ +import ko from 'ko'; import { i18nToNodes } from 'Common/Translator'; import { doc, createElement } from 'Common/Globals'; import { SaveSettingsStep } from 'Common/Enums'; diff --git a/dev/Model/FolderCollection.js b/dev/Model/FolderCollection.js index 809bba185..a53c024f5 100644 --- a/dev/Model/FolderCollection.js +++ b/dev/Model/FolderCollection.js @@ -10,12 +10,12 @@ import * as Local from 'Storage/Client'; import { AppUserStore } from 'Stores/User/App'; import { FolderUserStore } from 'Stores/User/Folder'; -import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { SettingsUserStore } from 'Stores/User/Settings'; import ko from 'ko'; -import { sortFolders } from 'Common/UtilsUser'; +import { sortFolders } from 'Common/Folders'; import { i18n, trigger as translatorTrigger } from 'Common/Translator'; import { AbstractModel } from 'Knoin/AbstractModel'; @@ -24,6 +24,8 @@ import { koComputable } from 'External/ko'; //import { mailBox } from 'Common/Links'; +import Remote from 'Remote/User/Fetch'; + const isPosNumeric = value => null != value && /^[0-9]*$/.test(value.toString()), @@ -40,6 +42,51 @@ const Spam: 0, Trash: 0, Archive: 0 + }, + + kolabTypes = { + configuration: 'CONFIGURATION', + event: 'CALENDAR', + contact: 'CONTACTS', + task: 'TASKS', + note: 'NOTES', + file: 'FILES', + journal: 'JOURNAL' + }, + + getKolabFolderName = type => kolabTypes[type] ? 'Kolab ' + i18n('SETTINGS_FOLDERS/TYPE_' + kolabTypes[type]) : '', + + getSystemFolderName = (type, def) => { + switch (type) { + case FolderType.Inbox: + case FolderType.Sent: + case FolderType.Drafts: + case FolderType.Trash: + case FolderType.Archive: + return i18n('FOLDER_LIST/' + getKeyByValue(FolderType, type).toUpperCase() + '_NAME'); + case FolderType.Spam: + return i18n('GLOBAL/SPAM'); + // no default + } + return def; + }; + +export const + /** + * @param {?Function} fCallback + */ + loadFolders = fCallback => { +// clearTimeout(this.foldersTimeout); + Remote.abort('Folders') + .post('Folders', FolderUserStore.foldersLoading) + .then(data => { + data = FolderCollectionModel.reviveFromJson(data.Result); + data && data.storeIt(); + fCallback && fCallback(true); + // Repeat every 15 minutes? +// this.foldersTimeout = setTimeout(loadFolders, 900000); + }) + .catch(() => fCallback && setTimeout(fCallback, 1, false)); }; export class FolderCollectionModel extends AbstractCollectionModel @@ -163,36 +210,6 @@ export class FolderCollectionModel extends AbstractCollectionModel } -function getKolabFolderName(type) -{ - const types = { - configuration: 'CONFIGURATION', - event: 'CALENDAR', - contact: 'CONTACTS', - task: 'TASKS', - note: 'NOTES', - file: 'FILES', - journal: 'JOURNAL' - }; - return types[type] ? 'Kolab ' + i18n('SETTINGS_FOLDERS/TYPE_' + types[type]) : ''; -} - -function getSystemFolderName(type, def) -{ - switch (type) { - case FolderType.Inbox: - case FolderType.Sent: - case FolderType.Drafts: - case FolderType.Trash: - case FolderType.Archive: - return i18n('FOLDER_LIST/' + getKeyByValue(FolderType, type).toUpperCase() + '_NAME'); - case FolderType.Spam: - return i18n('GLOBAL/SPAM'); - // no default - } - return def; -} - export class FolderModel extends AbstractModel { constructor() { super(); @@ -296,7 +313,7 @@ export class FolderModel extends AbstractModel { isInbox: () => FolderType.Inbox === folder.type(), isFlagged: () => FolderUserStore.currentFolder() === folder - && MessageUserStore.listSearch().includes('flagged'), + && MessagelistUserStore.listSearch().includes('flagged'), hasVisibleSubfolders: () => !!folder.subFolders().find(folder => folder.visible()), diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 1f4a08d18..d5a1c978f 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -532,52 +532,52 @@ export class MessageModel extends AbstractModel { * @param {MessageModel} message * @returns {MessageModel} */ - populateByMessageListItem(message) { - this.clear(); + static fromMessageListItem(message) { + let self = new MessageModel(); if (message) { - this.folder = message.folder; - this.uid = message.uid; - this.hash = message.hash; - this.requestHash = message.requestHash; - this.subject(message.subject()); - this.plain(message.plain()); - this.html(message.html()); + self.folder = message.folder; + self.uid = message.uid; + self.hash = message.hash; + self.requestHash = message.requestHash; + self.subject(message.subject()); + self.plain(message.plain()); + self.html(message.html()); - this.size(message.size()); - this.spamScore(message.spamScore()); - this.spamResult(message.spamResult()); - this.isSpam(message.isSpam()); - this.hasVirus(message.hasVirus()); - this.dateTimeStampInUTC(message.dateTimeStampInUTC()); - this.priority(message.priority()); + self.size(message.size()); + self.spamScore(message.spamScore()); + self.spamResult(message.spamResult()); + self.isSpam(message.isSpam()); + self.hasVirus(message.hasVirus()); + self.dateTimeStampInUTC(message.dateTimeStampInUTC()); + self.priority(message.priority()); - this.hasExternals(message.hasExternals()); + self.hasExternals(message.hasExternals()); - this.emails = message.emails; + self.emails = message.emails; - this.from = message.from; - this.to = message.to; - this.cc = message.cc; - this.bcc = message.bcc; - this.replyTo = message.replyTo; - this.deliveredTo = message.deliveredTo; - this.unsubsribeLinks(message.unsubsribeLinks); + self.from = message.from; + self.to = message.to; + self.cc = message.cc; + self.bcc = message.bcc; + self.replyTo = message.replyTo; + self.deliveredTo = message.deliveredTo; + self.unsubsribeLinks(message.unsubsribeLinks); - this.flags(message.flags()); + self.flags(message.flags()); - this.priority(message.priority()); + self.priority(message.priority()); - this.selected(message.selected()); - this.checked(message.checked()); - this.attachments(message.attachments()); + self.selected(message.selected()); + self.checked(message.checked()); + self.attachments(message.attachments()); - this.threads(message.threads()); + self.threads(message.threads()); } - this.computeSenderEmail(); + self.computeSenderEmail(); - return this; + return self; } showExternalImages() { diff --git a/dev/Remote/User/Fetch.js b/dev/Remote/User/Fetch.js index ca70ad472..8f95671e6 100644 --- a/dev/Remote/User/Fetch.js +++ b/dev/Remote/User/Fetch.js @@ -16,9 +16,7 @@ import { FolderUserStore } from 'Stores/User/Folder'; import { AbstractFetchRemote } from 'Remote/AbstractFetch'; -import { FolderCollectionModel } from 'Model/FolderCollection'; - -import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; class RemoteUserFetch extends AbstractFetchRemote { @@ -132,7 +130,7 @@ class RemoteUserFetch extends AbstractFetchRemote { UidNext: getFolderUidNext(folder) // Used to check for new messages }); } else if (SettingsUserStore.useThreads()) { - MessageUserStore.reloadFlagsAndCachedMessage(); + MessagelistUserStore.reloadFlagsAndCachedMessage(); } } @@ -200,23 +198,6 @@ class RemoteUserFetch extends AbstractFetchRemote { ); } - /** - * @param {?Function} fCallback - */ - foldersReload(fCallback) { -// clearTimeout(this.foldersTimeout); - this.abort('Folders') - .post('Folders', FolderUserStore.foldersLoading) - .then(data => { - data = FolderCollectionModel.reviveFromJson(data.Result); - data && data.storeIt(); - fCallback && fCallback(true); - // Repeat every 15 minutes? -// this.foldersTimeout = setTimeout(() => this.foldersReload(), 900000); - }) - .catch(() => fCallback && setTimeout(fCallback, 1, false)); - } - /* folderMove(sPrevFolderFullName, sNewFolderFullName, bSubscribe) { return this.post('FolderMove', FolderUserStore.foldersRenaming, { diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index 9736513f4..211a45af5 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -10,7 +10,7 @@ import { SettingsUserStore } from 'Stores/User/Settings'; import { AppUserStore } from 'Stores/User/App'; import { AccountUserStore } from 'Stores/User/Account'; import { FolderUserStore } from 'Stores/User/Folder'; -import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { ThemeStore } from 'Stores/Theme'; import { SystemDropDownUserView } from 'View/User/SystemDropDown'; @@ -70,11 +70,11 @@ export class MailBoxUserScreen extends AbstractScreen { FolderUserStore.currentFolder(folder); - MessageUserStore.listPage(1 > page ? 1 : page); - MessageUserStore.listSearch(search); - MessageUserStore.listThreadUid((folderHash === threadUid) ? 0 : pInt(threadUid)); + MessagelistUserStore.page(1 > page ? 1 : page); + MessagelistUserStore.listSearch(search); + MessagelistUserStore.threadUid((folderHash === threadUid) ? 0 : pInt(threadUid)); - rl.app.reloadMessageList(); + MessagelistUserStore.reload(); } } diff --git a/dev/Settings/User/Folders.js b/dev/Settings/User/Folders.js index 351096e15..fbafe0918 100644 --- a/dev/Settings/User/Folders.js +++ b/dev/Settings/User/Folders.js @@ -9,7 +9,7 @@ import { getNotification } from 'Common/Translator'; import { setFolder, getFolderFromCacheList, removeFolderFromCacheList } from 'Common/Cache'; import { Capa } from 'Common/Enums'; import { defaultOptionsAfterRender } from 'Common/Utils'; -import { sortFolders } from 'Common/UtilsUser'; +import { sortFolders } from 'Common/Folders'; import { initOnStartOrLangChange, i18n } from 'Common/Translator'; import { FolderUserStore } from 'Stores/User/Folder'; @@ -21,6 +21,7 @@ import { showScreenPopup } from 'Knoin/Knoin'; import { FolderCreatePopupView } from 'View/Popup/FolderCreate'; import { FolderSystemPopupView } from 'View/Popup/FolderSystem'; +import { loadFolders } from 'Model/FolderCollection'; const folderForDeletion = ko.observable(null).askDeleteHelper(); @@ -79,8 +80,8 @@ export class FoldersUserSettings /*extends AbstractViewSettings*/ { if (folder.subFolders.length) { Remote.setTrigger(FolderUserStore.foldersLoading, true); // clearTimeout(Remote.foldersTimeout); -// Remote.foldersTimeout = setTimeout(() => Remote.foldersReload(), 500); - setTimeout(() => Remote.foldersReload(), 500); +// Remote.foldersTimeout = setTimeout(loadFolders, 500); + setTimeout(loadFolders, 500); // TODO: rename all subfolders with folder.delimiter to prevent reload? } else { removeFolderFromCacheList(folder.fullName); diff --git a/dev/Settings/User/General.js b/dev/Settings/User/General.js index 140ab1978..57eefefb6 100644 --- a/dev/Settings/User/General.js +++ b/dev/Settings/User/General.js @@ -15,6 +15,7 @@ import { SettingsUserStore } from 'Stores/User/Settings'; import { IdentityUserStore } from 'Stores/User/Identity'; import { NotificationUserStore } from 'Stores/User/Notification'; import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import Remote from 'Remote/User/Fetch'; @@ -117,7 +118,7 @@ export class GeneralUserSettings /*extends AbstractViewSettings*/ { showImages: value => Remote.saveSetting('ShowImages', value ? 1 : 0), removeColors: value => { - let dom = MessageUserStore.messagesBodiesDom(); + let dom = MessageUserStore.bodiesDom(); if (dom) { dom.innerHTML = ''; } @@ -137,12 +138,12 @@ export class GeneralUserSettings /*extends AbstractViewSettings*/ { replySameFolder: value => Remote.saveSetting('ReplySameFolder', value ? 1 : 0), useThreads: value => { - MessageUserStore.list([]); + MessagelistUserStore([]); Remote.saveSetting('UseThreads', value ? 1 : 0); }, layout: value => { - MessageUserStore.list([]); + MessagelistUserStore([]); Remote.saveSetting('Layout', value, settingsSaveHelperSimpleFunction(this.layoutTrigger, this)); } }); diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index 60b5929ef..d1c51862b 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -7,7 +7,7 @@ import { forEachObjectEntry } from 'Common/Utils'; import { addObservablesTo, addSubscribablesTo, addComputablesTo } from 'External/ko'; import { getFolderInboxName, getFolderFromCacheList } from 'Common/Cache'; import { Settings } from 'Common/Globals'; -//import Remote from 'Remote/User/Fetch'; Circular dependency +//import Remote from 'Remote/User/Fetch'; // Circular dependency export const FolderUserStore = new class { constructor() { diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 5209cac08..4bbbcd6df 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -1,137 +1,30 @@ -import ko from 'ko'; -import { koComputable } from 'External/ko'; - -import { Scope, Notification } from 'Common/Enums'; -import { MessageSetAction } from 'Common/EnumsUser'; -import { $htmlCL, elementById } from 'Common/Globals'; -import { arrayLength, pInt, pString } from 'Common/Utils'; -import { addObservablesTo, addComputablesTo, addSubscribablesTo } from 'External/ko'; - -import { - getFolderInboxName, - addNewMessageCache, - setFolderUidNext, - getFolderFromCacheList, - setFolderHash, - MessageFlagsCache, - addRequestedMessage, - clearNewMessageCache -} from 'Common/Cache'; - -import { mailBox } from 'Common/Links'; -import { i18n, getNotification } from 'Common/Translator'; - -import { EmailCollectionModel } from 'Model/EmailCollection'; -import { MessageModel } from 'Model/Message'; -import { MessageCollectionModel } from 'Model/MessageCollection'; +import { Scope } from 'Common/Enums'; +import { elementById } from 'Common/Globals'; +import { addObservablesTo, addSubscribablesTo } from 'External/ko'; import { AppUserStore } from 'Stores/User/App'; -import { AccountUserStore } from 'Stores/User/Account'; -import { FolderUserStore } from 'Stores/User/Folder'; import { SettingsUserStore } from 'Stores/User/Settings'; -import { NotificationUserStore } from 'Stores/User/Notification'; - -//import Remote from 'Remote/User/Fetch'; Circular dependency - -const - isChecked = item => item.checked(); - -let MessageSeenTimer; export const MessageUserStore = new class { constructor() { - this.staticMessage = new MessageModel(); - - this.list = ko.observableArray().extend({ debounce: 0 }); - addObservablesTo(this, { - listCount: 0, - listSearch: '', - listThreadUid: 0, - listPage: 1, - listPageBeforeThread: 1, - listError: '', - - listEndHash: '', - listEndThreadUid: 0, - - listLoading: false, - // Happens when message(s) removed from list - listIsIncomplete: false, - - selectorMessageSelected: null, - selectorMessageFocused: null, - // message viewer message: null, - messageViewTrigger: false, - messageError: '', - messageLoading: false, - messageFullScreenMode: false, + error: '', + loading: false, + fullScreen: false, // Cache mail bodies - messagesBodiesDom: null, - messageActiveDom: null + bodiesDom: null, + activeDom: null }); - this.listDisableAutoSelect = ko.observable(false).extend({ falseTimeout: 500 }); - - // Computed Observables - - addComputablesTo(this, { - listIsLoading: () => { - const value = this.listLoading() | this.listIsIncomplete(); - $htmlCL.toggle('list-loading', value); - return value; - }, - - listPageCount: () => Math.max(1, Math.ceil(this.listCount() / SettingsUserStore.messagesPerPage())), - - mainMessageListSearch: { - read: this.listSearch, - write: value => rl.route.setHash( - mailBox(FolderUserStore.currentFolderFullNameHash(), 1, value.toString().trim(), this.listThreadUid()) - ) - }, - - isMessageSelected: () => null !== this.message(), - - listCheckedOrSelected: () => { - const - selectedMessage = this.selectorMessageSelected(), - focusedMessage = this.selectorMessageFocused(), - checked = this.list.filter(item => isChecked(item) || item === selectedMessage); - return checked.length ? checked : (focusedMessage ? [focusedMessage] : []); - }, - - listCheckedOrSelectedUidsWithSubMails: () => { - let result = []; - this.listCheckedOrSelected().forEach(message => { - result.push(message.uid); - if (1 < message.threadsLen()) { - result = result.concat(message.threads()).unique(); - } - }); - return result; - } - }); - - this.listChecked = koComputable(() => this.list.filter(isChecked)) - .extend({ rateLimit: 0 }); - - this.hasCheckedMessages = koComputable(() => !!this.list.find(isChecked)) - .extend({ rateLimit: 0 }); - - this.hasCheckedOrSelected = koComputable(() => !!(this.selectorMessageSelected() - || this.selectorMessageFocused() - || this.list.find(item => item.checked()))) - .extend({ rateLimit: 50 }); - // Subscribers addSubscribablesTo(this, { message: message => { - clearTimeout(MessageSeenTimer); + clearTimeout(this.MessageSeenTimer); + elementById('rl-right').classList.toggle('message-selected', !!message); if (message) { if (!SettingsUserStore.usePreviewPane()) { AppUserStore.focusedState(Scope.MessageView); @@ -139,19 +32,17 @@ export const MessageUserStore = new class { } else { AppUserStore.focusedState(Scope.MessageList); - this.messageFullScreenMode(false); + this.fullScreen(false); this.hideMessageBodies(); } }, - - isMessageSelected: value => elementById('rl-right').classList.toggle('message-selected', value) }); this.purgeMessageBodyCache = this.purgeMessageBodyCache.throttle(30000); } purgeMessageBodyCache() { - const messagesDom = this.messagesBodiesDom(), + const messagesDom = this.bodiesDom(), children = messagesDom && messagesDom.children; if (children) { while (15 < children.length) { @@ -160,390 +51,8 @@ export const MessageUserStore = new class { } } - initUidNextAndNewMessages(folder, uidNext, newMessages) { - if (getFolderInboxName() === folder && uidNext) { - if (arrayLength(newMessages)) { - newMessages.forEach(item => addNewMessageCache(folder, item.Uid)); - - NotificationUserStore.playSoundNotification(); - - const len = newMessages.length; - if (3 < len) { - NotificationUserStore.displayDesktopNotification( - AccountUserStore.email(), - i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', { - COUNT: len - }), - { Url: mailBox(newMessages[0].Folder) } - ); - } else { - newMessages.forEach(item => { - NotificationUserStore.displayDesktopNotification( - EmailCollectionModel.reviveFromJson(item.From).toString(), - item.Subject, - { Folder: item.Folder, Uid: item.Uid } - ); - }); - } - } - - setFolderUidNext(folder, uidNext); - } - } - hideMessageBodies() { - const messagesDom = this.messagesBodiesDom(); + const messagesDom = this.bodiesDom(); messagesDom && Array.from(messagesDom.children).forEach(el => el.hidden = true); } - - /** - * @param {string} fromFolderFullName - * @param {Array} uidForRemove - * @param {string=} toFolderFullName = '' - * @param {boolean=} copy = false - */ - removeMessagesFromList(fromFolderFullName, uidForRemove, toFolderFullName = '', copy = false) { - uidForRemove = uidForRemove.map(mValue => pInt(mValue)); - - let unseenCount = 0, - messageList = this.list, - currentMessage = this.message(); - - const trashFolder = FolderUserStore.trashFolder(), - spamFolder = FolderUserStore.spamFolder(), - fromFolder = getFolderFromCacheList(fromFolderFullName), - toFolder = toFolderFullName ? getFolderFromCacheList(toFolderFullName) : null, - messages = - FolderUserStore.currentFolderFullName() === fromFolderFullName - ? messageList.filter(item => item && uidForRemove.includes(pInt(item.uid))) - : []; - - messages.forEach(item => { - if (item && item.isUnseen()) { - ++unseenCount; - } - }); - - if (fromFolder && !copy) { - fromFolder.messageCountAll( - 0 <= fromFolder.messageCountAll() - uidForRemove.length ? fromFolder.messageCountAll() - uidForRemove.length : 0 - ); - - if (0 < unseenCount) { - fromFolder.messageCountUnread( - 0 <= fromFolder.messageCountUnread() - unseenCount ? fromFolder.messageCountUnread() - unseenCount : 0 - ); - } - } - - if (toFolder) { - if (trashFolder === toFolder.fullName || spamFolder === toFolder.fullName) { - unseenCount = 0; - } - - toFolder.messageCountAll(toFolder.messageCountAll() + uidForRemove.length); - if (0 < unseenCount) { - toFolder.messageCountUnread(toFolder.messageCountUnread() + unseenCount); - } - - toFolder.actionBlink(true); - } - - if (messages.length) { - if (copy) { - messages.forEach(item => item.checked(false)); - } else { - this.listIsIncomplete(true); - - messages.forEach(item => { - if (currentMessage && currentMessage.hash === item.hash) { - currentMessage = null; - this.message(null); - } - - item.deleted(true); - }); - - setTimeout(() => messages.forEach(item => messageList.remove(item)), 350); - } - } - - if (fromFolderFullName) { - setFolderHash(fromFolderFullName, ''); - } - - if (toFolderFullName) { - setFolderHash(toFolderFullName, ''); - } - - if (this.listThreadUid()) { - if ( - messageList.length && - !!messageList.find(item => !!(item && item.deleted() && item.uid == this.listThreadUid())) - ) { - const message = messageList.find(item => item && !item.deleted()); - if (message && this.listThreadUid() != message.uid) { - this.listThreadUid(message.uid); - - rl.route.setHash( - mailBox( - FolderUserStore.currentFolderFullNameHash(), - this.listPage(), - this.listSearch(), - this.listThreadUid() - ), - true, - true - ); - } else if (!message) { - if (1 < this.listPage()) { - this.listPage(this.listPage() - 1); - - rl.route.setHash( - mailBox( - FolderUserStore.currentFolderFullNameHash(), - this.listPage(), - this.listSearch(), - this.listThreadUid() - ), - true, - true - ); - } else { - this.listThreadUid(0); - - rl.route.setHash( - mailBox( - FolderUserStore.currentFolderFullNameHash(), - this.listPageBeforeThread(), - this.listSearch() - ), - true, - true - ); - } - } - } - } - } - - setMessage(data, cached, oMessage) { - let isNew = false, - json = data && data.Result, - message = oMessage || this.message(); - - if ( - json && - MessageModel.validJson(json) && - message && - message.folder === json.Folder - ) { - const threads = message.threads(), - messagesDom = this.messagesBodiesDom(); - if (!oMessage && message.uid != json.Uid && threads.includes(json.Uid)) { - message = MessageModel.reviveFromJson(json); - if (message) { - message.threads(threads); - MessageFlagsCache.initMessage(message); - - this.message(this.staticMessage.populateByMessageListItem(message)); - message = this.message(); - - isNew = true; - } - } - - if (message && message.uid == json.Uid) { - oMessage || this.messageError(''); -/* - if (cached) { - delete json.Flags; - } -*/ - isNew || message.revivePropertiesFromJson(json); - addRequestedMessage(message.folder, message.uid); - if (messagesDom) { - let id = 'rl-msg-' + message.hash.replace(/[^a-zA-Z0-9]/g, ''), - body = elementById(id); - if (body) { - message.body = body; - message.isHtml(body.classList.contains('html')); - message.hasImages(body.rlHasImages); - } else { - body = Element.fromHTML(' '); - message.body = body; - if (!SettingsUserStore.viewHTML() || !message.viewHtml()) { - message.viewPlain(); - } - - this.purgeMessageBodyCache(); - } - - messagesDom.append(body); - - oMessage || this.messageActiveDom(message.body); - - oMessage || this.hideMessageBodies(); - - oMessage || (message.body.hidden = false); - oMessage && message.viewPopupMessage(); - } - - MessageFlagsCache.initMessage(message); - if (message.isUnseen()) { - MessageSeenTimer = setTimeout( - () => rl.app.messageListAction(message.folder, MessageSetAction.SetSeen, [message]), - SettingsUserStore.messageReadDelay() * 1000 // seconds - ); - } - - if (message && isNew) { - let selectedMessage = this.selectorMessageSelected(); - if ( - selectedMessage && - (message.folder !== selectedMessage.folder || message.uid != selectedMessage.uid) - ) { - this.selectorMessageSelected(null); - if (1 === this.list.length) { - this.selectorMessageFocused(null); - } - } else if (!selectedMessage) { - selectedMessage = this.list.find( - subMessage => - subMessage && - subMessage.folder === message.folder && - subMessage.uid == message.uid - ); - - if (selectedMessage) { - this.selectorMessageSelected(selectedMessage); - this.selectorMessageFocused(selectedMessage); - } - } - } - } - } - } - - selectMessage(oMessage) { - if (oMessage) { - this.message(this.staticMessage.populateByMessageListItem(oMessage)); - this.populateMessageBody(this.message()); - } else { - this.message(null); - } - } - - selectMessageByFolderAndUid(sFolder, iUid) { - if (sFolder && iUid) { - this.message(this.staticMessage.populateByMessageListItem(null)); - this.message().folder = sFolder; - this.message().uid = iUid; - - this.populateMessageBody(this.message()); - } else { - this.message(null); - } - } - - populateMessageBody(oMessage, preload) { - if (oMessage) { - preload || this.hideMessageBodies(); - preload || this.messageLoading(true); - rl.app.Remote.message((iError, oData, bCached) => { - if (iError) { - if (Notification.RequestAborted !== iError && !preload) { - this.message(null); - this.messageError(getNotification(iError)); - } - } else { - this.setMessage(oData, bCached, preload ? oMessage : null); - } - preload || this.messageLoading(false); - }, oMessage.folder, oMessage.uid); - } - } - - /** - * @param {Array} list - * @returns {string} - */ - calculateMessageListHash(list) { - return list.map(message => message.hash + '_' + message.threadsLen() + '_' + message.flagHash()).join( - '|' - ); - } - - reloadFlagsAndCachedMessage() { - this.list.forEach(message => MessageFlagsCache.initMessage(message)); - MessageFlagsCache.initMessage(this.message()); - this.messageViewTrigger(!this.messageViewTrigger()); - } - - setMessageList(data, cached) { - const collection = MessageCollectionModel.reviveFromJson(data.Result, cached); - if (collection) { - let unreadCountChange = false; - - const - folder = getFolderFromCacheList(collection.Folder); - - if (folder && !cached) { - folder.expires = Date.now(); - - setFolderHash(collection.Folder, collection.FolderHash); - - if (null != collection.MessageCount) { - folder.messageCountAll(collection.MessageCount); - } - - if (null != collection.MessageUnseenCount) { - if (pInt(folder.messageCountUnread()) !== pInt(collection.MessageUnseenCount)) { - unreadCountChange = true; - MessageFlagsCache.clearFolder(folder.fullName); - } - - folder.messageCountUnread(collection.MessageUnseenCount); - } - - this.initUidNextAndNewMessages(folder.fullName, collection.UidNext, collection.NewMessages); - } - - this.listCount(collection.MessageResultCount); - this.listSearch(pString(collection.Search)); - this.listPage(Math.ceil(collection.Offset / SettingsUserStore.messagesPerPage() + 1)); - this.listThreadUid(collection.ThreadUid); - - this.listEndHash( - collection.Folder + - '|' + collection.Search + - '|' + this.listThreadUid() + - '|' + this.listPage() - ); - this.listEndThreadUid(collection.ThreadUid); - const message = this.message(); - if (message && collection.Folder !== message.folder) { - this.message(null); - } - - this.listDisableAutoSelect(true); - - this.list(collection); - this.listIsIncomplete(false); - - clearNewMessageCache(); - - if (folder && (cached || unreadCountChange || SettingsUserStore.useThreads())) { - rl.app.folderInformation(folder.fullName, collection); - } - } else { - this.listCount(0); - this.list([]); - this.listError(getNotification(Notification.CantGetMessageList)); - } - } }; diff --git a/dev/Stores/User/Messagelist.js b/dev/Stores/User/Messagelist.js new file mode 100644 index 000000000..2b1890e6d --- /dev/null +++ b/dev/Stores/User/Messagelist.js @@ -0,0 +1,435 @@ +import { koComputable } from 'External/ko'; + +import { MessageSetAction } from 'Common/EnumsUser'; +import { $htmlCL } from 'Common/Globals'; +import { arrayLength, pInt, pString } from 'Common/Utils'; +import { addObservablesTo, addComputablesTo } from 'External/ko'; + +import { + getFolderInboxName, + addNewMessageCache, + setFolderUidNext, + getFolderFromCacheList, + setFolderHash, + MessageFlagsCache, + clearNewMessageCache +} from 'Common/Cache'; + +import { mailBox } from 'Common/Links'; +import { i18n, getNotification } from 'Common/Translator'; + +import { EmailCollectionModel } from 'Model/EmailCollection'; +import { MessageCollectionModel } from 'Model/MessageCollection'; + +import { AccountUserStore } from 'Stores/User/Account'; +import { FolderUserStore } from 'Stores/User/Folder'; +import { MessageUserStore } from 'Stores/User/Message'; +import { NotificationUserStore } from 'Stores/User/Notification'; +import { SettingsUserStore } from 'Stores/User/Settings'; + +//import Remote from 'Remote/User/Fetch'; // Circular dependency + +const + isChecked = item => item.checked(); + +export const MessagelistUserStore = ko.observableArray().extend({ debounce: 0 }); + +addObservablesTo(MessagelistUserStore, { + count: 0, + listSearch: '', + threadUid: 0, + page: 1, + pageBeforeThread: 1, + error: '', + + endHash: '', + endThreadUid: 0, + + loading: false, + // Happens when message(s) removed from list + isIncomplete: false, + + selectedMessage: null, + focusedMessage: null +}); + +MessagelistUserStore.disableAutoSelect = ko.observable(false).extend({ falseTimeout: 500 }); + +// Computed Observables + +addComputablesTo(MessagelistUserStore, { + isLoading: () => { + const value = MessagelistUserStore.loading() | MessagelistUserStore.isIncomplete(); + $htmlCL.toggle('list-loading', value); + return value; + }, + + pageCount: () => Math.max(1, Math.ceil(MessagelistUserStore.count() / SettingsUserStore.messagesPerPage())), + + mainSearch: { + read: MessagelistUserStore.listSearch, + write: value => rl.route.setHash( + mailBox(FolderUserStore.currentFolderFullNameHash(), 1, + value.toString().trim(), MessagelistUserStore.threadUid()) + ) + }, + + listCheckedOrSelected: () => { + const + selectedMessage = MessagelistUserStore.selectedMessage(), + focusedMessage = MessagelistUserStore.focusedMessage(), + checked = MessagelistUserStore.filter(item => isChecked(item) || item === selectedMessage); + return checked.length ? checked : (focusedMessage ? [focusedMessage] : []); + }, + + listCheckedOrSelectedUidsWithSubMails: () => { + let result = []; + MessagelistUserStore.listCheckedOrSelected().forEach(message => { + result.push(message.uid); + if (1 < message.threadsLen()) { + result = result.concat(message.threads()).unique(); + } + }); + return result; + } +}); + +MessagelistUserStore.listChecked = koComputable( + () => MessagelistUserStore.filter(isChecked)).extend({ rateLimit: 0 } +); + +MessagelistUserStore.hasCheckedMessages = koComputable( + () => !!MessagelistUserStore.find(isChecked) +).extend({ rateLimit: 0 }); + +MessagelistUserStore.hasCheckedOrSelected = koComputable(() => + !!(MessagelistUserStore.selectedMessage() + || MessagelistUserStore.focusedMessage() + || MessagelistUserStore.find(item => item.checked())) + ).extend({ rateLimit: 50 }); + +MessagelistUserStore.initUidNextAndNewMessages = (folder, uidNext, newMessages) => { + if (getFolderInboxName() === folder && uidNext) { + if (arrayLength(newMessages)) { + newMessages.forEach(item => addNewMessageCache(folder, item.Uid)); + + NotificationUserStore.playSoundNotification(); + + const len = newMessages.length; + if (3 < len) { + NotificationUserStore.displayDesktopNotification( + AccountUserStore.email(), + i18n('MESSAGE_LIST/NEW_MESSAGE_NOTIFICATION', { + COUNT: len + }), + { Url: mailBox(newMessages[0].Folder) } + ); + } else { + newMessages.forEach(item => { + NotificationUserStore.displayDesktopNotification( + EmailCollectionModel.reviveFromJson(item.From).toString(), + item.Subject, + { Folder: item.Folder, Uid: item.Uid } + ); + }); + } + } + + setFolderUidNext(folder, uidNext); + } +} + +/** + * @param {boolean=} bDropPagePosition = false + * @param {boolean=} bDropCurrenFolderCache = false + */ +MessagelistUserStore.reload = (bDropPagePosition = false, bDropCurrenFolderCache = false) => { + let iOffset = (MessagelistUserStore.page() - 1) * SettingsUserStore.messagesPerPage(); + + if (bDropCurrenFolderCache) { + setFolderHash(FolderUserStore.currentFolderFullName(), ''); + } + + if (bDropPagePosition) { + MessagelistUserStore.page(1); + MessagelistUserStore.pageBeforeThread(1); + iOffset = 0; + + rl.route.setHash( + mailBox( + FolderUserStore.currentFolderFullNameHash(), + MessagelistUserStore.page(), + MessagelistUserStore.listSearch(), + MessagelistUserStore.threadUid() + ), + true, + true + ); + } + + MessagelistUserStore.loading(true); + MessagelistUserStore.error(''); + rl.app.Remote.messageList( + (iError, oData, bCached) => { + if (iError) { + if (Notification.RequestAborted !== iError) { + MessagelistUserStore([]); + MessagelistUserStore.error(getNotification(iError)); + } + } else { + const collection = MessageCollectionModel.reviveFromJson(oData.Result, bCached); + if (collection) { + let unreadCountChange = false; + + const + folder = getFolderFromCacheList(collection.Folder); + + if (folder && !bCached) { + folder.expires = Date.now(); + + setFolderHash(collection.Folder, collection.FolderHash); + + if (null != collection.MessageCount) { + folder.messageCountAll(collection.MessageCount); + } + + if (null != collection.MessageUnseenCount) { + if (pInt(folder.messageCountUnread()) !== pInt(collection.MessageUnseenCount)) { + unreadCountChange = true; + MessageFlagsCache.clearFolder(folder.fullName); + } + + folder.messageCountUnread(collection.MessageUnseenCount); + } + + MessagelistUserStore.initUidNextAndNewMessages(folder.fullName, collection.UidNext, collection.NewMessages); + } + + MessagelistUserStore.count(collection.MessageResultCount); + MessagelistUserStore.listSearch(pString(collection.Search)); + MessagelistUserStore.page(Math.ceil(collection.Offset / SettingsUserStore.messagesPerPage() + 1)); + MessagelistUserStore.threadUid(collection.ThreadUid); + + MessagelistUserStore.endHash( + collection.Folder + + '|' + collection.Search + + '|' + MessagelistUserStore.threadUid() + + '|' + MessagelistUserStore.page() + ); + MessagelistUserStore.endThreadUid(collection.ThreadUid); + const message = MessageUserStore.message(); + if (message && collection.Folder !== message.folder) { + MessageUserStore.message(null); + } + + MessagelistUserStore.disableAutoSelect(true); + + MessagelistUserStore(collection); + MessagelistUserStore.isIncomplete(false); + + clearNewMessageCache(); + + if (folder && (bCached || unreadCountChange || SettingsUserStore.useThreads())) { + rl.app.folderInformation(folder.fullName, collection); + } + } else { + MessagelistUserStore.count(0); + MessagelistUserStore([]); + MessagelistUserStore.error(getNotification(Notification.CantGetMessageList)); + } + } + MessagelistUserStore.loading(false); + }, + { + Folder: FolderUserStore.currentFolderFullName(), + Offset: iOffset, + Limit: SettingsUserStore.messagesPerPage(), + Search: MessagelistUserStore.listSearch(), + ThreadUid: MessagelistUserStore.threadUid() + } + ); +}; + +/** + * @param {string} sFolderFullName + * @param {number} iSetAction + * @param {Array=} messages = null + */ +MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => { + messages = messages || MessagelistUserStore.listChecked(); + + let folder = null, + alreadyUnread = 0, + rootUids = messages.map(oMessage => oMessage && oMessage.uid ? oMessage.uid : null) + .validUnique(), + length = rootUids.length; + + if (sFolderFullName && length) { + switch (iSetAction) { + case MessageSetAction.SetSeen: + length = 0; + // fallthrough is intentionally + case MessageSetAction.UnsetSeen: + rootUids.forEach(sSubUid => + alreadyUnread += MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction) + ); + + folder = getFolderFromCacheList(sFolderFullName); + if (folder) { + folder.messageCountUnread(folder.messageCountUnread() - alreadyUnread + length); + } + + rl.app.Remote.request('MessageSetSeen', null, { + Folder: sFolderFullName, + Uids: rootUids.join(','), + SetAction: iSetAction == MessageSetAction.SetSeen ? 1 : 0 + }); + break; + + case MessageSetAction.SetFlag: + case MessageSetAction.UnsetFlag: + rootUids.forEach(sSubUid => + MessageFlagsCache.storeBySetAction(sFolderFullName, sSubUid, iSetAction) + ); + rl.app.Remote.request('MessageSetFlagged', null, { + Folder: sFolderFullName, + Uids: rootUids.join(','), + SetAction: iSetAction == MessageSetAction.SetFlag ? 1 : 0 + }); + break; + // no default + } + + MessagelistUserStore.reloadFlagsAndCachedMessage(); + } +}; + +/** + * @param {string} fromFolderFullName + * @param {Array} uidForRemove + * @param {string=} toFolderFullName = '' + * @param {boolean=} copy = false + */ +MessagelistUserStore.removeMessagesFromList = ( + fromFolderFullName, uidForRemove, toFolderFullName = '', copy = false +) => { + uidForRemove = uidForRemove.map(mValue => pInt(mValue)); + + let unseenCount = 0, + messageList = MessagelistUserStore, + currentMessage = MessageUserStore.message(); + + const trashFolder = FolderUserStore.trashFolder(), + spamFolder = FolderUserStore.spamFolder(), + fromFolder = getFolderFromCacheList(fromFolderFullName), + toFolder = toFolderFullName ? getFolderFromCacheList(toFolderFullName) : null, + messages = + FolderUserStore.currentFolderFullName() === fromFolderFullName + ? messageList.filter(item => item && uidForRemove.includes(pInt(item.uid))) + : []; + + messages.forEach(item => { + if (item && item.isUnseen()) { + ++unseenCount; + } + }); + + if (fromFolder && !copy) { + fromFolder.messageCountAll( + 0 <= fromFolder.messageCountAll() - uidForRemove.length ? fromFolder.messageCountAll() - uidForRemove.length : 0 + ); + + if (0 < unseenCount) { + fromFolder.messageCountUnread( + 0 <= fromFolder.messageCountUnread() - unseenCount ? fromFolder.messageCountUnread() - unseenCount : 0 + ); + } + } + + if (toFolder) { + if (trashFolder === toFolder.fullName || spamFolder === toFolder.fullName) { + unseenCount = 0; + } + + toFolder.messageCountAll(toFolder.messageCountAll() + uidForRemove.length); + if (0 < unseenCount) { + toFolder.messageCountUnread(toFolder.messageCountUnread() + unseenCount); + } + + toFolder.actionBlink(true); + } + + if (messages.length) { + if (copy) { + messages.forEach(item => item.checked(false)); + } else { + MessagelistUserStore.isIncomplete(true); + + messages.forEach(item => { + if (currentMessage && currentMessage.hash === item.hash) { + currentMessage = null; + MessageUserStore.message(null); + } + + item.deleted(true); + }); + + setTimeout(() => messages.forEach(item => messageList.remove(item)), 350); + } + } + + if (fromFolderFullName) { + setFolderHash(fromFolderFullName, ''); + } + + if (toFolderFullName) { + setFolderHash(toFolderFullName, ''); + } + + if (MessagelistUserStore.threadUid()) { + if ( + messageList.length && + !!messageList.find(item => !!(item && item.deleted() && item.uid == MessagelistUserStore.threadUid())) + ) { + const message = messageList.find(item => item && !item.deleted()); + let setHash; + if (!message) { + if (1 < MessagelistUserStore.page()) { + MessagelistUserStore.page(MessagelistUserStore.page() - 1); + setHash = 1; + } else { + MessagelistUserStore.threadUid(0); + rl.route.setHash( + mailBox( + FolderUserStore.currentFolderFullNameHash(), + MessagelistUserStore.pageBeforeThread(), + MessagelistUserStore.listSearch() + ), + true, + true + ); + } + } else if (MessagelistUserStore.threadUid() != message.uid) { + MessagelistUserStore.threadUid(message.uid); + setHash = 1; + } + if (setHash) { + rl.route.setHash( + mailBox( + FolderUserStore.currentFolderFullNameHash(), + MessagelistUserStore.page(), + MessagelistUserStore.listSearch(), + MessagelistUserStore.threadUid() + ), + true, + true + ); + } + } + } +}, + +MessagelistUserStore.reloadFlagsAndCachedMessage = () => { + MessagelistUserStore.forEach(message => MessageFlagsCache.initMessage(message)); + MessageFlagsCache.initMessage(MessageUserStore.message()); +}; diff --git a/dev/View/Popup/AdvancedSearch.js b/dev/View/Popup/AdvancedSearch.js index 7d75edf74..a7e525ad7 100644 --- a/dev/View/Popup/AdvancedSearch.js +++ b/dev/View/Popup/AdvancedSearch.js @@ -2,7 +2,7 @@ import { koComputable } from 'External/ko'; import { i18n, trigger as translatorTrigger } from 'Common/Translator'; -import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { AbstractViewPopup } from 'Knoin/AbstractViews'; import { FolderUserStore } from 'Stores/User/Folder'; @@ -54,7 +54,7 @@ class AdvancedSearchPopupView extends AbstractViewPopup { submitForm() { const search = this.buildSearchString(); if (search) { - MessageUserStore.mainMessageListSearch(search); + MessagelistUserStore.mainSearch(search); } this.cancelCommand(); diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index d9278521d..4ba7b90f5 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -18,9 +18,9 @@ import { encodeHtml, HtmlEditor, htmlToPlain } from 'Common/Html'; import { koArrayWithDestroy } from 'External/ko'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; +import { messagesDeleteHelper } from 'Common/Folders'; import { serverRequest } from 'Common/Links'; -import { i18n, getNotification, getUploadErrorDescByCode } from 'Common/Translator'; -import { timestampToString } from 'Common/Momentor'; +import { i18n, getNotification, getUploadErrorDescByCode, timestampToString } from 'Common/Translator'; import { MessageFlagsCache, setFolderHash } from 'Common/Cache'; import { doc, Settings, SettingsGet, getFullscreenElement, exitFullscreen, elementById } from 'Common/Globals'; @@ -33,6 +33,7 @@ import { PgpUserStore } from 'Stores/User/Pgp'; import { OpenPGPUserStore } from 'Stores/User/OpenPGP'; import { GnuPGUserStore } from 'Stores/User/GnuPG'; import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import Remote from 'Remote/User/Fetch'; @@ -521,7 +522,7 @@ class ComposePopupView extends AbstractViewPopup { if (isArray(flagsCache)) { flagsCache.push(('forward' === this.aDraftInfo[0]) ? '$forwarded' : '\\answered'); MessageFlagsCache.setFor(this.aDraftInfo[2], this.aDraftInfo[1], flagsCache); - MessageUserStore.reloadFlagsAndCachedMessage(); + MessagelistUserStore.reloadFlagsAndCachedMessage(); setFolderHash(this.aDraftInfo[2], ''); } } @@ -632,8 +633,8 @@ class ComposePopupView extends AbstractViewPopup { const sFromFolderFullName = this.draftsFolder(), aUidForRemove = [this.draftUid()]; - rl.app.messagesDeleteHelper(sFromFolderFullName, aUidForRemove); - MessageUserStore.removeMessagesFromList(sFromFolderFullName, aUidForRemove); + messagesDeleteHelper(sFromFolderFullName, aUidForRemove); + MessagelistUserStore.removeMessagesFromList(sFromFolderFullName, aUidForRemove); this.closeCommand(); } } @@ -684,8 +685,18 @@ class ComposePopupView extends AbstractViewPopup { }, 60000); } + // getAutocomplete emailsSource(oData, fResponse) { - rl.app.getAutocomplete(oData.term, aData => fResponse(aData.map(oEmailItem => oEmailItem.toLine(false)))); + Remote.suggestions((iError, data) => { + if (!iError && isArray(data.Result)) { + fResponse( + data.Result.map(item => (item && item[0] ? (new EmailModel(item[0], item[1])).toLine(false) : null)) + .filter(v => v) + ); + } else if (Notification.RequestAborted !== iError) { + fResponse([]); + } + }, oData.term); } reloadDraftFolder() { @@ -693,7 +704,7 @@ class ComposePopupView extends AbstractViewPopup { if (draftsFolder && UNUSED_OPTION_VALUE !== draftsFolder) { setFolderHash(draftsFolder, ''); if (FolderUserStore.currentFolderFullName() === draftsFolder) { - rl.app.reloadMessageList(true); + MessagelistUserStore.reload(true); } else { rl.app.folderInformation(draftsFolder); } diff --git a/dev/View/Popup/Filter.js b/dev/View/Popup/Filter.js index ab57d24ad..d27372d2e 100644 --- a/dev/View/Popup/Filter.js +++ b/dev/View/Popup/Filter.js @@ -11,7 +11,7 @@ import { SieveUserStore } from 'Stores/User/Sieve'; import { AbstractViewPopup } from 'Knoin/AbstractViews'; -import { folderListOptionsBuilder } from 'Common/UtilsUser'; +import { folderListOptionsBuilder } from 'Common/Folders'; class FilterPopupView extends AbstractViewPopup { constructor() { diff --git a/dev/View/Popup/FolderClear.js b/dev/View/Popup/FolderClear.js index e3c118f2a..a551571d3 100644 --- a/dev/View/Popup/FolderClear.js +++ b/dev/View/Popup/FolderClear.js @@ -2,6 +2,7 @@ import { i18n, getNotification } from 'Common/Translator'; import { setFolderHash } from 'Common/Cache'; import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import Remote from 'Remote/User/Fetch'; @@ -38,7 +39,7 @@ class FolderClearPopupView extends AbstractViewPopup { const folderToClear = this.selectedFolder(); if (folderToClear) { MessageUserStore.message(null); - MessageUserStore.list([]); + MessagelistUserStore([]); this.clearingProcess(true); @@ -52,7 +53,7 @@ class FolderClearPopupView extends AbstractViewPopup { if (iError) { this.clearingError(getNotification(iError)); } else { - rl.app.reloadMessageList(true); + MessagelistUserStore.reload(true); this.cancelCommand(); } }, { diff --git a/dev/View/Popup/FolderCreate.js b/dev/View/Popup/FolderCreate.js index 8910fc3bb..a50c7a65d 100644 --- a/dev/View/Popup/FolderCreate.js +++ b/dev/View/Popup/FolderCreate.js @@ -3,7 +3,7 @@ import { koComputable } from 'External/ko'; import { Notification } from 'Common/Enums'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { defaultOptionsAfterRender } from 'Common/Utils'; -import { folderListOptionsBuilder, sortFolders } from 'Common/UtilsUser'; +import { folderListOptionsBuilder, sortFolders } from 'Common/Folders'; import { getNotification } from 'Common/Translator'; import { FolderUserStore } from 'Stores/User/Folder'; diff --git a/dev/View/Popup/FolderSystem.js b/dev/View/Popup/FolderSystem.js index 4e2a929e3..42c5bc912 100644 --- a/dev/View/Popup/FolderSystem.js +++ b/dev/View/Popup/FolderSystem.js @@ -4,7 +4,7 @@ import { koComputable, addSubscribablesTo } from 'External/ko'; import { SetSystemFoldersNotification } from 'Common/EnumsUser'; import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { defaultOptionsAfterRender } from 'Common/Utils'; -import { folderListOptionsBuilder } from 'Common/UtilsUser'; +import { folderListOptionsBuilder } from 'Common/Folders'; import { initOnStartOrLangChange, i18n } from 'Common/Translator'; import { FolderUserStore } from 'Stores/User/Folder'; diff --git a/dev/View/User/MailBox/FolderList.js b/dev/View/User/MailBox/FolderList.js index c78137ef6..fc4166aff 100644 --- a/dev/View/User/MailBox/FolderList.js +++ b/dev/View/User/MailBox/FolderList.js @@ -10,6 +10,7 @@ import { AppUserStore } from 'Stores/User/App'; import { SettingsUserStore } from 'Stores/User/Settings'; import { FolderUserStore } from 'Stores/User/Folder'; import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { showScreenPopup } from 'Knoin/Knoin'; import { AbstractViewLeft } from 'Knoin/AbstractViews'; @@ -22,6 +23,8 @@ import { isArray } from 'Common/Utils'; import { ClientSideKeyName } from 'Common/EnumsUser'; import * as Local from 'Storage/Client'; +import { moveMessagesToFolder } from 'Common/Folders'; + /** * @param {string} sFullName * @param {boolean} bExpanded @@ -109,9 +112,9 @@ export class MailFolderList extends AbstractViewLeft { if (folder) { if (moveAction()) { moveAction(false); - rl.app.moveMessagesToFolder( + moveMessagesToFolder( FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), folder.fullName, event.ctrlKey ); diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index 9aa1bd7ff..2f211611d 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -15,8 +15,9 @@ import { UNUSED_OPTION_VALUE } from 'Common/Consts'; import { doc, leftPanelDisabled, moveAction, Settings, SettingsCapa, SettingsGet, fireEvent } from 'Common/Globals'; -import { computedPaginatorHelper, showMessageComposer, folderListOptionsBuilder } from 'Common/UtilsUser'; +import { computedPaginatorHelper, showMessageComposer, populateMessageBody } from 'Common/UtilsUser'; import { FileInfo } from 'Common/File'; +import { folderListOptionsBuilder, moveMessagesToFolder } from 'Common/Folders'; import { mailBox, serverRequest } from 'Common/Links'; import { Selector } from 'Common/Selector'; @@ -34,6 +35,7 @@ import { AppUserStore } from 'Stores/User/App'; import { SettingsUserStore } from 'Stores/User/Settings'; import { FolderUserStore } from 'Stores/User/Folder'; import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { ThemeStore } from 'Stores/Theme'; import Remote from 'Remote/User/Fetch'; @@ -44,8 +46,18 @@ import { AbstractViewRight } from 'Knoin/AbstractViews'; import { FolderClearPopupView } from 'View/Popup/FolderClear'; import { AdvancedSearchPopupView } from 'View/Popup/AdvancedSearch'; +import { MessageModel } from 'Model/Message'; + const - canBeMovedHelper = () => MessageUserStore.hasCheckedOrSelected(); + canBeMovedHelper = () => MessagelistUserStore.hasCheckedOrSelected(), + + /** + * @param {string} sFolderFullName + * @param {number} iSetAction + * @param {Array=} aMessages = null + * @returns {void} + */ + listAction = (...args) => MessagelistUserStore.setAction(...args); export class MailMessageList extends AbstractViewRight { constructor() { @@ -62,23 +74,23 @@ export class MailMessageList extends AbstractViewRight { this.allowSearchAdv = SettingsCapa(Capa.SearchAdv); this.allowDangerousActions = SettingsCapa(Capa.DangerousActions); - this.messageList = MessageUserStore.list; + this.messageList = MessagelistUserStore; this.composeInEdit = AppUserStore.composeInEdit; this.isMobile = ThemeStore.isMobile; this.leftPanelDisabled = leftPanelDisabled; - this.messageListSearch = MessageUserStore.listSearch; - this.messageListError = MessageUserStore.listError; + this.messageListSearch = MessagelistUserStore.listSearch; + this.messageListError = MessagelistUserStore.error; this.popupVisibility = arePopupsVisible; this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList; - this.messageListThreadUid = MessageUserStore.listEndThreadUid; + this.messageListThreadUid = MessagelistUserStore.endThreadUid; - this.messageListIsLoading = MessageUserStore.listIsLoading; + this.messageListIsLoading = MessagelistUserStore.isLoading; initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT')); @@ -114,44 +126,44 @@ export class MailMessageList extends AbstractViewRight { ), messageListSearchDesc: () => { - const value = MessageUserStore.list().Search; + const value = MessagelistUserStore().Search; return value ? i18n('MESSAGE_LIST/SEARCH_RESULT_FOR', { SEARCH: value }) : '' }, - messageListPaginator: computedPaginatorHelper(MessageUserStore.listPage, - MessageUserStore.listPageCount), + messageListPaginator: computedPaginatorHelper(MessagelistUserStore.page, + MessagelistUserStore.pageCount), checkAll: { - read: () => 0 < MessageUserStore.listChecked().length, + read: () => 0 < MessagelistUserStore.listChecked().length, write: (value) => { value = !!value; - MessageUserStore.list.forEach(message => message.checked(value)); + MessagelistUserStore.forEach(message => message.checked(value)); } }, inputProxyMessageListSearch: { - read: MessageUserStore.mainMessageListSearch, + read: MessagelistUserStore.mainSearch, write: value => this.sLastSearchValue = value }, isIncompleteChecked: () => { - const c = MessageUserStore.listChecked().length; - return c && MessageUserStore.list.length > c; + const c = MessagelistUserStore.listChecked().length; + return c && MessagelistUserStore().length > c; }, - hasMessages: () => 0 < MessageUserStore.list().length, + hasMessages: () => 0 < MessagelistUserStore().length, - isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === MessageUserStore.list().Folder, + isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === MessagelistUserStore().Folder, isSpamDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(), - isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === MessageUserStore.list().Folder, + isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === MessagelistUserStore().Folder, - isDraftFolder: () => (FolderUserStore.draftsFolder() || 0) === MessageUserStore.list().Folder, + isDraftFolder: () => (FolderUserStore.draftsFolder() || 0) === MessagelistUserStore().Folder, - isSentFolder: () => (FolderUserStore.sentFolder() || 0) === MessageUserStore.list().Folder, + isSentFolder: () => (FolderUserStore.sentFolder() || 0) === MessagelistUserStore().Folder, - isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === MessageUserStore.list().Folder, + isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === MessagelistUserStore().Folder, isArchiveDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(), @@ -163,9 +175,9 @@ export class MailMessageList extends AbstractViewRight { isUnSpamVisible: () => this.isSpamFolder() && !this.isSpamDisabled() && !this.isDraftFolder() && !this.isSentFolder(), - mobileCheckedStateShow: () => ThemeStore.isMobile() ? 0 < MessageUserStore.listChecked().length : true, + mobileCheckedStateShow: () => ThemeStore.isMobile() ? 0 < MessagelistUserStore.listChecked().length : true, - mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessageUserStore.listChecked().length : true, + mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessagelistUserStore.listChecked().length : true, sortText: () => { let mode = FolderUserStore.sortMode(), @@ -181,20 +193,27 @@ export class MailMessageList extends AbstractViewRight { } }); - this.hasCheckedOrSelectedLines = MessageUserStore.hasCheckedOrSelected, + this.hasCheckedOrSelectedLines = MessagelistUserStore.hasCheckedOrSelected, this.selector = new Selector( - MessageUserStore.list, - MessageUserStore.selectorMessageSelected, - MessageUserStore.selectorMessageFocused, + MessagelistUserStore, + MessagelistUserStore.selectedMessage, + MessagelistUserStore.focusedMessage, '.messageListItem .actionHandle', '.messageListItem .checkboxMessage', '.messageListItem.focused' ); - this.selector.on('ItemSelect', message => MessageUserStore.selectMessage(message)); + this.selector.on('ItemSelect', message => { + if (message) { + MessageUserStore.message(MessageModel.fromMessageListItem(message)); + populateMessageBody(MessageUserStore.message()); + } else { + MessageUserStore.message(null); + } + }); - this.selector.on('MiddleClick', message => MessageUserStore.populateMessageBody(message, true)); + this.selector.on('MiddleClick', message => populateMessageBody(message, true)); this.selector.on('ItemGetUid', message => (message ? message.generateUid() : '')); @@ -213,7 +232,7 @@ export class MailMessageList extends AbstractViewRight { addEventListener('mailbox.message.show', e => { const sFolder = e.detail.Folder, iUid = e.detail.Uid; - const message = MessageUserStore.list.find( + const message = MessagelistUserStore.find( item => item && sFolder === item.folder && iUid == item.uid ); @@ -227,12 +246,19 @@ export class MailMessageList extends AbstractViewRight { if ('INBOX' !== sFolder) { rl.route.setHash(mailBox(sFolder)); } + if (sFolder && iUid) { + MessageUserStore.message(MessageModel.fromMessageListItem(null)); + MessageUserStore.message().folder = sFolder; + MessageUserStore.message().uid = iUid; - MessageUserStore.selectMessageByFolderAndUid(sFolder, iUid); + populateMessageBody(MessageUserStore.message()); + } else { + MessageUserStore.message(null); + } } }); - MessageUserStore.listEndHash.subscribe((() => + MessagelistUserStore.endHash.subscribe((() => this.selector.scrollToFocused() ).throttle(50)); @@ -260,15 +286,15 @@ export class MailMessageList extends AbstractViewRight { } reload() { - if (!MessageUserStore.listIsLoading()) { - rl.app.reloadMessageList(false, true); + if (!MessagelistUserStore.isLoading()) { + MessagelistUserStore.reload(false, true); } } multyForwardCommand() { showMessageComposer([ ComposeType.ForwardAsAttachment, - MessageUserStore.listCheckedOrSelected() + MessagelistUserStore.listCheckedOrSelected() ]); } @@ -277,7 +303,7 @@ export class MailMessageList extends AbstractViewRight { rl.app.deleteMessagesFromFolder( FolderType.Trash, FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), false ); } @@ -287,7 +313,7 @@ export class MailMessageList extends AbstractViewRight { rl.app.deleteMessagesFromFolder( FolderType.Trash, FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), true ); } @@ -296,7 +322,7 @@ export class MailMessageList extends AbstractViewRight { rl.app.deleteMessagesFromFolder( FolderType.Archive, FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), true ); } @@ -305,7 +331,7 @@ export class MailMessageList extends AbstractViewRight { rl.app.deleteMessagesFromFolder( FolderType.Spam, FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), true ); } @@ -314,7 +340,7 @@ export class MailMessageList extends AbstractViewRight { rl.app.deleteMessagesFromFolder( FolderType.NotSpam, FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), true ); } @@ -339,7 +365,7 @@ export class MailMessageList extends AbstractViewRight { } goToUpOrDown(up) { - if (MessageUserStore.listChecked().length) { + if (MessagelistUserStore.listChecked().length) { return false; } @@ -384,27 +410,28 @@ export class MailMessageList extends AbstractViewRight { } useAutoSelect() { - return !MessageUserStore.listDisableAutoSelect() - && !/is:unseen/.test(MessageUserStore.mainMessageListSearch()) + return !MessagelistUserStore.disableAutoSelect() + && !/is:unseen/.test(MessagelistUserStore.mainSearch()) && SettingsUserStore.usePreviewPane(); } searchEnterAction() { - MessageUserStore.mainMessageListSearch(this.sLastSearchValue); + MessagelistUserStore.mainSearch(this.sLastSearchValue); this.inputMessageListSearchFocus(false); } cancelSearch() { - MessageUserStore.mainMessageListSearch(''); + MessagelistUserStore.mainSearch(''); this.inputMessageListSearchFocus(false); } cancelThreadUid() { + // history.go(-1) better? rl.route.setHash( mailBox( FolderUserStore.currentFolderFullNameHash(), - MessageUserStore.listPageBeforeThread(), - MessageUserStore.listSearch() + MessagelistUserStore.pageBeforeThread(), + MessagelistUserStore.listSearch() ) ); } @@ -415,10 +442,10 @@ export class MailMessageList extends AbstractViewRight { * @returns {boolean} */ moveSelectedMessagesToFolder(sToFolderFullName, bCopy) { - if (MessageUserStore.hasCheckedOrSelected()) { - rl.app.moveMessagesToFolder( + if (MessagelistUserStore.hasCheckedOrSelected()) { + moveMessagesToFolder( FolderUserStore.currentFolderFullName(), - MessageUserStore.listCheckedOrSelectedUidsWithSubMails(), + MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(), sToFolderFullName, bCopy ); @@ -430,7 +457,7 @@ export class MailMessageList extends AbstractViewRight { getDragData(event) { const item = ko.dataFor(doc.elementFromPoint(event.clientX, event.clientY)); item && item.checked && item.checked(true); - const uids = MessageUserStore.listCheckedOrSelectedUidsWithSubMails(); + const uids = MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(); item && !uids.includes(item.uid) && uids.push(item.uid); return uids.length ? { copy: event.ctrlKey, @@ -439,34 +466,24 @@ export class MailMessageList extends AbstractViewRight { } : null; } - /** - * @param {string} sFolderFullName - * @param {number} iSetAction - * @param {Array=} aMessages = null - * @returns {void} - */ - setAction(sFolderFullName, iSetAction, aMessages) { - rl.app.messageListAction(sFolderFullName, iSetAction, aMessages); - } - listSetSeen() { - this.setAction( + listAction( FolderUserStore.currentFolderFullName(), MessageSetAction.SetSeen, - MessageUserStore.listCheckedOrSelected() + MessagelistUserStore.listCheckedOrSelected() ); } listSetAllSeen() { let sFolderFullName = FolderUserStore.currentFolderFullName(), - iThreadUid = MessageUserStore.listEndThreadUid(); + iThreadUid = MessagelistUserStore.endThreadUid(); if (sFolderFullName) { let cnt = 0; const uids = []; let folder = getFolderFromCacheList(sFolderFullName); if (folder) { - MessageUserStore.list.forEach(message => { + MessagelistUserStore.forEach(message => { if (message.isUnseen()) { ++cnt; } @@ -486,47 +503,47 @@ export class MailMessageList extends AbstractViewRight { Remote.messageSetSeenToAll(sFolderFullName, true, iThreadUid ? uids : null); - MessageUserStore.reloadFlagsAndCachedMessage(); + MessagelistUserStore.reloadFlagsAndCachedMessage(); } } } listUnsetSeen() { - this.setAction( + listAction( FolderUserStore.currentFolderFullName(), MessageSetAction.UnsetSeen, - MessageUserStore.listCheckedOrSelected() + MessagelistUserStore.listCheckedOrSelected() ); } listSetFlags() { - this.setAction( + listAction( FolderUserStore.currentFolderFullName(), MessageSetAction.SetFlag, - MessageUserStore.listCheckedOrSelected() + MessagelistUserStore.listCheckedOrSelected() ); } listUnsetFlags() { - this.setAction( + listAction( FolderUserStore.currentFolderFullName(), MessageSetAction.UnsetFlag, - MessageUserStore.listCheckedOrSelected() + MessagelistUserStore.listCheckedOrSelected() ); } flagMessages(currentMessage) { - const checked = MessageUserStore.listCheckedOrSelected(); + const checked = MessagelistUserStore.listCheckedOrSelected(); if (currentMessage) { const checkedUids = checked.map(message => message.uid); if (checkedUids.includes(currentMessage.uid)) { - this.setAction( + listAction( currentMessage.folder, currentMessage.isFlagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, checked ); } else { - this.setAction( + listAction( currentMessage.folder, currentMessage.isFlagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, [currentMessage] @@ -536,17 +553,17 @@ export class MailMessageList extends AbstractViewRight { } flagMessagesFast(bFlag) { - const checked = MessageUserStore.listCheckedOrSelected(); + const checked = MessagelistUserStore.listCheckedOrSelected(); if (checked.length) { if (undefined === bFlag) { const flagged = checked.filter(message => message.isFlagged()); - this.setAction( + listAction( checked[0].folder, checked.length === flagged.length ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, checked ); } else { - this.setAction( + listAction( checked[0].folder, !bFlag ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, checked @@ -556,17 +573,17 @@ export class MailMessageList extends AbstractViewRight { } seenMessagesFast(seen) { - const checked = MessageUserStore.listCheckedOrSelected(); + const checked = MessagelistUserStore.listCheckedOrSelected(); if (checked.length) { if (undefined === seen) { const unseen = checked.filter(message => message.isUnseen()); - this.setAction( + listAction( checked[0].folder, unseen.length ? MessageSetAction.SetSeen : MessageSetAction.UnsetSeen, checked ); } else { - this.setAction( + listAction( checked[0].folder, seen ? MessageSetAction.SetSeen : MessageSetAction.UnsetSeen, checked @@ -580,28 +597,28 @@ export class MailMessageList extends AbstractViewRight { mailBox( FolderUserStore.currentFolderFullNameHash(), page.value, - MessageUserStore.listSearch(), - MessageUserStore.listThreadUid() + MessagelistUserStore.listSearch(), + MessagelistUserStore.threadUid() ) ); } gotoThread(message) { if (message && 0 < message.threadsLen()) { - MessageUserStore.listPageBeforeThread(MessageUserStore.listPage()); + MessagelistUserStore.pageBeforeThread(MessagelistUserStore.page()); rl.route.setHash( - mailBox(FolderUserStore.currentFolderFullNameHash(), 1, MessageUserStore.listSearch(), message.uid) + mailBox(FolderUserStore.currentFolderFullNameHash(), 1, MessagelistUserStore.listSearch(), message.uid) ); } } listEmptyMessage() { if (!this.dragOver() - && !MessageUserStore.list().length - && !MessageUserStore.listIsLoading() - && !MessageUserStore.listError()) { - return i18n('MESSAGE_LIST/EMPTY_' + (MessageUserStore.listSearch() ? 'SEARCH_' : '') + 'LIST'); + && !MessagelistUserStore().length + && !MessagelistUserStore.isLoading() + && !MessagelistUserStore.error()) { + return i18n('MESSAGE_LIST/EMPTY_' + (MessagelistUserStore.listSearch() ? 'SEARCH_' : '') + 'LIST'); } return ''; } @@ -609,9 +626,9 @@ export class MailMessageList extends AbstractViewRight { clearListIsVisible() { return ( !this.messageListSearchDesc() && - !MessageUserStore.listError() && - !MessageUserStore.listEndThreadUid() && - MessageUserStore.list().length && + !MessagelistUserStore.error() && + !MessagelistUserStore.endThreadUid() && + MessagelistUserStore().length && (this.isSpamFolder() || this.isTrashFolder()) ); } @@ -668,13 +685,13 @@ export class MailMessageList extends AbstractViewRight { .on('onBodyDragLeave', () => this.dragOver(false)) .on('onSelect', (sUid, oData) => { if (sUid && oData && 'message/rfc822' === oData.Type) { - MessageUserStore.listLoading(true); + MessagelistUserStore.loading(true); return true; } return false; }) - .on('onComplete', () => rl.app.reloadMessageList(true, true)); + .on('onComplete', () => MessagelistUserStore.reload(true, true)); } // initShortcuts @@ -694,12 +711,12 @@ export class MailMessageList extends AbstractViewRight { // delete shortcuts.add('delete', 'shift', Scope.MessageList, () => { - MessageUserStore.listCheckedOrSelected().length && this.deleteWithoutMoveCommand(); + MessagelistUserStore.listCheckedOrSelected().length && this.deleteWithoutMoveCommand(); return false; }); // shortcuts.add('3', 'shift', Scope.MessageList, () => { shortcuts.add('delete', '', Scope.MessageList, () => { - MessageUserStore.listCheckedOrSelected().length && this.deleteCommand(); + MessagelistUserStore.listCheckedOrSelected().length && this.deleteCommand(); return false; }); @@ -728,9 +745,9 @@ export class MailMessageList extends AbstractViewRight { }); shortcuts.add('t', '', [Scope.MessageList], () => { - let message = MessageUserStore.selectorMessageSelected(); + let message = MessagelistUserStore.selectedMessage(); if (!message) { - message = MessageUserStore.selectorMessageFocused(); + message = MessagelistUserStore.focusedMessage(); } if (message && 0 < message.threadsLen()) { @@ -781,7 +798,7 @@ export class MailMessageList extends AbstractViewRight { if (this.messageListSearchDesc()) { this.cancelSearch(); return false; - } else if (MessageUserStore.listEndThreadUid()) { + } else if (MessagelistUserStore.endThreadUid()) { this.cancelThreadUid(); return false; } @@ -813,7 +830,7 @@ export class MailMessageList extends AbstractViewRight { prefetchNextTick() { if (!this.bPrefetch && !ifvisible.now() && !this.viewModelDom.hidden) { - const message = MessageUserStore.list.find( + const message = MessagelistUserStore.find( item => item && !hasRequestedMessage(item.folder, item.uid) ); if (message) { @@ -838,7 +855,7 @@ export class MailMessageList extends AbstractViewRight { advancedSearchClick() { SettingsCapa(Capa.SearchAdv) - && showScreenPopup(AdvancedSearchPopupView, [MessageUserStore.mainMessageListSearch()]); + && showScreenPopup(AdvancedSearchPopupView, [MessagelistUserStore.mainSearch()]); } quotaTooltip() { diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 6deaffaa7..d1896c29b 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -42,6 +42,7 @@ import { SettingsUserStore } from 'Stores/User/Settings'; import { AccountUserStore } from 'Stores/User/Account'; import { FolderUserStore } from 'Stores/User/Folder'; import { MessageUserStore } from 'Stores/User/Message'; +import { MessagelistUserStore } from 'Stores/User/Messagelist'; import { ThemeStore } from 'Stores/Theme'; import * as Local from 'Storage/Client'; @@ -66,10 +67,7 @@ export class MailMessageView extends AbstractViewRight { const createCommandReplyHelper = type => - createCommand(() => { - this.lastReplyAction(type); - this.replyOrforward(type); - }, this.canBeRepliedOrForwarded), + createCommand(() => this.replyOrforward(type), this.canBeRepliedOrForwarded), createCommandActionHelper = (folderType, useFolder) => createCommand(() => { @@ -101,13 +99,12 @@ export class MailMessageView extends AbstractViewRight { this.attachmentsActions = ko.observableArray(arrayLength(attachmentsActions) ? attachmentsActions : []); this.message = MessageUserStore.message; - this.hasCheckedMessages = MessageUserStore.hasCheckedMessages; - this.messageLoadingThrottle = MessageUserStore.messageLoading; - this.messagesBodiesDom = MessageUserStore.messagesBodiesDom; - this.messageActiveDom = MessageUserStore.messageActiveDom; - this.messageError = MessageUserStore.messageError; + this.hasCheckedMessages = MessagelistUserStore.hasCheckedMessages; + this.messageLoadingThrottle = MessageUserStore.loading; + this.messagesBodiesDom = MessageUserStore.bodiesDom; + this.messageError = MessageUserStore.error; - this.fullScreenMode = MessageUserStore.messageFullScreenMode; + this.fullScreenMode = MessageUserStore.fullScreen; this.messageListOfThreadsLoading = ko.observable(false).extend({ rateLimit: 1 }); this.highlightUnselectedAttachments = ko.observable(false).extend({ falseTimeout: 2000 }); @@ -148,7 +145,7 @@ export class MailMessageView extends AbstractViewRight { ) }, - messageVisibility: () => !MessageUserStore.messageLoading() && !!currentMessage(), + messageVisibility: () => !MessageUserStore.loading() && !!currentMessage(), canBeRepliedOrForwarded: () => !this.isDraftFolder() && this.messageVisibility(), @@ -177,7 +174,7 @@ export class MailMessageView extends AbstractViewRight { pgpSupported: () => currentMessage() && PgpUserStore.isSupported(), messageListOrViewLoading: - () => MessageUserStore.listIsLoading() | MessageUserStore.messageLoading() + () => MessagelistUserStore.isLoading() | MessageUserStore.loading() }); this.addSubscribables({ @@ -187,7 +184,7 @@ export class MailMessageView extends AbstractViewRight { lastReplyAction_: value => Local.set(ClientSideKeyName.LastReplyAction, value), message: message => { - this.messageActiveDom(null); + MessageUserStore.activeDom(null); if (message) { this.showAttachmentControls(false); @@ -206,7 +203,7 @@ export class MailMessageView extends AbstractViewRight { this.viewFromDkimData(message.fromDkimData()); this.viewToShort(message.toToLine(true, true)); } else { - MessageUserStore.selectorMessageSelected(null); + MessagelistUserStore.selectedMessage(null); this.viewHash = ''; @@ -272,6 +269,7 @@ export class MailMessageView extends AbstractViewRight { * @returns {void} */ replyOrforward(sType) { + this.lastReplyAction(sType); showMessageComposer([sType, currentMessage()]); } @@ -323,7 +321,7 @@ export class MailMessageView extends AbstractViewRight { if (eqs(event, '.messageItemHeader .subjectParent .flagParent')) { const message = currentMessage(); - message && rl.app.messageListAction( + message && MessagelistUserStore.setAction( message.folder, message.isFlagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag, [message] @@ -551,7 +549,7 @@ export class MailMessageView extends AbstractViewRight { * @returns {string} */ printableCheckedMessageCount() { - const cnt = MessageUserStore.listCheckedOrSelectedUidsWithSubMails().length; + const cnt = MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails().length; return 0 < cnt ? (100 > cnt ? cnt : '99+') : ''; } @@ -575,7 +573,7 @@ export class MailMessageView extends AbstractViewRight { MessageFlagsCache.store(oMessage); - MessageUserStore.reloadFlagsAndCachedMessage(); + MessagelistUserStore.reloadFlagsAndCachedMessage(); } } diff --git a/dev/View/User/SystemDropDown.js b/dev/View/User/SystemDropDown.js index d0e471735..cfd769390 100644 --- a/dev/View/User/SystemDropDown.js +++ b/dev/View/User/SystemDropDown.js @@ -72,12 +72,12 @@ export class SystemDropDownUserView extends AbstractViewRight { // MessageUserStore.setMessage(); // MessageUserStore.purgeMessageBodyCache(); // MessageUserStore.hideMessageBodies(); - MessageUserStore.list([]); + MessagelistUserStore([]); // FolderUserStore.folderList([]); - Remote.foldersReload(value => { + loadFolders(value => { if (value) { // 4. Change to INBOX = reload MessageList -// MessageUserStore.setMessageList(); +// MessagelistUserStore.setMessageList(); } }); AccountUserStore.loading(false); @@ -126,7 +126,7 @@ export class SystemDropDownUserView extends AbstractViewRight { onBuild() { shortcuts.add('m,contextmenu', '', [Scope.MessageList, Scope.MessageView, Scope.Settings], () => { if (!this.viewModelDom.hidden) { - MessageUserStore.messageFullScreenMode(false); + MessageUserStore.fullScreen(false); this.accountMenuDropdownTrigger(true); return false; }