diff --git a/dev/App/User.js b/dev/App/User.js index 0c3093beb..7101ab3be 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -831,24 +831,105 @@ } }; - AppUser.prototype.setMessageSeen = function (oMessage) + /** + * @param {string} sFolderFullNameRaw + * @param {string|bool} mUid + * @param {number} iSetAction + * @param {Array=} aMessages = null + */ + AppUser.prototype.messageListAction = function (sFolderFullNameRaw, mUid, iSetAction, aMessages) { - if (oMessage.unseen()) + var + bRoot = false, + aAllUids = [], + aRootUids = [], + oFolder = null, + iAlreadyUnread = 0 + ; + + if (Utils.isUnd(aMessages)) { - oMessage.unseen(false); - - var oFolder = Cache.getFolderFromCacheList(oMessage.folderFullNameRaw); - if (oFolder) - { - oFolder.messageCountUnread(0 <= oFolder.messageCountUnread() - 1 ? - oFolder.messageCountUnread() - 1 : 0); - } - - Cache.storeMessageFlagsToCache(oMessage); - this.reloadFlagsCurrentMessageListAndMessageFromCache(); + aMessages = MessageStore.messageListChecked(); } - Remote.messageSetSeen(Utils.emptyFunction, oMessage.folderFullNameRaw, [oMessage.uid], true); + if (true === mUid) + { + bRoot = true; + } + else if (aMessages && aMessages[0] && mUid && + sFolderFullNameRaw === aMessages[0].uid && mUid === aMessages[0].uid) + { + bRoot = true; + } + + _.each(aMessages, function (oMessage) { + aRootUids.push(oMessage.uid); + aAllUids = _.union(aAllUids, oMessage.threads(), [oMessage.uid]); + }); + + aAllUids = _.uniq(aAllUids); + aRootUids = _.uniq(aRootUids); + aRootUids = aAllUids; // always all + + if ('' !== sFolderFullNameRaw && 0 < aAllUids.length) + { + switch (iSetAction) { + case Enums.MessageSetAction.SetSeen: + + _.each(bRoot ? aAllUids : aRootUids, function (sSubUid) { + iAlreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + if (oFolder) + { + oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread); + } + + Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, bRoot ? aAllUids : aRootUids, true); + break; + + case Enums.MessageSetAction.UnsetSeen: + + _.each(aRootUids, function (sSubUid) { + iAlreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw); + if (oFolder) + { + oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread + aRootUids.length); + } + + Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, false); + break; + + case Enums.MessageSetAction.SetFlag: + + _.each(aRootUids, function (sSubUid) { + Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, true); + break; + + case Enums.MessageSetAction.UnsetFlag: + + _.each(bRoot ? aAllUids : aRootUids, function (sSubUid) { + Cache.storeMessageFlagsToCacheBySetAction( + sFolderFullNameRaw, sSubUid, iSetAction); + }); + + Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, bRoot ? aAllUids : aRootUids, false); + break; + } + + this.reloadFlagsCurrentMessageListAndMessageFromCache(); + MessageStore.message.viewTrigger(!MessageStore.message.viewTrigger()); + } }; AppUser.prototype.googleConnect = function () diff --git a/dev/Common/Cache.js b/dev/Common/Cache.js index e83773b65..d9f38ef3f 100644 --- a/dev/Common/Cache.js +++ b/dev/Common/Cache.js @@ -301,7 +301,8 @@ { var self = this, - aFlags = this.getMessageFlagsFromCache(oMessage.folderFullNameRaw, oMessage.uid), + sUid = oMessage.uid, + aFlags = this.getMessageFlagsFromCache(oMessage.folderFullNameRaw, sUid), mUnseenSubUid = null, mFlaggedSubUid = null ; @@ -318,13 +319,19 @@ if (0 < oMessage.threads().length) { - mUnseenSubUid = _.find(oMessage.threads(), function (iSubUid) { - var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, iSubUid); + mUnseenSubUid = _.find(oMessage.threads(), function (sSubUid) { + if (sUid === sSubUid){ + return false; + } + var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, sSubUid); return aFlags && 0 < aFlags.length && !!aFlags[0]; }); - mFlaggedSubUid = _.find(oMessage.threads(), function (iSubUid) { - var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, iSubUid); + mFlaggedSubUid = _.find(oMessage.threads(), function (sSubUid) { + if (sUid === sSubUid){ + return false; + } + var aFlags = self.getMessageFlagsFromCache(oMessage.folderFullNameRaw, sSubUid); return aFlags && 0 < aFlags.length && !!aFlags[1]; }); @@ -349,6 +356,7 @@ ); } }; + /** * @param {string} sFolder * @param {string} sUid @@ -362,6 +370,43 @@ } }; + /** + * @param {string} sFolder + * @param {string} sUid + * @param {number} iSetAction + */ + CacheUserStorage.prototype.storeMessageFlagsToCacheBySetAction = function (sFolder, sUid, iSetAction) + { + var iUnread = 0, aFlags = this.getMessageFlagsFromCache(sFolder, sUid); + if (Utils.isArray(aFlags) && 0 < aFlags.length) + { + if (aFlags[0]) + { + iUnread = 1; + } + + switch (iSetAction) + { + case Enums.MessageSetAction.SetSeen: + aFlags[0] = false; + break; + case Enums.MessageSetAction.UnsetSeen: + aFlags[0] = true; + break; + case Enums.MessageSetAction.SetFlag: + aFlags[1] = true; + break; + case Enums.MessageSetAction.UnsetFlag: + aFlags[1] = false; + break; + } + + this.setMessageFlagsToCache(sFolder, sUid, aFlags); + } + + return iUnread; + }; + module.exports = new CacheUserStorage(); }()); \ No newline at end of file diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 3c86b0d7b..7aa53f4ab 100644 --- a/dev/Common/Selector.js +++ b/dev/Common/Selector.js @@ -17,12 +17,13 @@ * @constructor * @param {koProperty} oKoList * @param {koProperty} oKoSelectedItem + * @param {koProperty} oKoFocusedItem * @param {string} sItemSelector * @param {string} sItemSelectedSelector * @param {string} sItemCheckedSelector * @param {string} sItemFocusedSelector */ - function Selector(oKoList, oKoSelectedItem, + function Selector(oKoList, oKoSelectedItem, oKoFocusedItem, sItemSelector, sItemSelectedSelector, sItemCheckedSelector, sItemFocusedSelector) { this.list = oKoList; @@ -37,8 +38,8 @@ return 0 < this.listChecked().length; }, this); - this.focusedItem = ko.observable(null); - this.selectedItem = oKoSelectedItem; + this.focusedItem = oKoFocusedItem || ko.observable(null); + this.selectedItem = oKoSelectedItem || ko.observable(null); this.selectedItemUseCallback = true; this.itemSelectedThrottle = _.debounce(_.bind(this.itemSelected, this), 300); diff --git a/dev/Model/Message.js b/dev/Model/Message.js index e684aa06b..611032989 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -133,18 +133,13 @@ this.sInReplyTo = ''; this.sReferences = ''; - this.parentUid = ko.observable(0); - this.threads = ko.observableArray([]); - this.threadsLen = ko.observable(0); this.hasUnseenSubMessage = ko.observable(false); this.hasFlaggedSubMessage = ko.observable(false); - this.lastInCollapsedThread = ko.observable(false); - this.lastInCollapsedThreadLoading = ko.observable(false); + this.threads = ko.observableArray([]); - this.threadsLenResult = ko.computed(function () { - var iCount = this.threadsLen(); - return 0 === this.parentUid() && 0 < iCount ? iCount + 1 : ''; + this.threadsLen = ko.computed(function () { + return this.threads().length; }, this); this.isImportant = ko.computed(function () { @@ -152,7 +147,7 @@ }, this); this.regDisposables([this.attachmentIconClass, this.fullFormatDateValue, - this.threadsLenResult, this.isImportant]); + this.threadsLen, this.isImportant]); } _.extend(MessageModel.prototype, AbstractModel.prototype); @@ -349,14 +344,10 @@ this.sInReplyTo = ''; this.sReferences = ''; - this.parentUid(0); this.threads([]); - this.threadsLen(0); + this.hasUnseenSubMessage(false); this.hasFlaggedSubMessage(false); - - this.lastInCollapsedThread(false); - this.lastInCollapsedThreadLoading(false); }; /** @@ -435,9 +426,7 @@ this.toEmailsString(MessageModel.emailsToLine(this.to, true)); this.toClearEmailsString(MessageModel.emailsToLineClear(this.to)); - this.parentUid(Utils.pInt(oJsonMessage.ParentThread)); this.threads(Utils.isArray(oJsonMessage.Threads) ? oJsonMessage.Threads : []); - this.threadsLen(Utils.pInt(oJsonMessage.ThreadsLen)); this.initFlagsByJson(oJsonMessage); this.computeSenderEmail(); @@ -685,11 +674,7 @@ { aResult.push('emptySubject'); } - if (0 < this.parentUid()) - { - aResult.push('hasParentMessage'); - } - if (0 < this.threadsLen() && 0 === this.parentUid()) + if (1 < this.threadsLen()) { aResult.push('hasChildrenMessage'); } @@ -994,9 +979,7 @@ this.sInReplyTo = ''; this.sReferences = ''; - this.parentUid(oMessage.parentUid()); this.threads(oMessage.threads()); - this.threadsLen(oMessage.threadsLen()); this.computeSenderEmail(); diff --git a/dev/Remote/User/Ajax.js b/dev/Remote/User/Ajax.js index 2819f569c..e76ff4e22 100644 --- a/dev/Remote/User/Ajax.js +++ b/dev/Remote/User/Ajax.js @@ -16,7 +16,6 @@ AppStore = require('Stores/User/App'), SettingsStore = require('Stores/User/Settings'), - MessageStore = require('Stores/User/Message'), AbstractAjaxRemote = require('Remote/AbstractAjax') ; @@ -349,7 +348,7 @@ sFolderHash, Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '', AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0', - AppStore.threadsAllowed() && sFolderFullNameRaw === MessageStore.messageListThreadFolder() ? MessageStore.messageListThreadUids().join(',') : '' + '' ].join(String.fromCharCode(0))), bSilent ? [] : ['MessageList']); } else @@ -361,7 +360,6 @@ 'Search': sSearch, 'UidNext': Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '', 'UseThreads': AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0', - 'ExpandedThreadUid': AppStore.threadsAllowed() && sFolderFullNameRaw === MessageStore.messageListThreadFolder() ? MessageStore.messageListThreadUids().join(',') : '' }, '' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout, '', bSilent ? [] : ['MessageList']); } }; @@ -404,6 +402,35 @@ return false; }; + /** + * @param {?Function} fCallback + * @param {string} sFolderFullNameRaw + * @param {number} iUid + * @return {boolean} + */ + RemoteUserStorage.prototype.messageThreadsFromCache = function (fCallback, sFolderFullNameRaw, iUid) + { + sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw); + iUid = Utils.pInt(iUid); + + if (Cache.getFolderFromCacheList(sFolderFullNameRaw) && 0 < iUid) + { + var sFolderHash = Cache.getFolderHash(sFolderFullNameRaw); + if (sFolderHash) + { + this.defaultRequest(fCallback, 'MessageThreadsFromCache', { + 'Folder': sFolderFullNameRaw, + 'FolderHash': sFolderHash, + 'Uid': iUid + }); + + return true; + } + } + + return false; + }; + /** * @param {?Function} fCallback * @param {Array} aExternals diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 7885eceea..8a505c4d0 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -21,9 +21,12 @@ Cache = require('Common/Cache'), + AppStore = require('Stores/User/App'), FolderStore = require('Stores/User/Folder'), SettingsStore = require('Stores/User/Settings'), + Remote = require('Remote/User/Ajax'), + MessageModel = require('Model/Message') ; @@ -41,9 +44,6 @@ this.messageListPage = ko.observable(1); this.messageListError = ko.observable(''); - this.messageListThreadFolder = ko.observable(''); - this.messageListThreadUids = ko.observableArray([]); - this.messageListEndFolder = ko.observable(''); this.messageListEndSearch = ko.observable(''); this.messageListEndPage = ko.observable(1); @@ -52,14 +52,29 @@ this.messageListIsNotCompleted = ko.observable(false); this.messageListCompleteLoadingThrottle = ko.observable(false).extend({'throttle': 200}); + this.messageListDisableAutoSelect = ko.observable(false).extend({'falseTimeout': 500}); + // message viewer this.message = ko.observable(null); - this.currentMessage = ko.observable(null); + + this.selectorMessageSelected = ko.observable(null); + this.selectorMessageFocused = ko.observable(null); this.message.focused = ko.observable(false); + this.message.viewTrigger = ko.observable(false); + + this.messageLastThreadUidsData = ko.observable(null); + this.messageError = ko.observable(''); - this.messageLoading = ko.observable(false); + + this.messageCurrentLoading = ko.observable(false); + this.messageThreadLoading = ko.observable(false); + + this.messageLoading = ko.computed(function () { + return !!(this.messageCurrentLoading() || this.messageThreadLoading()); + }, this); + this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50}); this.messageFullScreenMode = ko.observable(false); @@ -67,46 +82,11 @@ this.messagesBodiesDom = ko.observable(null); this.messageActiveDom = ko.observable(null); - this.messageListChecked = ko.computed(function () { - return _.filter(this.messageList(), function (oItem) { - return oItem.checked(); - }); - }, this).extend({'rateLimit': 0}); - - this.hasCheckedMessages = ko.computed(function () { - return 0 < this.messageListChecked().length; - }, this).extend({'rateLimit': 0}); - - this.messageListCheckedOrSelected = ko.computed(function () { - - var - aChecked = this.messageListChecked(), - oSelectedMessage = this.currentMessage() - ; - - return _.union(aChecked, oSelectedMessage ? [oSelectedMessage] : []); - - }, this); - - this.messageListCheckedOrSelectedUidsWithSubMails = ko.computed(function () { - var aList = []; - _.each(this.messageListCheckedOrSelected(), function (oMessage) { - if (oMessage) - { - aList.push(oMessage.uid); - if (0 < oMessage.threadsLen() && 0 === oMessage.parentUid() && oMessage.lastInCollapsedThread()) - { - aList = _.union(aList, oMessage.threads()); - } - } - }); - return aList; - }, this); - - this.computers(); this.subscribers(); + this.onMessageResponse = _.bind(this.onMessageResponse, this); + this.purgeMessageBodyCacheThrottle = _.throttle(this.purgeMessageBodyCache, 1000 * 30); } @@ -143,14 +123,46 @@ this.isMessageSelected = ko.computed(function () { return null !== this.message(); }, this); + + this.messageListChecked = ko.computed(function () { + return _.filter(this.messageList(), function (oItem) { + return oItem.checked(); + }); + }, this).extend({'rateLimit': 0}); + + this.hasCheckedMessages = ko.computed(function () { + return 0 < this.messageListChecked().length; + }, this).extend({'rateLimit': 0}); + + this.messageListCheckedOrSelected = ko.computed(function () { + + var + aChecked = this.messageListChecked(), + oSelectedMessage = this.selectorMessageSelected() + ; + + return _.union(aChecked, oSelectedMessage ? [oSelectedMessage] : []); + + }, this); + + this.messageListCheckedOrSelectedUidsWithSubMails = ko.computed(function () { + var aList = []; + _.each(this.messageListCheckedOrSelected(), function (oMessage) { + if (oMessage) + { + aList.push(oMessage.uid); + if (1 < oMessage.threadsLen()) + { + aList = _.union(aList, oMessage.threads()); + } + } + }); + return aList; + }, this); }; MessageUserStore.prototype.subscribers = function () { - this.messageListThreadFolder.subscribe(function () { - this.messageListThreadUids([]); - }, this); - this.messageListCompleteLoading.subscribe(function (bValue) { this.messageListCompleteLoadingThrottle(bValue); }, this); @@ -421,24 +433,19 @@ if ($oList && 0 < $oList.length) { - _.delay(function () { - $oList.each(function () { - var $self = $(this), iH = $self.height(); - if (0 === iH || 150 < iH) - { - $self.addClass('rl-bq-switcher hidden-bq'); - $('') - .insertBefore($self) - .click(function () { - $self.toggleClass('hidden-bq'); - Utils.windowResize(); - }) - .after('
') - .before('
') - ; - } - }); - }, 100); + $oList.each(function () { + var $self = $(this); + $self.addClass('rl-bq-switcher hidden-bq'); + $('') + .insertBefore($self) + .click(function () { + $self.toggleClass('hidden-bq'); + Utils.windowResize(); + }) + .after('
') + .before('
') + ; + }); } } }; @@ -446,6 +453,7 @@ MessageUserStore.prototype.setMessage = function (oData, bCached) { var + bNew = false, bIsHtml = false, bHasExternals = false, bHasInternals = false, @@ -456,143 +464,282 @@ bPgpSigned = false, bPgpEncrypted = false, oMessagesBodiesDom = this.messagesBodiesDom(), - oMessage = this.message() + oSelectedMessage = this.selectorMessageSelected(), + oMessage = this.message(), + aThreads = [] ; if (oData && oMessage && oData.Result && 'Object/Message' === oData.Result['@Object'] && - oMessage.folderFullNameRaw === oData.Result.Folder && oMessage.uid === oData.Result.Uid) + oMessage.folderFullNameRaw === oData.Result.Folder) { - this.messageError(''); - - oMessage.initUpdateByMessageJson(oData.Result); - Cache.addRequestedMessage(oMessage.folderFullNameRaw, oMessage.uid); - - if (!bCached) + aThreads = oMessage.threads(); + if (oMessage.uid !== oData.Result.Uid && 1 < aThreads.length && + -1 < Utils.inArray(oData.Result.Uid, aThreads)) { - oMessage.initFlagsByJson(oData.Result); + oMessage = MessageModel.newInstanceFromJson(oData.Result); + if (oMessage) + { + oMessage.threads(aThreads); + Cache.initMessageFlagsFromCache(oMessage); + + this.message(this.staticMessage.populateByMessageListItem(oMessage)); + oMessage = this.message(), + + bNew = true; + } } - oMessagesBodiesDom = oMessagesBodiesDom && oMessagesBodiesDom[0] ? oMessagesBodiesDom : null; - if (oMessagesBodiesDom) + if (oMessage && oMessage.uid === oData.Result.Uid) { - sId = 'rl-mgs-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''); - oTextBody = oMessagesBodiesDom.find('#' + sId); - if (!oTextBody || !oTextBody[0]) + this.messageError(''); + + oMessage.initUpdateByMessageJson(oData.Result); + Cache.addRequestedMessage(oMessage.folderFullNameRaw, oMessage.uid); + + if (!bCached) { - bHasExternals = !!oData.Result.HasExternals; - bHasInternals = !!oData.Result.HasInternals; + oMessage.initFlagsByJson(oData.Result); + } - oBody = $('
').hide().addClass('rl-cache-class'); - oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); - - if (Utils.isNormal(oData.Result.Html) && '' !== oData.Result.Html) + oMessagesBodiesDom = oMessagesBodiesDom && oMessagesBodiesDom[0] ? oMessagesBodiesDom : null; + if (oMessagesBodiesDom) + { + sId = 'rl-mgs-' + oMessage.hash.replace(/[^a-zA-Z0-9]/g, ''); + oTextBody = oMessagesBodiesDom.find('#' + sId); + if (!oTextBody || !oTextBody[0]) { - bIsHtml = true; - sResultHtml = oData.Result.Html.toString(); - } - else if (Utils.isNormal(oData.Result.Plain) && '' !== oData.Result.Plain) - { - bIsHtml = false; - sResultHtml = Utils.plainToHtml(oData.Result.Plain.toString(), false); + bHasExternals = !!oData.Result.HasExternals; + bHasInternals = !!oData.Result.HasInternals; - if ((oMessage.isPgpSigned() || oMessage.isPgpEncrypted()) && require('Stores/User/Pgp').capaOpenPGP()) + oBody = $('
').hide().addClass('rl-cache-class'); + oBody.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); + + if (Utils.isNormal(oData.Result.Html) && '' !== oData.Result.Html) { - oMessage.plainRaw = Utils.pString(oData.Result.Plain); - - bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oMessage.plainRaw); - if (!bPgpEncrypted) - { - bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oMessage.plainRaw) && - /-----BEGIN PGP SIGNATURE-----/.test(oMessage.plainRaw); - } - - Globals.$div.empty(); - if (bPgpSigned && oMessage.isPgpSigned()) - { - sResultHtml = - Globals.$div.append( - $('
').text(oMessage.plainRaw)
-									).html()
-								;
-							}
-							else if (bPgpEncrypted && oMessage.isPgpEncrypted())
-							{
-								sResultHtml =
-									Globals.$div.append(
-										$('
').text(oMessage.plainRaw)
-									).html()
-								;
-							}
-
-							Globals.$div.empty();
-
-							oMessage.isPgpSigned(bPgpSigned);
-							oMessage.isPgpEncrypted(bPgpEncrypted);
+							bIsHtml = true;
+							sResultHtml = oData.Result.Html.toString();
 						}
+						else if (Utils.isNormal(oData.Result.Plain) && '' !== oData.Result.Plain)
+						{
+							bIsHtml = false;
+							sResultHtml = Utils.plainToHtml(oData.Result.Plain.toString(), false);
+
+							if ((oMessage.isPgpSigned() || oMessage.isPgpEncrypted()) && require('Stores/User/Pgp').capaOpenPGP())
+							{
+								oMessage.plainRaw = Utils.pString(oData.Result.Plain);
+
+								bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oMessage.plainRaw);
+								if (!bPgpEncrypted)
+								{
+									bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oMessage.plainRaw) &&
+										/-----BEGIN PGP SIGNATURE-----/.test(oMessage.plainRaw);
+								}
+
+								Globals.$div.empty();
+								if (bPgpSigned && oMessage.isPgpSigned())
+								{
+									sResultHtml =
+										Globals.$div.append(
+											$('
').text(oMessage.plainRaw)
+										).html()
+									;
+								}
+								else if (bPgpEncrypted && oMessage.isPgpEncrypted())
+								{
+									sResultHtml =
+										Globals.$div.append(
+											$('
').text(oMessage.plainRaw)
+										).html()
+									;
+								}
+
+								Globals.$div.empty();
+
+								oMessage.isPgpSigned(bPgpSigned);
+								oMessage.isPgpEncrypted(bPgpEncrypted);
+							}
+						}
+						else
+						{
+							bIsHtml = false;
+						}
+
+						oBody
+							.html(Utils.findEmailAndLinks(sResultHtml))
+							.addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain'))
+						;
+
+						oMessage.isHtml(!!bIsHtml);
+						oMessage.hasImages(!!bHasExternals);
+						oMessage.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
+						oMessage.pgpSignedVerifyUser('');
+
+						oMessage.body = oBody;
+						if (oMessage.body)
+						{
+							oMessagesBodiesDom.append(oMessage.body);
+						}
+
+						oMessage.storeDataToDom();
+
+						if (bHasInternals)
+						{
+							oMessage.showInternalImages(true);
+						}
+
+						if (oMessage.hasImages() && SettingsStore.showImages())
+						{
+							oMessage.showExternalImages(true);
+						}
+
+						this.purgeMessageBodyCacheThrottle();
 					}
 					else
 					{
-						bIsHtml = false;
+						oMessage.body = oTextBody;
+						if (oMessage.body)
+						{
+							oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
+							oMessage.fetchDataToDom();
+						}
 					}
 
-					oBody
-						.html(Utils.findEmailAndLinks(sResultHtml))
-						.addClass('b-text-part ' + (bIsHtml ? 'html' : 'plain'))
-					;
+					this.messageActiveDom(oMessage.body);
 
-					oMessage.isHtml(!!bIsHtml);
-					oMessage.hasImages(!!bHasExternals);
-					oMessage.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
-					oMessage.pgpSignedVerifyUser('');
+					this.hideMessageBodies();
 
-					oMessage.body = oBody;
-					if (oMessage.body)
+					if (oBody)
 					{
-						oMessagesBodiesDom.append(oMessage.body);
+						this.initBlockquoteSwitcher(oBody);
 					}
 
-					oMessage.storeDataToDom();
-
-					if (bHasInternals)
-					{
-						oMessage.showInternalImages(true);
-					}
-
-					if (oMessage.hasImages() && SettingsStore.showImages())
-					{
-						oMessage.showExternalImages(true);
-					}
-
-					this.purgeMessageBodyCacheThrottle();
+					oMessage.body.show();
 				}
-				else
+
+				Cache.initMessageFlagsFromCache(oMessage);
+				if (oMessage.unseen() || oMessage.hasUnseenSubMessage())
 				{
-					oMessage.body = oTextBody;
-					if (oMessage.body)
-					{
-						oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
-						oMessage.fetchDataToDom();
-					}
+					require('App/User').messageListAction(oMessage.folderFullNameRaw,
+						oMessage.uid, Enums.MessageSetAction.SetSeen, [oMessage]);
 				}
 
-				this.messageActiveDom(oMessage.body);
-
-				this.hideMessageBodies();
-				oMessage.body.show();
-
-				if (oBody)
+				if (bNew)
 				{
-					this.initBlockquoteSwitcher(oBody);
+					oMessage = this.message();
+
+					if (oSelectedMessage && oMessage && (
+						oMessage.folderFullNameRaw !== oSelectedMessage.folderFullNameRaw ||
+						oMessage.uid !== oSelectedMessage.uid
+					))
+					{
+						this.selectorMessageSelected(null);
+						if (1 === this.messageList().length)
+						{
+							this.selectorMessageFocused(null);
+						}
+					}
+					else if (!oSelectedMessage && oMessage)
+					{
+						oSelectedMessage = _.find(this.messageList(), function (oSubMessage) {
+							return oSubMessage &&
+								oSubMessage.folderFullNameRaw === oMessage.folderFullNameRaw &&
+								oSubMessage.uid === oMessage.uid;
+						});
+
+						if (oSelectedMessage)
+						{
+							this.selectorMessageSelected(oSelectedMessage);
+							this.selectorMessageFocused(oSelectedMessage);
+						}
+					}
+
 				}
+
+				Utils.windowResize();
 			}
+		}
+	};
 
-			Cache.initMessageFlagsFromCache(oMessage);
-			if (oMessage.unseen())
+	MessageUserStore.prototype.selectMessage = function (oMessage)
+	{
+		if (oMessage)
+		{
+			this.message(this.staticMessage.populateByMessageListItem(oMessage));
+
+			this.populateMessageBody(this.message());
+
+			if (Enums.Layout.NoPreview === SettingsStore.layout())
 			{
-				require('App/User').setMessageSeen(oMessage);
+				kn.setHash(Links.messagePreview(), true);
+				this.message.focused(true);
 			}
+		}
+		else
+		{
+			this.message(null);
+		}
+	};
 
-			Utils.windowResize();
+	MessageUserStore.prototype.selectThreadMessage = function (sFolder, sUid)
+	{
+		if (Remote.message(this.onMessageResponse, sFolder, sUid))
+		{
+			this.messageThreadLoading(true);
+		}
+		else
+		{
+			Utils.log('Error: Unknown message request: ' + sFolder + ' ~ ' + sUid + ' [e-102]');
+		}
+
+		if (Enums.Layout.NoPreview === SettingsStore.layout())
+		{
+			kn.setHash(Links.messagePreview(), true);
+			this.message.focused(true);
+		}
+	};
+
+	MessageUserStore.prototype.populateMessageBody = function (oMessage)
+	{
+		if (oMessage)
+		{
+			if (Remote.message(this.onMessageResponse, oMessage.folderFullNameRaw, oMessage.uid))
+			{
+				this.messageCurrentLoading(true);
+			}
+			else
+			{
+				Utils.log('Error: Unknown message request: ' + oMessage.folderFullNameRaw + ' ~ ' + oMessage.uid + ' [e-101]');
+			}
+		}
+	};
+
+	/**
+	 * @param {string} sResult
+	 * @param {AjaxJsonDefaultResponse} oData
+	 * @param {boolean} bCached
+	 */
+	MessageUserStore.prototype.onMessageResponse = function (sResult, oData, bCached)
+	{
+		this.hideMessageBodies();
+
+		this.messageCurrentLoading(false);
+		this.messageThreadLoading(false);
+
+		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
+		{
+			this.setMessage(oData, bCached);
+		}
+		else if (Enums.StorageResultType.Unload === sResult)
+		{
+			this.message(null);
+			this.messageError('');
+		}
+		else if (Enums.StorageResultType.Abort !== sResult)
+		{
+			this.message(null);
+			this.messageError((oData && oData.ErrorCode ?
+				Translator.getNotification(oData.ErrorCode) :
+				Translator.getNotification(Enums.Notification.UnknownError)));
 		}
 	};
 
@@ -613,7 +760,7 @@
 			oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection']))
 		{
 			var
-				mLastCollapsedThreadUids = null,
+				self = this,
 				iIndex = 0,
 				iLen = 0,
 				iCount = 0,
@@ -630,11 +777,6 @@
 			iCount = Utils.pInt(oData.Result.MessageResultCount);
 			iOffset = Utils.pInt(oData.Result.Offset);
 
-			if (Utils.isNonEmptyArray(oData.Result.LastCollapsedThreadUids))
-			{
-				mLastCollapsedThreadUids = oData.Result.LastCollapsedThreadUids;
-			}
-
 			oFolder = Cache.getFolderFromCacheList(
 				Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : '');
 
@@ -692,9 +834,6 @@
 							Cache.storeMessageFlagsToCache(oMessage);
 						}
 
-						oMessage.lastInCollapsedThreadLoading(false);
-						oMessage.lastInCollapsedThread(mLastCollapsedThreadUids && -1 < Utils.inArray(Utils.pInt(oMessage.uid), mLastCollapsedThreadUids) ? true : false);
-
 						aList.push(oMessage);
 					}
 				}
@@ -708,11 +847,33 @@
 			this.messageListEndSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : '');
 			this.messageListEndPage(this.messageListPage());
 
+			this.messageListDisableAutoSelect(true);
+
 			this.messageList(aList);
 			this.messageListIsNotCompleted(false);
 
 			Cache.clearNewMessageCache();
 
+			if (AppStore.threadsAllowed() && SettingsStore.useThreads())
+			{
+				oMessage = this.message();
+				if (oMessage)
+				{
+					Remote.messageThreadsFromCache(function (sResult, oData) {
+
+						if (Enums.StorageResultType.Success === sResult && oData &&  oData.Result && oData.Result.ThreadUids)
+						{
+							self.messageLastThreadUidsData({
+								'Folder': oData.Result.Folder,
+								'Uid': oData.Result.Uid,
+								'Uids': oData.Result.ThreadUids
+							});
+						}
+
+					}, oMessage.folderFullNameRaw, oMessage.uid);
+				}
+			}
+
 			if (oFolder && (bCached || bUnreadCountChange || SettingsStore.useThreads()))
 			{
 				require('App/User').folderInformation(oFolder.fullNameRaw, aList);
diff --git a/dev/Styles/MessageView.less b/dev/Styles/MessageView.less
index dfb68c43a..0fb9c26a0 100644
--- a/dev/Styles/MessageView.less
+++ b/dev/Styles/MessageView.less
@@ -110,7 +110,6 @@ html.rl-no-preview-pane {
 				text-overflow: ellipsis;
 				overflow: hidden;
 				white-space: nowrap;
-				margin-left: 3px;
 			}
 
 			.senderParent {
@@ -121,8 +120,25 @@ html.rl-no-preview-pane {
 				margin-top: 5px;
 			}
 
+			.flagParent {
+
+				cursor: pointer;
+				margin-right: 5px;
+
+				.flagOn {
+					color: orange;
+				}
+
+				.flagOff {
+					opacity: 0.5;
+					&:hover {
+						opacity: 1;
+					}
+				}
+			}
+
 			.informationShort {
-				margin-left: 15px;
+				margin-left: 22px;
 				a {
 					.g-ui-link;
 				}
diff --git a/dev/View/Popup/Contacts.js b/dev/View/Popup/Contacts.js
index 42844cfd3..cbe6863fb 100644
--- a/dev/View/Popup/Contacts.js
+++ b/dev/View/Popup/Contacts.js
@@ -208,7 +208,7 @@
 			});
 		}, this);
 
-		this.selector = new Selector(this.contacts, this.currentContact,
+		this.selector = new Selector(this.contacts, this.currentContact, null,
 			'.e-contact-item .actionHandle', '.e-contact-item.selected', '.e-contact-item .checkboxItem',
 				'.e-contact-item.focused');
 
diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js
index 8111776e6..2ee73c4ff 100644
--- a/dev/View/User/MailBox/MessageList.js
+++ b/dev/View/User/MailBox/MessageList.js
@@ -52,8 +52,10 @@
 
 		this.message = MessageStore.message;
 		this.messageList = MessageStore.messageList;
+		this.messageListDisableAutoSelect = MessageStore.messageListDisableAutoSelect;
 		this.folderList = FolderStore.folderList;
-		this.currentMessage = MessageStore.currentMessage;
+		this.selectorMessageSelected = MessageStore.selectorMessageSelected;
+		this.selectorMessageFocused = MessageStore.selectorMessageFocused;
 		this.isMessageSelected = MessageStore.isMessageSelected;
 		this.messageListSearch = MessageStore.messageListSearch;
 		this.messageListError = MessageStore.messageListError;
@@ -224,27 +226,12 @@
 
 		this.quotaTooltip = _.bind(this.quotaTooltip, this);
 
-		this.selector = new Selector(this.messageList, this.currentMessage,
+		this.selector = new Selector(this.messageList, this.selectorMessageSelected, this.selectorMessageFocused,
 			'.messageListItem .actionHandle', '.messageListItem.selected', '.messageListItem .checkboxMessage',
 				'.messageListItem.focused');
 
 		this.selector.on('onItemSelect', _.bind(function (oMessage) {
-			if (oMessage)
-			{
-				MessageStore.message(MessageStore.staticMessage.populateByMessageListItem(oMessage));
-
-				this.populateMessageBody(MessageStore.message());
-
-				if (Enums.Layout.NoPreview === SettingsStore.layout())
-				{
-					kn.setHash(Links.messagePreview(), true);
-					MessageStore.message.focused(true);
-				}
-			}
-			else
-			{
-				MessageStore.message(null);
-			}
+			MessageStore.selectMessage(oMessage);
 		}, this));
 
 		this.selector.on('onItemGetUid', function (oMessage) {
@@ -281,6 +268,11 @@
 
 	MessageListMailBoxUserView.prototype.useAutoSelect = function ()
 	{
+		if (this.messageListDisableAutoSelect())
+		{
+			return false;
+		}
+
 		if (/is:unseen/.test(this.mainMessageListSearch()))
 		{
 			return false;
@@ -353,129 +345,15 @@
 		return oEl;
 	};
 
-	/**
-	 * @param {string} sResult
-	 * @param {AjaxJsonDefaultResponse} oData
-	 * @param {boolean} bCached
-	 */
-	MessageListMailBoxUserView.prototype.onMessageResponse = function (sResult, oData, bCached)
-	{
-		MessageStore.hideMessageBodies();
-		MessageStore.messageLoading(false);
-
-		if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
-		{
-			MessageStore.setMessage(oData, bCached);
-		}
-		else if (Enums.StorageResultType.Unload === sResult)
-		{
-			MessageStore.message(null);
-			MessageStore.messageError('');
-		}
-		else if (Enums.StorageResultType.Abort !== sResult)
-		{
-			MessageStore.message(null);
-			MessageStore.messageError((oData && oData.ErrorCode ?
-				Translator.getNotification(oData.ErrorCode) :
-				Translator.getNotification(Enums.Notification.UnknownError)));
-		}
-	};
-
-	MessageListMailBoxUserView.prototype.populateMessageBody = function (oMessage)
-	{
-		if (oMessage)
-		{
-			if (Remote.message(this.onMessageResponse, oMessage.folderFullNameRaw, oMessage.uid))
-			{
-				MessageStore.messageLoading(true);
-			}
-			else
-			{
-				Utils.log('Error: Unknown message request: ' + oMessage.folderFullNameRaw + ' ~ ' + oMessage.uid + ' [e-101]');
-			}
-		}
-	};
-
 	/**
 	 * @param {string} sFolderFullNameRaw
+	 * @param {string|bool} sUid
 	 * @param {number} iSetAction
 	 * @param {Array=} aMessages = null
 	 */
-	MessageListMailBoxUserView.prototype.setAction = function (sFolderFullNameRaw, iSetAction, aMessages)
+	MessageListMailBoxUserView.prototype.setAction = function (sFolderFullNameRaw, mUid, iSetAction, aMessages)
 	{
-		var
-			aUids = [],
-			oFolder = null,
-			iAlreadyUnread = 0
-		;
-
-		if (Utils.isUnd(aMessages))
-		{
-			aMessages = MessageStore.messageListChecked();
-		}
-
-		aUids = _.map(aMessages, function (oMessage) {
-			return oMessage.uid;
-		});
-
-		if ('' !== sFolderFullNameRaw && 0 < aUids.length)
-		{
-			switch (iSetAction) {
-			case Enums.MessageSetAction.SetSeen:
-				_.each(aMessages, function (oMessage) {
-					if (oMessage.unseen())
-					{
-						iAlreadyUnread++;
-					}
-
-					oMessage.unseen(false);
-					Cache.storeMessageFlagsToCache(oMessage);
-				});
-
-				oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
-				if (oFolder)
-				{
-					oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread);
-				}
-
-				Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aUids, true);
-				break;
-			case Enums.MessageSetAction.UnsetSeen:
-				_.each(aMessages, function (oMessage) {
-					if (oMessage.unseen())
-					{
-						iAlreadyUnread++;
-					}
-
-					oMessage.unseen(true);
-					Cache.storeMessageFlagsToCache(oMessage);
-				});
-
-				oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
-				if (oFolder)
-				{
-					oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread + aUids.length);
-				}
-				Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aUids, false);
-				break;
-			case Enums.MessageSetAction.SetFlag:
-				_.each(aMessages, function (oMessage) {
-					oMessage.flagged(true);
-					Cache.storeMessageFlagsToCache(oMessage);
-				});
-				Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aUids, true);
-				break;
-			case Enums.MessageSetAction.UnsetFlag:
-				_.each(aMessages, function (oMessage) {
-					oMessage.flagged(false);
-					Cache.storeMessageFlagsToCache(oMessage);
-				});
-				Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aUids, false);
-				break;
-			}
-
-			require('App/User').reloadFlagsCurrentMessageListAndMessageFromCache();
-		}
+		require('App/User').messageListAction(sFolderFullNameRaw, mUid, iSetAction, aMessages);
 	};
 
 	/**
@@ -532,8 +410,8 @@
 
 	MessageListMailBoxUserView.prototype.listSetSeen = function ()
 	{
-		this.setAction(FolderStore.currentFolderFullNameRaw(), Enums.MessageSetAction.SetSeen,
-			MessageStore.messageListCheckedOrSelected());
+		this.setAction(FolderStore.currentFolderFullNameRaw(), true,
+			Enums.MessageSetAction.SetSeen, MessageStore.messageListCheckedOrSelected());
 	};
 
 	MessageListMailBoxUserView.prototype.listSetAllSeen = function ()
@@ -543,20 +421,20 @@
 
 	MessageListMailBoxUserView.prototype.listUnsetSeen = function ()
 	{
-		this.setAction(FolderStore.currentFolderFullNameRaw(), Enums.MessageSetAction.UnsetSeen,
-			MessageStore.messageListCheckedOrSelected());
+		this.setAction(FolderStore.currentFolderFullNameRaw(), true,
+			Enums.MessageSetAction.UnsetSeen, MessageStore.messageListCheckedOrSelected());
 	};
 
 	MessageListMailBoxUserView.prototype.listSetFlags = function ()
 	{
-		this.setAction(FolderStore.currentFolderFullNameRaw(), Enums.MessageSetAction.SetFlag,
-			MessageStore.messageListCheckedOrSelected());
+		this.setAction(FolderStore.currentFolderFullNameRaw(), true,
+			Enums.MessageSetAction.SetFlag, MessageStore.messageListCheckedOrSelected());
 	};
 
 	MessageListMailBoxUserView.prototype.listUnsetFlags = function ()
 	{
-		this.setAction(FolderStore.currentFolderFullNameRaw(), Enums.MessageSetAction.UnsetFlag,
-			MessageStore.messageListCheckedOrSelected());
+		this.setAction(FolderStore.currentFolderFullNameRaw(), true,
+			Enums.MessageSetAction.UnsetFlag, MessageStore.messageListCheckedOrSelected());
 	};
 
 	MessageListMailBoxUserView.prototype.flagMessages = function (oCurrentMessage)
@@ -577,12 +455,12 @@
 
 			if (0 < aCheckedUids.length && -1 < Utils.inArray(oCurrentMessage.uid, aCheckedUids))
 			{
-				this.setAction(oCurrentMessage.folderFullNameRaw, oCurrentMessage.flagged() ?
+				this.setAction(oCurrentMessage.folderFullNameRaw, true, oCurrentMessage.flagged() ?
 					Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked);
 			}
 			else
 			{
-				this.setAction(oCurrentMessage.folderFullNameRaw, oCurrentMessage.flagged() ?
+				this.setAction(oCurrentMessage.folderFullNameRaw, true, oCurrentMessage.flagged() ?
 					Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oCurrentMessage]);
 			}
 		}
@@ -603,12 +481,12 @@
 
 			if (Utils.isUnd(bFlag))
 			{
-				this.setAction(aChecked[0].folderFullNameRaw,
+				this.setAction(aChecked[0].folderFullNameRaw, true,
 					aChecked.length === aFlagged.length ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked);
 			}
 			else
 			{
-				this.setAction(aChecked[0].folderFullNameRaw,
+				this.setAction(aChecked[0].folderFullNameRaw, true,
 					!bFlag ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, aChecked);
 			}
 		}
@@ -629,12 +507,12 @@
 
 			if (Utils.isUnd(bSeen))
 			{
-				this.setAction(aChecked[0].folderFullNameRaw,
+				this.setAction(aChecked[0].folderFullNameRaw, true,
 					0 < aUnseen.length ? Enums.MessageSetAction.SetSeen : Enums.MessageSetAction.UnsetSeen, aChecked);
 			}
 			else
 			{
-				this.setAction(aChecked[0].folderFullNameRaw,
+				this.setAction(aChecked[0].folderFullNameRaw, true,
 					bSeen ? Enums.MessageSetAction.SetSeen : Enums.MessageSetAction.UnsetSeen, aChecked);
 			}
 		}
@@ -647,38 +525,6 @@
 		this.oContentVisible = $('.b-content', oDom);
 		this.oContentScrollable = $('.content', this.oContentVisible);
 
-		this.oContentVisible.on('click', '.fullThreadHandle', function () {
-			var
-				aList = [],
-				oMessage = ko.dataFor(this)
-			;
-
-			if (oMessage && !oMessage.lastInCollapsedThreadLoading())
-			{
-				MessageStore.messageListThreadFolder(oMessage.folderFullNameRaw);
-
-				aList = MessageStore.messageListThreadUids();
-
-				if (oMessage.lastInCollapsedThread())
-				{
-					aList.push(0 < oMessage.parentUid() ? oMessage.parentUid() : oMessage.uid);
-				}
-				else
-				{
-					aList = _.without(aList, 0 < oMessage.parentUid() ? oMessage.parentUid() : oMessage.uid);
-				}
-
-				MessageStore.messageListThreadUids(_.uniq(aList));
-
-				oMessage.lastInCollapsedThreadLoading(true);
-				oMessage.lastInCollapsedThread(!oMessage.lastInCollapsedThread());
-
-				require('App/User').reloadMessageList();
-			}
-
-			return false;
-		});
-
 		this.selector.init(this.oContentVisible, this.oContentScrollable, Enums.KeyState.MessageList);
 
 		oDom
diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js
index f43b68aa2..58c78b704 100644
--- a/dev/View/User/MailBox/MessageView.js
+++ b/dev/View/User/MailBox/MessageView.js
@@ -58,11 +58,9 @@
 		this.pswp = null;
 
 		this.message = MessageStore.message;
-		this.currentMessage = MessageStore.currentMessage;
 		this.messageListChecked = MessageStore.messageListChecked;
 		this.hasCheckedMessages = MessageStore.hasCheckedMessages;
 		this.messageListCheckedOrSelectedUidsWithSubMails = MessageStore.messageListCheckedOrSelectedUidsWithSubMails;
-		this.messageLoading = MessageStore.messageLoading;
 		this.messageLoadingThrottle = MessageStore.messageLoadingThrottle;
 		this.messagesBodiesDom = MessageStore.messagesBodiesDom;
 		this.useThreads = SettingsStore.useThreads;
@@ -103,7 +101,7 @@
 		this.message.subscribe(function (oMessage) {
 			if (!oMessage)
 			{
-				this.currentMessage(null);
+				MessageStore.selectorMessageSelected(null);
 			}
 		}, this);
 
@@ -130,47 +128,52 @@
 		}, this.messageVisibility);
 
 		this.deleteCommand = Utils.createCommand(this, function () {
-			if (this.message())
+			var oMessage = this.message();
+			if (oMessage)
 			{
+				this.message(null);
 				require('App/User').deleteMessagesFromFolder(Enums.FolderType.Trash,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
+					oMessage.folderFullNameRaw, [oMessage.uid], true);
 			}
 		}, this.messageVisibility);
 
 		this.deleteWithoutMoveCommand = Utils.createCommand(this, function () {
-			if (this.message())
+			var oMessage = this.message();
+			if (oMessage)
 			{
+				this.message(null);
 				require('App/User').deleteMessagesFromFolder(Enums.FolderType.Trash,
-					FolderStore.currentFolderFullNameRaw(),
-					[this.message().uid], false);
+					oMessage.folderFullNameRaw, [oMessage.uid], false);
 			}
 		}, this.messageVisibility);
 
 		this.archiveCommand = Utils.createCommand(this, function () {
-			if (this.message())
+			var oMessage = this.message();
+			if (oMessage)
 			{
+				this.message(null);
 				require('App/User').deleteMessagesFromFolder(Enums.FolderType.Archive,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
+					oMessage.folderFullNameRaw, [oMessage.uid], true);
 			}
 		}, this.messageVisibility);
 
 		this.spamCommand = Utils.createCommand(this, function () {
-			if (this.message())
+			var oMessage = this.message();
+			if (oMessage)
 			{
+				this.message(null);
 				require('App/User').deleteMessagesFromFolder(Enums.FolderType.Spam,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
+					oMessage.folderFullNameRaw, [oMessage.uid], true);
 			}
 		}, this.messageVisibility);
 
 		this.notSpamCommand = Utils.createCommand(this, function () {
-			if (this.message())
+			var oMessage = this.message();
+			if (oMessage)
 			{
+				this.message(null);
 				require('App/User').deleteMessagesFromFolder(Enums.FolderType.NotSpam,
-					this.message().folderFullNameRaw,
-					[this.message().uid], true);
+					oMessage.folderFullNameRaw, [oMessage.uid], true);
 			}
 		}, this.messageVisibility);
 
@@ -178,6 +181,8 @@
 
 		this.viewBodyTopValue = ko.observable(0);
 
+		this.viewFolder = '';
+		this.viewUid = '';
 		this.viewHash = '';
 		this.viewSubject = ko.observable('');
 		this.viewFromShort = ko.observable('');
@@ -197,7 +202,94 @@
 		this.viewUserPic = ko.observable(Consts.DataImages.UserDotPic);
 		this.viewUserPicVisible = ko.observable(false);
 		this.viewIsImportant = ko.observable(false);
+		this.viewIsFlagged = ko.observable(false);
 
+// THREADS
+		this.viewThreads = ko.observableArray([]);
+		this.viewThreads.trigger = ko.observable(false);
+
+		MessageStore.messageLastThreadUidsData.subscribe(function (oData) {
+			if (oData && oData['Uids'])
+			{
+				oData['Uid'] = Utils.pString(oData['Uid']);
+				if (this.viewFolder === oData['Folder'] && this.viewUid === oData['Uid'])
+				{
+					this.viewThreads(oData['Uids']);
+					this.viewThreads.trigger(!this.viewThreads.trigger());
+				}
+
+				var oMessage = MessageStore.message();
+				if (oMessage && oMessage.folderFullNameRaw === oData['Folder'] && oMessage.uid === oData['Uid'])
+				{
+					oMessage.threads(oData['Uids']);
+				}
+
+				oMessage = _.find(MessageStore.messageList(), function (oMessage) {
+					return oMessage && oMessage.folderFullNameRaw === oData['Folder'] && oMessage.uid === oData['Uid'];
+				});
+
+				if (oMessage && oMessage.folderFullNameRaw === oData['Folder'] && oMessage.uid === oData['Uid'])
+				{
+					oMessage.threads(oData['Uids']);
+				}
+			}
+		}, this);
+
+		this.viewThreads.status = ko.computed(function () {
+
+			this.viewThreads.trigger();
+
+			var
+				iIndex = 0,
+				aResult = [false, '', '', '', ''],
+				aThreads = this.viewThreads.peek(),
+				iLen = aThreads.length
+			;
+
+			if (1 < iLen)
+			{
+				iIndex = Utils.inArray(this.viewUid, aThreads);
+				if (-1 < iIndex)
+				{
+					aResult[0] = true;
+					aResult[1] = (iIndex + 1) + ' / ' + iLen;
+					aResult[2] = aThreads[iIndex];
+					aResult[3] = 0 < iIndex && aThreads[iIndex - 1] ? aThreads[iIndex - 1] : '';
+					aResult[4] = aThreads[iIndex + 1] ? aThreads[iIndex + 1] : '';
+				}
+			}
+
+			return aResult;
+
+		}, this).extend({'notify': 'always'});
+
+		this.viewThreadsControlVisibility = ko.computed(function () {
+			return !!this.viewThreads.status()[0];
+		}, this);
+
+		this.viewThreadsControlDesc = ko.computed(function () {
+			return this.viewThreads.status()[1];
+		}, this);
+
+		this.viewThreadsControlBackAllow = ko.computed(function () {
+			return '' !== this.viewThreads.status()[4] && !this.messageLoadingThrottle();
+		}, this);
+
+		this.viewThreadsControlForwardAllow = ko.computed(function () {
+			return '' !== this.viewThreads.status()[3] && !this.messageLoadingThrottle();
+		}, this);
+
+		this.threadBackCommand = Utils.createCommand(this, function () {
+			var aStatus = this.viewThreads.status();
+			this.openThreadMessage(aStatus[4]);
+		}, this.viewThreadsControlBackAllow);
+
+		this.threadForwardCommand = Utils.createCommand(this, function () {
+			var aStatus = this.viewThreads.status();
+			this.openThreadMessage(aStatus[3]);
+		}, this.viewThreadsControlForwardAllow);
+
+// PGP
 		this.viewPgpPassword = ko.observable('');
 		this.viewPgpSignedVerifyStatus = ko.computed(function () {
 			return this.message() ? this.message().pgpSignedVerifyStatus() : Enums.SignedVerifyStatus.None;
@@ -260,6 +352,8 @@
 					this.scrollMessageToTop();
 				}
 
+				this.viewFolder = oMessage.folderFullNameRaw;
+				this.viewUid = oMessage.uid;
 				this.viewHash = oMessage.hash;
 				this.viewSubject(oMessage.subject());
 				this.viewFromShort(oMessage.fromToLine(true, true));
@@ -277,6 +371,10 @@
 				this.viewViewLink(oMessage.viewLink());
 				this.viewDownloadLink(oMessage.downloadLink());
 				this.viewIsImportant(oMessage.isImportant());
+				this.viewIsFlagged(oMessage.flagged());
+
+				this.viewThreads(oMessage.threads());
+				this.viewThreads.trigger(!this.viewThreads.trigger());
 
 				sLastEmail = oMessage.fromAsSingleEmail();
 				Cache.getUserPic(sLastEmail, function (sPic, sEmail) {
@@ -294,12 +392,29 @@
 			}
 			else
 			{
+				this.viewFolder = '';
+				this.viewUid = '';
 				this.viewHash = '';
+
+				this.viewThreads([]);
+
 				this.scrollMessageToTop();
 			}
 
 		}, this);
 
+		this.message.viewTrigger.subscribe(function () {
+			var oMessage = this.message();
+			if (oMessage)
+			{
+				this.viewIsFlagged(oMessage.flagged());
+			}
+			else
+			{
+				this.viewIsFlagged(false);
+			}
+		}, this);
+
 		this.fullScreenMode.subscribe(function (bValue) {
 			if (bValue)
 			{
@@ -333,6 +448,24 @@
 	kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView);
 	_.extend(MessageViewMailBoxUserView.prototype, AbstractView.prototype);
 
+	MessageViewMailBoxUserView.prototype.updateViewFlagsFromCache = function ()
+	{
+		var aFlags = this.getMessageFlagsFromCache(this.viewFolder, this.viewUid);
+		if (aFlags)
+		{
+			this.viewIsFlagged(!!aFlags[1]);
+		}
+	};
+
+	MessageViewMailBoxUserView.prototype.openThreadMessage = function (sUid)
+	{
+		var oMessage = this.message();
+		if (oMessage && sUid)
+		{
+			MessageStore.selectThreadMessage(oMessage.folderFullNameRaw, sUid);
+		}
+	};
+
 	MessageViewMailBoxUserView.prototype.isPgpActionVisible = function ()
 	{
 		return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
@@ -543,6 +676,9 @@
 					require('App/User').download(oAttachment.linkDownload());
 				}
 			})
+			.on('click', '.messageItemHeader .flagParent', function () {
+				self.flagViewMessage();
+			})
 		;
 
 		this.message.focused.subscribe(function (bValue) {
@@ -575,6 +711,19 @@
 		this.initShortcuts();
 	};
 
+	/**
+	 * @return {boolean}
+	 */
+	MessageViewMailBoxUserView.prototype.flagViewMessage = function ()
+	{
+		var oMessage = this.message();
+		if (oMessage)
+		{
+			require('App/User').messageListAction(oMessage.folderFullNameRaw, oMessage.uid,
+				oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]);
+		}
+	};
+
 	/**
 	 * @return {boolean}
 	 */
@@ -664,16 +813,26 @@
 			}
 		});
 
-		key('ctrl+left, command+left, ctrl+up, command+up', Enums.KeyState.MessageView, function () {
+		key('ctrl+up, command+up', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
 			self.goUpCommand();
 			return false;
 		});
 
-		key('ctrl+right, command+right, ctrl+down, command+down', Enums.KeyState.MessageView, function () {
+		key('ctrl+down, command+down', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
 			self.goDownCommand();
 			return false;
 		});
 
+		key('ctrl+left, command+left', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
+			self.threadForwardCommand();
+			return false;
+		});
+
+		key('ctrl+right, command+right', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
+			self.threadBackCommand();
+			return false;
+		});
+
 		// print
 		key('ctrl+p, command+p', Enums.KeyState.MessageView, function () {
 			if (self.message())
diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Config.php b/rainloop/v/0.0.0/app/libraries/MailSo/Config.php
index c5d83afb9..682b0439b 100644
--- a/rainloop/v/0.0.0/app/libraries/MailSo/Config.php
+++ b/rainloop/v/0.0.0/app/libraries/MailSo/Config.php
@@ -49,7 +49,7 @@ class Config
 	/**
 	 * @var int
 	 */
-	public static $LargeThreadLimit = 100;
+	public static $LargeThreadLimit = 50;
 
 	/**
 	 * @var bool
diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
index 262e69620..83dd2f66b 100644
--- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
+++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php
@@ -698,6 +698,18 @@ class MailClient
 		}
 	}
 
+	/**
+	 * @return string
+	 */
+	public function GenerateImapClientHash()
+	{
+		return \md5('ImapClientHash/'.
+			$this->oImapClient->GetLogginedUser().'@'.
+			$this->oImapClient->GetConnectedHost().':'.
+			$this->oImapClient->GetConnectedPort()
+		);
+	}
+
 	/**
 	 * @param string $sFolder
 	 * @param int $iCount
@@ -706,10 +718,12 @@ class MailClient
 	 *
 	 * @return string
 	 */
-	public static function GenerateHash($sFolder, $iCount, $iUnseenCount, $sUidNext)
+	public function GenerateFolderHash($sFolder, $iCount, $iUnseenCount, $sUidNext)
 	{
 		$iUnseenCount = 0; // unneccessery
-		return \md5($sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext);
+		return \md5('FolderHash/'.$sFolder.'-'.$iCount.'-'.$iUnseenCount.'-'.$sUidNext.'-'.
+			$this->GenerateImapClientHash()
+		);
 	}
 
 	/**
@@ -837,7 +851,7 @@ class MailClient
 
 		$aResult = array(
 			'Folder' => $sFolderName,
-			'Hash' => self::GenerateHash($sFolderName, $iCount, $iUnseenCount, $sUidNext),
+			'Hash' => $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext),
 			'MessageCount' => $iCount,
 			'MessageUnseenCount' => $iUnseenCount,
 			'UidNext' => $sUidNext,
@@ -866,7 +880,7 @@ class MailClient
 
 		$this->initFolderValues($sFolderName, $iCount, $iUnseenCount, $sUidNext);
 
-		return self::GenerateHash($sFolderName, $iCount, $iUnseenCount, $sUidNext);
+		return $this->GenerateFolderHash($sFolderName, $iCount, $iUnseenCount, $sUidNext);
 	}
 
 	/**
@@ -1410,7 +1424,8 @@ class MailClient
 	 * @param string $sFolderHash
 	 * @param array $aIndexOrUids
 	 * @param \MailSo\Cache\CacheClient $oCacher
-	 * @param int $iThreadLimit = 100
+	 * @param int $iThreadLimit = 50
+	 * @param bool $bCacheOnly = false
 	 *
 	 * @return array
 	 *
@@ -1418,7 +1433,7 @@ class MailClient
 	 * @throws \MailSo\Net\Exceptions\Exception
 	 * @throws \MailSo\Imap\Exceptions\Exception
 	 */
-	public function MessageListThreadsMap($sFolderName, $sFolderHash, $aIndexOrUids, $oCacher, $iThreadLimit = 100)
+	public function MessageListThreadsMap($sFolderName, $sFolderHash, $aIndexOrUids, $oCacher, $iThreadLimit = 50, $bCacheOnly = false)
 	{
 		$iThreadLimit = \is_int($iThreadLimit) && 0 < $iThreadLimit ? $iThreadLimit : 0;
 
@@ -1438,30 +1453,36 @@ class MailClient
 
 		if ($oCacher && $oCacher->IsInited())
 		{
-			$sSerializedHash =
+			$sSerializedHashKey =
 				'ThreadsMapSorted/'.$sSearchHash.'/'.
-				'Limit='.$iThreadLimit.'/'.
-				$this->oImapClient->GetLogginedUser().'@'.
-				$this->oImapClient->GetConnectedHost().':'.
-				$this->oImapClient->GetConnectedPort().'/'.
-				$sFolderName.'/'.$sFolderHash;
+				'Limit='.$iThreadLimit.'/'.$sFolderName.'/'.$sFolderHash;
 
-			$sSerializedUids = $oCacher->Get($sSerializedHash);
+			if ($this->oLogger)
+			{
+				$this->oLogger->Write($sSerializedHashKey);
+			}
+
+			$sSerializedUids = $oCacher->Get($sSerializedHashKey);
 			if (!empty($sSerializedUids))
 			{
-				$aSerializedUids = @\unserialize($sSerializedUids);
-				if (is_array($aSerializedUids))
+				$aSerializedUids = @\json_decode($sSerializedUids, true);
+				if (isset($aSerializedUids['ThreadsUids']) && \is_array($aSerializedUids['ThreadsUids']))
 				{
 					if ($this->oLogger)
 					{
-						$this->oLogger->Write('Get Serialized Thread UIDS from cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aSerializedUids).']');
+						$this->oLogger->Write('Get Serialized Thread UIDS from cache ("'.$sFolderName.'" / '.$sSearchHash.') [count:'.\count($aSerializedUids['ThreadsUids']).']');
 					}
 
-					return $aSerializedUids;
+					return $aSerializedUids['ThreadsUids'];
 				}
 			}
 		}
 
+		if ($bCacheOnly)
+		{
+			return null;
+		}
+
 		$this->oImapClient->FolderExamine($sFolderName);
 
 		$aThreadUids = array();
@@ -1471,6 +1492,7 @@ class MailClient
 		}
 		catch (\MailSo\Imap\Exceptions\RuntimeException $oException)
 		{
+			unset($oException);
 			$aThreadUids = array();
 		}
 
@@ -1582,9 +1604,11 @@ class MailClient
 		$aResult = $aLastResult;
 		unset($aLastResult);
 
-		if ($oCacher && $oCacher->IsInited() && !empty($sSerializedHash))
+		if ($oCacher && $oCacher->IsInited() && !empty($sSerializedHashKey))
 		{
-			$oCacher->Set($sSerializedHash, serialize($aResult));
+			$oCacher->Set($sSerializedHashKey, @\json_encode(array(
+				'ThreadsUids' => $aResult
+			)));
 
 			if ($this->oLogger)
 			{
@@ -1668,8 +1692,8 @@ class MailClient
 	private function getSearchUidsResult($sSearch, $sFolderName, $sFolderHash,
 		$bUseSortIfSupported = true, $bUseESearchOrESortRequest = false, $oCacher = null)
 	{
-		$bUidsFromCacher = false;
 		$aResultUids = false;
+		$bUidsFromCacher = false;
 		$bUseCacheAfterSearch = true;
 		$sSerializedHash = '';
 
@@ -1680,23 +1704,21 @@ class MailClient
 		$sSearchCriterias = $this->getImapSearchCriterias($sSearch, 0, $bUseCacheAfterSearch);
 		if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited())
 		{
-			$sSerializedHash =
+			$sSerializedHash = 'SearchSortUids/'.
 				($bUseSortIfSupported ? 'S': 'N').'/'.
-				$this->oImapClient->GetLogginedUser().'@'.
-				$this->oImapClient->GetConnectedHost().':'.
-				$this->oImapClient->GetConnectedPort().'/'.
-				$sFolderName.'/'.
-				$sSearchCriterias;
+				$this->GenerateImapClientHash().'/'.
+				$sFolderName.'/'.$sSearchCriterias;
 
 			$sSerializedLog = '"'.$sFolderName.'" / '.$sSearchCriterias.'';
 
 			$sSerialized = $oCacher->Get($sSerializedHash);
 			if (!empty($sSerialized))
 			{
-				$aSerialized = @\unserialize($sSerialized);
+				$aSerialized = @\json_decode($sSerialized, true);
 				if (\is_array($aSerialized) && isset($aSerialized['FolderHash'], $aSerialized['Uids']) &&
-					\is_array($aSerialized['Uids']) &&
-					($sFolderHash === $aSerialized['FolderHash']))
+					$sFolderHash === $aSerialized['FolderHash'] &&
+					\is_array($aSerialized['Uids'])
+				)
 				{
 					if ($this->oLogger)
 					{
@@ -1779,7 +1801,7 @@ class MailClient
 
 			if (!$bUidsFromCacher && $bUseCacheAfterSearch && \is_array($aResultUids) && $oCacher && $oCacher->IsInited() && 0 < \strlen($sSerializedHash))
 			{
-				$oCacher->Set($sSerializedHash, \serialize(array(
+				$oCacher->Set($sSerializedHash, @\json_encode(array(
 					'FolderHash' => $sFolderHash,
 					'Uids' => $aResultUids
 				)));
@@ -1794,6 +1816,54 @@ class MailClient
 		return $aResultUids;
 	}
 
+	/**
+	 * @param string $sFolderName
+	 * @param string $sFolderHash
+	 * @param string $sUid
+	 * @param \MailSo\Cache\CacheClient|null $oCacher = null
+	 *
+	 * @return array
+	 */
+	public function MessageThreadUidsFromCache($sFolderName, $sFolderHash, $sUid, $oCacher = null)
+	{
+		$aResult = array();
+		if (0 < \strlen($sFolderName) && 0 < \strlen($sFolderHash)  && 0 < \strlen($sUid) && $oCacher && $oCacher->IsInited())
+		{
+			$mThreads = $this->MessageListThreadsMap($sFolderName, $sFolderHash, array(), $oCacher,
+				\MailSo\Config::$LargeThreadLimit, true);
+
+			if (\is_array($mThreads))
+			{
+				$iUid = (int) $sUid;
+				foreach ($mThreads as $iSubUid => $aSubUids)
+				{
+					if ($iUid === $iSubUid)
+					{
+						$aResult = $aSubUids;
+						if (!\is_array($aResult))
+						{
+							$aResult = array();
+						}
+
+						\array_unshift($aResult, $iSubUid);
+						break;
+					}
+					else if (\is_array($aSubUids))
+					{
+						if (\in_array($iUid, $aSubUids))
+						{
+							$aResult = $aSubUids;
+							\array_unshift($aResult, $iSubUid);
+							break;
+						}
+					}
+				}
+			}
+		}
+
+		return \array_map('trim', $aResult);
+	}
+
 	/**
 	 * @param string $sFolderName
 	 * @param int $iOffset = 0
@@ -1803,7 +1873,6 @@ class MailClient
 	 * @param \MailSo\Cache\CacheClient|null $oCacher = null
 	 * @param bool $bUseSortIfSupported = false
 	 * @param bool $bUseThreadSortIfSupported = false
-	 * @param array $aExpandedThreadsUids = array()
 	 * @param bool $bUseESearchOrESortRequest = false
 	 *
 	 * @return \MailSo\Mail\MessageCollection
@@ -1812,9 +1881,8 @@ class MailClient
 	 * @throws \MailSo\Net\Exceptions\Exception
 	 * @throws \MailSo\Imap\Exceptions\Exception
 	 */
-	public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '',
-		$oCacher = null, $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false,
-		$aExpandedThreadsUids = array(), $bUseESearchOrESortRequest = false)
+	public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null,
+		$bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $bUseESearchOrESortRequest = false)
 	{
 		$sSearch = \trim($sSearch);
 		if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) ||
@@ -1831,8 +1899,6 @@ class MailClient
 		$oMessageCollection->Limit = $iLimit;
 		$oMessageCollection->Search = $sSearch;
 
-		$aLastCollapsedThreadUids = array();
-
 		$aThreads = array();
 
 		$iMessageCount = 0;
@@ -1853,7 +1919,7 @@ class MailClient
 		$this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext);
 		$iMessageCount = $iMessageRealCount;
 
-		$oMessageCollection->FolderHash = self::GenerateHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext);
+		$oMessageCollection->FolderHash = $this->GenerateFolderHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext);
 		$oMessageCollection->UidNext = $sUidNext;
 		$oMessageCollection->NewMessages = $this->getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sUidNext);
 
@@ -1889,11 +1955,9 @@ class MailClient
 						$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
 						$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
 
-					$aThreads = $this->MessageListThreadsMap($oMessageCollection->FolderName, $oMessageCollection->FolderHash, $aIndexOrUids,
-						$oCacher, \MailSo\Config::$LargeThreadLimit);
-
-					$aExpandedThreadsUids = \is_array($aExpandedThreadsUids) ? $aExpandedThreadsUids : array();
-					$bWatchExpanded = 0 < \count($aExpandedThreadsUids);
+					$aThreads = $this->MessageListThreadsMap(
+						$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
+						$aIndexOrUids, $oCacher, \MailSo\Config::$LargeThreadLimit);
 
 					$aNewIndexOrUids = array();
 					foreach ($aIndexOrUids as $iUid)
@@ -1901,21 +1965,6 @@ class MailClient
 						if (isset($aThreads[$iUid]))
 						{
 							$aNewIndexOrUids[] = $iUid;
-							if (\is_array($aThreads[$iUid]))
-							{
-								if ($bWatchExpanded && \in_array($iUid, $aExpandedThreadsUids))
-								{
-									$aSubArray = $aThreads[$iUid];
-									foreach ($aSubArray as $iSubRootUid)
-									{
-										$aNewIndexOrUids[] = $iSubRootUid;
-									}
-								}
-								else
-								{
-									$aLastCollapsedThreadUids[] = $iUid;
-								}
-							}
 						}
 					}
 
@@ -1966,38 +2015,20 @@ class MailClient
 			}
 		}
 
-		$aLastCollapsedThreadUidsForPage = array();
-
 		if (!$bSearch && $bUseThreadSortIfSupported && 0 < \count($aThreads))
 		{
-			$oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use (
-				$aThreads, $aLastCollapsedThreadUids, &$aLastCollapsedThreadUidsForPage)
-			{
-				$iUid = $oMessage->Uid();
-				if (\in_array($iUid, $aLastCollapsedThreadUids))
-				{
-					$aLastCollapsedThreadUidsForPage[] = $iUid;
-				}
+			$oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($aThreads) {
 
+				$iUid = $oMessage->Uid();
 				if (isset($aThreads[$iUid]) && \is_array($aThreads[$iUid]) && 0 < \count($aThreads[$iUid]))
 				{
-					$oMessage->SetThreads($aThreads[$iUid]);
-					$oMessage->SetThreadsLen(\count($aThreads[$iUid]));
-				}
-				else if (!isset($aThreads[$iUid]))
-				{
-					foreach ($aThreads as $iKeyUid => $mSubItem)
-					{
-						if (\is_array($mSubItem) && \in_array($iUid, $mSubItem))
-						{
-							$oMessage->SetParentThread($iKeyUid);
-							$oMessage->SetThreadsLen(\count($mSubItem));
-						}
-					}
+					$aSubThreads = $aThreads[$iUid];
+					\array_unshift($aSubThreads, $iUid);
+
+					$oMessage->SetThreads(\array_map('trim', $aSubThreads));
+					unset($aSubThreads);
 				}
 			});
-
-			$oMessageCollection->LastCollapsedThreadUids = $aLastCollapsedThreadUidsForPage;
 		}
 
 		return $oMessageCollection;
diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php
index c2f9f71cd..7424f1bbf 100644
--- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php
+++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/Message.php
@@ -162,16 +162,6 @@ class Message
 	 */
 	private $aThreads;
 
-	/**
-	 * @var int
-	 */
-	private $iParentThread;
-
-	/**
-	 * @var int
-	 */
-	private $iThreadsLen;
-
 	/**
 	 * @var bool
 	 */
@@ -240,8 +230,6 @@ class Message
 		$this->sReadReceipt = '';
 
 		$this->aThreads = array();
-		$this->iThreadsLen = 0;
-		$this->iParentThread = 0;
 
 		$this->bTextPartIsTrimmed = false;
 
@@ -542,38 +530,6 @@ class Message
 		$this->aThreads = \is_array($aThreads) ? $aThreads : array();
 	}
 
-	/**
-	 * @return int
-	 */
-	public function ThreadsLen()
-	{
-		return $this->iThreadsLen;
-	}
-
-	/**
-	 * @param int $iThreadsLen
-	 */
-	public function SetThreadsLen($iThreadsLen)
-	{
-		$this->iThreadsLen = $iThreadsLen;
-	}
-
-	/**
-	 * @return int
-	 */
-	public function ParentThread()
-	{
-		return $this->iParentThread;
-	}
-
-	/**
-	 * @param int $iParentThread
-	 */
-	public function SetParentThread($iParentThread)
-	{
-		$this->iParentThread = $iParentThread;
-	}
-
 	/**
 	 * @return boole
 	 */
@@ -664,7 +620,8 @@ class Message
 			$this->oDeliveredTo = $oHeaders->GetAsEmailCollection(\MailSo\Mime\Enumerations\Header::DELIVERED_TO, $bCharsetAutoDetect);
 
 			$this->sInReplyTo = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::IN_REPLY_TO);
-			$this->sReferences = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES);
+			$this->sReferences = \trim(\preg_replace('/[\s]+/', ' ',
+				$oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::REFERENCES)));
 
 			$sHeaderDate = $oHeaders->ValueByName(\MailSo\Mime\Enumerations\Header::DATE);
 			$this->sHeaderDate = $sHeaderDate;
diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php
index 8d0c99b12..7bfaec046 100644
--- a/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php
+++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mime/Message.php
@@ -256,7 +256,8 @@ class Message
 	 */
 	public function SetReferences($sReferences)
 	{
-		$this->aHeadersValue[\MailSo\Mime\Enumerations\Header::REFERENCES] = $sReferences;
+		$this->aHeadersValue[\MailSo\Mime\Enumerations\Header::REFERENCES] =
+			\trim(\preg_replace('/[\s]+/', ' ', $sReferences));
 
 		return $this;
 	}
diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php
index 9c702f064..19ce785b3 100644
--- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php
+++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php
@@ -5363,6 +5363,45 @@ class Actions
 		return $this->DefaultResponse(__FUNCTION__, $aResult);
 	}
 
+	/**
+	 * @return array
+	 *
+	 * @throws \MailSo\Base\Exceptions\Exception
+	 */
+	public function DoMessageThreadsFromCache()
+	{
+		$sFolder = $this->GetActionParam('Folder', '');
+		$sFolderHash = $this->GetActionParam('FolderHash', '');
+		$sUid = (string) $this->GetActionParam('Uid', '');
+
+		if (0 === \strlen($sFolder) || 0 === \strlen($sUid))
+		{
+			throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument);
+		}
+
+		$aResult = array(
+			'Folder' => $sFolder,
+			'Uid' => $sUid,
+			'FolderHash' => $sFolderHash,
+			'ThreadUids' => null
+		);
+
+		$oCache = $this->cacherForUids();
+		if ($oCache && $this->Config()->Get('labs', 'use_imap_thread', false))
+		{
+			$aThreadUids = $this->MailClient()->MessageThreadUidsFromCache(
+				$sFolder, $sFolderHash, $sUid, $oCache
+			);
+
+			if (\is_array($aThreadUids) && 1 < \count($aThreadUids))
+			{
+				$aResult['ThreadUids'] = $aThreadUids;
+			}
+		}
+
+		return $this->DefaultResponse(__FUNCTION__, $aResult);
+	}
+
 	/**
 	 * @return array
 	 *
@@ -5379,7 +5418,6 @@ class Actions
 		$sSearch = '';
 		$sUidNext = '';
 		$bUseThreads = false;
-		$sExpandedThreadUid = '';
 
 		$sRawKey = $this->GetActionParam('RawKey', '');
 		$aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9);
@@ -5392,7 +5430,6 @@ class Actions
 			$sSearch = (string) $aValues[3];
 			$sUidNext = (string) $aValues[6];
 			$bUseThreads = (bool) $aValues[7];
-			$sExpandedThreadUid = (string) $aValues[8];
 
 			$this->verifyCacheByKey($sRawKey);
 		}
@@ -5404,7 +5441,6 @@ class Actions
 			$sSearch = $this->GetActionParam('Search', '');
 			$sUidNext = $this->GetActionParam('UidNext', '');
 			$bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0');
-			$sExpandedThreadUid = (string) $this->GetActionParam('ExpandedThreadUid', '');
 		}
 
 		if (0 === strlen($sFolder))
@@ -5419,29 +5455,13 @@ class Actions
 			if (!$this->Config()->Get('labs', 'use_imap_thread', false))
 			{
 				$bUseThreads = false;
-				$sExpandedThreadUid = '';
-			}
-
-			$aExpandedThreadUid = array();
-			if (0 < \strlen($sExpandedThreadUid))
-			{
-				$aExpandedThreadUid = \explode(',', $sExpandedThreadUid);
-
-				$aExpandedThreadUid = \array_map(function ($sValue) {
-					$sValue = \trim($sValue);
-					return \is_numeric($sValue) ? (int) $sValue : 0;
-				}, $aExpandedThreadUid);
-
-				$aExpandedThreadUid = \array_filter($aExpandedThreadUid, function ($iValue) {
-					return 0 < $iValue;
-				});
 			}
 
 			$oMessageList = $this->MailClient()->MessageList(
 				$sFolder, $iOffset, $iLimit, $sSearch, $sUidNext,
 				$this->cacherForUids(),
 				!!$this->Config()->Get('labs', 'use_imap_sort', false),
-				$bUseThreads, $aExpandedThreadUid,
+				$bUseThreads,
 				!!$this->Config()->Get('labs', 'use_imap_esearch_esort', false)
 			);
 		}
@@ -8875,8 +8895,6 @@ class Actions
 					'DeliveredTo' => $this->responseObject($mResponse->DeliveredTo(), $sParent, $aParameters),
 					'Priority' => $mResponse->Priority(),
 					'Threads' => $mResponse->Threads(),
-					'ThreadsLen' => $mResponse->ThreadsLen(),
-					'ParentThread' => $mResponse->ParentThread(),
 					'Sensitivity' => $mResponse->Sensitivity(),
 					'ExternalProxy' => false,
 					'ReadReceipt' => ''
@@ -9149,7 +9167,7 @@ class Actions
 						'MessageCount' => (int) $mStatus['MESSAGES'],
 						'MessageUnseenCount' => (int) $mStatus['UNSEEN'],
 						'UidNext' => (string) $mStatus['UIDNEXT'],
-						'Hash' => \MailSo\Mail\MailClient::GenerateHash(
+						'Hash' => $this->MailClient()->GenerateFolderHash(
 							$mResponse->FullNameRaw(), $mStatus['MESSAGES'], $mStatus['UNSEEN'], $mStatus['UIDNEXT'])
 					);
 				}
@@ -9179,7 +9197,7 @@ class Actions
 					'FolderHash' => $mResponse->FolderHash,
 					'UidNext' => $mResponse->UidNext,
 					'NewMessages' => $this->responseObject($mResponse->NewMessages),
-					'LastCollapsedThreadUids' => $mResponse->LastCollapsedThreadUids,
+//					'LastCollapsedThreadUids' => $mResponse->LastCollapsedThreadUids,
 					'Offset' => $mResponse->Offset,
 					'Limit' => $mResponse->Limit,
 					'Search' => $mResponse->Search
diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Api.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Api.php
index ac183414e..c05c589fe 100644
--- a/rainloop/v/0.0.0/app/libraries/RainLoop/Api.php
+++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Api.php
@@ -92,7 +92,7 @@ class Api
 				(int) \RainLoop\Api::Config()->Get('labs', 'imap_message_list_date_filter', 0);
 
 			\MailSo\Config::$LargeThreadLimit =
-				(int) \RainLoop\Api::Config()->Get('labs', 'imap_large_thread_limit', 100);
+				(int) \RainLoop\Api::Config()->Get('labs', 'imap_large_thread_limit', 50);
 
 			\MailSo\Config::$SystemLogger = \RainLoop\Api::Logger();
 
diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php
index d8eccb6d9..0ef1d88cd 100644
--- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php
+++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php
@@ -291,7 +291,7 @@ Enables caching in the system'),
 				'imap_message_list_fast_simple_search' => array(true),
 				'imap_message_list_count_limit_trigger' => array(0),
 				'imap_message_list_date_filter' => array(0),
-				'imap_large_thread_limit' => array(100),
+				'imap_large_thread_limit' => array(50),
 				'imap_folder_list_limit' => array(200),
 				'imap_show_login_alert' => array(true),
 				'smtp_show_server_errors' => array(false),
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html
index 24d8a6c2e..6aec37979 100644
--- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html
+++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html
@@ -22,13 +22,12 @@
 			
 		
- - - x -     + + () +  
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html index 9bb8dc536..960345fa0 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html @@ -22,13 +22,12 @@
- - - x -     + + () +  
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html index a7ce09233..c691b7491 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -182,14 +182,14 @@
-
- + @@ -202,12 +202,15 @@
+ + + !
-
+