mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
Code refactoring (promises for folders management)
This commit is contained in:
parent
9bec1d0a8f
commit
bab64a7cea
19 changed files with 467 additions and 333 deletions
198
dev/App/User.js
198
dev/App/User.js
|
|
@ -26,7 +26,6 @@
|
|||
Cache = require('Common/Cache'),
|
||||
|
||||
SocialStore = require('Stores/Social'),
|
||||
AppStore = require('Stores/User/App'),
|
||||
SettingsStore = require('Stores/User/Settings'),
|
||||
AccountStore = require('Stores/User/Account'),
|
||||
IdentityStore = require('Stores/User/Identity'),
|
||||
|
|
@ -40,9 +39,9 @@
|
|||
Settings = require('Storage/Settings'),
|
||||
|
||||
Remote = require('Remote/User/Ajax'),
|
||||
Promises = require('Promises/User/Ajax'),
|
||||
|
||||
EmailModel = require('Model/Email'),
|
||||
FolderModel = require('Model/Folder'),
|
||||
AccountModel = require('Model/Account'),
|
||||
IdentityModel = require('Model/Identity'),
|
||||
TemplateModel = require('Model/Template'),
|
||||
|
|
@ -434,27 +433,35 @@
|
|||
/**
|
||||
* @param {Function=} fCallback
|
||||
*/
|
||||
AppUser.prototype.folders = function (fCallback)
|
||||
AppUser.prototype.foldersReload = function (fCallback)
|
||||
{
|
||||
FolderStore.foldersLoading(true);
|
||||
|
||||
Remote.folders(_.bind(function (sResult, oData) {
|
||||
|
||||
var bResult = false;
|
||||
FolderStore.foldersLoading(false);
|
||||
|
||||
if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
|
||||
{
|
||||
bResult = true;
|
||||
this.setFolders(oData);
|
||||
}
|
||||
|
||||
Promises.foldersReload(FolderStore.foldersLoading).then(function (bValue) {
|
||||
if (fCallback)
|
||||
{
|
||||
fCallback(bResult);
|
||||
fCallback(!!bValue);
|
||||
}
|
||||
}).fail(function () {
|
||||
if (fCallback)
|
||||
{
|
||||
fCallback(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
}, this));
|
||||
AppUser.prototype.foldersPromisesActionHelper = function (oPromise, iErrorDefCode)
|
||||
{
|
||||
Promises
|
||||
.abort('Folders')
|
||||
.fastResolve(true)
|
||||
.then(function () {
|
||||
return oPromise;
|
||||
})
|
||||
.fail(function (iErrorCode) {
|
||||
FolderStore.folderList.error(Translator.getNotification(iErrorCode, '', iErrorDefCode));
|
||||
}).fin(function () {
|
||||
Promises.foldersReloadWithTimeout(FolderStore.foldersLoading);
|
||||
}).done()
|
||||
;
|
||||
};
|
||||
|
||||
AppUser.prototype.reloadOpenPgpKeys = function ()
|
||||
|
|
@ -1026,157 +1033,6 @@
|
|||
}, sQuery);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} sNamespace
|
||||
* @param {Array} aFolders
|
||||
* @return {Array}
|
||||
*/
|
||||
AppUser.prototype.folderResponseParseRec = function (sNamespace, aFolders)
|
||||
{
|
||||
var
|
||||
self = this,
|
||||
iIndex = 0,
|
||||
iLen = 0,
|
||||
oFolder = null,
|
||||
oCacheFolder = null,
|
||||
sFolderFullNameRaw = '',
|
||||
aSubFolders = [],
|
||||
aList = []
|
||||
;
|
||||
|
||||
for (iIndex = 0, iLen = aFolders.length; iIndex < iLen; iIndex++)
|
||||
{
|
||||
oFolder = aFolders[iIndex];
|
||||
if (oFolder)
|
||||
{
|
||||
sFolderFullNameRaw = oFolder.FullNameRaw;
|
||||
|
||||
oCacheFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
||||
if (!oCacheFolder)
|
||||
{
|
||||
oCacheFolder = FolderModel.newInstanceFromJson(oFolder);
|
||||
if (oCacheFolder)
|
||||
{
|
||||
Cache.setFolderToCacheList(sFolderFullNameRaw, oCacheFolder);
|
||||
Cache.setFolderFullNameRaw(oCacheFolder.fullNameHash, sFolderFullNameRaw, oCacheFolder);
|
||||
}
|
||||
}
|
||||
|
||||
if (oCacheFolder)
|
||||
{
|
||||
oCacheFolder.collapsed(!self.isFolderExpanded(oCacheFolder.fullNameHash));
|
||||
|
||||
if (oFolder.Extended)
|
||||
{
|
||||
if (oFolder.Extended.Hash)
|
||||
{
|
||||
Cache.setFolderHash(oCacheFolder.fullNameRaw, oFolder.Extended.Hash);
|
||||
}
|
||||
|
||||
if (Utils.isNormal(oFolder.Extended.MessageCount))
|
||||
{
|
||||
oCacheFolder.messageCountAll(oFolder.Extended.MessageCount);
|
||||
}
|
||||
|
||||
if (Utils.isNormal(oFolder.Extended.MessageUnseenCount))
|
||||
{
|
||||
oCacheFolder.messageCountUnread(oFolder.Extended.MessageUnseenCount);
|
||||
}
|
||||
}
|
||||
|
||||
aSubFolders = oFolder['SubFolders'];
|
||||
if (aSubFolders && 'Collection/FolderCollection' === aSubFolders['@Object'] &&
|
||||
aSubFolders['@Collection'] && Utils.isArray(aSubFolders['@Collection']))
|
||||
{
|
||||
oCacheFolder.subFolders(
|
||||
this.folderResponseParseRec(sNamespace, aSubFolders['@Collection']));
|
||||
}
|
||||
|
||||
aList.push(oCacheFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aList;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {*} oData
|
||||
*/
|
||||
AppUser.prototype.setFolders = function (oData)
|
||||
{
|
||||
var
|
||||
bUpdate = false,
|
||||
fNormalizeFolder = function (sFolderFullNameRaw) {
|
||||
return ('' === sFolderFullNameRaw || Consts.Values.UnuseOptionValue === sFolderFullNameRaw ||
|
||||
null !== Cache.getFolderFromCacheList(sFolderFullNameRaw)) ? sFolderFullNameRaw : '';
|
||||
}
|
||||
;
|
||||
|
||||
if (oData && oData.Result && 'Collection/FolderCollection' === oData.Result['@Object'] &&
|
||||
oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection']))
|
||||
{
|
||||
if (!Utils.isUnd(oData.Result.Namespace))
|
||||
{
|
||||
FolderStore.namespace = oData.Result.Namespace;
|
||||
}
|
||||
|
||||
AppStore.threadsAllowed(!!Settings.settingsGet('UseImapThread') &&
|
||||
oData.Result.IsThreadsSupported && true);
|
||||
|
||||
FolderStore.folderList(this.folderResponseParseRec(FolderStore.namespace, oData.Result['@Collection']));
|
||||
FolderStore.folderList.optimized(!!oData.Result.Optimized);
|
||||
|
||||
if (oData.Result['SystemFolders'] && '' === '' +
|
||||
Settings.settingsGet('SentFolder') +
|
||||
Settings.settingsGet('DraftFolder') +
|
||||
Settings.settingsGet('SpamFolder') +
|
||||
Settings.settingsGet('TrashFolder') +
|
||||
Settings.settingsGet('ArchiveFolder') +
|
||||
Settings.settingsGet('NullFolder'))
|
||||
{
|
||||
// TODO Magic Numbers
|
||||
Settings.settingsSet('SentFolder', oData.Result['SystemFolders'][2] || null);
|
||||
Settings.settingsSet('DraftFolder', oData.Result['SystemFolders'][3] || null);
|
||||
Settings.settingsSet('SpamFolder', oData.Result['SystemFolders'][4] || null);
|
||||
Settings.settingsSet('TrashFolder', oData.Result['SystemFolders'][5] || null);
|
||||
Settings.settingsSet('ArchiveFolder', oData.Result['SystemFolders'][12] || null);
|
||||
|
||||
bUpdate = true;
|
||||
}
|
||||
|
||||
FolderStore.sentFolder(fNormalizeFolder(Settings.settingsGet('SentFolder')));
|
||||
FolderStore.draftFolder(fNormalizeFolder(Settings.settingsGet('DraftFolder')));
|
||||
FolderStore.spamFolder(fNormalizeFolder(Settings.settingsGet('SpamFolder')));
|
||||
FolderStore.trashFolder(fNormalizeFolder(Settings.settingsGet('TrashFolder')));
|
||||
FolderStore.archiveFolder(fNormalizeFolder(Settings.settingsGet('ArchiveFolder')));
|
||||
|
||||
if (bUpdate)
|
||||
{
|
||||
Remote.saveSystemFolders(Utils.emptyFunction, {
|
||||
'SentFolder': FolderStore.sentFolder(),
|
||||
'DraftFolder': FolderStore.draftFolder(),
|
||||
'SpamFolder': FolderStore.spamFolder(),
|
||||
'TrashFolder': FolderStore.trashFolder(),
|
||||
'ArchiveFolder': FolderStore.archiveFolder(),
|
||||
'NullFolder': 'NullFolder'
|
||||
});
|
||||
}
|
||||
|
||||
Local.set(Enums.ClientSideKeyName.FoldersLashHash, oData.Result.FoldersHash);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} sFullNameHash
|
||||
* @return {boolean}
|
||||
*/
|
||||
AppUser.prototype.isFolderExpanded = function (sFullNameHash)
|
||||
{
|
||||
var aExpandedList = Local.get(Enums.ClientSideKeyName.ExpandedFolders);
|
||||
return Utils.isArray(aExpandedList) && -1 !== _.indexOf(aExpandedList, sFullNameHash);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} sFullNameHash
|
||||
* @param {boolean} bExpanded
|
||||
|
|
@ -1429,7 +1285,7 @@
|
|||
|
||||
//require.ensure([], function() { // require code splitting
|
||||
|
||||
self.folders(_.bind(function (bValue) {
|
||||
self.foldersReload(_.bind(function (bValue) {
|
||||
|
||||
kn.hideLoading();
|
||||
|
||||
|
|
@ -1498,7 +1354,7 @@
|
|||
});
|
||||
|
||||
Events.sub('interval.20m', function () {
|
||||
self.folders();
|
||||
self.foldersReload();
|
||||
});
|
||||
|
||||
iContactsSyncInterval = 5 <= iContactsSyncInterval ? iContactsSyncInterval : 20;
|
||||
|
|
|
|||
|
|
@ -235,9 +235,10 @@
|
|||
/**
|
||||
* @param {number} iCode
|
||||
* @param {*=} mMessage = ''
|
||||
* @param {*=} mDefCode = null
|
||||
* @return {string}
|
||||
*/
|
||||
Translator.prototype.getNotification = function (iCode, mMessage)
|
||||
Translator.prototype.getNotification = function (iCode, mMessage, mDefCode)
|
||||
{
|
||||
iCode = window.parseInt(iCode, 10) || 0;
|
||||
if (Enums.Notification.ClientViewError === iCode && mMessage)
|
||||
|
|
@ -245,7 +246,9 @@
|
|||
return mMessage;
|
||||
}
|
||||
|
||||
return _.isUndefined(this.notificationI18N[iCode]) ? '' : this.notificationI18N[iCode];
|
||||
return _.isUndefined(this.notificationI18N[iCode]) ? (
|
||||
mDefCode && _.isUndefined(this.notificationI18N[mDefCode]) ? this.notificationI18N[mDefCode] : ''
|
||||
) : this.notificationI18N[iCode];
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -20,30 +20,18 @@
|
|||
{
|
||||
AbstractModel.call(this, 'MessageSimpleModel');
|
||||
|
||||
this.selected = false;
|
||||
|
||||
this.folderFullNameRaw = '';
|
||||
this.uid = '';
|
||||
this.size = 0;
|
||||
|
||||
this.sender = '';
|
||||
this.subject = '';
|
||||
|
||||
this.dateUTC = 0;
|
||||
this.clear();
|
||||
}
|
||||
|
||||
_.extend(MessageSimpleModel.prototype, AbstractModel.prototype);
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param {AjaxJsonMessage} oJsonMessage
|
||||
* @return {?MessageSimpleModel}
|
||||
*/
|
||||
MessageSimpleModel.newInstanceFromJson = function (oJsonMessage)
|
||||
{
|
||||
var oMessageModel = new MessageSimpleModel();
|
||||
return oMessageModel.initByJson(oJsonMessage) ? oMessageModel : null;
|
||||
};
|
||||
MessageSimpleModel.prototype.selected = false;
|
||||
MessageSimpleModel.prototype.folderFullNameRaw = '';
|
||||
MessageSimpleModel.prototype.uid = '';
|
||||
MessageSimpleModel.prototype.size = 0;
|
||||
MessageSimpleModel.prototype.sender = '';
|
||||
MessageSimpleModel.prototype.subject = '';
|
||||
MessageSimpleModel.prototype.dateInUTC = 0;
|
||||
|
||||
MessageSimpleModel.prototype.clear = function ()
|
||||
{
|
||||
|
|
@ -56,7 +44,7 @@
|
|||
this.sender = '';
|
||||
this.subject = '';
|
||||
|
||||
this.dateUTC = 0;
|
||||
this.dateInUTC = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -80,7 +68,7 @@
|
|||
|
||||
this.subject = oJsonMessage.Subject;
|
||||
|
||||
this.dateUTC = Utils.pInt(oJsonMessage.DateTimeStampInUTC);
|
||||
this.dateInUTC = Utils.pInt(oJsonMessage.DateTimeStampInUTC);
|
||||
|
||||
bResult = true;
|
||||
}
|
||||
|
|
@ -88,6 +76,17 @@
|
|||
return bResult;
|
||||
};
|
||||
|
||||
/**
|
||||
* @static
|
||||
* @param {AjaxJsonMessage} oJsonMessage
|
||||
* @return {?MessageSimpleModel}
|
||||
*/
|
||||
MessageSimpleModel.newInstanceFromJson = function (oJsonMessage)
|
||||
{
|
||||
var oMessageModel = new MessageSimpleModel();
|
||||
return oMessageModel.initByJson(oJsonMessage) ? oMessageModel : null;
|
||||
};
|
||||
|
||||
module.exports = MessageSimpleModel;
|
||||
|
||||
}());
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
var
|
||||
$ = require('$'),
|
||||
_ = require('_'),
|
||||
Q = require('Q'),
|
||||
|
||||
Consts = require('Common/Consts'),
|
||||
|
|
@ -12,7 +13,9 @@
|
|||
Utils = require('Common/Utils'),
|
||||
Links = require('Common/Links'),
|
||||
|
||||
Settings = require('Storage/Settings')
|
||||
Settings = require('Storage/Settings'),
|
||||
|
||||
AbstractBasicPromises = require('Promises/AbstractBasic')
|
||||
;
|
||||
|
||||
/**
|
||||
|
|
@ -20,21 +23,18 @@
|
|||
*/
|
||||
function AbstractAjaxPromises()
|
||||
{
|
||||
this.oRequests = {};
|
||||
AbstractBasicPromises.call(this);
|
||||
|
||||
this.clear();
|
||||
}
|
||||
|
||||
AbstractAjaxPromises.prototype.func = function (fFunc)
|
||||
{
|
||||
fFunc();
|
||||
_.extend(AbstractAjaxPromises.prototype, AbstractBasicPromises.prototype);
|
||||
|
||||
return this;
|
||||
};
|
||||
AbstractAjaxPromises.prototype.oRequests = {};
|
||||
|
||||
AbstractAjaxPromises.prototype.fastResolve = function (mData)
|
||||
AbstractAjaxPromises.prototype.clear = function ()
|
||||
{
|
||||
var oDeferred = Q.defer();
|
||||
oDeferred.resolve(mData);
|
||||
return oDeferred.promise;
|
||||
this.oRequests = {};
|
||||
};
|
||||
|
||||
AbstractAjaxPromises.prototype.abort = function (sAction, bClearOnly)
|
||||
|
|
@ -43,7 +43,7 @@
|
|||
{
|
||||
if (!bClearOnly && this.oRequests[sAction].abort)
|
||||
{
|
||||
this.oRequests[sAction].__aborted = true;
|
||||
this.oRequests[sAction].__aborted__ = true;
|
||||
this.oRequests[sAction].abort();
|
||||
}
|
||||
|
||||
|
|
@ -71,10 +71,7 @@
|
|||
oParameters['XToken'] = Settings.settingsGet('Token');
|
||||
}
|
||||
|
||||
if (fTrigger)
|
||||
{
|
||||
fTrigger(true);
|
||||
}
|
||||
this.setTrigger(fTrigger, true);
|
||||
|
||||
oH = $.ajax({
|
||||
'type': bPost ? 'POST' : 'GET',
|
||||
|
|
@ -96,8 +93,8 @@
|
|||
{
|
||||
if (oData && oData.Result && sAction === oData.Action)
|
||||
{
|
||||
oData.Result.__cached__ = bCached;
|
||||
oDeferred.resolve(oData.Result);
|
||||
oData.__cached__ = bCached;
|
||||
oDeferred.resolve(oData);
|
||||
}
|
||||
else if (oData && oData.Action)
|
||||
{
|
||||
|
|
@ -114,7 +111,7 @@
|
|||
}
|
||||
else if ('abort' === sTextStatus)
|
||||
{
|
||||
if (!oData || !oData.__aborted)
|
||||
if (!oData || !oData.__aborted__)
|
||||
{
|
||||
oDeferred.reject(Enums.Notification.AjaxAbort);
|
||||
}
|
||||
|
|
@ -130,10 +127,7 @@
|
|||
delete self.oRequests[sAction];
|
||||
}
|
||||
|
||||
if (fTrigger)
|
||||
{
|
||||
fTrigger(false);
|
||||
}
|
||||
self.setTrigger(fTrigger, false);
|
||||
});
|
||||
|
||||
if (oH)
|
||||
|
|
|
|||
56
dev/Promises/AbstractBasic.js
Normal file
56
dev/Promises/AbstractBasic.js
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
var
|
||||
_ = require('_'),
|
||||
Q = require('Q'),
|
||||
|
||||
Utils = require('Common/Utils')
|
||||
;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function AbstractBasicPromises()
|
||||
{
|
||||
this.oPromisesStack = {};
|
||||
}
|
||||
|
||||
AbstractBasicPromises.prototype.func = function (fFunc)
|
||||
{
|
||||
fFunc();
|
||||
return this;
|
||||
};
|
||||
|
||||
AbstractBasicPromises.prototype.fastResolve = function (mData)
|
||||
{
|
||||
var oDeferred = Q.defer();
|
||||
oDeferred.resolve(mData);
|
||||
return oDeferred.promise;
|
||||
};
|
||||
|
||||
AbstractBasicPromises.prototype.fastReject = function (mData)
|
||||
{
|
||||
var oDeferred = Q.defer();
|
||||
oDeferred.reject(mData);
|
||||
return oDeferred.promise;
|
||||
};
|
||||
|
||||
AbstractBasicPromises.prototype.setTrigger = function (mTrigger, bValue)
|
||||
{
|
||||
if (mTrigger)
|
||||
{
|
||||
_.each(Utils.isArray(mTrigger) ? mTrigger : [mTrigger], function (fTrigger) {
|
||||
if (fTrigger)
|
||||
{
|
||||
fTrigger(!!bValue);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = AbstractBasicPromises;
|
||||
|
||||
}());
|
||||
|
|
@ -4,8 +4,20 @@
|
|||
'use strict';
|
||||
|
||||
var
|
||||
window = require('window'),
|
||||
_ = require('_'),
|
||||
|
||||
// Enums = require('Common/Enums'),
|
||||
// Utils = require('Common/Utils'),
|
||||
// Base64 = require('Common/Base64'),
|
||||
// Cache = require('Common/Cache'),
|
||||
//
|
||||
// AppStore = require('Stores/User/App'),
|
||||
// SettingsStore = require('Stores/User/Settings'),
|
||||
|
||||
MessageSimpleModel = require('Model/MessageSimple'),
|
||||
|
||||
PromisesPopulator = require('Promises/User/Populator'),
|
||||
AbstractAjaxPromises = require('Promises/AbstractAjax')
|
||||
;
|
||||
|
||||
|
|
@ -13,17 +25,19 @@
|
|||
* @constructor
|
||||
* @extends AbstractAjaxPromises
|
||||
*/
|
||||
function PromisesUserAjax()
|
||||
function UserAjaxUserPromises()
|
||||
{
|
||||
AbstractAjaxPromises.call(this);
|
||||
|
||||
this.messageListSimpleHash = '';
|
||||
this.messageListSimpleCache = null;
|
||||
|
||||
this.sSubSubQuery = '&ss=/';
|
||||
}
|
||||
|
||||
_.extend(PromisesUserAjax.prototype, AbstractAjaxPromises.prototype);
|
||||
_.extend(UserAjaxUserPromises.prototype, AbstractAjaxPromises.prototype);
|
||||
|
||||
PromisesUserAjax.prototype.messageListSimple = function (sFolder, aUids, fTrigger)
|
||||
UserAjaxUserPromises.prototype.messageListSimple = function (sFolder, aUids, fTrigger)
|
||||
{
|
||||
var self = this, sHash = sFolder + '~' + aUids.join('/');
|
||||
if (sHash === this.messageListSimpleHash && this.messageListSimpleCache)
|
||||
|
|
@ -35,23 +49,93 @@
|
|||
.postRequest('MessageListSimple', fTrigger, {
|
||||
'Folder': sFolder,
|
||||
'Uids': aUids
|
||||
}).then(function (aData) {
|
||||
|
||||
var
|
||||
MessageSimpleModel = require('Model/MessageSimple'),
|
||||
aResult = _.compact(_.map(aData, function (aItem) {
|
||||
return MessageSimpleModel.newInstanceFromJson(aItem);
|
||||
}))
|
||||
;
|
||||
}).then(function (oData) {
|
||||
|
||||
self.messageListSimpleHash = sHash;
|
||||
self.messageListSimpleCache = aResult;
|
||||
self.messageListSimpleCache = _.compact(_.map(oData.Result, function (aItem) {
|
||||
return MessageSimpleModel.newInstanceFromJson(aItem);
|
||||
}));
|
||||
|
||||
return aResult;
|
||||
return self.messageListSimpleCache;
|
||||
|
||||
}, function (iError) {
|
||||
|
||||
self.messageListSimpleHash = '';
|
||||
self.messageListSimpleCache = null;
|
||||
|
||||
return self.fastReject(iError);
|
||||
})
|
||||
;
|
||||
};
|
||||
|
||||
module.exports = new PromisesUserAjax();
|
||||
UserAjaxUserPromises.prototype.foldersReload = function (fTrigger)
|
||||
{
|
||||
return this.abort('Folders')
|
||||
.postRequest('Folders', fTrigger).then(function (oData) {
|
||||
PromisesPopulator.foldersList(oData.Result);
|
||||
PromisesPopulator.foldersAdditionalParameters(oData.Result);
|
||||
return true;
|
||||
});
|
||||
};
|
||||
|
||||
UserAjaxUserPromises.prototype._folders_timeout_ = 0;
|
||||
UserAjaxUserPromises.prototype.foldersReloadWithTimeout = function (fTrigger)
|
||||
{
|
||||
this.setTrigger(fTrigger, true);
|
||||
|
||||
var self = this;
|
||||
window.clearTimeout(this._folders_timeout_);
|
||||
this._folders_timeout_ = window.setTimeout(function () {
|
||||
self.foldersReload(fTrigger);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
|
||||
UserAjaxUserPromises.prototype.folderDelete = function (sFolderFullNameRaw, fTrigger)
|
||||
{
|
||||
return this.postRequest('FolderDelete', fTrigger, {
|
||||
'Folder': sFolderFullNameRaw
|
||||
});
|
||||
};
|
||||
|
||||
UserAjaxUserPromises.prototype.folderCreate = function (sNewFolderName, sParentName, fTrigger)
|
||||
{
|
||||
return this.postRequest('FolderCreate', fTrigger, {
|
||||
'Folder': sNewFolderName,
|
||||
'Parent': sParentName
|
||||
});
|
||||
};
|
||||
|
||||
UserAjaxUserPromises.prototype.folderRename = function (sPrevFolderFullNameRaw, sNewFolderName, fTrigger)
|
||||
{
|
||||
return this.postRequest('FolderRename', fTrigger, {
|
||||
'Folder': sPrevFolderFullNameRaw,
|
||||
'NewFolderName': sNewFolderName
|
||||
});
|
||||
};
|
||||
|
||||
// UserAjaxUserPromises.prototype.message = function (sFolderFullNameRaw, iUid, fTrigger)
|
||||
// {
|
||||
// sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
|
||||
// iUid = Utils.pInt(iUid);
|
||||
//
|
||||
// if (Cache.getFolderFromCacheList(sFolderFullNameRaw) && 0 >= iUid)
|
||||
// {
|
||||
// return this.abort('Message')
|
||||
// .getRequest('Message', fTrigger,
|
||||
// this.sSubSubQuery + Base64.urlsafe_encode([
|
||||
// sFolderFullNameRaw, iUid,
|
||||
// AppStore.projectHash(),
|
||||
// AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0'
|
||||
// ].join(String.fromCharCode(0))))
|
||||
// .then(function (oData) {
|
||||
// return oData;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// return this.fastReject(Enums.Notification.UnknownError);
|
||||
// };
|
||||
|
||||
module.exports = new UserAjaxUserPromises();
|
||||
|
||||
}());
|
||||
199
dev/Promises/User/Populator.js
Normal file
199
dev/Promises/User/Populator.js
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
|
||||
(function () {
|
||||
|
||||
'use strict';
|
||||
|
||||
var
|
||||
_ = require('_'),
|
||||
|
||||
Consts = require('Common/Consts'),
|
||||
Enums = require('Common/Enums'),
|
||||
Utils = require('Common/Utils'),
|
||||
Cache = require('Common/Cache'),
|
||||
|
||||
AppStore = require('Stores/User/App'),
|
||||
FolderStore = require('Stores/User/Folder'),
|
||||
|
||||
Settings = require('Storage/Settings'),
|
||||
Local = require('Storage/Client'),
|
||||
|
||||
FolderModel = require('Model/Folder'),
|
||||
|
||||
AbstractBasicPromises = require('Promises/AbstractBasic')
|
||||
;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
*/
|
||||
function PromisesUserPopulator()
|
||||
{
|
||||
AbstractBasicPromises.call(this);
|
||||
}
|
||||
|
||||
_.extend(PromisesUserPopulator.prototype, AbstractBasicPromises.prototype);
|
||||
|
||||
/**
|
||||
* @param {string} sFullNameHash
|
||||
* @return {boolean}
|
||||
*/
|
||||
PromisesUserPopulator.prototype.isFolderExpanded = function (sFullNameHash)
|
||||
{
|
||||
var aExpandedList = Local.get(Enums.ClientSideKeyName.ExpandedFolders);
|
||||
return Utils.isArray(aExpandedList) && -1 !== _.indexOf(aExpandedList, sFullNameHash);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} sFolderFullNameRaw
|
||||
* @return {string}
|
||||
*/
|
||||
PromisesUserPopulator.prototype.normalizeFolder = function (sFolderFullNameRaw)
|
||||
{
|
||||
return ('' === sFolderFullNameRaw || Consts.Values.UnuseOptionValue === sFolderFullNameRaw ||
|
||||
null !== Cache.getFolderFromCacheList(sFolderFullNameRaw)) ? sFolderFullNameRaw : '';
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string} sNamespace
|
||||
* @param {Array} aFolders
|
||||
* @return {Array}
|
||||
*/
|
||||
PromisesUserPopulator.prototype.folderResponseParseRec = function (sNamespace, aFolders)
|
||||
{
|
||||
var
|
||||
self = this,
|
||||
iIndex = 0,
|
||||
iLen = 0,
|
||||
oFolder = null,
|
||||
oCacheFolder = null,
|
||||
sFolderFullNameRaw = '',
|
||||
aSubFolders = [],
|
||||
aList = []
|
||||
;
|
||||
|
||||
for (iIndex = 0, iLen = aFolders.length; iIndex < iLen; iIndex++)
|
||||
{
|
||||
oFolder = aFolders[iIndex];
|
||||
if (oFolder)
|
||||
{
|
||||
sFolderFullNameRaw = oFolder.FullNameRaw;
|
||||
|
||||
oCacheFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw);
|
||||
if (!oCacheFolder)
|
||||
{
|
||||
oCacheFolder = FolderModel.newInstanceFromJson(oFolder);
|
||||
if (oCacheFolder)
|
||||
{
|
||||
Cache.setFolderToCacheList(sFolderFullNameRaw, oCacheFolder);
|
||||
Cache.setFolderFullNameRaw(oCacheFolder.fullNameHash, sFolderFullNameRaw, oCacheFolder);
|
||||
}
|
||||
}
|
||||
|
||||
if (oCacheFolder)
|
||||
{
|
||||
oCacheFolder.collapsed(!self.isFolderExpanded(oCacheFolder.fullNameHash));
|
||||
|
||||
if (oFolder.Extended)
|
||||
{
|
||||
if (oFolder.Extended.Hash)
|
||||
{
|
||||
Cache.setFolderHash(oCacheFolder.fullNameRaw, oFolder.Extended.Hash);
|
||||
}
|
||||
|
||||
if (Utils.isNormal(oFolder.Extended.MessageCount))
|
||||
{
|
||||
oCacheFolder.messageCountAll(oFolder.Extended.MessageCount);
|
||||
}
|
||||
|
||||
if (Utils.isNormal(oFolder.Extended.MessageUnseenCount))
|
||||
{
|
||||
oCacheFolder.messageCountUnread(oFolder.Extended.MessageUnseenCount);
|
||||
}
|
||||
}
|
||||
|
||||
aSubFolders = oFolder['SubFolders'];
|
||||
if (aSubFolders && 'Collection/FolderCollection' === aSubFolders['@Object'] &&
|
||||
aSubFolders['@Collection'] && Utils.isArray(aSubFolders['@Collection']))
|
||||
{
|
||||
oCacheFolder.subFolders(
|
||||
this.folderResponseParseRec(sNamespace, aSubFolders['@Collection']));
|
||||
}
|
||||
|
||||
aList.push(oCacheFolder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return aList;
|
||||
};
|
||||
|
||||
PromisesUserPopulator.prototype.foldersList = function (oData)
|
||||
{
|
||||
if (oData && 'Collection/FolderCollection' === oData['@Object'] &&
|
||||
oData['@Collection'] && Utils.isArray(oData['@Collection']))
|
||||
{
|
||||
FolderStore.folderList(this.folderResponseParseRec(
|
||||
Utils.isUnd(oData.Namespace) ? '' : oData.Namespace, oData['@Collection']));
|
||||
}
|
||||
};
|
||||
|
||||
PromisesUserPopulator.prototype.foldersAdditionalParameters = function (oData)
|
||||
{
|
||||
if (oData && oData && 'Collection/FolderCollection' === oData['@Object'] &&
|
||||
oData['@Collection'] && Utils.isArray(oData['@Collection']))
|
||||
{
|
||||
if (!Utils.isUnd(oData.Namespace))
|
||||
{
|
||||
FolderStore.namespace = oData.Namespace;
|
||||
}
|
||||
|
||||
AppStore.threadsAllowed(
|
||||
!!Settings.settingsGet('UseImapThread') &&
|
||||
oData.IsThreadsSupported && true);
|
||||
|
||||
FolderStore.folderList.optimized(!!oData.Optimized);
|
||||
|
||||
var bUpdate = false;
|
||||
|
||||
if (oData['SystemFolders'] && '' === '' +
|
||||
Settings.settingsGet('SentFolder') +
|
||||
Settings.settingsGet('DraftFolder') +
|
||||
Settings.settingsGet('SpamFolder') +
|
||||
Settings.settingsGet('TrashFolder') +
|
||||
Settings.settingsGet('ArchiveFolder') +
|
||||
Settings.settingsGet('NullFolder'))
|
||||
{
|
||||
// TODO Magic Numbers
|
||||
Settings.settingsSet('SentFolder', oData['SystemFolders'][2] || null);
|
||||
Settings.settingsSet('DraftFolder', oData['SystemFolders'][3] || null);
|
||||
Settings.settingsSet('SpamFolder', oData['SystemFolders'][4] || null);
|
||||
Settings.settingsSet('TrashFolder', oData['SystemFolders'][5] || null);
|
||||
Settings.settingsSet('ArchiveFolder', oData['SystemFolders'][12] || null);
|
||||
|
||||
bUpdate = true;
|
||||
}
|
||||
|
||||
FolderStore.sentFolder(this.normalizeFolder(Settings.settingsGet('SentFolder')));
|
||||
FolderStore.draftFolder(this.normalizeFolder(Settings.settingsGet('DraftFolder')));
|
||||
FolderStore.spamFolder(this.normalizeFolder(Settings.settingsGet('SpamFolder')));
|
||||
FolderStore.trashFolder(this.normalizeFolder(Settings.settingsGet('TrashFolder')));
|
||||
FolderStore.archiveFolder(this.normalizeFolder(Settings.settingsGet('ArchiveFolder')));
|
||||
|
||||
if (bUpdate)
|
||||
{
|
||||
require('Remote/User/Ajax').saveSystemFolders(Utils.emptyFunction, {
|
||||
'SentFolder': FolderStore.sentFolder(),
|
||||
'DraftFolder': FolderStore.draftFolder(),
|
||||
'SpamFolder': FolderStore.spamFolder(),
|
||||
'TrashFolder': FolderStore.trashFolder(),
|
||||
'ArchiveFolder': FolderStore.archiveFolder(),
|
||||
'NullFolder': 'NullFolder'
|
||||
});
|
||||
}
|
||||
|
||||
Local.set(Enums.ClientSideKeyName.FoldersLashHash, oData.FoldersHash);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = new PromisesUserPopulator();
|
||||
|
||||
}());
|
||||
|
|
@ -721,43 +721,6 @@
|
|||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
* @param {string} sNewFolderName
|
||||
* @param {string} sParentName
|
||||
*/
|
||||
RemoteUserAjax.prototype.folderCreate = function (fCallback, sNewFolderName, sParentName)
|
||||
{
|
||||
this.defaultRequest(fCallback, 'FolderCreate', {
|
||||
'Folder': sNewFolderName,
|
||||
'Parent': sParentName
|
||||
}, null, '', ['Folders']);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
* @param {string} sFolderFullNameRaw
|
||||
*/
|
||||
RemoteUserAjax.prototype.folderDelete = function (fCallback, sFolderFullNameRaw)
|
||||
{
|
||||
this.defaultRequest(fCallback, 'FolderDelete', {
|
||||
'Folder': sFolderFullNameRaw
|
||||
}, null, '', ['Folders']);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
* @param {string} sPrevFolderFullNameRaw
|
||||
* @param {string} sNewFolderName
|
||||
*/
|
||||
RemoteUserAjax.prototype.folderRename = function (fCallback, sPrevFolderFullNameRaw, sNewFolderName)
|
||||
{
|
||||
this.defaultRequest(fCallback, 'FolderRename', {
|
||||
'Folder': sPrevFolderFullNameRaw,
|
||||
'NewFolderName': sNewFolderName
|
||||
}, null, '', ['Folders']);
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {?Function} fCallback
|
||||
* @param {string} sFolderFullNameRaw
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
FolderStore = require('Stores/User/Folder'),
|
||||
|
||||
Promises = require('Promises/User/Ajax'),
|
||||
Remote = require('Remote/User/Ajax')
|
||||
;
|
||||
|
||||
|
|
@ -69,19 +70,9 @@
|
|||
{
|
||||
Local.set(Enums.ClientSideKeyName.FoldersLashHash, '');
|
||||
|
||||
FolderStore.foldersRenaming(true);
|
||||
Remote.folderRename(function (sResult, oData) {
|
||||
|
||||
FolderStore.foldersRenaming(false);
|
||||
if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result)
|
||||
{
|
||||
FolderStore.folderList.error(
|
||||
oData && oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) : Translator.i18n('NOTIFICATIONS/CANT_RENAME_FOLDER'));
|
||||
}
|
||||
|
||||
require('App/User').folders();
|
||||
|
||||
}, oFolder.fullNameRaw, sEditName);
|
||||
require('App/User').foldersPromisesActionHelper(
|
||||
Promises.folderRename(oFolder.fullNameRaw, sEditName, FolderStore.foldersRenaming),
|
||||
Enums.Notification.CantRenameFolder);
|
||||
|
||||
Cache.removeFolderFromCacheList(oFolder.fullNameRaw);
|
||||
|
||||
|
|
@ -140,19 +131,9 @@
|
|||
|
||||
FolderStore.folderList.remove(fRemoveFolder);
|
||||
|
||||
FolderStore.foldersDeleting(true);
|
||||
Remote.folderDelete(function (sResult, oData) {
|
||||
|
||||
FolderStore.foldersDeleting(false);
|
||||
if (Enums.StorageResultType.Success !== sResult || !oData || !oData.Result)
|
||||
{
|
||||
FolderStore.folderList.error(
|
||||
oData && oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) : Translator.i18n('NOTIFICATIONS/CANT_DELETE_FOLDER'));
|
||||
}
|
||||
|
||||
require('App/User').folders();
|
||||
|
||||
}, oFolderToRemove.fullNameRaw);
|
||||
require('App/User').foldersPromisesActionHelper(
|
||||
Promises.folderDelete(oFolderToRemove.fullNameRaw, FolderStore.foldersDeleting),
|
||||
Enums.Notification.CantDeleteFolder);
|
||||
|
||||
Cache.removeFolderFromCacheList(oFolderToRemove.fullNameRaw);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
'use strict';
|
||||
|
||||
var
|
||||
_ = require('_'),
|
||||
ko = require('ko'),
|
||||
|
||||
Utils = require('Common/Utils'),
|
||||
|
|
|
|||
|
|
@ -685,10 +685,6 @@
|
|||
{
|
||||
this.messageThreadLoading(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.log('Error: Unknown message request: ' + sFolder + ' ~ ' + sUid + ' [e-102]');
|
||||
}
|
||||
|
||||
if (Enums.Layout.NoPreview === SettingsStore.layout())
|
||||
{
|
||||
|
|
@ -705,10 +701,6 @@
|
|||
{
|
||||
this.messageCurrentLoading(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
Utils.log('Error: Unknown message request: ' + oMessage.folderFullNameRaw + ' ~ ' + oMessage.uid + ' [e-101]');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -398,7 +398,7 @@ html.rl-no-preview-pane {
|
|||
background-color: #999;
|
||||
color: #fff;
|
||||
border-radius: 6px;
|
||||
padding: 0px 4px;
|
||||
padding: 1px 6px 1px 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
FolderStore = require('Stores/User/Folder'),
|
||||
|
||||
Remote = require('Remote/User/Ajax'),
|
||||
Promises = require('Promises/User/Ajax'),
|
||||
|
||||
kn = require('Knoin/Knoin'),
|
||||
AbstractView = require('Knoin/AbstractView')
|
||||
|
|
@ -76,21 +76,9 @@
|
|||
sParentFolderName = FolderStore.namespace.substr(0, FolderStore.namespace.length - 1);
|
||||
}
|
||||
|
||||
FolderStore.foldersCreating(true);
|
||||
Remote.folderCreate(function (sResult, oData) {
|
||||
|
||||
FolderStore.foldersCreating(false);
|
||||
if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
|
||||
{
|
||||
require('App/User').folders();
|
||||
}
|
||||
else
|
||||
{
|
||||
FolderStore.folderList.error(
|
||||
oData && oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) : Translator.i18n('NOTIFICATIONS/CANT_CREATE_FOLDER'));
|
||||
}
|
||||
|
||||
}, this.folderName(), sParentFolderName);
|
||||
require('App/User').foldersPromisesActionHelper(
|
||||
Promises.folderCreate(this.folderName(), sParentFolderName, FolderStore.foldersCreating),
|
||||
Enums.Notification.CantCreateFolder);
|
||||
|
||||
this.cancelCommand();
|
||||
|
||||
|
|
|
|||
|
|
@ -212,7 +212,11 @@
|
|||
this.viewThreads.trigger = ko.observable(false);
|
||||
|
||||
this.viewThreadMessages = ko.observableArray([]);
|
||||
this.viewThreadMessages.hash = '';
|
||||
this.viewThreadMessages.error = ko.observable('');
|
||||
|
||||
this.viewThreadMessages.subscribe(function () {
|
||||
this.viewThreadMessages.error('');
|
||||
}, this);
|
||||
|
||||
MessageStore.messageLastThreadUidsData.subscribe(function (oData) {
|
||||
if (oData && oData['Uids'])
|
||||
|
|
@ -320,11 +324,11 @@
|
|||
|
||||
self.viewThreadMessages(aList);
|
||||
|
||||
}, function (iErrorCode) {
|
||||
|
||||
window.alert(Translator.getNotification(iErrorCode));
|
||||
|
||||
});
|
||||
}).fail(function (iErrorCode) {
|
||||
self.viewThreadMessages([]);
|
||||
self.viewThreadMessages.error(Translator.getNotification(
|
||||
iErrorCode, '', Enums.Notification.CantGetMessageList));
|
||||
}).done();
|
||||
}
|
||||
|
||||
}, function () {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "RainLoop",
|
||||
"title": "RainLoop Webmail",
|
||||
"version": "1.8.2",
|
||||
"release": "288",
|
||||
"release": "289",
|
||||
"description": "Simple, modern & fast web-based email client",
|
||||
"homepage": "http://rainloop.net",
|
||||
"main": "gulpfile.js",
|
||||
|
|
@ -67,5 +67,9 @@
|
|||
"gulp-beautify": "*",
|
||||
"gulp-plumber": "*",
|
||||
"gulp-concat-util": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"gulp": "gulp",
|
||||
"w": "gulp w"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5009,6 +5009,9 @@ class Actions
|
|||
*/
|
||||
public function DoFolders()
|
||||
{
|
||||
// \sleep(1);
|
||||
// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::MailServerError);
|
||||
|
||||
$sRawKey = (string) $this->GetActionParam('RawKey', '');
|
||||
if (0 < \strlen($sRawKey))
|
||||
{
|
||||
|
|
@ -5153,6 +5156,9 @@ class Actions
|
|||
*/
|
||||
public function DoFolderCreate()
|
||||
{
|
||||
// \sleep(1);
|
||||
// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantCreateFolder);
|
||||
|
||||
$this->initMailClientConnection();
|
||||
|
||||
try
|
||||
|
|
@ -5232,6 +5238,9 @@ class Actions
|
|||
*/
|
||||
public function DoFolderDelete()
|
||||
{
|
||||
// \sleep(1);
|
||||
// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantRenameFolder);
|
||||
|
||||
$this->initMailClientConnection();
|
||||
|
||||
$sFolderFullNameRaw = $this->GetActionParam('Folder', '');
|
||||
|
|
|
|||
|
|
@ -62,6 +62,8 @@
|
|||
<tbody>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_FULLSCREEN_TOGGLE">Toggle fullscreen mode</td><td>Enter</td></tr>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_BLOCKQUOTES_TOGGLE">Toggle message blockquotes</td><td>B</td></tr>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_THREAD_NEXT">Next message in thread</td><td>Ctrl + ←, Command + ←</td></tr>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_THREAD_PREV">Previous message in thread</td><td>Ctrl + →, Command + →</td></tr>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_PRINT">Print</td><td>Ctrl + P, Command + P</td></tr>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_EXIT_FULLSCREEN">Exit fullscreen mode</td><td>Esc</td></tr>
|
||||
<tr><td class="i18n" data-i18n-text="SHORTCUTS_HELP/LABEL_CLOSE_MESSAGE">Close message (No preview pane layout)</td><td>Esc</td></tr>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
<div style="margin: 30px">
|
||||
<h1 style="display: inline; color: #fff; text-shadow: 1px 2px 0px rgba(0, 0, 0, 0.5);">RainLoop Webmail</h1>
|
||||
|
||||
|
||||
|
||||
|
||||
<h3 style="display: inline; color: #fff; text-shadow: 1px 2px 0px rgba(0, 0, 0, 0.5);">(<span data-bind="text: version"></span>)</h3>
|
||||
<h3><a href="http://rainloop.net/" target="_blank">http://rainloop.net</a></h3>
|
||||
</div>
|
||||
|
|
@ -206,13 +206,16 @@
|
|||
</a>
|
||||
<ul class="dropdown-menu pull-right g-ui-menu thread-list" role="menu" aria-labelledby="thread-list-view-dropdown-id"
|
||||
style="min-width: 400px; max-width: 400px; width: 400px;">
|
||||
<div data-bind="visible: '' !== viewThreadMessages.error()" style="color: red; text-align: center; padding: 10px">
|
||||
<spam data-bind="text: viewThreadMessages.error"></spam>
|
||||
</div >
|
||||
<div data-bind="visible: messageListOfThreadsLoading" style="text-align: center; padding: 10px">
|
||||
<i class="icon-spinner animated" />
|
||||
</div >
|
||||
<div data-bind="foreach: viewThreadMessages, visible: !messageListOfThreadsLoading()">
|
||||
<li class="e-item thread-list-message" role="presentation" data-bind="css: {'selected': selected}">
|
||||
<a class="e-link menuitem" href="#" tabindex="-1" onclick="return false;">
|
||||
<span class="thread-date pull-right" data-moment-format="SHORT" data-bind="moment: dateUTC"></span>
|
||||
<span class="thread-date pull-right" data-moment-format="SHORT" data-bind="moment: dateInUTC"></span>
|
||||
<div style="text-overflow: ellipsis; overflow: hidden;">
|
||||
<span class="thread-from" data-bind="text: sender"></span>
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue