Merge pull request #3 from RainLoop/master

Merge with base
This commit is contained in:
Andreas Adler 2015-04-28 18:27:09 +02:00
commit 5fc83df67e
140 changed files with 3349 additions and 1341 deletions

View file

@ -1,6 +1,8 @@
RainLoop Webmail RainLoop Webmail
================== ==================
[![Join the chat at https://gitter.im/RainLoop/rainloop-webmail](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/RainLoop/rainloop-webmail?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Simple, modern & fast web-based email client. Simple, modern & fast web-based email client.
Modest system requirements, decent performance, simple installation and upgrade, no database required - all these make RainLoop Webmail a perfect choice for your email solution. Modest system requirements, decent performance, simple installation and upgrade, no database required - all these make RainLoop Webmail a perfect choice for your email solution.

View file

@ -48,7 +48,7 @@ function saveLangStructure($sLangFile, $aLang)
} }
} }
\file_put_contents(LANGS_PATH.'/'.$sLangFile, implode("\n", $aResultLines)); \file_put_contents(LANGS_PATH.'/'.$sLangFile, implode("\n", $aResultLines)."\n");
} }
$sNL = "\n"; $sNL = "\n";

19
build/test.php Normal file
View file

@ -0,0 +1,19 @@
<?php
// simple connection test
$host = 'imap.gmail.com';
$port = 993;
echo $host.':'.$port;
$errorStr = '';
$errorNo = 0;
$connection = stream_socket_client($host.':'.$port, $errorNo, $errorStr, 5, STREAM_CLIENT_CONNECT);
if (is_resource($connection)) {
echo ' = OK';
fclose($connection);
} else {
echo ' = ERROR ([#'.$errorNo.'] '.$errorStr.')';
}

View file

@ -73,7 +73,7 @@
}, 50)); }, 50));
// TODO // DEBUG
// Events.sub({ // Events.sub({
// 'window.resize': function () { // 'window.resize': function () {
// window.console.log('window.resize'); // window.console.log('window.resize');
@ -284,14 +284,17 @@
ko.components.register('x-script', require('Component/Script')); ko.components.register('x-script', require('Component/Script'));
if (Settings.settingsGet('MaterialDesign') && Globals.bAnimationSupported) if (/**false && /**/Settings.settingsGet('MaterialDesign') && Globals.bAnimationSupported)
{ {
ko.components.register('Checkbox', require('Component/MaterialDesign/Checkbox')); ko.components.register('Checkbox', require('Component/MaterialDesign/Checkbox'));
ko.components.register('CheckboxSimple', require('Component/Checkbox'));
} }
else else
{ {
// ko.components.register('Checkbox', require('Component/Classic/Checkbox')); // ko.components.register('Checkbox', require('Component/Classic/Checkbox'));
// ko.components.register('CheckboxSimple', require('Component/Classic/Checkbox'));
ko.components.register('Checkbox', require('Component/Checkbox')); ko.components.register('Checkbox', require('Component/Checkbox'));
ko.components.register('CheckboxSimple', require('Component/Checkbox'));
} }
Translator.initOnStartOrLangChange(Translator.initNotificationLanguage, Translator); Translator.initOnStartOrLangChange(Translator.initNotificationLanguage, Translator);

View file

@ -97,6 +97,12 @@
Events.pub('interval.20m'); Events.pub('interval.20m');
}, 60000 * 15); }, 60000 * 15);
window.setTimeout(function () {
window.setInterval(function () {
Events.pub('interval.2m-after5m');
}, 60000 * 2);
}, 60000 * 5);
window.setTimeout(function () { window.setTimeout(function () {
window.setInterval(function () { window.setInterval(function () {
Events.pub('interval.5m-after5m'); Events.pub('interval.5m-after5m');
@ -174,7 +180,15 @@
if (Utils.isUnd(bDropPagePosition) ? false : !!bDropPagePosition) if (Utils.isUnd(bDropPagePosition) ? false : !!bDropPagePosition)
{ {
MessageStore.messageListPage(1); MessageStore.messageListPage(1);
MessageStore.messageListPageBeforeThread(1);
iOffset = 0; iOffset = 0;
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
MessageStore.messageListPage(),
MessageStore.messageListSearch(),
MessageStore.messageListThreadUid()
), true, true);
} }
MessageStore.messageListLoading(true); MessageStore.messageListLoading(true);
@ -201,17 +215,18 @@
); );
} }
}, FolderStore.currentFolderFullNameRaw(), iOffset, SettingsStore.messagesPerPage(), MessageStore.messageListSearch()); }, FolderStore.currentFolderFullNameRaw(), iOffset, SettingsStore.messagesPerPage(),
MessageStore.messageListSearch(), MessageStore.messageListThreadUid());
}; };
AppUser.prototype.recacheInboxMessageList = function () AppUser.prototype.recacheInboxMessageList = function ()
{ {
Remote.messageList(Utils.emptyFunction, Cache.getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', true); Remote.messageList(Utils.emptyFunction, Cache.getFolderInboxName(), 0, SettingsStore.messagesPerPage(), '', '', true);
}; };
/** /**
* @param {Function} fResultFunc * @param {Function} fResultFunc
* @returns {boolean} * @return {boolean}
*/ */
AppUser.prototype.contactsSync = function (fResultFunc) AppUser.prototype.contactsSync = function (fResultFunc)
{ {
@ -754,7 +769,7 @@
var var
self = this, self = this,
iUtc = Momentor.momentNowUnix(), iUtc = Momentor.momentNowUnix(),
aFolders = FolderStore.getNextFolderNames(bBoot) aFolders = FolderStore.getNextFolderNames()
; ;
if (Utils.isNonEmptyArray(aFolders)) if (Utils.isNonEmptyArray(aFolders))
@ -824,7 +839,8 @@
}); });
if (bBoot) if (bBoot)
{ _.delay(function () { {
_.delay(function () {
self.folderInformationMultiply(true); self.folderInformationMultiply(true);
}, 2000); }, 2000);
} }
@ -843,10 +859,8 @@
AppUser.prototype.messageListAction = function (sFolderFullNameRaw, mUid, iSetAction, aMessages) AppUser.prototype.messageListAction = function (sFolderFullNameRaw, mUid, iSetAction, aMessages)
{ {
var var
bRoot = false,
aAllUids = [],
aRootUids = [],
oFolder = null, oFolder = null,
aRootUids = [],
iAlreadyUnread = 0 iAlreadyUnread = 0
; ;
@ -855,31 +869,16 @@
aMessages = MessageStore.messageListChecked(); aMessages = MessageStore.messageListChecked();
} }
if (true === mUid) aRootUids = _.uniq(_.compact(_.map(aMessages, function (oMessage) {
{ return (oMessage && oMessage.uid) ? oMessage.uid : null;
bRoot = true; })));
}
else if (aMessages && aMessages[0] && mUid &&
sFolderFullNameRaw === aMessages[0].uid && mUid === aMessages[0].uid)
{
bRoot = true;
}
_.each(aMessages, function (oMessage) { if ('' !== sFolderFullNameRaw && 0 < aRootUids.length)
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) { switch (iSetAction) {
case Enums.MessageSetAction.SetSeen: case Enums.MessageSetAction.SetSeen:
_.each(bRoot ? aAllUids : aRootUids, function (sSubUid) { _.each(aRootUids, function (sSubUid) {
iAlreadyUnread += Cache.storeMessageFlagsToCacheBySetAction( iAlreadyUnread += Cache.storeMessageFlagsToCacheBySetAction(
sFolderFullNameRaw, sSubUid, iSetAction); sFolderFullNameRaw, sSubUid, iSetAction);
}); });
@ -890,7 +889,7 @@
oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread); oFolder.messageCountUnread(oFolder.messageCountUnread() - iAlreadyUnread);
} }
Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, bRoot ? aAllUids : aRootUids, true); Remote.messageSetSeen(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, true);
break; break;
case Enums.MessageSetAction.UnsetSeen: case Enums.MessageSetAction.UnsetSeen:
@ -921,12 +920,12 @@
case Enums.MessageSetAction.UnsetFlag: case Enums.MessageSetAction.UnsetFlag:
_.each(bRoot ? aAllUids : aRootUids, function (sSubUid) { _.each(aRootUids, function (sSubUid) {
Cache.storeMessageFlagsToCacheBySetAction( Cache.storeMessageFlagsToCacheBySetAction(
sFolderFullNameRaw, sSubUid, iSetAction); sFolderFullNameRaw, sSubUid, iSetAction);
}); });
Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, bRoot ? aAllUids : aRootUids, false); Remote.messageSetFlagged(Utils.emptyFunction, sFolderFullNameRaw, aRootUids, false);
break; break;
} }
@ -1369,7 +1368,7 @@
} }
}); });
Events.sub('interval.5m-after5m', function () { Events.sub('interval.2m-after5m', function () {
self.folderInformationMultiply(); self.folderInformationMultiply();
}); });

View file

@ -20,58 +20,90 @@
{ {
var self = this; var self = this;
this.obj = this.createNewObject(); // this.userMedia = window.navigator.getUserMedia || window.navigator.webkitGetUserMedia ||
this.objForNotification = this.createNewObject(); // window.navigator.mozGetUserMedia || window.navigator.msGetUserMedia;
//
// this.audioContext = window.AudioContext || window.webkitAudioContext;
// if (!this.audioContext || !window.Float32Array)
// {
// this.audioContext = null;
// this.userMedia = null;
// }
this.supported = !Globals.bMobileDevice && !Globals.bSafari && this.player = this.createNewObject();
this.obj && '' !== this.obj.canPlayType('audio/mpeg');
if (this.obj && this.supported) this.supported = !Globals.bMobileDevice && !Globals.bSafari && !!this.player && !!this.player.play;
if (this.supported && this.player.canPlayType)
{ {
this.objForNotification.src = Links.sound('new-mail.mp3'); this.supportedMp3 = '' !== this.player.canPlayType('audio/mpeg;').replace(/no/, '');
this.supportedWav = '' !== this.player.canPlayType('audio/wav; codecs="1"').replace(/no/, '');
this.supportedOgg = '' !== this.player.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, '');
this.supportedNotification = this.supported && this.supportedMp3;
}
$(this.obj).on('ended error', function () { if (!this.player || (!this.supportedMp3 && !this.supportedOgg && !this.supportedWav))
{
this.supported = false;
this.supportedMp3 = false;
this.supportedOgg = false;
this.supportedWav = false;
this.supportedNotification = false;
}
if (this.supported)
{
$(this.player).on('ended error', function () {
self.stop(); self.stop();
}); });
Events.sub('audio.api.stop', function () { Events.sub('audio.api.stop', function () {
self.stop(); self.stop();
}); });
Events.sub('audio.api.play', function (sUrl, sName) {
self.playMp3(sUrl, sName);
});
} }
} }
Audio.prototype.obj = null; Audio.prototype.player = null;
Audio.prototype.objForNotification = null; Audio.prototype.notificator = null;
Audio.prototype.supported = false; Audio.prototype.supported = false;
Audio.prototype.supportedMp3 = false;
Audio.prototype.supportedOgg = false;
Audio.prototype.supportedWav = false;
Audio.prototype.supportedNotification = false;
// Audio.prototype.record = function ()
// {
// this.getUserMedia({audio:true}, function () {
// window.console.log(arguments);
// }, function(oError) {
// window.console.log(arguments);
// });
// };
Audio.prototype.createNewObject = function () Audio.prototype.createNewObject = function ()
{ {
var obj = window.Audio ? new window.Audio() : null; var player = window.Audio ? new window.Audio() : null;
if (obj && obj.canPlayType) if (player && player.canPlayType && player.pause && player.play)
{ {
obj.preload = 'none'; player.preload = 'none';
obj.loop = false; player.loop = false;
obj.autoplay = false; player.autoplay = false;
obj.muted = false; player.muted = false;
} }
return obj; return player;
}; };
Audio.prototype.paused = function () Audio.prototype.paused = function ()
{ {
return this.supported ? !!this.obj.paused : true; return this.supported ? !!this.player.paused : true;
}; };
Audio.prototype.stop = function () Audio.prototype.stop = function ()
{ {
if (this.supported && this.obj.pause) if (this.supported && this.player.pause)
{ {
this.obj.pause(); this.player.pause();
} }
Events.pub('audio.stop'); Events.pub('audio.stop');
@ -79,15 +111,11 @@
Audio.prototype.pause = Audio.prototype.stop; Audio.prototype.pause = Audio.prototype.stop;
Audio.prototype.playMp3 = function (sUrl, sName) Audio.prototype.clearName = function (sName, sExt)
{ {
if (this.supported && this.obj.play) sExt = sExt || '';
{
this.obj.src = sUrl;
this.obj.play();
sName = Utils.isUnd(sName) ? '' : Utils.trim(sName); sName = Utils.isUnd(sName) ? '' : Utils.trim(sName);
if ('.mp3' === sName.toLowerCase().substr(-4)) if (sExt && '.' + sExt === sName.toLowerCase().substr((sExt.length + 1) * -1))
{ {
sName = Utils.trim(sName.substr(0, sName.length - 4)); sName = Utils.trim(sName.substr(0, sName.length - 4));
} }
@ -97,15 +125,59 @@
sName = 'audio'; sName = 'audio';
} }
Events.pub('audio.start', [sName]); return sName;
};
Audio.prototype.playMp3 = function (sUrl, sName)
{
if (this.supported && this.supportedMp3)
{
this.player.src = sUrl;
this.player.play();
Events.pub('audio.start', [this.clearName(sName, 'mp3'), 'mp3']);
}
};
Audio.prototype.playOgg = function (sUrl, sName)
{
if (this.supported && this.supportedOgg)
{
this.player.src = sUrl;
this.player.play();
sName = this.clearName(sName, 'oga');
sName = this.clearName(sName, 'ogg');
Events.pub('audio.start', [sName, 'ogg']);
}
};
Audio.prototype.playWav = function (sUrl, sName)
{
if (this.supported && this.supportedWav)
{
this.player.src = sUrl;
this.player.play();
Events.pub('audio.start', [this.clearName(sName, 'wav'), 'wav']);
} }
}; };
Audio.prototype.playNotification = function () Audio.prototype.playNotification = function ()
{ {
if (this.supported && this.objForNotification.play) if (this.supported && this.supportedMp3)
{ {
this.objForNotification.play(); if (!this.notificator)
{
this.notificator = this.createNewObject();
this.notificator.src = Links.sound('new-mail.mp3');
}
if (this.notificator && this.notificator.play)
{
this.notificator.play();
}
} }
}; };

View file

@ -302,13 +302,17 @@
if (aFlags && 0 < aFlags.length) if (aFlags && 0 < aFlags.length)
{ {
oMessage.unseen(!!aFlags[0]);
oMessage.flagged(!!aFlags[1]); oMessage.flagged(!!aFlags[1]);
if (!oMessage.__simple_message__)
{
oMessage.unseen(!!aFlags[0]);
oMessage.answered(!!aFlags[2]); oMessage.answered(!!aFlags[2]);
oMessage.forwarded(!!aFlags[3]); oMessage.forwarded(!!aFlags[3]);
oMessage.isReadReceipt(!!aFlags[4]); oMessage.isReadReceipt(!!aFlags[4]);
oMessage.deletedMark(!!aFlags[5]); oMessage.deletedMark(!!aFlags[5]);
} }
}
if (0 < oMessage.threads().length) if (0 < oMessage.threads().length)
{ {

View file

@ -5,6 +5,27 @@
var Enums = {}; var Enums = {};
/**
* @enum {string}
*/
Enums.FileType = {
'Unknown': 'unknown',
'Text': 'text',
'Html': 'html',
'Code': 'code',
'Eml': 'eml',
'WordText': 'word-text',
'Pdf': 'pdf',
'Image': 'image',
'Audio': 'audio',
'Video': 'video',
'Sheet': 'sheet',
'Presentation': 'presentation',
'Certificate': 'certificate',
'CertificateBin': 'certificate-bin',
'Archive': 'archive'
};
/** /**
* @enum {string} * @enum {string}
*/ */

View file

@ -18,6 +18,7 @@
Globals.$win = $(window); Globals.$win = $(window);
Globals.$doc = $(window.document); Globals.$doc = $(window.document);
Globals.$html = $('html'); Globals.$html = $('html');
Globals.$body = $('body');
Globals.$div = $('<div></div>'); Globals.$div = $('<div></div>');
Globals.$win.__sizes = [0, 0]; Globals.$win.__sizes = [0, 0];
@ -268,7 +269,7 @@
}); });
Globals.keyScopeReal.subscribe(function (sValue) { Globals.keyScopeReal.subscribe(function (sValue) {
// window.console.log('keyScope=' + sValue); // TODO // window.console.log('keyScope=' + sValue); // DEBUG
key.setScope(sValue); key.setScope(sValue);
}); });

View file

@ -31,6 +31,8 @@
this.resize = _.throttle(_.bind(this.resize, this), 100); this.resize = _.throttle(_.bind(this.resize, this), 100);
this.__inited = false;
this.init(); this.init();
} }
@ -190,7 +192,7 @@
HtmlEditor.prototype.setHtml = function (sHtml, bFocus) HtmlEditor.prototype.setHtml = function (sHtml, bFocus)
{ {
if (this.editor) if (this.editor && this.__inited)
{ {
this.modeToggle(true); this.modeToggle(true);
@ -207,7 +209,7 @@
HtmlEditor.prototype.setPlain = function (sPlain, bFocus) HtmlEditor.prototype.setPlain = function (sPlain, bFocus)
{ {
if (this.editor) if (this.editor && this.__inited)
{ {
this.modeToggle(false); this.modeToggle(false);
if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain) if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain)
@ -259,7 +261,7 @@
} }
oConfig.enterMode = window.CKEDITOR.ENTER_BR; oConfig.enterMode = window.CKEDITOR.ENTER_BR;
oConfig.shiftEnterMode = window.CKEDITOR.ENTER_BR; oConfig.shiftEnterMode = window.CKEDITOR.ENTER_P;
oConfig.language = Globals.oHtmlEditorLangsMap[sLanguage] || 'en'; oConfig.language = Globals.oHtmlEditorLangsMap[sLanguage] || 'en';
if (window.CKEDITOR.env) if (window.CKEDITOR.env)
@ -267,17 +269,6 @@
window.CKEDITOR.env.isCompatible = true; window.CKEDITOR.env.isCompatible = true;
} }
// oConfig.allowedContent = {
// $1: {
// elements: window.CKEDITOR.dtd,
// attributes: true,
// styles: true,
// classes: true
// }
// };
//
// oConfig.disallowedContent = 'script; style; iframe; frame; *[on*]';
window.CKEDITOR.dtd.$removeEmpty['p'] = 1; window.CKEDITOR.dtd.$removeEmpty['p'] = 1;
self.editor = window.CKEDITOR.appendTo(self.$element[0], oConfig); self.editor = window.CKEDITOR.appendTo(self.$element[0], oConfig);
@ -307,8 +298,6 @@
self.focusTrigger(); self.focusTrigger();
}); });
if (self.fOnReady)
{
self.editor.on('instanceReady', function () { self.editor.on('instanceReady', function () {
if (self.editor.removeMenuItem) if (self.editor.removeMenuItem)
@ -320,11 +309,18 @@
self.editor.setKeystroke(window.CKEDITOR.CTRL + 65 /* A */, 'selectAll'); self.editor.setKeystroke(window.CKEDITOR.CTRL + 65 /* A */, 'selectAll');
self.fOnReady();
self.__resizable = true; self.__resizable = true;
self.__inited = true;
self.resize(); self.resize();
});
if (self.fOnReady)
{
self.fOnReady();
} }
});
} }
; ;

View file

@ -59,47 +59,62 @@
/** /**
* @param {string} sDownload * @param {string} sDownload
* @param {string=} sCustomSpecSuffix
* @return {string} * @return {string}
*/ */
Links.prototype.attachmentDownload = function (sDownload) Links.prototype.attachmentDownload = function (sDownload, sCustomSpecSuffix)
{ {
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/Download/' + this.subQueryPrefix() + '/' + sDownload; sCustomSpecSuffix = Utils.isUnd(sCustomSpecSuffix) ? this.sSpecSuffix : sCustomSpecSuffix;
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + sCustomSpecSuffix + '/Download/' +
this.subQueryPrefix() + '/' + sDownload;
}; };
/** /**
* @param {string} sDownload * @param {string} sDownload
* @param {string=} sCustomSpecSuffix
* @return {string} * @return {string}
*/ */
Links.prototype.attachmentPreview = function (sDownload) Links.prototype.attachmentPreview = function (sDownload, sCustomSpecSuffix)
{ {
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/View/' + this.subQueryPrefix() + '/' + sDownload; sCustomSpecSuffix = Utils.isUnd(sCustomSpecSuffix) ? this.sSpecSuffix : sCustomSpecSuffix;
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + sCustomSpecSuffix + '/View/' +
this.subQueryPrefix() + '/' + sDownload;
}; };
/** /**
* @param {string} sDownload * @param {string} sDownload
* @param {string=} sCustomSpecSuffix
* @return {string} * @return {string}
*/ */
Links.prototype.attachmentThumbnailPreview = function (sDownload) Links.prototype.attachmentThumbnailPreview = function (sDownload, sCustomSpecSuffix)
{ {
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ViewThumbnail/' + this.subQueryPrefix() + '/' + sDownload; sCustomSpecSuffix = Utils.isUnd(sCustomSpecSuffix) ? this.sSpecSuffix : sCustomSpecSuffix;
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + sCustomSpecSuffix + '/ViewThumbnail/' +
this.subQueryPrefix() + '/' + sDownload;
}; };
/** /**
* @param {string} sDownload * @param {string} sDownload
* @param {string=} sCustomSpecSuffix
* @return {string} * @return {string}
*/ */
Links.prototype.attachmentPreviewAsPlain = function (sDownload) Links.prototype.attachmentPreviewAsPlain = function (sDownload, sCustomSpecSuffix)
{ {
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/ViewAsPlain/' + this.subQueryPrefix() + '/' + sDownload; sCustomSpecSuffix = Utils.isUnd(sCustomSpecSuffix) ? this.sSpecSuffix : sCustomSpecSuffix;
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + sCustomSpecSuffix + '/ViewAsPlain/' +
this.subQueryPrefix() + '/' + sDownload;
}; };
/** /**
* @param {string} sDownload * @param {string} sDownload
* @param {string=} sCustomSpecSuffix
* @return {string} * @return {string}
*/ */
Links.prototype.attachmentFramed = function (sDownload) Links.prototype.attachmentFramed = function (sDownload, sCustomSpecSuffix)
{ {
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + this.sSpecSuffix + '/FramedView/' + this.subQueryPrefix() + '/' + sDownload; sCustomSpecSuffix = Utils.isUnd(sCustomSpecSuffix) ? this.sSpecSuffix : sCustomSpecSuffix;
return this.sServer + '/Raw/' + this.subQueryPrefix() + '/' + sCustomSpecSuffix + '/FramedView/' +
this.subQueryPrefix() + '/' + sDownload;
}; };
/** /**
@ -257,23 +272,30 @@
* @param {string} sFolder * @param {string} sFolder
* @param {number=} iPage = 1 * @param {number=} iPage = 1
* @param {string=} sSearch = '' * @param {string=} sSearch = ''
* @param {string=} sThreadUid = ''
* @return {string} * @return {string}
*/ */
Links.prototype.mailBox = function (sFolder, iPage, sSearch) Links.prototype.mailBox = function (sFolder, iPage, sSearch, sThreadUid)
{ {
iPage = Utils.isNormal(iPage) ? Utils.pInt(iPage) : 1; iPage = Utils.isNormal(iPage) ? Utils.pInt(iPage) : 1;
sSearch = Utils.pString(sSearch); sSearch = Utils.pString(sSearch);
var sResult = this.sBase + 'mailbox/'; var
sResult = this.sBase + 'mailbox/',
iThreadUid = Utils.pInt(sThreadUid)
;
if ('' !== sFolder) if ('' !== sFolder)
{ {
sResult += encodeURI(sFolder); sResult += encodeURI(sFolder) + (0 < iThreadUid ? '~' + iThreadUid : '');
} }
if (1 < iPage) if (1 < iPage)
{ {
sResult = sResult.replace(/[\/]+$/, ''); sResult = sResult.replace(/[\/]+$/, '');
sResult += '/p' + iPage; sResult += '/p' + iPage;
} }
if ('' !== sSearch) if ('' !== sSearch)
{ {
sResult = sResult.replace(/[\/]+$/, ''); sResult = sResult.replace(/[\/]+$/, '');

View file

@ -435,7 +435,7 @@
}; };
/** /**
* @returns {boolean} * @return {boolean}
*/ */
Selector.prototype.autoSelect = function () Selector.prototype.autoSelect = function ()
{ {
@ -452,7 +452,7 @@
/** /**
* @param {Object} oItem * @param {Object} oItem
* @returns {string} * @return {string}
*/ */
Selector.prototype.getItemUid = function (oItem) Selector.prototype.getItemUid = function (oItem)
{ {
@ -633,13 +633,20 @@
var var
iOffset = 20, iOffset = 20,
aList = this.list(),
oFocused = $(this.sItemFocusedSelector, this.oContentScrollable), oFocused = $(this.sItemFocusedSelector, this.oContentScrollable),
oPos = oFocused.position(), oPos = oFocused.position(),
iVisibleHeight = this.oContentVisible.height(), iVisibleHeight = this.oContentVisible.height(),
iFocusedHeight = oFocused.outerHeight() iFocusedHeight = oFocused.outerHeight()
; ;
if (oPos && (oPos.top < 0 || oPos.top + iFocusedHeight > iVisibleHeight)) if (aList && aList[0] && aList[0].focused())
{
this.oContentScrollable.scrollTop(0);
return true;
}
else if (oPos && (oPos.top < 0 || oPos.top + iFocusedHeight > iVisibleHeight))
{ {
if (oPos.top < 0) if (oPos.top < 0)
{ {

View file

@ -144,7 +144,7 @@
/** /**
* @param {string} sMailToUrl * @param {string} sMailToUrl
* @param {Function} PopupComposeVoreModel * @param {Function} PopupComposeVoreModel
* @returns {boolean} * @return {boolean}
*/ */
Utils.mailToHelper = function (sMailToUrl, PopupComposeVoreModel) Utils.mailToHelper = function (sMailToUrl, PopupComposeVoreModel)
{ {
@ -598,7 +598,7 @@
* @param {string} sTheme * @param {string} sTheme
* @return {string} * @return {string}
*/ */
Utils.convertThemeName = function (sTheme) Utils.convertThemeName = _.memoize(function (sTheme)
{ {
if ('@custom' === sTheme.substr(-7)) if ('@custom' === sTheme.substr(-7))
{ {
@ -606,7 +606,7 @@
} }
return Utils.trim(sTheme.replace(/[^a-zA-Z0-9]+/g, ' ').replace(/([A-Z])/g, ' $1').replace(/[\s]+/g, ' ')); return Utils.trim(sTheme.replace(/[^a-zA-Z0-9]+/g, ' ').replace(/([A-Z])/g, ' $1').replace(/[\s]+/g, ' '));
}; });
/** /**
* @param {string} sName * @param {string} sName
@ -822,40 +822,6 @@
sText = '', sText = '',
splitPlainText = function (sText)
{
var
iLen = 100,
sPrefix = '',
sSubText = '',
sResult = sText,
iSpacePos = 0,
iNewLinePos = 0
;
while (sResult.length > iLen)
{
sSubText = sResult.substring(0, iLen);
iSpacePos = sSubText.lastIndexOf(' ');
iNewLinePos = sSubText.lastIndexOf('\n');
if (-1 !== iNewLinePos)
{
iSpacePos = iNewLinePos;
}
if (-1 === iSpacePos)
{
iSpacePos = iLen;
}
sPrefix += sSubText.substring(0, iSpacePos) + '\n';
sResult = sResult.substring(iSpacePos + 1);
}
return sPrefix + sResult;
},
convertBlockquote = function (sText) { convertBlockquote = function (sText) {
sText = Utils.trim(sText); sText = Utils.trim(sText);
sText = '> ' + sText.replace(/\n/gm, '\n> '); sText = '> ' + sText.replace(/\n/gm, '\n> ');
@ -929,7 +895,7 @@
.replace(/&amp;/gi, '&') .replace(/&amp;/gi, '&')
; ;
sText = splitPlainText(Utils.trim(sText)); sText = Utils.splitPlainText(Utils.trim(sText));
iPos = 0; iPos = 0;
iLimit = 800; iLimit = 800;

View file

@ -48,6 +48,7 @@
this.inverted = Utils.isUnd(oParams.inverted) ? false : !!oParams.inverted; this.inverted = Utils.isUnd(oParams.inverted) ? false : !!oParams.inverted;
this.labeled = !Utils.isUnd(oParams.label); this.labeled = !Utils.isUnd(oParams.label);
this.labelAnimated = !!oParams.labelAnimated;
} }
_.extend(AbstracCheckbox.prototype, AbstractComponent.prototype); _.extend(AbstracCheckbox.prototype, AbstractComponent.prototype);
@ -59,6 +60,13 @@
} }
}; };
AbstracCheckbox.prototype.keypress = function() {
if (!this.readOnly && this.enable() && !this.disable())
{
this.value(!this.value());
}
};
AbstracCheckbox.componentExportHelper = AbstractComponent.componentExportHelper; AbstracCheckbox.componentExportHelper = AbstractComponent.componentExportHelper;
module.exports = AbstracCheckbox; module.exports = AbstracCheckbox;

View file

@ -9,10 +9,21 @@
; ;
Opentip.styles.rainloop = { Opentip.styles.rainloop = {
'extends': 'standard', 'extends': 'standard',
'fixed': true, 'fixed': true,
'target': true, 'target': true,
'delay': 0.2,
'hideDelay': 0,
'hideEffect': 'fade',
'hideEffectDuration': 0.2,
'showEffect': 'fade',
'showEffectDuration': 0.2,
'showOn': 'mouseover click', 'showOn': 'mouseover click',
'removeElementsOnHide': true, 'removeElementsOnHide': true,
@ -26,8 +37,7 @@
Opentip.styles.rainloopTip = { Opentip.styles.rainloopTip = {
'extends': 'rainloop', 'extends': 'rainloop',
'stemLength': 3, 'delay': 0.4,
'stemBase': 5,
'group': 'rainloopTips' 'group': 'rainloopTips'
}; };

79
dev/External/ko.js vendored
View file

@ -24,31 +24,41 @@
'init': function (oElement, fValueAccessor) { 'init': function (oElement, fValueAccessor) {
var var
fValue = fValueAccessor(),
oEditor = null, oEditor = null,
fValue = fValueAccessor(),
fUpdateEditorValue = function () { fUpdateEditorValue = function () {
if (oEditor) if (fValue && fValue.__editor)
{ {
oEditor.setHtmlOrPlain(fValue()); fValue.__editor.setHtmlOrPlain(fValue());
} }
}, },
fUpdateKoValue = function () { fUpdateKoValue = function () {
if (oEditor) if (fValue && fValue.__editor)
{ {
fValue(oEditor.getDataWithHtmlMark()); fValue(fValue.__editor.getDataWithHtmlMark());
} }
}, },
fOnReady = function () {
fValue.__editor = oEditor;
fUpdateEditorValue();
},
HtmlEditor = require('Common/HtmlEditor') HtmlEditor = require('Common/HtmlEditor')
; ;
if (fValue) if (ko.isObservable(fValue) && HtmlEditor)
{ {
oEditor = new HtmlEditor(oElement, fUpdateKoValue, fUpdateEditorValue, fUpdateKoValue); oEditor = new HtmlEditor(oElement, fUpdateKoValue, fOnReady, fUpdateKoValue);
fValue.__editor = oEditor;
fValue.__fetchEditorValue = fUpdateKoValue; fValue.__fetchEditorValue = fUpdateKoValue;
fValue.__updateEditorValue = fUpdateEditorValue;
fValue.subscribe(fUpdateEditorValue); fValue.subscribe(fUpdateEditorValue);
// ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () {
// });
} }
} }
}; };
@ -61,6 +71,7 @@
sValue = '', sValue = '',
Translator = null, Translator = null,
$oEl = $(oElement), $oEl = $(oElement),
fValue = fValueAccessor(),
bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'), bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'),
Globals = require('Common/Globals') Globals = require('Common/Globals')
; ;
@ -68,7 +79,7 @@
if (!Globals.bMobileDevice || bMobile) if (!Globals.bMobileDevice || bMobile)
{ {
bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
sValue = ko.unwrap(fValueAccessor()); sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
oElement.__opentip = new Opentip(oElement, { oElement.__opentip = new Opentip(oElement, {
'style': 'rainloopTip', 'style': 'rainloopTip',
@ -78,12 +89,21 @@
Globals.dropdownVisibility.subscribe(function (bV) { Globals.dropdownVisibility.subscribe(function (bV) {
if (bV) { if (bV) {
oElement.__opentip.deactivate(); oElement.__opentip.hide();
} else {
oElement.__opentip.activate();
} }
}); });
if ('' === sValue)
{
oElement.__opentip.hide();
oElement.__opentip.deactivate();
oElement.__opentip.setContent('');
}
else
{
oElement.__opentip.activate();
}
if (bi18n) if (bi18n)
{ {
Translator = require('Common/Translator'); Translator = require('Common/Translator');
@ -113,6 +133,7 @@
bi18n = true, bi18n = true,
sValue = '', sValue = '',
$oEl = $(oElement), $oEl = $(oElement),
fValue = fValueAccessor(),
bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'), bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'),
Globals = require('Common/Globals') Globals = require('Common/Globals')
; ;
@ -120,7 +141,7 @@
if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip) if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip)
{ {
bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
sValue = ko.unwrap(fValueAccessor()); sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
if (sValue) if (sValue)
{ {
@ -165,7 +186,8 @@
var var
$oEl = $(oElement), $oEl = $(oElement),
sValue = ko.unwrap(fValueAccessor()), fValue = fValueAccessor(),
sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue),
oOpenTips = oElement.__opentip oOpenTips = oElement.__opentip
; ;
@ -295,6 +317,18 @@
} }
}; };
ko.bindingHandlers.keypress = {
'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
$(oElement).on('keypress.koKeyPress', function (oEvent) {
fValueAccessor().call(oViewModel, oEvent);
});
ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () {
$(oElement).off('keypress.koKeyPress');
});
}
};
ko.bindingHandlers.onEnter = { ko.bindingHandlers.onEnter = {
'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) { 'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
$(oElement).on('keypress.koOnEnter', function (oEvent) { $(oElement).on('keypress.koOnEnter', function (oEvent) {
@ -311,6 +345,21 @@
} }
}; };
ko.bindingHandlers.onTab = {
'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
$(oElement).on('keydown.koOnTab', function (oEvent) {
if (oEvent && 9 === window.parseInt(oEvent.keyCode, 10))
{
return fValueAccessor().call(oViewModel, !!oEvent.shiftKey);
}
});
ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () {
$(oElement).off('keydown.koOnTab');
});
}
};
ko.bindingHandlers.onEsc = { ko.bindingHandlers.onEsc = {
'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) { 'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
$(oElement).on('keypress.koOnEsc', function (oEvent) { $(oElement).on('keypress.koOnEsc', function (oEvent) {

View file

@ -170,8 +170,6 @@
Globals.popupVisibilityNames.push(this.viewModelName); Globals.popupVisibilityNames.push(this.viewModelName);
oViewModel.viewModelDom.css('z-index', 3000 + Globals.popupVisibilityNames().length + 10); oViewModel.viewModelDom.css('z-index', 3000 + Globals.popupVisibilityNames().length + 10);
// Utils.delegateRun(this, 'onShow'); // moved to showScreenPopup function (for parameters)
if (this.onShowTrigger) if (this.onShowTrigger)
{ {
this.onShowTrigger(!this.onShowTrigger()); this.onShowTrigger(!this.onShowTrigger());
@ -257,6 +255,8 @@
if (ViewModelClassToShow.__vm && ViewModelClassToShow.__dom) if (ViewModelClassToShow.__vm && ViewModelClassToShow.__dom)
{ {
Utils.delegateRun(ViewModelClassToShow.__vm, 'onBeforeShow', aParameters || []);
ViewModelClassToShow.__vm.modalVisibility(true); ViewModelClassToShow.__vm.modalVisibility(true);
Utils.delegateRun(ViewModelClassToShow.__vm, 'onShow', aParameters || []); Utils.delegateRun(ViewModelClassToShow.__vm, 'onShow', aParameters || []);
@ -383,6 +383,8 @@
if (ViewModelClass.__vm && ViewModelClass.__dom && if (ViewModelClass.__vm && ViewModelClass.__dom &&
'Popups' !== ViewModelClass.__vm.viewModelPosition()) 'Popups' !== ViewModelClass.__vm.viewModelPosition())
{ {
Utils.delegateRun(ViewModelClass.__vm, 'onBeforeShow');
ViewModelClass.__dom.show(); ViewModelClass.__dom.show();
ViewModelClass.__vm.viewModelVisibility(true); ViewModelClass.__vm.viewModelVisibility(true);

View file

@ -8,6 +8,7 @@
_ = require('_'), _ = require('_'),
ko = require('ko'), ko = require('ko'),
Enums = require('Common/Enums'),
Globals = require('Common/Globals'), Globals = require('Common/Globals'),
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
Links = require('Common/Links'), Links = require('Common/Links'),
@ -27,6 +28,8 @@
this.mimeType = ''; this.mimeType = '';
this.fileName = ''; this.fileName = '';
this.fileNameExt = '';
this.fileType = Enums.FileType.Unknown;
this.estimatedSize = 0; this.estimatedSize = 0;
this.friendlySize = ''; this.friendlySize = '';
this.isInline = false; this.isInline = false;
@ -57,6 +60,8 @@
AttachmentModel.prototype.mimeType = ''; AttachmentModel.prototype.mimeType = '';
AttachmentModel.prototype.fileName = ''; AttachmentModel.prototype.fileName = '';
AttachmentModel.prototype.fileType = '';
AttachmentModel.prototype.fileNameExt = '';
AttachmentModel.prototype.estimatedSize = 0; AttachmentModel.prototype.estimatedSize = 0;
AttachmentModel.prototype.friendlySize = ''; AttachmentModel.prototype.friendlySize = '';
AttachmentModel.prototype.isInline = false; AttachmentModel.prototype.isInline = false;
@ -97,6 +102,9 @@
this.friendlySize = Utils.friendlySize(this.estimatedSize); this.friendlySize = Utils.friendlySize(this.estimatedSize);
this.cidWithOutTags = this.cid.replace(/^<+/, '').replace(/>+$/, ''); this.cidWithOutTags = this.cid.replace(/^<+/, '').replace(/>+$/, '');
this.fileNameExt = Utils.getFileExtension(this.fileName);
this.fileType = AttachmentModel.staticFileType(this.fileNameExt, this.mimeType);
bResult = true; bResult = true;
} }
@ -108,9 +116,7 @@
*/ */
AttachmentModel.prototype.isImage = function () AttachmentModel.prototype.isImage = function ()
{ {
return -1 < Utils.inArray(this.mimeType.toLowerCase(), return Enums.FileType.Image === this.fileType;
['image/png', 'image/jpg', 'image/jpeg', 'image/gif']
);
}; };
/** /**
@ -118,7 +124,26 @@
*/ */
AttachmentModel.prototype.isMp3 = function () AttachmentModel.prototype.isMp3 = function ()
{ {
return Audio.supported && '.mp3' === this.fileName.toLowerCase().substr(-4); return Enums.FileType.Audio === this.fileType &&
'mp3' === this.fileNameExt;
};
/**
* @return {boolean}
*/
AttachmentModel.prototype.isOgg = function ()
{
return Enums.FileType.Audio === this.fileType &&
('oga' === this.fileNameExt || 'ogg' === this.fileNameExt);
};
/**
* @return {boolean}
*/
AttachmentModel.prototype.isWav = function ()
{
return Enums.FileType.Audio === this.fileType &&
'wav' === this.fileNameExt;
}; };
/** /**
@ -134,8 +159,12 @@
*/ */
AttachmentModel.prototype.isText = function () AttachmentModel.prototype.isText = function ()
{ {
return -1 < Utils.inArray(this.mimeType, ['application/pgp-signature', 'message/delivery-status', 'message/rfc822']) || return Enums.FileType.Text === this.fileType ||
('text/' === this.mimeType.substr(0, 5) && -1 === Utils.inArray(this.mimeType, ['text/html'])); Enums.FileType.Eml === this.fileType ||
Enums.FileType.Certificate === this.fileType ||
Enums.FileType.Html === this.fileType ||
Enums.FileType.Code === this.fileType
;
}; };
/** /**
@ -143,7 +172,7 @@
*/ */
AttachmentModel.prototype.isPdf = function () AttachmentModel.prototype.isPdf = function ()
{ {
return Globals.bAllowPdfPreview && 'application/pdf' === this.mimeType; return Enums.FileType.Pdf === this.fileType;
}; };
/** /**
@ -152,7 +181,7 @@
AttachmentModel.prototype.isFramed = function () AttachmentModel.prototype.isFramed = function ()
{ {
return this.framed && (Globals.__APP__ && Globals.__APP__.googlePreviewSupported()) && return this.framed && (Globals.__APP__ && Globals.__APP__.googlePreviewSupported()) &&
!this.isPdf() && !this.isText() && !this.isImage(); !(this.isPdf() && Globals.bAllowPdfPreview) && !this.isText() && !this.isImage();
}; };
/** /**
@ -160,7 +189,8 @@
*/ */
AttachmentModel.prototype.hasPreview = function () AttachmentModel.prototype.hasPreview = function ()
{ {
return this.isImage() || this.isPdf() || this.isText() || this.isFramed(); return this.isImage() || (this.isPdf() && Globals.bAllowPdfPreview) ||
this.isText() || this.isFramed();
}; };
/** /**
@ -168,7 +198,10 @@
*/ */
AttachmentModel.prototype.hasPreplay = function () AttachmentModel.prototype.hasPreplay = function ()
{ {
return this.isMp3(); return (Audio.supportedMp3 && this.isMp3()) ||
(Audio.supportedOgg && this.isOgg()) ||
(Audio.supportedWav && this.isWav())
;
}; };
/** /**
@ -229,7 +262,7 @@
switch (true) switch (true)
{ {
case this.isImage(): case this.isImage():
case this.isPdf(): case this.isPdf() && Globals.bAllowPdfPreview:
sResult = this.linkPreview(); sResult = this.linkPreview();
break; break;
case this.isText(): case this.isText():
@ -274,76 +307,90 @@
}; };
/** /**
* @param {string} sExt
* @param {string} sMimeType * @param {string} sMimeType
* @returns {string} * @return {string}
*/ */
AttachmentModel.staticIconClassHelper = function (sMimeType) AttachmentModel.staticFileType = _.memoize(function (sExt, sMimeType)
{ {
sExt = Utils.trim(sExt).toLowerCase();
sMimeType = Utils.trim(sMimeType).toLowerCase(); sMimeType = Utils.trim(sMimeType).toLowerCase();
var var
sText = '', sResult = Enums.FileType.Unknown,
sClass = 'icon-file', aMimeTypeParts = sMimeType.split('/')
aParts = sMimeType.split('/')
; ;
if (aParts && aParts[1]) switch (true)
{ {
if ('image' === aParts[0]) case 'image' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [
{ 'png', 'jpg', 'jpeg', 'gif', 'bmp'
sClass = 'icon-file-image'; ]):
} sResult = Enums.FileType.Image;
else if ('text' === aParts[0]) break;
{ case 'audio' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [
sClass = 'icon-file-text'; 'mp3', 'ogg', 'oga', 'wav'
} ]):
else if ('audio' === aParts[0]) sResult = Enums.FileType.Audio;
{ break;
sClass = 'icon-file-music'; case 'video' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [
} 'mkv', 'avi'
else if ('video' === aParts[0]) ]):
{ sResult = Enums.FileType.Video;
sClass = 'icon-file-movie'; break;
} case -1 < Utils.inArray(sExt, [
else if (-1 < Utils.inArray(aParts[1], 'php', 'js', 'css'
['zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2', 'x-zip', 'x-7z', 'x-rar', 'x-tar', 'x-gzip', 'x-bzip', 'x-bzip2', 'x-zip-compressed', 'x-7z-compressed', 'x-rar-compressed'])) ]):
{ sResult = Enums.FileType.Code;
sClass = 'icon-file-zip'; break;
} case 'eml' === sExt || -1 < Utils.inArray(sMimeType, [
else if (-1 < Utils.inArray(aParts[1],
['pdf', 'x-pdf']))
{
sText = 'pdf';
sClass = 'icon-none';
}
// else if (-1 < Utils.inArray(aParts[1], [
// 'exe', 'x-exe', 'x-winexe', 'bat'
// ]))
// {
// sClass = 'icon-console';
// }
else if (-1 < Utils.inArray(sMimeType, [
'application/pgp-signature', 'application/pkcs7-signature'
]))
{
sClass = 'icon-file-certificate';
}
else if (-1 < Utils.inArray(sMimeType, [
'message/delivery-status', 'message/rfc822' 'message/delivery-status', 'message/rfc822'
])) ]):
{ sResult = Enums.FileType.Eml;
sClass = 'icon-file-text'; break;
} case ('text' === aMimeTypeParts[0] && 'html' !== aMimeTypeParts[1]) || -1 < Utils.inArray(sExt, [
else if (-1 < Utils.inArray(aParts[1], [ 'txt', 'log'
]):
sResult = Enums.FileType.Text;
break;
case ('text/html' === sMimeType) || -1 < Utils.inArray(sExt, [
'html'
]):
sResult = Enums.FileType.Html;
break;
case -1 < Utils.inArray(aMimeTypeParts[1], [
'zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2', 'x-zip', 'x-7z', 'x-rar', 'x-tar', 'x-gzip', 'x-bzip', 'x-bzip2', 'x-zip-compressed', 'x-7z-compressed', 'x-rar-compressed'
]) || -1 < Utils.inArray(sExt, [
'zip', '7z', 'tar', 'rar', 'gzip', 'bzip', 'bzip2'
]):
sResult = Enums.FileType.Archive;
break;
case -1 < Utils.inArray(aMimeTypeParts[1], ['pdf', 'x-pdf']) || -1 < Utils.inArray(sExt, [
'pdf'
]):
sResult = Enums.FileType.Pdf;
break;
case -1 < Utils.inArray(sMimeType, [
'application/pgp-signature', 'application/pgp-keys'
]) || -1 < Utils.inArray(sExt, [
'asc', 'pem', 'ppk'
]):
sResult = Enums.FileType.Certificate;
break;
case -1 < Utils.inArray(sMimeType, ['application/pkcs7-signature']) ||
-1 < Utils.inArray(sExt, ['p7s']):
sResult = Enums.FileType.CertificateBin;
break;
case -1 < Utils.inArray(aMimeTypeParts[1], [
'rtf', 'msword', 'vnd.msword', 'vnd.openxmlformats-officedocument.wordprocessingml.document', 'rtf', 'msword', 'vnd.msword', 'vnd.openxmlformats-officedocument.wordprocessingml.document',
'vnd.openxmlformats-officedocument.wordprocessingml.template', 'vnd.openxmlformats-officedocument.wordprocessingml.template',
'vnd.ms-word.document.macroEnabled.12', 'vnd.ms-word.document.macroEnabled.12',
'vnd.ms-word.template.macroEnabled.12' 'vnd.ms-word.template.macroEnabled.12'
])) ]):
{ sResult = Enums.FileType.WordText;
sClass = 'icon-file-text'; break;
} case -1 < Utils.inArray(aMimeTypeParts[1], [
else if (-1 < Utils.inArray(aParts[1], [
'excel', 'ms-excel', 'vnd.ms-excel', 'excel', 'ms-excel', 'vnd.ms-excel',
'vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'vnd.openxmlformats-officedocument.spreadsheetml.template', 'vnd.openxmlformats-officedocument.spreadsheetml.template',
@ -351,11 +398,10 @@
'vnd.ms-excel.template.macroEnabled.12', 'vnd.ms-excel.template.macroEnabled.12',
'vnd.ms-excel.addin.macroEnabled.12', 'vnd.ms-excel.addin.macroEnabled.12',
'vnd.ms-excel.sheet.binary.macroEnabled.12' 'vnd.ms-excel.sheet.binary.macroEnabled.12'
])) ]):
{ sResult = Enums.FileType.Sheet;
sClass = 'icon-file-excel'; break;
} case -1 < Utils.inArray(aMimeTypeParts[1], [
else if (-1 < Utils.inArray(aParts[1], [
'powerpoint', 'ms-powerpoint', 'vnd.ms-powerpoint', 'powerpoint', 'ms-powerpoint', 'vnd.ms-powerpoint',
'vnd.openxmlformats-officedocument.presentationml.presentation', 'vnd.openxmlformats-officedocument.presentationml.presentation',
'vnd.openxmlformats-officedocument.presentationml.template', 'vnd.openxmlformats-officedocument.presentationml.template',
@ -364,29 +410,142 @@
'vnd.ms-powerpoint.presentation.macroEnabled.12', 'vnd.ms-powerpoint.presentation.macroEnabled.12',
'vnd.ms-powerpoint.template.macroEnabled.12', 'vnd.ms-powerpoint.template.macroEnabled.12',
'vnd.ms-powerpoint.slideshow.macroEnabled.12' 'vnd.ms-powerpoint.slideshow.macroEnabled.12'
])) ]):
{ sResult = Enums.FileType.Presentation;
sClass = 'icon-file-chart-graph'; break;
} }
return sResult;
});
/**
* @param {string} sFileType
* @return {string}
*/
AttachmentModel.staticIconClass = _.memoize(function (sFileType)
{
var
sText = '',
sClass = 'icon-file'
;
switch (sFileType)
{
case Enums.FileType.Text:
case Enums.FileType.Eml:
case Enums.FileType.WordText:
sClass = 'icon-file-text';
break;
case Enums.FileType.Html:
case Enums.FileType.Code:
sClass = 'icon-file-code';
break;
case Enums.FileType.Image:
sClass = 'icon-file-image';
break;
case Enums.FileType.Audio:
sClass = 'icon-file-music';
break;
case Enums.FileType.Video:
sClass = 'icon-file-movie';
break;
case Enums.FileType.Archive:
sClass = 'icon-file-zip';
break;
case Enums.FileType.Certificate:
case Enums.FileType.CertificateBin:
sClass = 'icon-file-certificate';
break;
case Enums.FileType.Sheet:
sClass = 'icon-file-excel';
break;
case Enums.FileType.Presentation:
sClass = 'icon-file-chart-graph';
break;
case Enums.FileType.Pdf:
sText = 'pdf';
sClass = 'icon-none';
break;
} }
return [sClass, sText]; return [sClass, sText];
});
/**
* @param {string} sFileType
* @return {string}
*/
AttachmentModel.staticCombinedIconClass = function (aData)
{
var
sClass = '',
aTypes = []
;
if (Utils.isNonEmptyArray(aData))
{
sClass = 'icon-attachment';
aTypes = _.uniq(_.compact(_.map(aData, function (aItem) {
return aItem ? AttachmentModel.staticFileType(
Utils.getFileExtension(aItem[0]), aItem[1]) : '';
})));
if (aTypes && 1 === aTypes.length && aTypes[0])
{
switch (aTypes[0])
{
case Enums.FileType.Text:
case Enums.FileType.WordText:
sClass = 'icon-file-text';
break;
case Enums.FileType.Html:
case Enums.FileType.Code:
sClass = 'icon-file-code';
break;
case Enums.FileType.Image:
sClass = 'icon-file-image';
break;
case Enums.FileType.Audio:
sClass = 'icon-file-music';
break;
case Enums.FileType.Video:
sClass = 'icon-file-movie';
break;
case Enums.FileType.Archive:
sClass = 'icon-file-zip';
break;
case Enums.FileType.Certificate:
case Enums.FileType.CertificateBin:
sClass = 'icon-file-certificate';
break;
case Enums.FileType.Sheet:
sClass = 'icon-file-excel';
break;
case Enums.FileType.Presentation:
sClass = 'icon-file-chart-graph';
break;
}
}
}
return sClass;
}; };
/** /**
* @returns {string} * @return {string}
*/ */
AttachmentModel.prototype.iconClass = function () AttachmentModel.prototype.iconClass = function ()
{ {
return AttachmentModel.staticIconClassHelper(this.mimeType)[0]; return AttachmentModel.staticIconClass(this.fileType)[0];
}; };
/** /**
* @returns {string} * @return {string}
*/ */
AttachmentModel.prototype.iconText = function () AttachmentModel.prototype.iconText = function ()
{ {
return AttachmentModel.staticIconClassHelper(this.mimeType)[1]; return AttachmentModel.staticIconClass(this.fileType)[1];
}; };
module.exports = AttachmentModel; module.exports = AttachmentModel;

View file

@ -70,7 +70,11 @@
return Utils.mimeContentType(this.fileName()); return Utils.mimeContentType(this.fileName());
}, this); }, this);
this.regDisposables([this.friendlySize]); this.fileExt = ko.computed(function () {
return Utils.getFileExtension(this.fileName());
}, this);
this.regDisposables([this.progressText, this.progressStyle, this.title, this.friendlySize, this.mimeType, this.fileExt]);
} }
_.extend(ComposeAttachmentModel.prototype, AbstractModel.prototype); _.extend(ComposeAttachmentModel.prototype, AbstractModel.prototype);
@ -108,7 +112,8 @@
*/ */
ComposeAttachmentModel.prototype.iconClass = function () ComposeAttachmentModel.prototype.iconClass = function ()
{ {
return AttachmentModel.staticIconClassHelper(this.mimeType())[0]; return AttachmentModel.staticIconClass(
AttachmentModel.staticFileType(this.fileExt(), this.mimeType()))[0];
}; };
/** /**
@ -116,7 +121,8 @@
*/ */
ComposeAttachmentModel.prototype.iconText = function () ComposeAttachmentModel.prototype.iconText = function ()
{ {
return AttachmentModel.staticIconClassHelper(this.mimeType())[1]; return AttachmentModel.staticIconClass(
AttachmentModel.staticFileType(this.fileExt(), this.mimeType()))[1];
}; };
module.exports = ComposeAttachmentModel; module.exports = ComposeAttachmentModel;

View file

@ -66,7 +66,7 @@
}; };
/** /**
* @returns {boolean} * @return {boolean}
*/ */
EmailModel.prototype.validate = function () EmailModel.prototype.validate = function ()
{ {

View file

@ -43,6 +43,7 @@
this.edited = ko.observable(false); this.edited = ko.observable(false);
this.collapsed = ko.observable(true); this.collapsed = ko.observable(true);
this.subScribed = ko.observable(true); this.subScribed = ko.observable(true);
this.checkable = ko.observable(false);
this.subFolders = ko.observableArray([]); this.subFolders = ko.observableArray([]);
this.deleteAccess = ko.observable(false); this.deleteAccess = ko.observable(false);
this.actionBlink = ko.observable(false).extend({'falseTimeout': 1000}); this.actionBlink = ko.observable(false).extend({'falseTimeout': 1000});
@ -105,6 +106,7 @@
}, this); }, this);
this.hidden = ko.computed(function () { this.hidden = ko.computed(function () {
var var
bSystem = this.isSystemFolder(), bSystem = this.isSystemFolder(),
bSubFolders = this.hasSubScribedSubfolders() bSubFolders = this.hasSubScribedSubfolders()
@ -182,6 +184,8 @@
return !this.isSystemFolder() && this.selectable && sInboxFolderName !== this.fullNameRaw; return !this.isSystemFolder() && this.selectable && sInboxFolderName !== this.fullNameRaw;
}, this); }, this);
this.canBeChecked = this.canBeSubScribed;
// this.visible.subscribe(function () { // this.visible.subscribe(function () {
// Utils.timeOutAction('folder-list-folder-visibility-change', function () { // Utils.timeOutAction('folder-list-folder-visibility-change', function () {
// Globals.$win.trigger('folder-list-folder-visibility-change'); // Globals.$win.trigger('folder-list-folder-visibility-change');
@ -281,7 +285,8 @@
}); });
this.hasUnreadMessages = ko.computed(function () { this.hasUnreadMessages = ko.computed(function () {
return 0 < this.messageCountUnread(); return 0 < this.messageCountUnread() &&
(this.isSystemFolder() || this.checkable());
}, this); }, this);
this.hasSubScribedUnreadMessagesSubfolders = ko.computed(function () { this.hasSubScribedUnreadMessagesSubfolders = ko.computed(function () {
@ -352,6 +357,8 @@
this.existen = !!oJsonFolder.IsExists; this.existen = !!oJsonFolder.IsExists;
this.subScribed(!!oJsonFolder.IsSubscribed); this.subScribed(!!oJsonFolder.IsSubscribed);
this.checkable(!!oJsonFolder.Checkable);
this.type(sInboxFolderName === this.fullNameRaw ? Enums.FolderType.Inbox : Enums.FolderType.User); this.type(sInboxFolderName === this.fullNameRaw ? Enums.FolderType.Inbox : Enums.FolderType.User);
bResult = true; bResult = true;

View file

@ -74,30 +74,11 @@
this.selected = ko.observable(false); this.selected = ko.observable(false);
this.checked = ko.observable(false); this.checked = ko.observable(false);
this.hasAttachments = ko.observable(false); this.hasAttachments = ko.observable(false);
this.attachmentsMainType = ko.observable(''); this.attachmentsSpecData = ko.observableArray([]);
this.attachmentIconClass = ko.computed(function () { this.attachmentIconClass = ko.computed(function () {
var sClass = ''; return AttachmentModel.staticCombinedIconClass(
if (this.hasAttachments()) this.hasAttachments() ? this.attachmentsSpecData() : []);
{
sClass = 'icon-attachment';
switch (this.attachmentsMainType())
{
case 'image':
sClass = 'icon-image';
break;
case 'archive':
sClass = 'icon-file-zip';
break;
case 'doc':
sClass = 'icon-file-text';
break;
case 'certificate':
sClass = 'icon-file-certificate';
break;
}
}
return sClass;
}, this); }, this);
this.body = null; this.body = null;
@ -193,7 +174,7 @@
this.selected(false); this.selected(false);
this.checked(false); this.checked(false);
this.hasAttachments(false); this.hasAttachments(false);
this.attachmentsMainType(''); this.attachmentsSpecData([]);
this.body = null; this.body = null;
this.isHtml(false); this.isHtml(false);
@ -287,7 +268,8 @@
this.dateTimeStampInUTC(Utils.pInt(oJsonMessage.DateTimeStampInUTC)); this.dateTimeStampInUTC(Utils.pInt(oJsonMessage.DateTimeStampInUTC));
this.hasAttachments(!!oJsonMessage.HasAttachments); this.hasAttachments(!!oJsonMessage.HasAttachments);
this.attachmentsMainType(oJsonMessage.AttachmentsMainType); this.attachmentsSpecData(Utils.isArray(oJsonMessage.AttachmentsSpecData) ?
oJsonMessage.AttachmentsSpecData : []);
this.fromEmailString(MessageHelper.emailArrayToString(this.from, true)); this.fromEmailString(MessageHelper.emailArrayToString(this.from, true));
this.fromClearEmailString(MessageHelper.emailArrayToStringClear(this.from)); this.fromClearEmailString(MessageHelper.emailArrayToStringClear(this.from));
@ -337,7 +319,8 @@
} }
this.hasAttachments(!!oJsonMessage.HasAttachments); this.hasAttachments(!!oJsonMessage.HasAttachments);
this.attachmentsMainType(oJsonMessage.AttachmentsMainType); this.attachmentsSpecData(Utils.isArray(oJsonMessage.AttachmentsSpecData) ?
oJsonMessage.AttachmentsSpecData : []);
this.foundedCIDs = Utils.isArray(oJsonMessage.FoundedCIDs) ? oJsonMessage.FoundedCIDs : []; this.foundedCIDs = Utils.isArray(oJsonMessage.FoundedCIDs) ? oJsonMessage.FoundedCIDs : [];
this.attachments(this.initAttachmentsFromJson(oJsonMessage.Attachments)); this.attachments(this.initAttachmentsFromJson(oJsonMessage.Attachments));
@ -524,15 +507,6 @@
if (this.hasAttachments()) if (this.hasAttachments())
{ {
aResult.push('withAttachments'); aResult.push('withAttachments');
switch (this.attachmentsMainType())
{
case 'image':
aResult.push('imageOnlyAttachments');
break;
case 'archive':
aResult.push('archiveOnlyAttachments');
break;
}
} }
if (this.newForAnimation()) if (this.newForAnimation())
{ {
@ -797,7 +771,7 @@
}; };
/** /**
* @returns {string} * @return {string}
*/ */
MessageModel.prototype.generateUid = function () MessageModel.prototype.generateUid = function ()
{ {
@ -850,7 +824,7 @@
this.selected(oMessage.selected()); this.selected(oMessage.selected());
this.checked(oMessage.checked()); this.checked(oMessage.checked());
this.hasAttachments(oMessage.hasAttachments()); this.hasAttachments(oMessage.hasAttachments());
this.attachmentsMainType(oMessage.attachmentsMainType()); this.attachmentsSpecData(oMessage.attachmentsSpecData());
this.body = null; this.body = null;
@ -1004,7 +978,7 @@
} }
}; };
MessageModel.prototype.storeDataToDom = function () MessageModel.prototype.storeDataInDom = function ()
{ {
if (this.body) if (this.body)
{ {
@ -1023,7 +997,7 @@
} }
}; };
MessageModel.prototype.storePgpVerifyDataToDom = function () MessageModel.prototype.storePgpVerifyDataInDom = function ()
{ {
if (this.body && require('Stores/User/Pgp').capaOpenPGP()) if (this.body && require('Stores/User/Pgp').capaOpenPGP())
{ {
@ -1032,7 +1006,7 @@
} }
}; };
MessageModel.prototype.fetchDataToDom = function () MessageModel.prototype.fetchDataFromDom = function ()
{ {
if (this.body) if (this.body)
{ {
@ -1116,7 +1090,7 @@
} }
catch (oExc) {} catch (oExc) {}
this.storePgpVerifyDataToDom(); this.storePgpVerifyDataInDom();
} }
}; };
@ -1188,7 +1162,7 @@
} }
catch (oExc) {} catch (oExc) {}
this.storePgpVerifyDataToDom(); this.storePgpVerifyDataInDom();
} }
}; };

View file

@ -29,8 +29,6 @@
MessageFullModel.prototype.requestHash = ''; MessageFullModel.prototype.requestHash = '';
MessageFullModel.prototype.proxy = false; MessageFullModel.prototype.proxy = false;
MessageFullModel.prototype.hasAttachments = false; MessageFullModel.prototype.hasAttachments = false;
MessageFullModel.prototype.attachmentsMainType = '';
MessageFullModel.prototype.attachmentsClass = '';
MessageFullModel.prototype.clear = function () MessageFullModel.prototype.clear = function ()
{ {
@ -43,37 +41,6 @@
this.proxy = false; this.proxy = false;
this.hasAttachments = false; this.hasAttachments = false;
this.attachmentsMainType = '';
this.attachmentsClass = '';
};
/**
* @return {string}
*/
MessageFullModel.prototype.getAttachmentsClass = function ()
{
var sClass = '';
if (this.hasAttachments)
{
sClass = 'icon-attachment';
switch (this.attachmentsMainType)
{
case 'image':
sClass = 'icon-image';
break;
case 'archive':
sClass = 'icon-file-zip';
break;
case 'doc':
sClass = 'icon-file-text';
break;
case 'certificate':
sClass = 'icon-file-certificate';
break;
}
}
return sClass;
}; };
/** /**
@ -98,8 +65,6 @@
this.proxy = !!oJson.ExternalProxy; this.proxy = !!oJson.ExternalProxy;
this.hasAttachments = !!oJson.HasAttachments; this.hasAttachments = !!oJson.HasAttachments;
this.attachmentsMainType = Utils.pString(oJson.AttachmentsMainType);
this.attachmentsClass = this.getAttachmentsClass();
bResult = true; bResult = true;
} }

View file

@ -5,6 +5,7 @@
var var
_ = require('_'), _ = require('_'),
ko = require('ko'),
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
@ -20,11 +21,18 @@
function MessageSimpleModel(sSuperName) function MessageSimpleModel(sSuperName)
{ {
AbstractModel.call(this, sSuperName || 'MessageSimpleModel'); AbstractModel.call(this, sSuperName || 'MessageSimpleModel');
this.flagged = ko.observable(false);
this.selected = ko.observable(false);
} }
_.extend(MessageSimpleModel.prototype, AbstractModel.prototype); _.extend(MessageSimpleModel.prototype, AbstractModel.prototype);
MessageSimpleModel.prototype.__simple_message__ = true;
MessageSimpleModel.prototype.folder = ''; MessageSimpleModel.prototype.folder = '';
MessageSimpleModel.prototype.folderFullNameRaw = '';
MessageSimpleModel.prototype.uid = ''; MessageSimpleModel.prototype.uid = '';
MessageSimpleModel.prototype.subject = ''; MessageSimpleModel.prototype.subject = '';
@ -48,6 +56,8 @@
MessageSimpleModel.prototype.clear = function () MessageSimpleModel.prototype.clear = function ()
{ {
this.folder = ''; this.folder = '';
this.folderFullNameRaw = '';
this.uid = ''; this.uid = '';
this.subject = ''; this.subject = '';
@ -68,6 +78,9 @@
this.size = 0; this.size = 0;
this.timestamp = 0; this.timestamp = 0;
this.flagged(false);
this.selected(false);
}; };
/** /**
@ -81,6 +94,8 @@
if (oJson && 'Object/Message' === oJson['@Object']) if (oJson && 'Object/Message' === oJson['@Object'])
{ {
this.folder = Utils.pString(oJson.Folder); this.folder = Utils.pString(oJson.Folder);
this.folderFullNameRaw = this.folder;
this.uid = Utils.pString(oJson.Uid); this.uid = Utils.pString(oJson.Uid);
this.subject = Utils.pString(oJson.Subject); this.subject = Utils.pString(oJson.Subject);
@ -112,6 +127,9 @@
this.toAsString = MessageHelper.emailArrayToString(this.to, true); this.toAsString = MessageHelper.emailArrayToString(this.to, true);
this.toAsStringClear = MessageHelper.emailArrayToStringClear(this.to); this.toAsStringClear = MessageHelper.emailArrayToStringClear(this.to);
this.flagged(false);
this.selected(false);
this.populateSenderEmail(); this.populateSenderEmail();
bResult = true; bResult = true;
@ -132,6 +150,14 @@
} }
}; };
/**
* @return {Array}
*/
MessageSimpleModel.prototype.threads = function ()
{
return [];
};
/** /**
* @static * @static
* @param {Object} oJson * @param {Object} oJson

View file

@ -16,8 +16,6 @@
// AppStore = require('Stores/User/App'), // AppStore = require('Stores/User/App'),
// SettingsStore = require('Stores/User/Settings'), // SettingsStore = require('Stores/User/Settings'),
MessageSimpleModel = require('Model/MessageSimple'),
PromisesPopulator = require('Promises/User/Populator'), PromisesPopulator = require('Promises/User/Populator'),
AbstractAjaxPromises = require('Promises/AbstractAjax') AbstractAjaxPromises = require('Promises/AbstractAjax')
; ;
@ -29,44 +27,10 @@
function UserAjaxUserPromises() function UserAjaxUserPromises()
{ {
AbstractAjaxPromises.call(this); AbstractAjaxPromises.call(this);
this.messageListSimpleHash = '';
this.messageListSimpleCache = null;
} }
_.extend(UserAjaxUserPromises.prototype, AbstractAjaxPromises.prototype); _.extend(UserAjaxUserPromises.prototype, AbstractAjaxPromises.prototype);
UserAjaxUserPromises.prototype.messageListSimple = function (sFolder, aUids, fTrigger)
{
var self = this, sHash = sFolder + '~' + aUids.join('/');
if (sHash === this.messageListSimpleHash && this.messageListSimpleCache)
{
return this.fastResolve(this.messageListSimpleCache);
}
return this.abort('MessageListSimple')
.postRequest('MessageListSimple', fTrigger, {
'Folder': sFolder,
'Uids': aUids
}).then(function (oData) {
self.messageListSimpleHash = sHash;
self.messageListSimpleCache = _.compact(_.map(oData.Result, function (aItem) {
return MessageSimpleModel.newInstanceFromJson(aItem);
}));
return self.messageListSimpleCache;
}, function (iError) {
self.messageListSimpleHash = '';
self.messageListSimpleCache = null;
return self.fastReject(iError);
})
;
};
UserAjaxUserPromises.prototype.foldersReload = function (fTrigger) UserAjaxUserPromises.prototype.foldersReload = function (fTrigger)
{ {
return this.abort('Folders') return this.abort('Folders')
@ -112,6 +76,14 @@
}); });
}; };
UserAjaxUserPromises.prototype.attachmentsActions = function (sAction, aHashes, fTrigger)
{
return this.postRequest('AttachmentsActions', fTrigger, {
'Do': sAction,
'Hashes': aHashes
});
};
UserAjaxUserPromises.prototype.welcomeClose = function () UserAjaxUserPromises.prototype.welcomeClose = function ()
{ {
return this.postRequest('WelcomeClose'); return this.postRequest('WelcomeClose');

View file

@ -313,39 +313,30 @@
}); });
}; };
/**
* @param {?Function} fCallback
* @param {string} sFolderFullNameRaw
* @param {Array} aUids
*/
RemoteUserAjax.prototype.messageListSimple = function (fCallback, sFolderFullNameRaw, aUids)
{
return this.defaultRequest(fCallback, 'MessageListSimple', {
'Folder': Utils.pString(sFolderFullNameRaw),
'Uids': aUids
}, Consts.Defaults.DefaultAjaxTimeout, '', ['MessageListSimple']);
};
/** /**
* @param {?Function} fCallback * @param {?Function} fCallback
* @param {string} sFolderFullNameRaw * @param {string} sFolderFullNameRaw
* @param {number=} iOffset = 0 * @param {number=} iOffset = 0
* @param {number=} iLimit = 20 * @param {number=} iLimit = 20
* @param {string=} sSearch = '' * @param {string=} sSearch = ''
* @param {string=} sThreadUid = ''
* @param {boolean=} bSilent = false * @param {boolean=} bSilent = false
*/ */
RemoteUserAjax.prototype.messageList = function (fCallback, sFolderFullNameRaw, iOffset, iLimit, sSearch, bSilent) RemoteUserAjax.prototype.messageList = function (fCallback, sFolderFullNameRaw, iOffset, iLimit, sSearch, sThreadUid, bSilent)
{ {
sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw); sFolderFullNameRaw = Utils.pString(sFolderFullNameRaw);
var var
sFolderHash = Cache.getFolderHash(sFolderFullNameRaw) sFolderHash = Cache.getFolderHash(sFolderFullNameRaw),
bUseThreads = AppStore.threadsAllowed() && SettingsStore.useThreads(),
sInboxUidNext = Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : ''
; ;
bSilent = Utils.isUnd(bSilent) ? false : !!bSilent; bSilent = Utils.isUnd(bSilent) ? false : !!bSilent;
iOffset = Utils.isUnd(iOffset) ? 0 : Utils.pInt(iOffset); iOffset = Utils.isUnd(iOffset) ? 0 : Utils.pInt(iOffset);
iLimit = Utils.isUnd(iOffset) ? 20 : Utils.pInt(iLimit); iLimit = Utils.isUnd(iOffset) ? 20 : Utils.pInt(iLimit);
sSearch = Utils.pString(sSearch); sSearch = Utils.pString(sSearch);
sThreadUid = Utils.pString(sThreadUid);
if ('' !== sFolderHash && ('' === sSearch || -1 === sSearch.indexOf('is:'))) if ('' !== sFolderHash && ('' === sSearch || -1 === sSearch.indexOf('is:')))
{ {
@ -358,9 +349,9 @@
sSearch, sSearch,
AppStore.projectHash(), AppStore.projectHash(),
sFolderHash, sFolderHash,
Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '', sInboxUidNext,
AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0', bUseThreads ? '1' : '0',
'' bUseThreads ? sThreadUid : ''
].join(String.fromCharCode(0))), bSilent ? [] : ['MessageList']); ].join(String.fromCharCode(0))), bSilent ? [] : ['MessageList']);
} }
else else
@ -370,9 +361,11 @@
'Offset': iOffset, 'Offset': iOffset,
'Limit': iLimit, 'Limit': iLimit,
'Search': sSearch, 'Search': sSearch,
'UidNext': Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '', 'UidNext': sInboxUidNext,
'UseThreads': AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0' 'UseThreads': bUseThreads ? '1' : '0',
}, '' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout, '', bSilent ? [] : ['MessageList']); 'ThreadUid': bUseThreads ? sThreadUid : ''
}, '' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout,
'', bSilent ? [] : ['MessageList']);
} }
}; };
@ -414,35 +407,6 @@
return false; return false;
}; };
/**
* @param {?Function} fCallback
* @param {string} sFolderFullNameRaw
* @param {number} iUid
* @return {boolean}
*/
RemoteUserAjax.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 {?Function} fCallback
* @param {Array} aExternals * @param {Array} aExternals
@ -744,6 +708,19 @@
}); });
}; };
/**
* @param {?Function} fCallback
* @param {string} sFolderFullNameRaw
* @param {boolean} bCheckable
*/
RemoteUserAjax.prototype.folderSetCheckable = function (fCallback, sFolderFullNameRaw, bCheckable)
{
this.defaultRequest(fCallback, 'FolderCheckable', {
'Folder': sFolderFullNameRaw,
'Checkable': bCheckable ? '1' : '0'
});
};
/** /**
* @param {?Function} fCallback * @param {?Function} fCallback
* @param {string} sFolder * @param {string} sFolder

View file

@ -131,6 +131,7 @@
// show // show
if (self.oCurrentSubScreen) if (self.oCurrentSubScreen)
{ {
Utils.delegateRun(self.oCurrentSubScreen, 'onBeforeShow');
self.oCurrentSubScreen.viewModelDom.show(); self.oCurrentSubScreen.viewModelDom.show();
Utils.delegateRun(self.oCurrentSubScreen, 'onShow'); Utils.delegateRun(self.oCurrentSubScreen, 'onShow');
Utils.delegateRun(self.oCurrentSubScreen, 'onShowWithDelay', [], 200); Utils.delegateRun(self.oCurrentSubScreen, 'onShowWithDelay', [], 200);

View file

@ -75,15 +75,23 @@
MailBoxUserScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch) MailBoxUserScreen.prototype.onRoute = function (sFolderHash, iPage, sSearch)
{ {
var var
sFolderFullNameRaw = Cache.getFolderFullNameRaw(sFolderHash), sThreadUid = sFolderHash.replace(/^(.+)~([\d]+)$/, '$2'),
oFolder = Cache.getFolderFromCacheList(sFolderFullNameRaw) oFolder = Cache.getFolderFromCacheList(Cache.getFolderFullNameRaw(
sFolderHash.replace(/~([\d]+)$/, '')))
; ;
if (oFolder) if (oFolder)
{ {
if (sFolderHash === sThreadUid)
{
sThreadUid = '';
}
FolderStore.currentFolder(oFolder); FolderStore.currentFolder(oFolder);
MessageStore.messageListPage(iPage); MessageStore.messageListPage(iPage);
MessageStore.messageListSearch(sSearch); MessageStore.messageListSearch(sSearch);
MessageStore.messageListThreadUid(sThreadUid);
require('App/User').reloadMessageList(); require('App/User').reloadMessageList();
} }
@ -163,9 +171,9 @@
; ;
return [ return [
[/^([a-zA-Z0-9]+)\/p([1-9][0-9]*)\/(.+)\/?$/, {'normalize_': fNormS}], [/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)\/(.+)\/?$/, {'normalize_': fNormS}],
[/^([a-zA-Z0-9]+)\/p([1-9][0-9]*)$/, {'normalize_': fNormS}], [/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)$/, {'normalize_': fNormS}],
[/^([a-zA-Z0-9]+)\/(.+)\/?$/, {'normalize_': fNormD}], [/^([a-zA-Z0-9~]+)\/(.+)\/?$/, {'normalize_': fNormD}],
[/^([^\/]*)$/, {'normalize_': fNormS}] [/^([^\/]*)$/, {'normalize_': fNormS}]
]; ];
}; };

View file

@ -144,8 +144,7 @@
}, 50); }, 50);
} }
else
{
_.delay(function () { _.delay(function () {
var var
@ -166,7 +165,6 @@
}); });
}, 50); }, 50);
}
}; };
module.exports = BrandingAdminSettings; module.exports = BrandingAdminSettings;

View file

@ -57,7 +57,7 @@
}; };
/** /**
* @returns {boolean} * @return {boolean}
*/ */
LicensingAdminSettings.prototype.licenseIsUnlim = function () LicensingAdminSettings.prototype.licenseIsUnlim = function ()
{ {
@ -65,7 +65,7 @@
}; };
/** /**
* @returns {string} * @return {string}
*/ */
LicensingAdminSettings.prototype.licenseExpiredMomentValue = function () LicensingAdminSettings.prototype.licenseExpiredMomentValue = function ()
{ {

View file

@ -28,6 +28,8 @@
{ {
this.folderList = FolderStore.folderList; this.folderList = FolderStore.folderList;
this.folderListHelp = ko.observable('').extend({'throttle': 100});
this.loading = ko.computed(function () { this.loading = ko.computed(function () {
var var
@ -95,6 +97,25 @@
FolderStore.folderList.error(''); FolderStore.folderList.error('');
}; };
FoldersUserSettings.prototype.onBuild = function (oDom)
{
var self = this;
oDom
.on('mouseover', '.delete-folder-parent', function () {
self.folderListHelp(Translator.i18n('SETTINGS_FOLDERS/HELP_DELETE_FOLDER'));
})
.on('mouseover', '.subscribe-folder-parent', function () {
self.folderListHelp(Translator.i18n('SETTINGS_FOLDERS/HELP_SHOW_HIDE_FOLDER'));
})
.on('mouseover', '.check-folder-parent', function () {
self.folderListHelp(Translator.i18n('SETTINGS_FOLDERS/HELP_SHOW_HIDE_FOLDER_COUNT'));
})
.on('mouseout', '.subscribe-folder-parent, .check-folder-parent, .delete-folder-parent', function () {
self.folderListHelp('');
})
;
};
FoldersUserSettings.prototype.createFolder = function () FoldersUserSettings.prototype.createFolder = function ()
{ {
require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderCreate')); require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderCreate'));
@ -160,6 +181,20 @@
oFolder.subScribed(false); oFolder.subScribed(false);
}; };
FoldersUserSettings.prototype.checkableTrueFolder = function (oFolder)
{
Remote.folderSetCheckable(Utils.emptyFunction, oFolder.fullNameRaw, true);
oFolder.checkable(true);
};
FoldersUserSettings.prototype.checkableFalseFolder = function (oFolder)
{
Remote.folderSetCheckable(Utils.emptyFunction, oFolder.fullNameRaw, false);
oFolder.checkable(false);
};
module.exports = FoldersUserSettings; module.exports = FoldersUserSettings;
}()); }());

View file

@ -22,14 +22,11 @@
this.language = ko.observable('') this.language = ko.observable('')
.extend({'limitedList': this.languages}); .extend({'limitedList': this.languages});
this.userLanguage = ko.observable('')
.extend({'limitedList': this.languages});
this.languageAdmin = ko.observable('') this.languageAdmin = ko.observable('')
.extend({'limitedList': this.languagesAdmin}); .extend({'limitedList': this.languagesAdmin});
this.userLanguageAdmin = ko.observable('') this.userLanguage = ko.observable('');
.extend({'limitedList': this.languagesAdmin}); this.userLanguageAdmin = ko.observable('');
} }
LanguageStore.prototype.populate = function () LanguageStore.prototype.populate = function ()

View file

@ -59,7 +59,7 @@
}; };
/** /**
* @returns {boolean} * @return {boolean}
*/ */
AccountUserStore.prototype.isRootAccount = function () AccountUserStore.prototype.isRootAccount = function ()
{ {

View file

@ -8,6 +8,7 @@
Enums = require('Common/Enums'), Enums = require('Common/Enums'),
Globals = require('Common/Globals'), Globals = require('Common/Globals'),
Utils = require('Common/Utils'),
Settings = require('Storage/Settings'), Settings = require('Storage/Settings'),
@ -52,6 +53,8 @@
this.contactsIsAllowed = ko.observable(false); this.contactsIsAllowed = ko.observable(false);
this.attahcmentsActions = ko.observableArray([]);
this.devEmail = ''; this.devEmail = '';
this.devPassword = ''; this.devPassword = '';
} }
@ -67,6 +70,9 @@
this.contactsIsAllowed(!!Settings.settingsGet('ContactsIsAllowed')); this.contactsIsAllowed(!!Settings.settingsGet('ContactsIsAllowed'));
var mAttahcmentsActions = Settings.settingsGet('AttahcmentsActions');
this.attahcmentsActions(Utils.isNonEmptyArray(mAttahcmentsActions) ? mAttahcmentsActions : []);
this.devEmail = Settings.settingsGet('DevEmail'); this.devEmail = Settings.settingsGet('DevEmail');
this.devPassword = Settings.settingsGet('DevPassword'); this.devPassword = Settings.settingsGet('DevPassword');
}; };

View file

@ -181,13 +181,10 @@
}; };
/** /**
* @param {boolean=} bBoot = false * @return {Array}
* @returns {Array}
*/ */
FolderUserStore.prototype.getNextFolderNames = function (bBoot) FolderUserStore.prototype.getNextFolderNames = function ()
{ {
bBoot = Utils.isUnd(bBoot) ? false : !!bBoot;
var var
aResult = [], aResult = [],
iLimit = 5, iLimit = 5,
@ -200,7 +197,8 @@
if (oFolder && sInboxFolderName !== oFolder.fullNameRaw && if (oFolder && sInboxFolderName !== oFolder.fullNameRaw &&
oFolder.selectable && oFolder.existen && oFolder.selectable && oFolder.existen &&
iTimeout > oFolder.interval && iTimeout > oFolder.interval &&
(!bBoot || oFolder.subScribed())) (oFolder.isSystemFolder() || (oFolder.subScribed() && oFolder.checkable()))
)
{ {
aTimeouts.push([oFolder.interval, oFolder.fullNameRaw]); aTimeouts.push([oFolder.interval, oFolder.fullNameRaw]);
} }
@ -236,7 +234,9 @@
return iLimit <= aResult.length; return iLimit <= aResult.length;
}); });
return _.uniq(aResult); aResult = _.uniq(aResult);
return aResult;
}; };
module.exports = new FolderUserStore(); module.exports = new FolderUserStore();

View file

@ -41,11 +41,14 @@
this.messageListCount = ko.observable(0); this.messageListCount = ko.observable(0);
this.messageListSearch = ko.observable(''); this.messageListSearch = ko.observable('');
this.messageListThreadUid = ko.observable('');
this.messageListPage = ko.observable(1); this.messageListPage = ko.observable(1);
this.messageListPageBeforeThread = ko.observable(1);
this.messageListError = ko.observable(''); this.messageListError = ko.observable('');
this.messageListEndFolder = ko.observable(''); this.messageListEndFolder = ko.observable('');
this.messageListEndSearch = ko.observable(''); this.messageListEndSearch = ko.observable('');
this.messageListEndThreadUid = ko.observable('');
this.messageListEndPage = ko.observable(1); this.messageListEndPage = ko.observable(1);
this.messageListLoading = ko.observable(false); this.messageListLoading = ko.observable(false);
@ -63,15 +66,12 @@
this.message.viewTrigger = ko.observable(false); this.message.viewTrigger = ko.observable(false);
this.messageLastThreadUidsData = ko.observable(null);
this.messageError = ko.observable(''); this.messageError = ko.observable('');
this.messageCurrentLoading = ko.observable(false); this.messageCurrentLoading = ko.observable(false);
this.messageThreadLoading = ko.observable(false);
this.messageLoading = ko.computed(function () { this.messageLoading = ko.computed(function () {
return !!(this.messageCurrentLoading() || this.messageThreadLoading()); return this.messageCurrentLoading();
}, this); }, this);
this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50}); this.messageLoadingThrottle = ko.observable(false).extend({'throttle': 50});
@ -91,8 +91,12 @@
MessageUserStore.prototype.computers = function () MessageUserStore.prototype.computers = function ()
{ {
var self = this;
this.messageListEndHash = ko.computed(function () { this.messageListEndHash = ko.computed(function () {
return this.messageListEndFolder() + '|' + this.messageListEndSearch() + '|' + this.messageListEndPage(); return this.messageListEndFolder() + '|' + this.messageListEndSearch() +
'|' + this.messageListEndThreadUid() +
'|' + this.messageListEndPage();
}, this); }, this);
this.messageListPageCount = ko.computed(function () { this.messageListPageCount = ko.computed(function () {
@ -105,7 +109,8 @@
'read': this.messageListSearch, 'read': this.messageListSearch,
'write': function (sValue) { 'write': function (sValue) {
kn.setHash(Links.mailBox( kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(), 1, Utils.trim(sValue.toString()) FolderStore.currentFolderFullNameHash(), 1,
Utils.trim(sValue.toString()), self.messageListThreadUid()
)); ));
}, },
'owner': this 'owner': this
@ -322,6 +327,7 @@
var var
self = this, self = this,
iUnseenCount = 0, iUnseenCount = 0,
oMessage = null,
aMessageList = this.messageList(), aMessageList = this.messageList(),
oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw), oFromFolder = Cache.getFolderFromCacheList(sFromFolderFullNameRaw),
oToFolder = '' === sToFolderFullNameRaw ? null : Cache.getFolderFromCacheList(sToFolderFullNameRaw || ''), oToFolder = '' === sToFolderFullNameRaw ? null : Cache.getFolderFromCacheList(sToFolderFullNameRaw || ''),
@ -401,6 +407,56 @@
{ {
Cache.setFolderHash(sToFolderFullNameRaw, ''); Cache.setFolderHash(sToFolderFullNameRaw, '');
} }
if ('' !== this.messageListThreadUid())
{
aMessageList = this.messageList();
if (aMessageList && 0 < aMessageList.length && !!_.find(aMessageList, function (oMessage) {
return !!(oMessage && oMessage.deleted() && oMessage.uid === self.messageListThreadUid());
}))
{
oMessage = _.find(aMessageList, function (oMessage) {
return oMessage && !oMessage.deleted();
});
if (oMessage && this.messageListThreadUid() !== Utils.pString(oMessage.uid))
{
this.messageListThreadUid(Utils.pString(oMessage.uid));
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
this.messageListPage(),
this.messageListSearch(),
this.messageListThreadUid()
), true, true);
}
else if (!oMessage)
{
if (1 < this.messageListPage())
{
this.messageListPage(this.messageListPage() - 1);
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
this.messageListPage(),
this.messageListSearch(),
this.messageListThreadUid()
), true, true);
}
else
{
this.messageListThreadUid('');
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
this.messageListPageBeforeThread(),
this.messageListSearch()
), true, true);
}
}
}
}
}; };
MessageUserStore.prototype.addBlockquoteSwitcherCallback = function () MessageUserStore.prototype.addBlockquoteSwitcherCallback = function ()
@ -569,7 +625,7 @@
oMessagesBodiesDom.append(oMessage.body); oMessagesBodiesDom.append(oMessage.body);
} }
oMessage.storeDataToDom(); oMessage.storeDataInDom();
if (bHasInternals) if (bHasInternals)
{ {
@ -589,7 +645,7 @@
if (oMessage.body) if (oMessage.body)
{ {
oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount); oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
oMessage.fetchDataToDom(); oMessage.fetchDataFromDom();
} }
} }
@ -663,14 +719,6 @@
} }
}; };
MessageUserStore.prototype.selectThreadMessage = function (sFolder, sUid)
{
if (Remote.message(this.onMessageResponse, sFolder, sUid))
{
this.messageThreadLoading(true);
}
};
MessageUserStore.prototype.populateMessageBody = function (oMessage) MessageUserStore.prototype.populateMessageBody = function (oMessage)
{ {
if (oMessage) if (oMessage)
@ -692,7 +740,6 @@
this.hideMessageBodies(); this.hideMessageBodies();
this.messageCurrentLoading(false); this.messageCurrentLoading(false);
this.messageThreadLoading(false);
if (Enums.StorageResultType.Success === sResult && oData && oData.Result) if (Enums.StorageResultType.Success === sResult && oData && oData.Result)
{ {
@ -714,7 +761,7 @@
/** /**
* @param {Array} aList * @param {Array} aList
* @returns {string} * @return {string}
*/ */
MessageUserStore.prototype.calculateMessageListHash = function (aList) MessageUserStore.prototype.calculateMessageListHash = function (aList)
{ {
@ -729,7 +776,6 @@
oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection'])) oData.Result['@Collection'] && Utils.isArray(oData.Result['@Collection']))
{ {
var var
self = this,
iIndex = 0, iIndex = 0,
iLen = 0, iLen = 0,
iCount = 0, iCount = 0,
@ -811,9 +857,11 @@
this.messageListCount(iCount); this.messageListCount(iCount);
this.messageListSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : ''); this.messageListSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : '');
this.messageListPage(window.Math.ceil((iOffset / SettingsStore.messagesPerPage()) + 1)); this.messageListPage(window.Math.ceil((iOffset / SettingsStore.messagesPerPage()) + 1));
this.messageListThreadUid(Utils.isNormal(oData.Result.ThreadUid) ? Utils.pString(oData.Result.ThreadUid) : '');
this.messageListEndFolder(Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : ''); this.messageListEndFolder(Utils.isNormal(oData.Result.Folder) ? oData.Result.Folder : '');
this.messageListEndSearch(Utils.isNormal(oData.Result.Search) ? oData.Result.Search : ''); this.messageListEndSearch(this.messageListSearch());
this.messageListEndThreadUid(this.messageListThreadUid());
this.messageListEndPage(this.messageListPage()); this.messageListEndPage(this.messageListPage());
this.messageListDisableAutoSelect(true); this.messageListDisableAutoSelect(true);
@ -823,26 +871,6 @@
Cache.clearNewMessageCache(); 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())) if (oFolder && (bCached || bUnreadCountChange || SettingsStore.useThreads()))
{ {
require('App/User').folderInformation(oFolder.fullNameRaw, aList); require('App/User').folderInformation(oFolder.fullNameRaw, aList);

View file

@ -142,7 +142,7 @@
NotificationUserStore.prototype.initNotificationPlayer = function () NotificationUserStore.prototype.initNotificationPlayer = function ()
{ {
if (Audio && Audio.supported) if (Audio && Audio.supportedNotification)
{ {
this.soundNotificationIsSupported(true); this.soundNotificationIsSupported(true);
} }
@ -155,7 +155,7 @@
NotificationUserStore.prototype.playSoundNotification = function (bSkipSetting) NotificationUserStore.prototype.playSoundNotification = function (bSkipSetting)
{ {
if (Audio && Audio.supported && (bSkipSetting ? true : this.enableSoundNotification())) if (Audio && Audio.supportedNotification && (bSkipSetting ? true : this.enableSoundNotification()))
{ {
Audio.playNotification(); Audio.playNotification();
} }

View file

@ -72,7 +72,7 @@
/** /**
* @param {string} sEmail * @param {string} sEmail
* @param {string=} sPassword * @param {string=} sPassword
* @returns {?} * @return {?}
*/ */
PgpUserStore.prototype.findPrivateKeyByEmail = function (sEmail, sPassword) PgpUserStore.prototype.findPrivateKeyByEmail = function (sEmail, sPassword)
{ {
@ -109,7 +109,7 @@
/** /**
* @param {string=} sPassword * @param {string=} sPassword
* @returns {?} * @return {?}
*/ */
PgpUserStore.prototype.findSelfPrivateKey = function (sPassword) PgpUserStore.prototype.findSelfPrivateKey = function (sPassword)
{ {

View file

@ -6,8 +6,15 @@
} }
.flag-name { .flag-name {
border-bottom: 1px dashed #555; border-bottom: 1px dashed #555;
cursor: pointer; cursor: pointer;
padding: 2px;
&:focus {
outline: 1px;
outline-style: dotted;
}
} }
} }

View file

@ -21,6 +21,15 @@
0% {background-position: 0 0;} 100% {background-position: 60px 0;} 0% {background-position: 0 0;} 100% {background-position: 60px 0;}
} }
@keyframes login-form-shake {
0% {transform: translateX(0);}
12.5% {transform: translateX(-6px) rotateY(-5deg)}
37.5% {transform: translateX(5px) rotateY(4deg)}
62.5% {transform: translateX(-3px) rotateY(-2deg)}
87.5% {transform: translateX(2px) rotateY(1deg)}
100% {transform: translateX(0)}
}
html.csstransitions.rl-started-trigger.no-mobile .b-login-content .loginFormWrapper { html.csstransitions.rl-started-trigger.no-mobile .b-login-content .loginFormWrapper {
/*transform: scale(1.1);*/ /*transform: scale(1.1);*/
transform: translateY(-20px); transform: translateY(-20px);
@ -56,6 +65,14 @@ html.csstransitions.rl-started-trigger.no-mobile .b-login-content .loginFormWrap
} }
} }
&.cssanimations.csstransitions.no-mobile .b-login-content .errorAnimated {
animation: login-form-shake 400ms ease-in-out;
}
&.cssanimations.csstransitions.no-mobile .b-login-content .afterLoginHide {
opacity: 0;
}
/* &.csstransitions.no-mobile #rl-content { /* &.csstransitions.no-mobile #rl-content {
.transition(opacity 0.3s ease-out); .transition(opacity 0.3s ease-out);
}*/ }*/

View file

@ -16,7 +16,7 @@
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2); box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04), 0 1px 5px rgba(0, 0, 0, 0.1); box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.04), 0 1px 5px rgba(0, 0, 0, 0.1);
border-radius: 2px; border-radius: 3px;
&.waiting { &.waiting {
opacity: 0.6; opacity: 0.6;
@ -120,6 +120,15 @@
color: #aaa; color: #aaa;
} }
.attachmentIcon {
&.icon-none {
display: none;
}
&.icon-file-certificate {
margin-left: 15px;
}
}
.attachmentIconText { .attachmentIconText {
display: inline-block; display: inline-block;
font-size: 28px; font-size: 28px;
@ -131,15 +140,13 @@
font-style: normal; font-style: normal;
} }
.attachmentIcon.icon-none { /*
display: none;
}
.attachmentIconParent.hasPreview.isImage { .attachmentIconParent.hasPreview.isImage {
.iconMain { .iconMain {
display: none; display: none;
} }
} }
*/
.attachmentIconParent.hasPreview:hover { .attachmentIconParent.hasPreview:hover {
.iconPreview { .iconPreview {

View file

@ -37,13 +37,36 @@
.e-component { .e-component {
&.e-select {
select:focus {
outline: 1px;
outline-style: dotted;
}
}
&.e-checkbox { &.e-checkbox {
margin-bottom: 6px;
margin-left: -2px;
padding: 2px;
cursor: pointer; cursor: pointer;
&:focus {
outline: 1px;
outline-style: dotted;
}
.e-checkbox-icon {
padding: 1px 0 0 1px;
}
&.disabled { &.disabled {
cursor: default; cursor: default;
color: #999; color: #999;
outline: 0;
outline-style: none;
} }
} }
@ -60,11 +83,12 @@
.e-component.material-design { .e-component.material-design {
margin-top: 2px;
margin-bottom: 6px;
&.e-checkbox { &.e-checkbox {
margin-top: 2px;
padding: 2px 2px 1px 2px;
.sub-checkbox-container { .sub-checkbox-container {
display: inline-block; display: inline-block;
position: relative; position: relative;

View file

@ -9,6 +9,7 @@
display: inline-block; display: inline-block;
vertical-align: middle; vertical-align: middle;
text-align: center; text-align: center;
perspective: 500px;
.descWrapper { .descWrapper {
@ -59,7 +60,11 @@
} }
} }
.signMeLabel, .languageLabel { .languageLabel {
margin-top: 5px;
}
.signMeLabel .e-checkbox {
margin-top: 5px; margin-top: 5px;
} }

View file

@ -178,6 +178,17 @@ html.rl-no-preview-pane {
border-bottom: 1px solid #eee; border-bottom: 1px solid #eee;
} }
.listThreadUidDesc {
font-size: 16px;
padding: 7px 20px 6px 20px;
background-color: #aaa;
border-bottom: 1px solid #888;
color: #fff;
cursor: pointer;
text-shadow: 0 1px 0 #000;
text-align: center;
}
.delimiter { .delimiter {
display: block; display: block;
height: 1px; height: 1px;
@ -313,6 +324,12 @@ html.rl-no-preview-pane {
&.e-single-line .dateParent { &.e-single-line .dateParent {
} }
.threadsParent {
display: inline-block;
float: right;
position: relative;
}
.attachmentParent { .attachmentParent {
display: inline-block; display: inline-block;
float: right; float: right;
@ -322,7 +339,7 @@ html.rl-no-preview-pane {
&.e-single-line .attachmentParent { &.e-single-line .attachmentParent {
float: left; float: left;
margin: 0 5px 0 0; margin: 0 8px 0 0;
} }
.senderParent { .senderParent {
@ -395,11 +412,25 @@ html.rl-no-preview-pane {
.threads-len { .threads-len {
.threads-len-data { .threads-len-data {
background-color: #999;
color: #fff; background-color: #eee;
color: #666;
border-radius: 6px; border-radius: 6px;
padding: 1px 6px 1px 5px; padding: 2px 0px 1px 6px;
margin-right: 5px; margin-right: 2px;
border: 1px solid #ccc;
[class^="icon-"],
[class*=" icon-"] {
font-size: 14px;
}
&:hover {
background-color: #aaa;
border-color: #666;
text-shadow: 0 1px 0 #999;
color: #fff;
}
} }
} }

View file

@ -76,6 +76,23 @@ html.rl-no-preview-pane {
right: 10px; right: 10px;
} }
.flagParent {
cursor: pointer;
margin-right: 5px;
.flagOn {
color: orange;
}
.flagOff {
opacity: 0.5;
&:hover {
opacity: 1;
}
}
}
.messageItemHeader { .messageItemHeader {
position: absolute; position: absolute;
@ -120,23 +137,6 @@ html.rl-no-preview-pane {
margin-top: 5px; margin-top: 5px;
} }
.flagParent {
cursor: pointer;
margin-right: 5px;
.flagOn {
color: orange;
}
.flagOff {
opacity: 0.5;
&:hover {
opacity: 1;
}
}
}
.informationShort { .informationShort {
margin-left: 22px; margin-left: 22px;
a { a {
@ -263,6 +263,14 @@ html.rl-no-preview-pane {
} }
} }
&.unselectedAttachmentsError {
.attachmentItem {
box-shadow: 0 1px 4px red;
box-shadow: 0 1px 5px rgba(255, 0, 0, 0.4);
box-shadow: 0 0 0 1px rgba(255, 0, 0, 0.2), 0 1px 5px rgba(255, 0, 0, 0.3);
}
}
.controls-handle { .controls-handle {
position: absolute; position: absolute;
bottom: 5px; bottom: 5px;

View file

@ -86,7 +86,7 @@
} }
} }
.delete-folder, .subscribe-folder, .unsubscribe-folder { .delete-folder, .subscribe-folder, .unsubscribe-folder, .check-folder, .uncheck-folder {
cursor: pointer; cursor: pointer;
opacity: 0.6; opacity: 0.6;
} }
@ -94,6 +94,10 @@
.unsubscribe-folder { .unsubscribe-folder {
opacity: 0.2; opacity: 0.2;
} }
.uncheck-folder {
opacity: 0.1;
}
} }
.folder-padding.deep-1 { .folder-padding.deep-1 {

View file

@ -22,5 +22,11 @@
.flag-name { .flag-name {
border-bottom: 1px dashed #555; border-bottom: 1px dashed #555;
cursor: pointer; cursor: pointer;
padding: 2px;
&:focus {
outline: 1px;
outline-style: dotted;
}
} }
} }

View file

@ -21,6 +21,12 @@
color: #336699; color: #336699;
text-decoration: underline; text-decoration: underline;
cursor: pointer; cursor: pointer;
padding: 2px;
&:focus {
outline: 1px;
outline-style: dotted;
}
} }
.g-ui-min-height-300 { .g-ui-min-height-300 {
@ -181,12 +187,21 @@ e-spinner {
} }
.e-languages { .e-languages {
margin-top: 8px; margin-top: 8px;
color: #333; color: #333;
.flag-name { .flag-name {
color: #333; color: #333;
/*text-decoration: underline;*/
border-bottom: 1px dashed #333; border-bottom: 1px dashed #333;
cursor: pointer;
padding: 2px;
&:focus {
outline: 1px;
outline-style: dotted;
}
} }
} }

View file

@ -7,7 +7,7 @@ label {
cursor: pointer; cursor: pointer;
} }
label.inline { label.inline, span.inline {
display: inline-block; display: inline-block;
} }

View file

@ -33,6 +33,7 @@
} }
.opentip-container { .opentip-container {
z-index: 2001 !important; z-index: 2001 !important;
.ot-content { .ot-content {
font-size: 13px; font-size: 13px;
@ -41,4 +42,30 @@
&.style-rainloopErrorTip .ot-content { &.style-rainloopErrorTip .ot-content {
color: red; color: red;
} }
&.ot-show-effect-none, &.ot-hide-effect-none {
transition: none !important;
}
&.ot-show-effect-fade {
transition: none;
&.ot-hidden {
opacity: 0;
}
&.ot-going-to-show {
opacity: 0;
transition: opacity 0.2s ease-in-out;
}
&.ot-showing {
opacity: 1;
transition: opacity 0.2s ease-in-out;
}
&.ot-visible {
opacity: 1;
transition: none;
}
}
} }

View file

@ -142,7 +142,7 @@
border-color: transparent; border-color: transparent;
border-top-color: #999; border-top-color: #999;
animation: rotation .8s infinite linear; animation: rotation .8s infinite ease-in-out;
} }
&.big { &.big {

View file

@ -34,8 +34,17 @@
this.loginError = ko.observable(false); this.loginError = ko.observable(false);
this.passwordError = ko.observable(false); this.passwordError = ko.observable(false);
this.loginErrorAnimation = ko.observable(false).extend({'falseTimeout': 500});
this.passwordErrorAnimation = ko.observable(false).extend({'falseTimeout': 500});
this.loginFocus = ko.observable(false); this.loginFocus = ko.observable(false);
this.formHidden = ko.observable(false);
this.formError = ko.computed(function () {
return this.loginErrorAnimation() || this.passwordErrorAnimation();
}, this);
this.login.subscribe(function () { this.login.subscribe(function () {
this.loginError(false); this.loginError(false);
}, this); }, this);
@ -44,6 +53,14 @@
this.passwordError(false); this.passwordError(false);
}, this); }, this);
this.loginError.subscribe(function (bV) {
this.loginErrorAnimation(!!bV);
}, this);
this.passwordError.subscribe(function (bV) {
this.passwordErrorAnimation(!!bV);
}, this);
this.submitRequest = ko.observable(false); this.submitRequest = ko.observable(false);
this.submitError = ko.observable(''); this.submitError = ko.observable('');
@ -51,6 +68,9 @@
Utils.triggerAutocompleteInputChange(); Utils.triggerAutocompleteInputChange();
this.loginError(false);
this.passwordError(false);
this.loginError('' === Utils.trim(this.login())); this.loginError('' === Utils.trim(this.login()));
this.passwordError('' === Utils.trim(this.password())); this.passwordError('' === Utils.trim(this.password()));

View file

@ -137,7 +137,7 @@
}; };
/** /**
* @returns {boolean} * @return {boolean}
*/ */
ActivatePopupView.prototype.validateSubscriptionKey = function () ActivatePopupView.prototype.validateSubscriptionKey = function ()
{ {

View file

@ -8,6 +8,7 @@
ko = require('ko'), ko = require('ko'),
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
Translator = require('Common/Translator'),
MessageStore = require('Stores/User/Message'), MessageStore = require('Stores/User/Message'),
@ -46,6 +47,19 @@
this.cancelCommand(); this.cancelCommand();
}); });
this.selectedDates = ko.computed(function () {
Translator.trigger();
return [
{'id': -1, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_ALL')},
{'id': 3, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_3_DAYS')},
{'id': 7, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_7_DAYS')},
{'id': 30, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_MONTH')},
{'id': 90, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_3_MONTHS')},
{'id': 180, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_6_MONTHS')},
{'id': 365, 'name': Translator.i18n('SEARCH/LABEL_ADV_DATE_YEAR')}
];
}, this);
kn.constructorEnd(this); kn.constructorEnd(this);
} }

View file

@ -1,4 +1,6 @@
/* global require, module */
(function () { (function () {
'use strict'; 'use strict';
@ -523,7 +525,6 @@
if (window.Dropbox) if (window.Dropbox)
{ {
window.Dropbox.choose({ window.Dropbox.choose({
//'iframe': true,
'success': function(aFiles) { 'success': function(aFiles) {
if (aFiles && aFiles[0] && aFiles[0]['link']) if (aFiles && aFiles[0] && aFiles[0]['link'])
@ -995,7 +996,7 @@
* *
* @param {Array} aList * @param {Array} aList
* @param {boolean} bFriendly * @param {boolean} bFriendly
* @returns {string} * @return {string}
*/ */
ComposePopupView.prototype.emailArrayToStringLineHelper = function (aList, bFriendly) ComposePopupView.prototype.emailArrayToStringLineHelper = function (aList, bFriendly)
{ {
@ -1097,6 +1098,26 @@
oText.find('.rlBlockquoteSwitcher').off('.rlBlockquoteSwitcher').remove(); oText.find('.rlBlockquoteSwitcher').off('.rlBlockquoteSwitcher').remove();
oText.find('[data-html-editor-font-wrapper]').removeAttr('data-html-editor-font-wrapper'); oText.find('[data-html-editor-font-wrapper]').removeAttr('data-html-editor-font-wrapper');
(function () {
var oTmp = null, iLimit = 0;
while (true)
{
iLimit++;
oTmp = oText.children();
if (10 > iLimit && oTmp.is('div') && 1 === oTmp.length)
{
oTmp.children().unwrap();
continue;
}
break;
}
}());
sText = oText.html(); sText = oText.html();
} }
@ -1414,11 +1435,11 @@
Events.sub('window.resize.real', this.resizerTrigger); Events.sub('window.resize.real', this.resizerTrigger);
Events.sub('window.resize.real', _.debounce(this.resizerTrigger, 50)); Events.sub('window.resize.real', _.debounce(this.resizerTrigger, 50));
if (this.dropboxEnabled()) if (this.dropboxEnabled() && this.dropboxApiKey() && !window.Dropbox)
{ {
oScript = window.document.createElement('script'); oScript = window.document.createElement('script');
oScript.type = 'text/javascript'; oScript.type = 'text/javascript';
oScript.src = 'https://www.dropbox.com/static/api/1/dropins.js'; oScript.src = 'https://www.dropbox.com/static/api/2/dropins.js';
$(oScript).attr('id', 'dropboxjs').attr('data-app-key', self.dropboxApiKey()); $(oScript).attr('id', 'dropboxjs').attr('data-app-key', self.dropboxApiKey());
window.document.body.appendChild(oScript); window.document.body.appendChild(oScript);

View file

@ -24,15 +24,16 @@
var self = this; var self = this;
this.fLang = null; this.fLang = null;
this.sUserLanguage = ''; this.userLanguage = ko.observable('');
this.langs = ko.observableArray([]); this.langs = ko.observableArray([]);
this.languages = ko.computed(function () { this.languages = ko.computed(function () {
var sUserLanguage = self.userLanguage();
return _.map(self.langs(), function (sLanguage) { return _.map(self.langs(), function (sLanguage) {
return { return {
'key': sLanguage, 'key': sLanguage,
'user': sLanguage === self.sUserLanguage, 'user': sLanguage === sUserLanguage,
'selected': ko.observable(false), 'selected': ko.observable(false),
'fullName': Utils.convertLangName(sLanguage) 'fullName': Utils.convertLangName(sLanguage)
}; };
@ -63,21 +64,22 @@
}); });
}; };
LanguagesPopupView.prototype.onBeforeShow = function ()
{
this.fLang = null;
this.userLanguage('');
this.langs([]);
};
LanguagesPopupView.prototype.onShow = function (fLanguage, aLangs, sUserLanguage) LanguagesPopupView.prototype.onShow = function (fLanguage, aLangs, sUserLanguage)
{ {
this.fLang = fLanguage; this.fLang = fLanguage;
this.sUserLanguage = sUserLanguage || ''; this.userLanguage(sUserLanguage || '');
this.langs(aLangs); this.langs(aLangs);
}; };
LanguagesPopupView.prototype.onHide = function ()
{
this.fLang = null;
this.sUserLanguage = '';
this.langs([]);
};
LanguagesPopupView.prototype.changeLanguage = function (sLang) LanguagesPopupView.prototype.changeLanguage = function (sLang)
{ {
if (this.fLang) if (this.fLang)

View file

@ -42,6 +42,7 @@
this.additionalCode = ko.observable(''); this.additionalCode = ko.observable('');
this.additionalCode.error = ko.observable(false); this.additionalCode.error = ko.observable(false);
this.additionalCode.errorAnimation = ko.observable(false).extend({'falseTimeout': 500});
this.additionalCode.focused = ko.observable(false); this.additionalCode.focused = ko.observable(false);
this.additionalCode.visibility = ko.observable(false); this.additionalCode.visibility = ko.observable(false);
this.additionalCodeSignMe = ko.observable(false); this.additionalCodeSignMe = ko.observable(false);
@ -56,6 +57,16 @@
this.emailError = ko.observable(false); this.emailError = ko.observable(false);
this.passwordError = ko.observable(false); this.passwordError = ko.observable(false);
this.emailErrorAnimation = ko.observable(false).extend({'falseTimeout': 500});
this.passwordErrorAnimation = ko.observable(false).extend({'falseTimeout': 500});
this.formHidden = ko.observable(false);
this.formError = ko.computed(function () {
return this.emailErrorAnimation() || this.passwordErrorAnimation() ||
(this.additionalCode.visibility() && this.additionalCode.errorAnimation());
}, this);
this.emailFocus = ko.observable(false); this.emailFocus = ko.observable(false);
this.passwordFocus = ko.observable(false); this.passwordFocus = ko.observable(false);
this.submitFocus = ko.observable(false); this.submitFocus = ko.observable(false);
@ -78,6 +89,18 @@
this.additionalCode.error(false); this.additionalCode.error(false);
}, this); }, this);
this.emailError.subscribe(function (bV) {
this.emailErrorAnimation(!!bV);
}, this);
this.passwordError.subscribe(function (bV) {
this.passwordErrorAnimation(!!bV);
}, this);
this.additionalCode.error.subscribe(function (bV) {
this.additionalCode.errorAnimation(!!bV);
}, this);
this.submitRequest = ko.observable(false); this.submitRequest = ko.observable(false);
this.submitError = ko.observable(''); this.submitError = ko.observable('');
this.submitErrorAddidional = ko.observable(''); this.submitErrorAddidional = ko.observable('');
@ -115,16 +138,34 @@
Utils.triggerAutocompleteInputChange(); Utils.triggerAutocompleteInputChange();
this.emailError(false);
this.passwordError(false);
this.emailError('' === Utils.trim(this.email())); this.emailError('' === Utils.trim(this.email()));
this.passwordError('' === Utils.trim(this.password())); this.passwordError('' === Utils.trim(this.password()));
if (this.additionalCode.visibility()) if (this.additionalCode.visibility())
{ {
this.additionalCode.error(false);
this.additionalCode.error('' === Utils.trim(this.additionalCode())); this.additionalCode.error('' === Utils.trim(this.additionalCode()));
} }
if (this.emailError() || this.passwordError() || this.additionalCode.error()) if (this.emailError() || this.passwordError() ||
(this.additionalCode.visibility() && this.additionalCode.error()))
{ {
switch (true)
{
case this.emailError():
this.emailFocus(true);
break;
case this.passwordError():
this.passwordFocus(true);
break;
case this.additionalCode.visibility() && this.additionalCode.error():
this.additionalCode.focused(true);
break;
}
return false; return false;
} }
@ -426,6 +467,21 @@
]); ]);
}; };
LoginUserView.prototype.selectLanguageOnTab = function (bShift)
{
if (!bShift)
{
var self = this;
_.delay(function () {
self.emailFocus(true);
}, 5);
return false;
}
return true;
};
module.exports = LoginUserView; module.exports = LoginUserView;
}()); }());

View file

@ -62,6 +62,7 @@
this.selectorMessageFocused = MessageStore.selectorMessageFocused; this.selectorMessageFocused = MessageStore.selectorMessageFocused;
this.isMessageSelected = MessageStore.isMessageSelected; this.isMessageSelected = MessageStore.isMessageSelected;
this.messageListSearch = MessageStore.messageListSearch; this.messageListSearch = MessageStore.messageListSearch;
this.messageListThreadUid = MessageStore.messageListThreadUid;
this.messageListError = MessageStore.messageListError; this.messageListError = MessageStore.messageListError;
this.folderMenuForMove = FolderStore.folderMenuForMove; this.folderMenuForMove = FolderStore.folderMenuForMove;
@ -69,6 +70,7 @@
this.mainMessageListSearch = MessageStore.mainMessageListSearch; this.mainMessageListSearch = MessageStore.mainMessageListSearch;
this.messageListEndFolder = MessageStore.messageListEndFolder; this.messageListEndFolder = MessageStore.messageListEndFolder;
this.messageListEndThreadUid = MessageStore.messageListEndThreadUid;
this.messageListChecked = MessageStore.messageListChecked; this.messageListChecked = MessageStore.messageListChecked;
this.messageListCheckedOrSelected = MessageStore.messageListCheckedOrSelected; this.messageListCheckedOrSelected = MessageStore.messageListCheckedOrSelected;
@ -374,7 +376,7 @@
}; };
/** /**
* @returns {string} * @return {string}
*/ */
MessageListMailBoxUserView.prototype.printableMessageCountForDeletion = function () MessageListMailBoxUserView.prototype.printableMessageCountForDeletion = function ()
{ {
@ -388,6 +390,15 @@
this.inputMessageListSearchFocus(false); this.inputMessageListSearchFocus(false);
}; };
MessageListMailBoxUserView.prototype.cancelThreadUid = function ()
{
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
MessageStore.messageListPageBeforeThread(),
MessageStore.messageListSearch()
));
};
/** /**
* @param {string} sToFolderFullNameRaw * @param {string} sToFolderFullNameRaw
* @param {boolean} bCopy * @param {boolean} bCopy
@ -611,11 +622,34 @@
kn.setHash(Links.mailBox( kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(), FolderStore.currentFolderFullNameHash(),
oPage.value, oPage.value,
MessageStore.messageListSearch() MessageStore.messageListSearch(),
MessageStore.messageListThreadUid()
)); ));
} }
}; };
MessageListMailBoxUserView.prototype.gotoThread = function (oMessage)
{
if (oMessage && 0 < oMessage.threadsLen())
{
MessageStore.messageListPageBeforeThread(MessageStore.messageListPage());
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
1,
MessageStore.messageListSearch(),
oMessage.uid
));
}
};
MessageListMailBoxUserView.prototype.clearListIsVisible = function ()
{
return '' === this.messageListSearchDesc() && '' === this.messageListError() &&
'' === MessageStore.messageListEndThreadUid() &&
0 < this.messageList().length && (this.isSpamFolder() || this.isTrashFolder());
};
MessageListMailBoxUserView.prototype.onBuild = function (oDom) MessageListMailBoxUserView.prototype.onBuild = function (oDom)
{ {
var self = this; var self = this;
@ -641,6 +675,12 @@
.on('click', '.messageList .messageListItem .flagParent', function () { .on('click', '.messageList .messageListItem .flagParent', function () {
self.flagMessages(ko.dataFor(this)); self.flagMessages(ko.dataFor(this));
}) })
.on('click', '.messageList .messageListItem .threads-len', function () {
self.gotoThread(ko.dataFor(this));
})
.on('dblclick', '.messageList .messageListItem .actionHandle', function () {
self.gotoThread(ko.dataFor(this));
})
; ;
this.initUploaderForAppend(); this.initUploaderForAppend();
@ -723,6 +763,22 @@
return false; return false;
}); });
key('t', [Enums.KeyState.MessageList], function () {
var oMessage = self.selectorMessageSelected();
if (!oMessage)
{
oMessage = self.selectorMessageFocused();
}
if (oMessage && 0 < oMessage.threadsLen())
{
self.gotoThread(oMessage);
}
return false;
});
// move // move
key('m', Enums.KeyState.MessageList, function () { key('m', Enums.KeyState.MessageList, function () {
self.moveDropdownTrigger(true); self.moveDropdownTrigger(true);
@ -759,6 +815,11 @@
self.cancelSearch(); self.cancelSearch();
return false; return false;
} }
else if ('' !== self.messageListEndThreadUid())
{
self.cancelThreadUid();
return false;
}
}); });
// change focused state // change focused state

View file

@ -4,6 +4,7 @@
'use strict'; 'use strict';
var var
window = require('window'),
_ = require('_'), _ = require('_'),
$ = require('$'), $ = require('$'),
ko = require('ko'), ko = require('ko'),
@ -19,9 +20,11 @@
Events = require('Common/Events'), Events = require('Common/Events'),
Translator = require('Common/Translator'), Translator = require('Common/Translator'),
Audio = require('Common/Audio'), Audio = require('Common/Audio'),
Links = require('Common/Links'),
Cache = require('Common/Cache'), Cache = require('Common/Cache'),
SocialStore = require('Stores/Social'),
AppStore = require('Stores/User/App'), AppStore = require('Stores/User/App'),
SettingsStore = require('Stores/User/Settings'), SettingsStore = require('Stores/User/Settings'),
AccountStore = require('Stores/User/Account'), AccountStore = require('Stores/User/Account'),
@ -56,11 +59,14 @@
} }
; ;
this.oDom = null;
this.oHeaderDom = null; this.oHeaderDom = null;
this.oMessageScrollerDom = null; this.oMessageScrollerDom = null;
this.pswp = null; this.pswp = null;
this.attahcmentsActions = AppStore.attahcmentsActions;
this.message = MessageStore.message; this.message = MessageStore.message;
this.messageListChecked = MessageStore.messageListChecked; this.messageListChecked = MessageStore.messageListChecked;
this.hasCheckedMessages = MessageStore.hasCheckedMessages; this.hasCheckedMessages = MessageStore.hasCheckedMessages;
@ -79,9 +85,65 @@
this.messageListOfThreadsLoading = ko.observable(false).extend({'rateLimit': 1}); this.messageListOfThreadsLoading = ko.observable(false).extend({'rateLimit': 1});
this.allowAttachmnetControls = ko.observable(false); this.highlightUnselectedAttachments = ko.observable(false).extend({'falseTimeout': 2000});
this.showAttachmnetControls = ko.observable(false); this.showAttachmnetControls = ko.observable(false);
this.allowAttachmnetControls = ko.computed(function () {
return 0 < this.attahcmentsActions().length;
}, this);
this.downloadAsZipAllowed = ko.computed(function () {
return -1 < Utils.inArray('zip', this.attahcmentsActions());
}, this);
this.downloadAsZipLoading = ko.observable(false);
this.downloadAsZipError = ko.observable(false).extend({'falseTimeout': 7000});
this.saveToOwnCloudAllowed = ko.computed(function () {
return -1 < Utils.inArray('owncloud', this.attahcmentsActions());
}, this);
this.saveToOwnCloudLoading = ko.observable(false);
this.saveToOwnCloudSuccess = ko.observable(false).extend({'falseTimeout': 2000});
this.saveToOwnCloudError = ko.observable(false).extend({'falseTimeout': 7000});
this.saveToOwnCloudSuccess.subscribe(function (bV) {
if (bV)
{
this.saveToOwnCloudError(false);
}
}, this);
this.saveToOwnCloudError.subscribe(function (bV) {
if (bV)
{
this.saveToOwnCloudSuccess(false);
}
}, this);
this.saveToDropboxAllowed = ko.computed(function () {
return -1 < Utils.inArray('dropbox', this.attahcmentsActions());
}, this);
this.saveToDropboxLoading = ko.observable(false);
this.saveToDropboxSuccess = ko.observable(false).extend({'falseTimeout': 2000});
this.saveToDropboxError = ko.observable(false).extend({'falseTimeout': 7000});
this.saveToDropboxSuccess.subscribe(function (bV) {
if (bV)
{
this.saveToDropboxError(false);
}
}, this);
this.saveToDropboxError.subscribe(function (bV) {
if (bV)
{
this.saveToDropboxSuccess(false);
}
}, this);
this.showAttachmnetControls.subscribe(function (bV) { this.showAttachmnetControls.subscribe(function (bV) {
if (this.message()) if (this.message())
{ {
@ -115,12 +177,6 @@
this.moreDropdownTrigger = ko.observable(false); this.moreDropdownTrigger = ko.observable(false);
this.messageDomFocused = ko.observable(false).extend({'rateLimit': 0}); this.messageDomFocused = ko.observable(false).extend({'rateLimit': 0});
// TODO
// ko.computed(function () {
// window.console.log('focus:' + AppStore.focusedState() + ', dom:' +
// this.messageDomFocused() + ', key:' + Globals.keyScope() + ' ~ ' + Globals.keyScopeReal());
// }, this).extend({'throttle': 1});
this.messageVisibility = ko.computed(function () { this.messageVisibility = ko.computed(function () {
return !this.messageLoadingThrottle() && !!this.message(); return !this.messageLoadingThrottle() && !!this.message();
}, this); }, this);
@ -204,6 +260,9 @@
} }
}, this.messageVisibility); }, this.messageVisibility);
this.dropboxEnabled = SocialStore.dropbox.enabled;
this.dropboxApiKey = SocialStore.dropbox.apiKey;
// viewer // viewer
this.viewBodyTopValue = ko.observable(0); this.viewBodyTopValue = ko.observable(0);
@ -230,140 +289,6 @@
this.viewIsImportant = ko.observable(false); this.viewIsImportant = ko.observable(false);
this.viewIsFlagged = ko.observable(false); this.viewIsFlagged = ko.observable(false);
// THREADS
this.viewThreads = ko.observableArray([]);
this.viewThreads.trigger = ko.observable(false);
this.viewThreadMessages = ko.observableArray([]);
this.viewThreadMessages.error = ko.observable('');
this.viewThreadMessages.showMore = ko.observable(false);
this.viewThreadMessages.limit = 6;
this.viewThreadMessages.subscribe(function (aList) {
this.viewThreadMessages.error('');
this.viewThreadMessages.showMore(this.viewThreadMessages.limit >= aList.length);
}, this);
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);
this.threadsDropdownTrigger = ko.observable(false);
this.threadListCommand = Utils.createCommand(this, function () {
var
self = this,
sFolder = this.viewFolder,
sUid = this.viewUid,
aUids = this.viewThreads(),
aStatus = this.viewThreads.status()
;
if (aStatus && aStatus[0])
{
self.viewThreadMessages([]);
Promises.messageListSimple(sFolder, aUids, this.messageListOfThreadsLoading).then(function (aList) {
_.each(aList, function (oItem) {
if (oItem && oItem.uid)
{
oItem.selected = sUid === oItem.uid;
}
});
self.viewThreadMessages(aList);
}).fail(function (iErrorCode) {
self.viewThreadMessages([]);
self.viewThreadMessages.error(Translator.getNotification(
iErrorCode, '', Enums.Notification.CantGetMessageList));
}).done();
}
}, function () {
return !this.messageLoadingThrottle() &&
!this.messageListOfThreadsLoading();
});
// PGP // PGP
this.viewPgpPassword = ko.observable(''); this.viewPgpPassword = ko.observable('');
this.viewPgpSignedVerifyStatus = ko.computed(function () { this.viewPgpSignedVerifyStatus = ko.computed(function () {
@ -449,9 +374,6 @@
this.viewIsImportant(oMessage.isImportant()); this.viewIsImportant(oMessage.isImportant());
this.viewIsFlagged(oMessage.flagged()); this.viewIsFlagged(oMessage.flagged());
this.viewThreads(oMessage.threads());
this.viewThreads.trigger(!this.viewThreads.trigger());
sLastEmail = oMessage.fromAsSingleEmail(); sLastEmail = oMessage.fromAsSingleEmail();
Cache.getUserPic(sLastEmail, function (sPic, sEmail) { Cache.getUserPic(sLastEmail, function (sPic, sEmail) {
if (sPic !== self.viewUserPic() && sLastEmail === sEmail) if (sPic !== self.viewUserPic() && sLastEmail === sEmail)
@ -472,8 +394,6 @@
this.viewUid = ''; this.viewUid = '';
this.viewHash = ''; this.viewHash = '';
this.viewThreads([]);
this.scrollMessageToTop(); this.scrollMessageToTop();
} }
@ -532,24 +452,6 @@
kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView); kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView);
_.extend(MessageViewMailBoxUserView.prototype, AbstractView.prototype); _.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 () MessageViewMailBoxUserView.prototype.isPgpActionVisible = function ()
{ {
return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus(); return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
@ -633,10 +535,13 @@
{ {
var var
self = this, self = this,
oScript = null,
sErrorMessage = Translator.i18n('PREVIEW_POPUP/IMAGE_ERROR'), sErrorMessage = Translator.i18n('PREVIEW_POPUP/IMAGE_ERROR'),
fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this) fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this)
; ;
this.oDom = oDom;
this.fullScreenMode.subscribe(function (bValue) { this.fullScreenMode.subscribe(function (bValue) {
if (bValue && self.message()) if (bValue && self.message())
{ {
@ -659,6 +564,16 @@
Utils.windowResize(250); Utils.windowResize(250);
}); });
if (this.dropboxEnabled() && this.dropboxApiKey() && !window.Dropbox)
{
oScript = window.document.createElement('script');
oScript.type = 'text/javascript';
oScript.src = 'https://www.dropbox.com/static/api/2/dropins.js';
$(oScript).attr('id', 'dropboxjs').attr('data-app-key', self.dropboxApiKey());
window.document.body.appendChild(oScript);
}
this.oHeaderDom = $('.messageItemHeader', oDom); this.oHeaderDom = $('.messageItemHeader', oDom);
this.oHeaderDom = this.oHeaderDom[0] ? this.oHeaderDom : null; this.oHeaderDom = this.oHeaderDom[0] ? this.oHeaderDom : null;
@ -667,9 +582,10 @@
if (this.pswpDom) if (this.pswpDom)
{ {
oDom oDom
.on('click', '.attachmentImagePreview[data-index]', function (oEvent) { .on('click', '.attachmentImagePreview.visible', function (oEvent) {
var var
iIndex = 0,
oPs = null, oPs = null,
oEl = oEvent.currentTarget || null, oEl = oEvent.currentTarget || null,
aItems = [] aItems = []
@ -686,14 +602,18 @@
// } // }
; ;
oDom.find('.attachmentImagePreview[data-index]').each(function (index, oSubElement) { oDom.find('.attachmentImagePreview.visible').each(function (index, oSubElement) {
var var
$oItem = $(oSubElement) $oItem = $(oSubElement)
; ;
if (oEl === oSubElement)
{
iIndex = index;
}
aItems.push({ aItems.push({
// 'el': $oItem,
'w': 600, 'h': 400, 'w': 600, 'h': 400,
'src': $oItem.attr('href'), 'src': $oItem.attr('href'),
'title': $oItem.attr('title') || '' 'title': $oItem.attr('title') || ''
@ -705,7 +625,7 @@
Globals.useKeyboardShortcuts(false); Globals.useKeyboardShortcuts(false);
oPs = new PhotoSwipe(self.pswpDom, PhotoSwipeUI_Default, aItems, { oPs = new PhotoSwipe(self.pswpDom, PhotoSwipeUI_Default, aItems, {
'index': Utils.pInt($(oEl).data('index')), 'index': iIndex,
'bgOpacity': 0.85, 'bgOpacity': 0.85,
'loadingIndicatorDelay': 500, 'loadingIndicatorDelay': 500,
'errorMsg': '<div class="pswp__error-msg">' + sErrorMessage + '</div>', 'errorMsg': '<div class="pswp__error-msg">' + sErrorMessage + '</div>',
@ -759,35 +679,20 @@
} }
var oAttachment = ko.dataFor(this); var oAttachment = ko.dataFor(this);
if (oAttachment && oAttachment.isMp3() && Audio.supported) if (oAttachment && Audio.supported)
{ {
switch (true)
{
case Audio.supportedMp3 && oAttachment.isMp3():
Audio.playMp3(oAttachment.linkDownload(), oAttachment.fileName); Audio.playMp3(oAttachment.linkDownload(), oAttachment.fileName);
break;
case Audio.supportedOgg && oAttachment.isOgg():
Audio.playOgg(oAttachment.linkDownload(), oAttachment.fileName);
break;
case Audio.supportedWav && oAttachment.isWav():
Audio.playWav(oAttachment.linkDownload(), oAttachment.fileName);
break;
} }
})
.on('click', '.thread-list .more-threads', function (e) {
var oLast = null;
if (!e || 0 === e.clientX) // probably enter
{
// It's a bad bad hack :(
oLast = $('.thread-list .e-item.thread-list-message.real-msg.more-that:first a.e-link', oDom);
}
self.viewThreadMessages.showMore(true);
self.threadsDropdownTrigger(true);
if (oLast)
{
oLast.focus();
}
return false;
})
.on('click', '.thread-list .thread-list-message', function () {
var oMessage = ko.dataFor(this);
if (oMessage && oMessage.folder && oMessage.uid)
{
self.openThreadMessage(oMessage.uid);
} }
}) })
.on('click', '.attachmentsPlace .attachmentItem .attachmentNameParent', function () { .on('click', '.attachmentsPlace .attachmentItem .attachmentNameParent', function () {
@ -801,8 +706,26 @@
require('App/User').download(oAttachment.linkDownload()); require('App/User').download(oAttachment.linkDownload());
} }
}) })
.on('click', '.messageItemHeader .flagParent', function () { .on('click', '.messageItemHeader .subjectParent .flagParent', function () {
self.flagViewMessage(); var oMessage = self.message();
if (oMessage)
{
require('App/User').messageListAction(oMessage.folderFullNameRaw, oMessage.uid,
oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]);
}
})
.on('click', '.thread-list .flagParent', function () {
var oMessage = ko.dataFor(this);
if (oMessage && oMessage.folder && oMessage.uid)
{
require('App/User').messageListAction(
oMessage.folder, oMessage.uid,
oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]);
}
self.threadsDropdownTrigger(true);
return false;
}) })
; ;
@ -824,17 +747,16 @@
this.initShortcuts(); this.initShortcuts();
}; };
/** MessageViewMailBoxUserView.prototype.selectSelectedThreadMessage = function ()
* @return {boolean}
*/
MessageViewMailBoxUserView.prototype.flagViewMessage = function ()
{ {
var oMessage = this.message(); var self = this;
if (oMessage) _.delay(function () {
var oLast = self.oDom ? $('.thread-list .e-item.thread-list-message.real-msg.selected a.e-link', self.oDom) : null;
if (oLast && oLast[0])
{ {
require('App/User').messageListAction(oMessage.folderFullNameRaw, oMessage.uid, oLast.focus();
oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]);
} }
}, 30);
}; };
/** /**
@ -926,35 +848,16 @@
} }
}); });
key('t', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { key('ctrl+up, command+up, ctrl+left, command+left', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
if (MessageStore.message() && self.viewThreadsControlVisibility())
{
self.threadsDropdownTrigger(true);
self.threadListCommand();
return false;
}
});
key('ctrl+up, command+up', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
self.goUpCommand(); self.goUpCommand();
return false; return false;
}); });
key('ctrl+down, command+down', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { key('ctrl+down, command+down, ctrl+right, command+right', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
self.goDownCommand(); self.goDownCommand();
return false; 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 // print
key('ctrl+p, command+p', Enums.KeyState.MessageView, function () { key('ctrl+p, command+p', Enums.KeyState.MessageView, function () {
if (self.message()) if (self.message())
@ -1107,6 +1010,121 @@
} }
}; };
MessageViewMailBoxUserView.prototype.getAttachmentsHashes = function ()
{
return _.compact(_.map(this.message() ? this.message().attachments() : [], function (oItem) {
return oItem && oItem.checked() ? oItem.download : '';
}));
};
MessageViewMailBoxUserView.prototype.downloadAsZip = function ()
{
var self = this, aHashes = this.getAttachmentsHashes();
if (0 < aHashes.length)
{
Promises.attachmentsActions('Zip', aHashes, this.downloadAsZipLoading).then(function (oResult) {
if (oResult && oResult.Result && oResult.Result.Files &&
oResult.Result.Files[0] && oResult.Result.Files[0].Hash)
{
require('App/User').download(
Links.attachmentDownload(oResult.Result.Files[0].Hash));
}
else
{
self.downloadAsZipError(true);
}
}).fail(function () {
self.downloadAsZipError(true);
});
}
else
{
this.highlightUnselectedAttachments(true);
}
};
MessageViewMailBoxUserView.prototype.saveToOwnCloud = function ()
{
var self = this, aHashes = this.getAttachmentsHashes();
if (0 < aHashes.length)
{
Promises.attachmentsActions('OwnCloud', aHashes, this.saveToOwnCloudLoading).then(function (oResult) {
if (oResult && oResult.Result)
{
self.saveToOwnCloudSuccess(true);
}
else
{
self.saveToOwnCloudError(true);
}
}).fail(function () {
self.saveToOwnCloudError(true);
});
}
else
{
this.highlightUnselectedAttachments(true);
}
};
MessageViewMailBoxUserView.prototype.saveToDropbox = function ()
{
var self = this, aFiles = [], aHashes = this.getAttachmentsHashes();
if (0 < aHashes.length)
{
if (window.Dropbox)
{
Promises.attachmentsActions('Dropbox', aHashes, this.saveToDropboxLoading).then(function (oResult) {
if (oResult && oResult.Result && oResult.Result.Url && oResult.Result.ShortLife && oResult.Result.Files)
{
if (window.Dropbox && Utils.isArray(oResult.Result.Files))
{
_.each(oResult.Result.Files, function (oItem) {
aFiles.push({
'url': oResult.Result.Url +
Links.attachmentDownload(oItem.Hash, oResult.Result.ShortLife),
'filename': oItem.FileName
});
});
window.Dropbox.save({
'files': aFiles,
'progress': function () {
self.saveToDropboxLoading(true);
self.saveToDropboxError(false);
self.saveToDropboxSuccess(false);
},
'cancel': function () {
self.saveToDropboxSuccess(false);
self.saveToDropboxError(false);
self.saveToDropboxLoading(false);
},
'success': function () {
self.saveToDropboxSuccess(true);
self.saveToDropboxLoading(false);
},
'error': function () {
self.saveToDropboxError(true);
self.saveToDropboxLoading(false);
}
});
}
else
{
self.saveToDropboxError(true);
}
}
}).fail(function () {
self.saveToDropboxError(true);
});
}
}
else
{
this.highlightUnselectedAttachments(true);
}
};
/** /**
* @param {MessageModel} oMessage * @param {MessageModel} oMessage
*/ */
@ -1119,7 +1137,7 @@
}; };
/** /**
* @returns {string} * @return {string}
*/ */
MessageViewMailBoxUserView.prototype.printableCheckedMessageCount = function () MessageViewMailBoxUserView.prototype.printableCheckedMessageCount = function ()
{ {

View file

@ -1,8 +1,8 @@
{ {
"name": "RainLoop", "name": "RainLoop",
"title": "RainLoop Webmail", "title": "RainLoop Webmail",
"version": "1.8.3", "version": "1.8.4",
"release": "298", "release": "312",
"description": "Simple, modern & fast web-based email client", "description": "Simple, modern & fast web-based email client",
"homepage": "http://rainloop.net", "homepage": "http://rainloop.net",
"main": "gulpfile.js", "main": "gulpfile.js",
@ -36,7 +36,7 @@
"plugins" "plugins"
], ],
"readmeFilename": "README.md", "readmeFilename": "README.md",
"ownCloudPackageVersion": "3.2", "ownCloudPackageVersion": "3.3",
"engines": { "engines": {
"node": ">= 0.10.0" "node": ">= 0.10.0"
}, },

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 RainLoop Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1 @@
Plugin that adds functionality to change the email account password (Vesta Control Panel).

View file

@ -0,0 +1 @@
1.0

View file

@ -0,0 +1,146 @@
<?php
class VestaChangePasswordDriver implements \RainLoop\Providers\ChangePassword\ChangePasswordInterface
{
/**
* @var string
*/
private $sHost = '';
/**
* @var string
*/
private $iPort = 8083;
/**
* @var string
*/
private $sAllowedEmails = '';
/**
* @var \MailSo\Log\Logger
*/
private $oLogger = null;
/**
* @param string $sHost
* @param int $iPort
*
* @return \VestaChangePasswordDriver
*/
public function SetConfig($sHost, $iPort)
{
$this->sHost = $sHost;
$this->iPort = $iPort;
return $this;
}
/**
* @param string $sAllowedEmails
*
* @return \VestaChangePasswordDriver
*/
public function SetAllowedEmails($sAllowedEmails)
{
$this->sAllowedEmails = $sAllowedEmails;
return $this;
}
/**
* @param \MailSo\Log\Logger $oLogger
*
* @return \VestaChangePasswordDriver
*/
public function SetLogger($oLogger)
{
if ($oLogger instanceof \MailSo\Log\Logger)
{
$this->oLogger = $oLogger;
}
return $this;
}
/**
* @param \RainLoop\Account $oAccount
*
* @return bool
*/
public function PasswordChangePossibility($oAccount)
{
return $oAccount && $oAccount->Email() &&
\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $this->sAllowedEmails);
}
/**
* @param \RainLoop\Account $oAccount
* @param string $sPrevPassword
* @param string $sNewPassword
*
* @return bool
*/
public function ChangePassword(\RainLoop\Account $oAccount, $sPrevPassword, $sNewPassword)
{
if ($this->oLogger)
{
$this->oLogger->Write('Vesta: Try to change password for '.$oAccount->Email());
}
$bResult = false;
if (!empty($this->sHost) && 0 < $this->iPort && $oAccount)
{
$sEmail = \trim(\strtolower($oAccount->Email()));
$sHost = \trim($this->sHost);
$sHost = \str_replace('{user:host-imap}', $oAccount->Domain()->IncHost(), $sHost);
$sHost = \str_replace('{user:host-smtp}', $oAccount->Domain()->OutHost(), $sHost);
$sHost = \str_replace('{user:domain}', \MailSo\Base\Utils::GetDomainFromEmail($sEmail), $sHost);
$sHost = \rtrim($this->sHost, '/\\');
$sHost = 'https://'.$sHost;
$sUrl = $sHost.':'.$this->iPort.'/reset/mail/';
$iCode = 0;
$oHttp = \MailSo\Base\Http::SingletonInstance();
if ($this->oLogger)
{
$this->oLogger->Write('Vesta[Api Request]:'.$sUrl);
}
$mResult = $oHttp->SendPostRequest($sUrl,
array(
'email' => $sEmail,
'password' => $sPrevPassword,
'new' => $sNewPassword,
), 'MailSo Http User Agent (v1)', $iCode, $this->oLogger);
if (false !== $mResult && 200 === $iCode)
{
$aRes = null;
@\parse_str($mResult, $aRes);
if (is_array($aRes) && (!isset($aRes['error']) || (int) $aRes['error'] !== 1))
{
$bResult = true;
}
else
{
if ($this->oLogger)
{
$this->oLogger->Write('Vesta[Error]: Response: '.$mResult);
}
}
}
else
{
if ($this->oLogger)
{
$this->oLogger->Write('Vesta[Error]: Empty Response: Code:'.$iCode);
}
}
}
return $bResult;
}
}

View file

@ -0,0 +1,55 @@
<?php
class VestaChangePasswordPlugin extends \RainLoop\Plugins\AbstractPlugin
{
public function Init()
{
$this->addHook('main.fabrica', 'MainFabrica');
}
/**
* @param string $sName
* @param mixed $oProvider
*/
public function MainFabrica($sName, &$oProvider)
{
switch ($sName)
{
case 'change-password':
$sHost = \trim($this->Config()->Get('plugin', 'vesta_host', ''));
$iPort = (int) $this->Config()->Get('plugin', 'vesta_port', 8083);
if (!empty($sHost) && 0 < $iPort)
{
include_once __DIR__.'/VestaChangePasswordDriver.php';
$oProvider = new VestaChangePasswordDriver();
$oProvider->SetLogger($this->Manager()->Actions()->Logger());
$oProvider->SetConfig($sHost, $iPort);
$oProvider->SetAllowedEmails(\strtolower(\trim($this->Config()->Get('plugin', 'allowed_emails', ''))));
}
break;
}
}
/**
* @return array
*/
public function configMapping()
{
return array(
\RainLoop\Plugins\Property::NewInstance('vesta_host')->SetLabel('Vesta Host')
->SetDefaultValue('')
->SetDescription('Allowed patterns: {user:host-imap}, {user:host-smtp}, {user:domain}'),
\RainLoop\Plugins\Property::NewInstance('Vesta_port')->SetLabel('Vesta Port')
->SetType(\RainLoop\Enumerations\PluginPropertyType::INT)
->SetDefaultValue(8083),
\RainLoop\Plugins\Property::NewInstance('allowed_emails')->SetLabel('Allowed emails')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Allowed emails, space as delimiter, wildcard supported. Example: user1@domain1.net user2@domain1.net *@domain2.net')
->SetDefaultValue('*')
);
}
}

View file

@ -34,4 +34,5 @@ class Charset
const ISO_8859_8 = 'iso-8859-8'; const ISO_8859_8 = 'iso-8859-8';
const ISO_8859_8_I = 'iso-8859-8-i'; const ISO_8859_8_I = 'iso-8859-8-i';
const ISO_2022_JP = 'iso-2022-jp'; const ISO_2022_JP = 'iso-2022-jp';
const CP858 = 'cp858';
} }

View file

@ -567,6 +567,12 @@ class Http
return false; return false;
} }
$sUrl = \trim($sUrl);
if ('//' === substr($sUrl, 0, 2))
{
$sUrl = 'http:'.$sUrl;
}
$aOptions = array( $aOptions = array(
CURLOPT_URL => $sUrl, CURLOPT_URL => $sUrl,
CURLOPT_HEADER => false, CURLOPT_HEADER => false,
@ -679,10 +685,10 @@ class Http
{ {
if ($bSetCacheHeader) if ($bSetCacheHeader)
{ {
\header('Cache-Control: public', true); @\header('Cache-Control: public', true);
\header('Pragma: public', true); @\header('Pragma: public', true);
\header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iUtcTimeStamp - $iExpireTime).' UTC', true); @\header('Last-Modified: '.\gmdate('D, d M Y H:i:s', $iUtcTimeStamp - $iExpireTime).' UTC', true);
\header('Expires: '.\gmdate('D, j M Y H:i:s', $iUtcTimeStamp + $iExpireTime).' UTC', true); @\header('Expires: '.\gmdate('D, j M Y H:i:s', $iUtcTimeStamp + $iExpireTime).' UTC', true);
if (0 < strlen($sEtag)) if (0 < strlen($sEtag))
{ {

View file

@ -235,6 +235,56 @@ END;
return $sCharset; return $sCharset;
} }
/**
* @param string $sFilePath
* @param function $fFileExistsCallback = null
*
* @return string
*/
public static function SmartFileExists($sFilePath, $fFileExistsCallback = null)
{
$sFilePath = \str_replace('\\', '/', \trim($sFilePath));
if (!$fFileExistsCallback)
{
$fFileExistsCallback = function ($sPath) {
return \file_exists($sPath);
};
}
if (!\call_user_func($fFileExistsCallback, $sFilePath))
{
return $sFilePath;
}
$aFileInfo = \pathinfo($sFilePath);
$iIndex = 0;
do
{
$iIndex++;
$sFilePathNew = $aFileInfo['dirname'].'/'.
\preg_replace('/\(\d{1,2}\)$/', '', $aFileInfo['filename']).
' ('.$iIndex.')'.
(empty($aFileInfo['extension']) ? '' : '.'.$aFileInfo['extension'])
;
if (!\call_user_func($fFileExistsCallback, $sFilePathNew))
{
$sFilePath = $sFilePathNew;
break;
}
else if (10 < $iIndex)
{
break;
}
}
while (true);
return $sFilePath;
}
/** /**
* @return bool * @return bool
*/ */

View file

@ -83,6 +83,8 @@ abstract class Driver
$this->bTypedPrefix = true; $this->bTypedPrefix = true;
$this->bGuidPrefix = true; $this->bGuidPrefix = true;
$this->iTimeOffset = 0;
$this->iWriteOnTimeoutOnly = 0; $this->iWriteOnTimeoutOnly = 0;
$this->bWriteOnErrorOnly = false; $this->bWriteOnErrorOnly = false;
$this->bWriteOnPhpErrorOnly = false; $this->bWriteOnPhpErrorOnly = false;
@ -106,6 +108,17 @@ abstract class Driver
); );
} }
/**
* @param int $iTimeOffset
*
* @return \MailSo\Log\Driver
*/
public function SetTimeOffset($iTimeOffset)
{
$this->iTimeOffset = $iTimeOffset;
return $this;
}
/** /**
* @return \MailSo\Log\Driver * @return \MailSo\Log\Driver
*/ */
@ -217,7 +230,7 @@ abstract class Driver
protected function getTimeWithMicroSec() protected function getTimeWithMicroSec()
{ {
$aMicroTimeItems = \explode(' ', \microtime()); $aMicroTimeItems = \explode(' ', \microtime());
return \gmdate($this->sDatePattern, $aMicroTimeItems[1]).'.'. return \MailSo\Log\Logger::DateHelper($this->sDatePattern, $this->iTimeOffset, $aMicroTimeItems[1]).'.'.
\str_pad((int) ($aMicroTimeItems[0] * 1000), 3, '0', STR_PAD_LEFT); \str_pad((int) ($aMicroTimeItems[0] * 1000), 3, '0', STR_PAD_LEFT);
} }

View file

@ -91,6 +91,21 @@ class Logger extends \MailSo\Base\Collection
return $oInstance; return $oInstance;
} }
/**
* @param string $sFormat
* @param int $iTimeOffset = 0
* @param int $iTimestamp = 0
*
* @return string
*/
public static function DateHelper($sFormat, $iTimeOffset = 0, $iTimestamp = null)
{
$iTimestamp = null === $iTimestamp ? \time() : (int) $iTimestamp;
$iTimeOffset = (int) $iTimeOffset;
return \gmdate($sFormat, $iTimestamp + $iTimeOffset * 3600);
}
/** /**
* @return bool * @return bool
*/ */

View file

@ -58,62 +58,17 @@ class AttachmentCollection extends \MailSo\Base\Collection
} }
/** /**
* @return int * @return array
*/ */
public function ImageCount() public function SpecData()
{ {
$aList = $this->FilterList(function ($oAttachment) { return $this->MapList(function ($oAttachment) {
return $oAttachment && $oAttachment->IsImage(); if ($oAttachment)
});
return \is_array($aList) ? \count($aList) : 0;
}
/**
* @return int
*/
public function ArchiveCount()
{ {
$aList = $this->FilterList(function ($oAttachment) { return array($oAttachment->FileName(true), $oAttachment->MimeType());
return $oAttachment && $oAttachment->IsArchive(); }
return null;
}); });
return \is_array($aList) ? \count($aList) : 0;
}
/**
* @return int
*/
public function PdfCount()
{
$aList = $this->FilterList(function ($oAttachment) {
return $oAttachment && $oAttachment->IsPdf();
});
return \is_array($aList) ? \count($aList) : 0;
}
/**
* @return int
*/
public function DocCount()
{
$aList = $this->FilterList(function ($oAttachment) {
return $oAttachment && $oAttachment->IsDoc();
});
return \is_array($aList) ? \count($aList) : 0;
}
/**
* @return int
*/
public function CertificateCount()
{
$aList = $this->FilterList(function ($oAttachment) {
return $oAttachment && $oAttachment->IsPgpSignature();
});
return \is_array($aList) ? \count($aList) : 0;
} }
} }

View file

@ -1911,6 +1911,89 @@ class MailClient
return $oMessageCollection->GetAsArray(); return $oMessageCollection->GetAsArray();
} }
/**
* @param \MailSo\Cache\CacheClient|null $oCacher
* @param string $sSearch
* @param string $sFolderName
* @param string $sFolderHash
* @param bool $bUseSortIfSupported = false
*
* @return array
*
* @throws \MailSo\Base\Exceptions\InvalidArgumentException
* @throws \MailSo\Net\Exceptions\Exception
* @throws \MailSo\Imap\Exceptions\Exception
*/
public function GetUids($oCacher, $sSearch, $sFolderName, $sFolderHash, $bUseSortIfSupported = false)
{
$aResultUids = false;
$bUidsFromCacher = false;
$bUseCacheAfterSearch = true;
$sSerializedHash = '';
$sSerializedLog = '';
$bUseSortIfSupported = $bUseSortIfSupported ? !!$this->oImapClient->IsSupported('SORT') : false;
if (0 < \strlen($sSearch))
{
$bUseSortIfSupported = false;
}
$sSearchCriterias = $this->getImapSearchCriterias($sSearch, 0, $bUseCacheAfterSearch);
if ($bUseCacheAfterSearch && $oCacher && $oCacher->IsInited())
{
$sSerializedHash = 'GetUids/'.
($bUseSortIfSupported ? 'S': 'N').'/'.
$this->GenerateImapClientHash().'/'.
$sFolderName.'/'.$sSearchCriterias;
$sSerializedLog = '"'.$sFolderName.'" / '.$sSearchCriterias.'';
$sSerialized = $oCacher->Get($sSerializedHash);
if (!empty($sSerialized))
{
$aSerialized = @\json_decode($sSerialized, true);
if (\is_array($aSerialized) && isset($aSerialized['FolderHash'], $aSerialized['Uids']) &&
$sFolderHash === $aSerialized['FolderHash'] &&
\is_array($aSerialized['Uids'])
)
{
if ($this->oLogger)
{
$this->oLogger->Write('Get Serialized UIDS from cache ('.$sSerializedLog.') [count:'.\count($aSerialized['Uids']).']');
}
$aResultUids = $aSerialized['Uids'];
$bUidsFromCacher = true;
}
}
}
if (!\is_array($aResultUids))
{
$aResultUids = $bUseSortIfSupported ?
$this->oImapClient->MessageSimpleSort(array('REVERSE ARRIVAL'), $sSearchCriterias, true) :
$this->oImapClient->MessageSimpleSearch($sSearchCriterias, true)
;
if (!$bUidsFromCacher && $bUseCacheAfterSearch && \is_array($aResultUids) && $oCacher && $oCacher->IsInited() && 0 < \strlen($sSerializedHash))
{
$oCacher->Set($sSerializedHash, @\json_encode(array(
'FolderHash' => $sFolderHash,
'Uids' => $aResultUids
)));
if ($this->oLogger)
{
$this->oLogger->Write('Save Serialized UIDS to cache ('.$sSerializedLog.') [count:'.\count($aResultUids).']');
}
}
}
return $aResultUids;
}
/** /**
* @param string $sFolderName * @param string $sFolderName
* @param int $iOffset = 0 * @param int $iOffset = 0
@ -1921,6 +2004,7 @@ class MailClient
* @param bool $bUseSortIfSupported = false * @param bool $bUseSortIfSupported = false
* @param bool $bUseThreadSortIfSupported = false * @param bool $bUseThreadSortIfSupported = false
* @param bool $bUseESearchOrESortRequest = false * @param bool $bUseESearchOrESortRequest = false
* @param string $sThreadUid = ''
* *
* @return \MailSo\Mail\MessageCollection * @return \MailSo\Mail\MessageCollection
* *
@ -1929,7 +2013,7 @@ class MailClient
* @throws \MailSo\Imap\Exceptions\Exception * @throws \MailSo\Imap\Exceptions\Exception
*/ */
public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null, public function MessageList($sFolderName, $iOffset = 0, $iLimit = 10, $sSearch = '', $sPrevUidNext = '', $oCacher = null,
$bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $bUseESearchOrESortRequest = false) $bUseSortIfSupported = false, $bUseThreadSortIfSupported = false, $sThreadUid = '')
{ {
$sSearch = \trim($sSearch); $sSearch = \trim($sSearch);
if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) || if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) ||
@ -1945,10 +2029,14 @@ class MailClient
$oMessageCollection->Offset = $iOffset; $oMessageCollection->Offset = $iOffset;
$oMessageCollection->Limit = $iLimit; $oMessageCollection->Limit = $iLimit;
$oMessageCollection->Search = $sSearch; $oMessageCollection->Search = $sSearch;
$oMessageCollection->ThreadUid = $sThreadUid;
$aThreads = array(); $aUids = array();
$mAllSortedUids = null;
$mAllThreads = null;
$iThreadUid = empty($sThreadUid) ? 0 : (int) $sThreadUid;
$iMessageCount = 0;
$iMessageRealCount = 0; $iMessageRealCount = 0;
$iMessageUnseenCount = 0; $iMessageUnseenCount = 0;
$sUidNext = '0'; $sUidNext = '0';
@ -1958,17 +2046,26 @@ class MailClient
$bUseThreadSortIfSupported = $bUseThreadSortIfSupported ? $bUseThreadSortIfSupported = $bUseThreadSortIfSupported ?
($this->oImapClient->IsSupported('THREAD=REFS') || $this->oImapClient->IsSupported('THREAD=REFERENCES') || $this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT')) : false; ($this->oImapClient->IsSupported('THREAD=REFS') || $this->oImapClient->IsSupported('THREAD=REFERENCES') || $this->oImapClient->IsSupported('THREAD=ORDEREDSUBJECT')) : false;
if (!empty($sThreadUid) && !$bUseThreadSortIfSupported)
{
throw new \MailSo\Base\Exceptions\InvalidArgumentException();
}
if (!$oCacher || !($oCacher instanceof \MailSo\Cache\CacheClient)) if (!$oCacher || !($oCacher instanceof \MailSo\Cache\CacheClient))
{ {
$oCacher = null; $oCacher = null;
} }
$this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); $this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext);
$iMessageCount = $iMessageRealCount;
$oMessageCollection->FolderHash = $this->GenerateFolderHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext); $oMessageCollection->FolderHash = $this->GenerateFolderHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext);
$oMessageCollection->UidNext = $sUidNext; $oMessageCollection->UidNext = $sUidNext;
$oMessageCollection->NewMessages = $this->getFolderNextMessageInformation($sFolderName, $sPrevUidNext, $sUidNext);
if (empty($sThreadUid) && 0 < \strlen($sPrevUidNext) && 'INBOX' === $sFolderName)
{
$oMessageCollection->NewMessages = $this->getFolderNextMessageInformation(
$sFolderName, $sPrevUidNext, $sUidNext);
}
$bSearch = false; $bSearch = false;
$bMessageListOptimization = 0 < \MailSo\Config::$MessageListCountLimitTrigger && $bMessageListOptimization = 0 < \MailSo\Config::$MessageListCountLimitTrigger &&
@ -1978,98 +2075,119 @@ class MailClient
{ {
$bUseSortIfSupported = false; $bUseSortIfSupported = false;
$bUseThreadSortIfSupported = false; $bUseThreadSortIfSupported = false;
$bUseESearchOrESortRequest = false;
} }
if (0 < $iMessageRealCount) if (0 < $iMessageRealCount)
{ {
$bIndexAsUid = false; $mAllSortedUids = $this->GetUids($oCacher, '',
$aIndexOrUids = array(); $oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported);
if (0 < \strlen($sSearch)) $mAllThreads = $bUseThreadSortIfSupported ? $this->MessageListThreadsMap(
$oMessageCollection->FolderName, $oMessageCollection->FolderHash, $mAllSortedUids, $oCacher) : null;
if ($bUseThreadSortIfSupported && 0 < $iThreadUid && \is_array($mAllThreads))
{ {
$aIndexOrUids = $this->getSearchUidsResult($sSearch, $aUids = array();
$oMessageCollection->FolderName, $oMessageCollection->FolderHash, $iResultRootUid = 0;
$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
$bIndexAsUid = true; if (isset($mAllThreads[$iThreadUid]))
{
$iResultRootUid = $iThreadUid;
if (\is_array($mAllThreads[$iThreadUid]))
{
$aUids = $mAllThreads[$iThreadUid];
}
} }
else else
{ {
if ($bUseThreadSortIfSupported && 1 < $iMessageCount) foreach ($mAllThreads as $iRootUid => $mSubUids)
{ {
$aIndexOrUids = $this->getSearchUidsResult('', if (\is_array($mSubUids) && \in_array($iThreadUid, $mSubUids))
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
$aThreads = $this->MessageListThreadsMap(
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$aIndexOrUids, $oCacher);
$aNewIndexOrUids = array();
foreach ($aIndexOrUids as $iUid)
{ {
if (isset($aThreads[$iUid])) $iResultRootUid = $iRootUid;
{ $aUids = $mSubUids;
$aNewIndexOrUids[] = $iUid; continue;
}
} }
} }
$aIndexOrUids = $aNewIndexOrUids; if (0 < $iResultRootUid && \in_array($iResultRootUid, $mAllSortedUids))
unset($aNewIndexOrUids); {
\array_unshift($aUids, $iResultRootUid);
$iMessageCount = \count($aIndexOrUids); }
$bIndexAsUid = true; }
else if ($bUseThreadSortIfSupported && \is_array($mAllThreads))
{
$aUids = \array_keys($mAllThreads);
} }
else else
{ {
$aIndexOrUids = array(1); $bUseThreadSortIfSupported = false;
$bIndexAsUid = false; $aUids = $mAllSortedUids;
}
if (1 < $iMessageCount) if (0 < \strlen($sSearch) && \is_array($aUids))
{ {
if (0 === \MailSo\Config::$MessageListDateFilter && $aSearchedUids = $this->GetUids($oCacher, $sSearch, $oMessageCollection->FolderName, $oMessageCollection->FolderHash);
($bMessageListOptimization || !$bUseSortIfSupported)) if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids))
{ {
$aIndexOrUids = \array_reverse(\range(1, $iMessageCount)); $aFlippedSearchedUids = \array_flip($aSearchedUids);
$bSearch = true;
$aNewUids = array();
foreach ($aUids as $iUid)
{
if (isset($aFlippedSearchedUids[$iUid]))
{
$aNewUids[] = $iUid;
}
else if ($bUseThreadSortIfSupported && 0 === $iThreadUid && isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid]))
{
foreach ($mAllThreads[$iUid] as $iSubUid)
{
if (isset($aFlippedSearchedUids[$iSubUid]))
{
$aNewUids[] = $iUid;
continue;
}
}
}
}
$aUids = \array_unique($aNewUids);
unset($aNewUids);
} }
else else
{ {
$aIndexOrUids = $this->getSearchUidsResult('', $aUids = array();
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
$bIndexAsUid = true;
}
}
} }
} }
if (\is_array($aIndexOrUids)) if (\is_array($aUids))
{ {
$oMessageCollection->MessageCount = $iMessageRealCount; $oMessageCollection->MessageCount = $iMessageRealCount;
$oMessageCollection->MessageUnseenCount = $iMessageUnseenCount; $oMessageCollection->MessageUnseenCount = $iMessageUnseenCount;
$oMessageCollection->MessageResultCount = 0 === \strlen($sSearch) $oMessageCollection->MessageResultCount = \count($aUids);
? $iMessageCount : \count($aIndexOrUids);
if (0 < \count($aIndexOrUids)) if (0 < \count($aUids))
{ {
$iOffset = (0 > $iOffset) ? 0 : $iOffset; $iOffset = (0 > $iOffset) ? 0 : $iOffset;
$aRequestIndexOrUids = \array_slice($aIndexOrUids, $iOffset, $iLimit); $aRequestUids = \array_slice($aUids, $iOffset, $iLimit);
$this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestIndexOrUids, $bIndexAsUid); $this->MessageListByRequestIndexOrUids($oMessageCollection, $aRequestUids, true);
} }
} }
} }
if (!$bSearch && $bUseThreadSortIfSupported && 0 < \count($aThreads)) if ($bUseThreadSortIfSupported && 0 === $iThreadUid && \is_array($mAllThreads) && 0 < \count($mAllThreads))
{ {
$oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($aThreads) { $oMessageCollection->ForeachList(function (/* @var $oMessage \MailSo\Mail\Message */ $oMessage) use ($mAllThreads) {
$iUid = $oMessage->Uid(); $iUid = $oMessage->Uid();
if (isset($aThreads[$iUid]) && \is_array($aThreads[$iUid]) && 0 < \count($aThreads[$iUid])) if (isset($mAllThreads[$iUid]) && \is_array($mAllThreads[$iUid]) && 0 < \count($mAllThreads[$iUid]))
{ {
$aSubThreads = $aThreads[$iUid]; $aSubThreads = $mAllThreads[$iUid];
\array_unshift($aSubThreads, $iUid); \array_unshift($aSubThreads, $iUid);
$oMessage->SetThreads(\array_map('trim', $aSubThreads)); $oMessage->SetThreads(\array_map('trim', $aSubThreads));
@ -2263,7 +2381,10 @@ class MailClient
{ {
$aSubscribedFolders = $this->oImapClient->FolderSubscribeList($sParent, $sListPattern); $aSubscribedFolders = $this->oImapClient->FolderSubscribeList($sParent, $sListPattern);
} }
catch (\Exception $oException) {} catch (\Exception $oException)
{
unset($oException);
}
} }
$aImapSubscribedFoldersHelper = null; $aImapSubscribedFoldersHelper = null;

View file

@ -62,6 +62,11 @@ class MessageCollection extends \MailSo\Base\Collection
*/ */
public $UidNext; public $UidNext;
/**
* @var string
*/
public $ThreadUid;
/** /**
* @var array * @var array
*/ */
@ -108,6 +113,7 @@ class MessageCollection extends \MailSo\Base\Collection
$this->Limit = 0; $this->Limit = 0;
$this->Search = ''; $this->Search = '';
$this->UidNext = ''; $this->UidNext = '';
$this->ThreadUid = '';
$this->NewMessages = array(); $this->NewMessages = array();
$this->LastCollapsedThreadUids = array(); $this->LastCollapsedThreadUids = array();

View file

@ -407,8 +407,9 @@ class Actions
{ {
if (false !== \strpos($sLine, '{date:')) if (false !== \strpos($sLine, '{date:'))
{ {
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) { $iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
return \gmdate($aMatch[1]); $sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) use ($iTimeOffset) {
return \MailSo\Log\Logger::DateHelper($aMatch[1], $iTimeOffset);
}, $sLine); }, $sLine);
$sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine); $sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine);
@ -939,6 +940,8 @@ class Actions
if (!!$this->Config()->Get('logs', 'enable', false)) if (!!$this->Config()->Get('logs', 'enable', false))
{ {
$iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
$this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true)); $this->oLogger->SetShowSecter(!$this->Config()->Get('logs', 'hide_passwords', true));
$sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName( $sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName(
@ -956,6 +959,7 @@ class Actions
->WriteOnErrorOnly($this->Config()->Get('logs', 'write_on_error_only', false)) ->WriteOnErrorOnly($this->Config()->Get('logs', 'write_on_error_only', false))
->WriteOnPhpErrorOnly($this->Config()->Get('logs', 'write_on_php_error_only', false)) ->WriteOnPhpErrorOnly($this->Config()->Get('logs', 'write_on_php_error_only', false))
->WriteOnTimeoutOnly($this->Config()->Get('logs', 'write_on_timeout_only', 0)) ->WriteOnTimeoutOnly($this->Config()->Get('logs', 'write_on_timeout_only', 0))
->SetTimeOffset($iTimeOffset)
); );
if (!$this->Config()->Get('debug', 'enable', false)) if (!$this->Config()->Get('debug', 'enable', false))
@ -967,7 +971,10 @@ class Actions
$oHttp = $this->Http(); $oHttp = $this->Http();
$this->oLogger->Write('[DATE:'.\gmdate('d.m.y').'][RL:'.APP_VERSION.'][PHP:'.PHP_VERSION.'][IP:'. $this->oLogger->Write('[DATE:'.\MailSo\Log\Logger::DateHelper('d.m.y', $iTimeOffset).
(0 !== $iTimeOffset ? '][OFFSET:'.(0 < $iTimeOffset ? '+' : '-').
\str_pad((string) \abs($iTimeOffset), 2, '0', STR_PAD_LEFT) : '').
'][RL:'.APP_VERSION.'][PHP:'.PHP_VERSION.'][IP:'.
$oHttp->GetClientIp($this->Config()->Get('labs', 'http_client_ip_check_proxy', false)).'][PID:'. $oHttp->GetClientIp($this->Config()->Get('labs', 'http_client_ip_check_proxy', false)).'][PID:'.
(\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['. (\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['.
$oHttp->GetServer('SERVER_SOFTWARE', '~').']['. $oHttp->GetServer('SERVER_SOFTWARE', '~').']['.
@ -1315,12 +1322,34 @@ class Actions
'AllowAppendMessage' => (bool) $oConfig->Get('labs', 'allow_message_append', false), 'AllowAppendMessage' => (bool) $oConfig->Get('labs', 'allow_message_append', false),
'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true), 'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true),
'PremType' => $this->PremType(), 'PremType' => $this->PremType(),
'ZipSupported' => !!\class_exists('ZipArchive'),
'Admin' => array(), 'Admin' => array(),
'Capa' => array(), 'Capa' => array(),
'AttahcmentsActions' => array(),
'Plugins' => array() 'Plugins' => array()
); );
if ($oConfig->Get('capa', 'attachments_actions', false))
{
if (!!\class_exists('ZipArchive'))
{
$aResult['AttahcmentsActions'][] = 'zip';
}
if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('\\OCP\\Files'))
{
$aResult['AttahcmentsActions'][] = 'owncloud';
}
if ($oConfig->Get('social', 'dropbox_enable', false) && 0 < \strlen(\trim($oConfig->Get('social', 'dropbox_api_key', ''))))
{
$aResult['AttahcmentsActions'][] = 'dropbox';
}
}
$aResult['AllowDropboxSocial'] = (bool) $oConfig->Get('social', 'dropbox_enable', false);
$aResult['DropboxApiKey'] = \trim($oConfig->Get('social', 'dropbox_api_key', ''));
if ($aResult['UseRsaEncryption'] && if ($aResult['UseRsaEncryption'] &&
\file_exists(APP_PRIVATE_DATA.'rsa/public') && \file_exists(APP_PRIVATE_DATA.'rsa/private')) \file_exists(APP_PRIVATE_DATA.'rsa/public') && \file_exists(APP_PRIVATE_DATA.'rsa/private'))
{ {
@ -1709,8 +1738,9 @@ class Actions
$aResult['LanguageAdmin'] = $this->ValidateLanguage($sLanguageAdmin, '', true); $aResult['LanguageAdmin'] = $this->ValidateLanguage($sLanguageAdmin, '', true);
$aResult['UserLanguageRaw'] = $this->detectUserLanguage(); $aResult['UserLanguageRaw'] = $this->detectUserLanguage();
$aResult['UserLanguage'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], $aResult['Language'], false);
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], $aResult['LanguageAdmin'], true); $aResult['UserLanguage'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], '', false, true, true);
$aResult['UserLanguageAdmin'] = $this->ValidateLanguage($aResult['UserLanguageRaw'], '', true, true, true);
$aResult['LangLink'] = './?/Lang/0/'.($bAdmin ? 'Admin' : 'App').'/'. $aResult['LangLink'] = './?/Lang/0/'.($bAdmin ? 'Admin' : 'App').'/'.
($bAdmin ? $aResult['LanguageAdmin'] : $aResult['Language']).'/'.$sStaticCache.'/'; ($bAdmin ? $aResult['LanguageAdmin'] : $aResult['Language']).'/'.$sStaticCache.'/';
@ -1755,14 +1785,30 @@ class Actions
return \preg_replace('/[^a-zA-Z0-9]+/', '-', $this->getUserLanguageFromHeader()); return \preg_replace('/[^a-zA-Z0-9]+/', '-', $this->getUserLanguageFromHeader());
} }
/**
* @param int $iWait = 1
* @param int $iDelay = 1
*/
private function requestSleep($iWait = 1, $iDelay = 1)
{
if (0 < $iDelay && 0 < $iWait)
{
if ($iWait > \time() - APP_START_TIME)
{
\sleep($iDelay);
}
}
}
private function loginErrorDelay() private function loginErrorDelay()
{ {
$iDelay = (int) $this->Config()->Get('labs', 'login_fault_delay', 0); $iDelay = (int) $this->Config()->Get('labs', 'login_fault_delay', 0);
if (0 < $iDelay) if (0 < $iDelay)
{ {
\sleep($iDelay); $this->requestSleep(1, $iDelay);
} }
} }
/** /**
* @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Model\Account $oAccount
*/ */
@ -2630,6 +2676,209 @@ class Actions
return $this->FalseResponse(__FUNCTION__); return $this->FalseResponse(__FUNCTION__);
} }
/**
* @return array
*
* @throws \MailSo\Base\Exceptions\Exception
*/
public function DoAttachmentsActions()
{
if (!$this->Config()->Get('capa', 'attachments_actions', false))
{
return $this->FalseResponse(__FUNCTION__);
}
$oAccount = $this->initMailClientConnection();
$sAction = $this->GetActionParam('Do', '');
$aHashes = $this->GetActionParam('Hashes', null);
$mResult = false;
$bError = false;
$aData = false;
if (\is_array($aHashes) && 0 < \count($aHashes))
{
$aData = array();
foreach ($aHashes as $sZipHash)
{
$aResult = $this->getMimeFileByHash($oAccount, $sZipHash);
if (\is_array($aResult) && !empty($aResult['FileHash']))
{
$aData[] = $aResult;
}
else
{
$bError = true;
break;
}
}
}
$oFilesProvider = $this->FilesProvider();
if (!empty($sAction) && !$bError && \is_array($aData) && 0 < \count($aData) &&
$oFilesProvider && $oFilesProvider->IsActive())
{
$bError = false;
switch (\strtolower($sAction))
{
case 'zip':
if (\class_exists('ZipArchive'))
{
$sZipHash = \MailSo\Base\Utils::Md5Rand();
$sZipFileName = $oFilesProvider->GenerateLocalFullFileName($oAccount, $sZipHash);
if (!empty($sZipFileName))
{
$oZip = new \ZipArchive();
$oZip->open($sZipFileName, \ZIPARCHIVE::OVERWRITE);
$oZip->setArchiveComment('RainLoop/'.APP_VERSION);
foreach ($aData as $aItem)
{
$sFileName = (string) (isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat');
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
if (!empty($sFileHash))
{
$sFullFileNameHash = $oFilesProvider->GetFileName($oAccount, $sFileHash);
if (!$oZip->addFile($sFullFileNameHash, $sFileName))
{
$bError = true;
}
}
}
if (!$bError)
{
$bError = !$oZip->close();
}
else
{
$oZip->close();
}
}
foreach ($aData as $aItem)
{
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
if (!empty($sFileHash))
{
$oFilesProvider->Clear($oAccount, $sFileHash);
}
}
if (!$bError)
{
$mResult = array(
'Files' => array(array(
'FileName' => 'attachments.zip',
'Hash' => \RainLoop\Utils::EncodeKeyValues(array(
'V' => APP_VERSION,
'Account' => $oAccount ? \md5($oAccount->Hash()) : '',
'FileName' => 'attachments.zip',
'MimeType' => 'application/zip',
'FileHash' => $sZipHash
))
))
);
}
}
break;
case 'owncloud':
$mResult = false;
if (\RainLoop\Utils::IsOwnCloudLoggedIn() && \class_exists('\\OCP\\Files'))
{
$sSaveFolder = $this->Config()->Get('labs', 'owncloud_save_folder', '');
if (!empty($sSaveFolder))
{
$sSaveFolder = 'Attachments';
}
$oFiles = \OCP\Files::getStorage('files');
if ($oFilesProvider && $oFiles && $oFilesProvider->IsActive() &&
\method_exists($oFiles, 'file_put_contents'))
{
if (!$oFiles->is_dir($sSaveFolder))
{
$oFiles->mkdir($sSaveFolder);
}
$mResult = true;
foreach ($aData as $aItem)
{
$sSavedFileName = isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat';
$sSavedFileHash = !empty($aItem['FileHash']) ? $aItem['FileHash'] : '';
if (!empty($sSavedFileHash))
{
$fFile = $oFilesProvider->GetFile($oAccount, $sSavedFileHash, 'rb');
if (\is_resource($fFile))
{
$sSavedFileNameFull = \MailSo\Base\Utils::SmartFileExists($sSaveFolder.'/'.$sSavedFileName, function ($sPath) use ($oFiles) {
return $oFiles->file_exists($sPath);
});
if (!$oFiles->file_put_contents($sSavedFileNameFull, $fFile))
{
$mResult = false;
}
if (\is_resource($fFile))
{
@\fclose($fFile);
}
}
}
}
}
}
foreach ($aData as $aItem)
{
$sFileHash = (string) (isset($aItem['FileHash']) ? $aItem['FileHash'] : '');
if (!empty($sFileHash))
{
$oFilesProvider->Clear($oAccount, $sFileHash);
}
}
break;
case 'dropbox':
$mResult = array(
'ShortLife' => '_'.$this->GetShortLifeSpecAuthToken(),
'Url' => \preg_replace('/\?(.*)$/', '', $this->Http()->GetFullUrl()),
'Files' => array()
);
foreach ($aData as $aItem)
{
$mResult['Files'][] = array(
'FileName' => isset($aItem['FileName']) ? $aItem['FileName'] : 'file.dat',
'Hash' => \RainLoop\Utils::EncodeKeyValues($aItem)
);
}
break;
}
}
else
{
$bError = true;
}
$this->requestSleep();
return $this->DefaultResponse(__FUNCTION__, $bError ? false : $mResult);
}
/** /**
* @return array * @return array
* *
@ -3531,7 +3780,6 @@ class Actions
*/ */
public function DoAdminLicensing() public function DoAdminLicensing()
{ {
$iStart = \time();
$this->IsAdminLoggined(); $this->IsAdminLoggined();
$bForce = '1' === (string) $this->GetActionParam('Force', '0'); $bForce = '1' === (string) $this->GetActionParam('Force', '0');
@ -3543,10 +3791,7 @@ class Actions
{ {
$sValue = $this->licenseHelper($bForce); $sValue = $this->licenseHelper($bForce);
if ($iStart === \time()) $this->requestSleep();
{
\sleep(1);
}
$iExpired = 0; $iExpired = 0;
if ($this->licenseParser($sValue, $iExpired)) if ($this->licenseParser($sValue, $iExpired))
@ -3583,7 +3828,6 @@ class Actions
*/ */
public function DoAdminLicensingActivate() public function DoAdminLicensingActivate()
{ {
$iStart = \time();
$this->IsAdminLoggined(); $this->IsAdminLoggined();
$sDomain = (string) $this->GetActionParam('Domain', ''); $sDomain = (string) $this->GetActionParam('Domain', '');
@ -3599,7 +3843,6 @@ class Actions
$oHttp = \MailSo\Base\Http::SingletonInstance(); $oHttp = \MailSo\Base\Http::SingletonInstance();
\sleep(1);
$sValue = $oHttp->GetUrlAsString(APP_API_PATH.'activate/'.\urlencode($sDomain).'/'.\urlencode($sKey), $sValue = $oHttp->GetUrlAsString(APP_API_PATH.'activate/'.\urlencode($sDomain).'/'.\urlencode($sKey),
'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 10, 'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 10,
$this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''), $this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''),
@ -3611,10 +3854,7 @@ class Actions
$sValue = ''; $sValue = '';
} }
if ($iStart + 2 > \time()) $this->requestSleep();
{
\sleep(1);
}
$aMatch = array(); $aMatch = array();
if ('OK' === $sValue) if ('OK' === $sValue)
@ -5240,12 +5480,12 @@ class Actions
{ {
$this->initMailClientConnection(); $this->initMailClientConnection();
$sFolderNameInUtf = $this->GetActionParam('Folder', ''); $sFolderFullNameRaw = $this->GetActionParam('Folder', '');
$bSubscribe = '1' === (string) $this->GetActionParam('Subscribe', '0'); $bSubscribe = '1' === (string) $this->GetActionParam('Subscribe', '0');
try try
{ {
$this->MailClient()->FolderSubscribe($sFolderNameInUtf, !!$bSubscribe); $this->MailClient()->FolderSubscribe($sFolderFullNameRaw, !!$bSubscribe);
} }
catch (\Exception $oException) catch (\Exception $oException)
{ {
@ -5262,6 +5502,51 @@ class Actions
return $this->TrueResponse(__FUNCTION__); return $this->TrueResponse(__FUNCTION__);
} }
/**
* @return array
*/
public function DoFolderCheckable()
{
$oAccount = $this->getAccountFromToken();
$sFolderFullNameRaw = $this->GetActionParam('Folder', '');
$bCheckable = '1' === (string) $this->GetActionParam('Checkable', '0');
$oSettingsLocal = $this->SettingsProvider(true)->Load($oAccount);
$sCheckableFolder = $oSettingsLocal->GetConf('CheckableFolder', '[]');
$aCheckableFolder = @\json_decode($sCheckableFolder);
if (!\is_array($aCheckableFolder))
{
$aCheckableFolder = array();
}
if ($bCheckable)
{
$aCheckableFolder[] = $sFolderFullNameRaw;
}
else
{
$aCheckableFolderNew = array();
foreach ($aCheckableFolder as $sFolder)
{
if ($sFolder !== $sFolderFullNameRaw)
{
$aCheckableFolderNew[] = $sFolder;
}
}
$aCheckableFolder = $aCheckableFolderNew;
}
$aCheckableFolder = \array_unique($aCheckableFolder);
$oSettingsLocal->SetConf('CheckableFolder', @\json_encode($aCheckableFolder));
return $this->DefaultResponse(__FUNCTION__,
$this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal));
}
/** /**
* @return array * @return array
* *
@ -5443,45 +5728,6 @@ class Actions
return $this->DefaultResponse(__FUNCTION__, $aResult); 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 * @return array
* *
@ -5498,11 +5744,12 @@ class Actions
$sSearch = ''; $sSearch = '';
$sUidNext = ''; $sUidNext = '';
$bUseThreads = false; $bUseThreads = false;
$sThreadUid = '';
$sRawKey = $this->GetActionParam('RawKey', ''); $sRawKey = $this->GetActionParam('RawKey', '');
$aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9); $aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9);
if (\is_array($aValues) && 9 === \count($aValues)) if (\is_array($aValues) && 7 < \count($aValues))
{ {
$sFolder =(string) $aValues[0]; $sFolder =(string) $aValues[0];
$iOffset = (int) $aValues[1]; $iOffset = (int) $aValues[1];
@ -5511,6 +5758,11 @@ class Actions
$sUidNext = (string) $aValues[6]; $sUidNext = (string) $aValues[6];
$bUseThreads = (bool) $aValues[7]; $bUseThreads = (bool) $aValues[7];
if ($bUseThreads)
{
$sThreadUid = isset($aValues[8]) ? (string) $aValues[8] : '';
}
$this->verifyCacheByKey($sRawKey); $this->verifyCacheByKey($sRawKey);
} }
else else
@ -5521,6 +5773,11 @@ class Actions
$sSearch = $this->GetActionParam('Search', ''); $sSearch = $this->GetActionParam('Search', '');
$sUidNext = $this->GetActionParam('UidNext', ''); $sUidNext = $this->GetActionParam('UidNext', '');
$bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0'); $bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0');
if ($bUseThreads)
{
$sThreadUid = (string) $this->GetActionParam('ThreadUid', '');
}
} }
if (0 === strlen($sFolder)) if (0 === strlen($sFolder))
@ -5542,7 +5799,7 @@ class Actions
$this->cacherForUids(), $this->cacherForUids(),
!!$this->Config()->Get('labs', 'use_imap_sort', false), !!$this->Config()->Get('labs', 'use_imap_sort', false),
$bUseThreads, $bUseThreads,
!!$this->Config()->Get('labs', 'use_imap_esearch_esort', false) $sThreadUid
); );
} }
catch (\Exception $oException) catch (\Exception $oException)
@ -5558,40 +5815,6 @@ class Actions
return $this->DefaultResponse(__FUNCTION__, $oMessageList); return $this->DefaultResponse(__FUNCTION__, $oMessageList);
} }
/**
* @return array
*
* @throws \MailSo\Base\Exceptions\Exception
*/
public function DoMessageListSimple()
{
// \sleep(2);
// throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList);
$sFolder = $this->GetActionParam('Folder', '');
$aUids = $this->GetActionParam('Uids', null);
if (0 === \strlen($sFolder) || !\is_array($aUids))
{
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument);
}
$this->initMailClientConnection();
$aMessageList = array();
try
{
$aMessageList = $this->MailClient()->MessageListSimple($sFolder, $aUids);
}
catch (\Exception $oException)
{
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::CantGetMessageList, $oException);
}
return $this->DefaultResponse(__FUNCTION__, $aMessageList);
}
/** /**
* @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Model\Account $oAccount
* @param bool $bWithDraftInfo = true * @param bool $bWithDraftInfo = true
@ -6565,7 +6788,8 @@ class Actions
)) ))
); );
\sleep(1); $this->requestSleep();
return $this->DefaultResponse(__FUNCTION__, return $this->DefaultResponse(__FUNCTION__,
$this->getTwoFactorInfo($oAccount)); $this->getTwoFactorInfo($oAccount));
} }
@ -6649,7 +6873,8 @@ class Actions
// $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode) // $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode)
// )); // ));
\sleep(1); $this->requestSleep();
return $this->DefaultResponse(__FUNCTION__, return $this->DefaultResponse(__FUNCTION__,
$this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode)); $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode));
} }
@ -7713,10 +7938,10 @@ class Actions
$iUid = (int) (isset($aValues['Uid']) ? $aValues['Uid'] : 0); $iUid = (int) (isset($aValues['Uid']) ? $aValues['Uid'] : 0);
$sMimeIndex = (string) (isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''); $sMimeIndex = (string) (isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : '');
header('Content-Type: text/plain', true); \header('Content-Type: text/plain', true);
return $this->MailClient()->MessageMimeStream(function ($rResource) { return $this->MailClient()->MessageMimeStream(function ($rResource) {
if (is_resource($rResource)) if (\is_resource($rResource))
{ {
\MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource); \MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource);
} }
@ -7944,6 +8169,57 @@ class Actions
} }
} }
/**
* @param \RainLoop\Model\Account $oAccount
* @param string $sHash
*
* @return array
*/
private function getMimeFileByHash($oAccount, $sHash)
{
$aValues = $this->getDecodedRawKeyValue($sHash);
$sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : '';
$iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0;
$sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : '';
$sContentTypeIn = (string) isset($aValues['MimeType']) ? $aValues['MimeType'] : '';
$sFileNameIn = (string) isset($aValues['FileName']) ? $aValues['FileName'] : '';
$oFileProvider = $this->FilesProvider();
$sResultHash = '';
$mResult = $this->MailClient()->MessageMimeStream(function($rResource, $sContentType, $sFileName, $sMimeIndex = '')
use ($oAccount, $oFileProvider, $sFileNameIn, $sContentTypeIn, &$sResultHash) {
if ($oAccount && \is_resource($rResource))
{
$sHash = \MailSo\Base\Utils::Md5Rand($sFileNameIn.'~'.$sContentTypeIn);
$rTempResource = $oFileProvider->GetFile($oAccount, $sHash, 'wb+');
if (@\is_resource($rTempResource))
{
if (false !== \MailSo\Base\Utils::MultipleStreamWriter($rResource, array($rTempResource)))
{
$sResultHash = $sHash;
}
@\fclose($rTempResource);
}
}
}, $sFolder, $iUid, true, $sMimeIndex);
$aValues['FileHash'] = '';
if ($mResult)
{
$aValues['FileHash'] = $sResultHash;
}
return $aValues;
}
/** /**
* @param bool $bDownload * @param bool $bDownload
* @param bool $bThumbnail = false * @param bool $bThumbnail = false
@ -7956,16 +8232,51 @@ class Actions
$aValues = $this->getDecodedRawKeyValue($sRawKey); $aValues = $this->getDecodedRawKeyValue($sRawKey);
$sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : ''; $sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : '';
$iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0; $iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0;
$sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : ''; $sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : '';
$sContentTypeIn = (string) isset($aValues['MimeType']) ? $aValues['MimeType'] : ''; $sContentTypeIn = isset($aValues['MimeType']) ? (string) $aValues['MimeType'] : '';
$sFileNameIn = (string) isset($aValues['FileName']) ? $aValues['FileName'] : ''; $sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : '';
$sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : '';
if (!empty($sFileHashIn))
{
$this->verifyCacheByKey($sRawKey);
$oAccount = $this->getAccountFromToken();
$sContentTypeOut = empty($sContentTypeIn) ?
\MailSo\Base\Utils::MimeContentType($sFileNameIn) : $sContentTypeIn;
$sFileNameOut = $this->MainClearFileName($sFileNameIn, $sContentTypeIn, $sMimeIndex);
$rResource = $this->FilesProvider()->GetFile($oAccount, $sFileHashIn);
if (\is_resource($rResource))
{
$sFileNameOut = \MailSo\Base\Utils::ConvertEncoding(
$sFileNameOut, \MailSo\Base\Enumerations\Charset::UTF_8,
\MailSo\Base\Enumerations\Charset::CP858);
\header('Content-Type: '.$sContentTypeOut);
\header('Content-Disposition: attachment; '.
\trim(\MailSo\Base\Utils::EncodeHeaderUtf8AttributeValue('filename', $sFileNameOut)), true);
\header('Accept-Ranges: none', true);
\header('Content-Transfer-Encoding: binary');
\MailSo\Base\Utils::FpassthruWithTimeLimitReset($rResource);
return true;
}
return false;
}
else
{
if (!empty($sFolder) && 0 < $iUid) if (!empty($sFolder) && 0 < $iUid)
{ {
$this->verifyCacheByKey($sRawKey); $this->verifyCacheByKey($sRawKey);
} }
}
$oAccount = $this->initMailClientConnection(); $oAccount = $this->initMailClientConnection();
@ -8358,10 +8669,11 @@ class Actions
* @param string $sDefault = '' * @param string $sDefault = ''
* @param bool $bAdmin = false * @param bool $bAdmin = false
* @param bool $bSearchShortName = false * @param bool $bSearchShortName = false
* @param bool $bAllowEmptyResult = false
* *
* @return string * @return string
*/ */
public function ValidateLanguage($sLanguage, $sDefault = '', $bAdmin = false, $bSearchShortName = false) public function ValidateLanguage($sLanguage, $sDefault = '', $bAdmin = false, $bSearchShortName = false, $bAllowEmptyResult = false)
{ {
$sResult = ''; $sResult = '';
$aLang = $this->GetLanguages($bAdmin); $aLang = $this->GetLanguages($bAdmin);
@ -8387,7 +8699,7 @@ class Actions
$sResult = $sDefault; $sResult = $sDefault;
} }
if (empty($sResult)) if (empty($sResult) && !$bAllowEmptyResult)
{ {
$sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en'); $sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en');
$sResult = \in_array($sResult, $aLang) ? $sResult : 'en'; $sResult = \in_array($sResult, $aLang) ? $sResult : 'en';
@ -9039,17 +9351,49 @@ class Actions
if (\is_object($mResponse)) if (\is_object($mResponse))
{ {
$bHook = true; $bHook = true;
$self = $this;
$sClassName = \get_class($mResponse); $sClassName = \get_class($mResponse);
$bHasSimpleJsonFunc = \method_exists($mResponse, 'ToSimpleJSON'); $bHasSimpleJsonFunc = \method_exists($mResponse, 'ToSimpleJSON');
$bThumb = $this->GetCapa(false, \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS); $bThumb = $this->GetCapa(false, \RainLoop\Enumerations\Capa::ATTACHMENT_THUMBNAILS);
$oAccountCache = null;
$fGetAccount = function () use ($self, &$oAccountCache) {
if (null === $oAccountCache)
{
$oAccount = $self->getAccountFromToken(false);
$oAccountCache = $oAccount;
}
return $oAccountCache;
};
$aCheckableFoldersCache = null;
$fGetCheckableFolder = function () use ($self, &$aCheckableFoldersCache) {
if (null === $aCheckableFoldersCache)
{
$oAccount = $self->getAccountFromToken(false);
$oSettingsLocal = $self->SettingsProvider(true)->Load($oAccount);
$sCheckable = $oSettingsLocal->GetConf('CheckableFolder', '[]');
$aCheckable = @\json_decode($sCheckable);
if (!\is_array($aCheckable))
{
$aCheckable = array();
}
$aCheckableFoldersCache = $aCheckable;
}
return $aCheckableFoldersCache;
};
if ($bHasSimpleJsonFunc) if ($bHasSimpleJsonFunc)
{ {
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), $mResponse->ToSimpleJSON(true)); $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), $mResponse->ToSimpleJSON(true));
} }
else if ('MailSo\Mail\Message' === $sClassName) else if ('MailSo\Mail\Message' === $sClassName)
{ {
$oAccount = $this->getAccountFromToken(false); $oAccount = call_user_func($fGetAccount);
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array(
'Folder' => $mResponse->Folder(), 'Folder' => $mResponse->Folder(),
@ -9079,28 +9423,7 @@ class Actions
$iAttachmentsCount = $oAttachments ? $oAttachments->Count() : 0; $iAttachmentsCount = $oAttachments ? $oAttachments->Count() : 0;
$mResult['HasAttachments'] = 0 < $iAttachmentsCount; $mResult['HasAttachments'] = 0 < $iAttachmentsCount;
$mResult['AttachmentsMainType'] = ''; $mResult['AttachmentsSpecData'] = $mResult['HasAttachments'] ? $oAttachments->SpecData() : array();
if (0 < $iAttachmentsCount)
{
switch (true)
{
case $iAttachmentsCount === $oAttachments->ImageCount():
$mResult['AttachmentsMainType'] = 'image';
break;
case $iAttachmentsCount === $oAttachments->ArchiveCount():
$mResult['AttachmentsMainType'] = 'archive';
break;
case $iAttachmentsCount === $oAttachments->PdfCount():
$mResult['AttachmentsMainType'] = 'pdf';
break;
case $iAttachmentsCount === $oAttachments->DocCount():
$mResult['AttachmentsMainType'] = 'doc';
break;
case $iAttachmentsCount === $oAttachments->CertificateCount():
$mResult['AttachmentsMainType'] = 'certificate';
break;
}
}
$sSubject = $mResult['Subject']; $sSubject = $mResult['Subject'];
$mResult['Hash'] = \md5($mResult['Folder'].$mResult['Uid']); $mResult['Hash'] = \md5($mResult['Folder'].$mResult['Uid']);
@ -9345,6 +9668,12 @@ class Actions
); );
} }
$aCheckableFolder = \call_user_func($fGetCheckableFolder);
if (!\is_array($aCheckableFolder))
{
$aCheckableFolder = array();
}
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array( $mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array(
'Name' => $mResponse->Name(), 'Name' => $mResponse->Name(),
'FullName' => $mResponse->FullName(), 'FullName' => $mResponse->FullName(),
@ -9356,6 +9685,7 @@ class Actions
'IsExists' => $mResponse->IsExists(), 'IsExists' => $mResponse->IsExists(),
'IsSelectable' => $mResponse->IsSelectable(), 'IsSelectable' => $mResponse->IsSelectable(),
'Flags' => $mResponse->FlagsLowerCase(), 'Flags' => $mResponse->FlagsLowerCase(),
'Checkable' => \in_array($mResponse->FullNameRaw(), $aCheckableFolder),
'Extended' => $aExtended, 'Extended' => $aExtended,
'SubFolders' => $this->responseObject($mResponse->SubFolders(), $sParent, $aParameters) 'SubFolders' => $this->responseObject($mResponse->SubFolders(), $sParent, $aParameters)
)); ));
@ -9369,8 +9699,8 @@ class Actions
'Folder' => $mResponse->FolderName, 'Folder' => $mResponse->FolderName,
'FolderHash' => $mResponse->FolderHash, 'FolderHash' => $mResponse->FolderHash,
'UidNext' => $mResponse->UidNext, 'UidNext' => $mResponse->UidNext,
'ThreadUid' => $mResponse->ThreadUid,
'NewMessages' => $this->responseObject($mResponse->NewMessages), 'NewMessages' => $this->responseObject($mResponse->NewMessages),
// 'LastCollapsedThreadUids' => $mResponse->LastCollapsedThreadUids,
'Offset' => $mResponse->Offset, 'Offset' => $mResponse->Offset,
'Limit' => $mResponse->Limit, 'Limit' => $mResponse->Limit,
'Search' => $mResponse->Search 'Search' => $mResponse->Search

View file

@ -133,7 +133,8 @@ class Application extends \RainLoop\Config\AbstractConfig
'capa' => array( 'capa' => array(
'filters' => array(true), 'filters' => array(true),
'templates' => array(true) 'templates' => array(true),
'attachments_actions' => array(true)
), ),
'login' => array( 'login' => array(
@ -186,6 +187,8 @@ Values:
'hide_passwords' => array(true, 'Required for development purposes only. 'hide_passwords' => array(true, 'Required for development purposes only.
Disabling this option is not recommended.'), Disabling this option is not recommended.'),
'time_offset' => array(0),
'filename' => array('log-{date:Y-m-d}.txt', 'filename' => array('log-{date:Y-m-d}.txt',
'Log filename. 'Log filename.
For security reasons, some characters are removed from filename. For security reasons, some characters are removed from filename.
@ -282,7 +285,6 @@ Enables caching in the system'),
'use_app_debug_js' => array(false), 'use_app_debug_js' => array(false),
'use_app_debug_css' => array(false), 'use_app_debug_css' => array(false),
'use_imap_sort' => array(true), 'use_imap_sort' => array(true),
'use_imap_esearch_esort' => array(true),
'use_imap_force_selection' => array(false), 'use_imap_force_selection' => array(false),
'use_imap_list_subscribe' => array(true), 'use_imap_list_subscribe' => array(true),
'use_imap_thread' => array(true), 'use_imap_thread' => array(true),
@ -301,6 +303,7 @@ Enables caching in the system'),
'smtp_show_server_errors' => array(false), 'smtp_show_server_errors' => array(false),
'sieve_allow_raw_script' => array(false), 'sieve_allow_raw_script' => array(false),
'sieve_utf8_folder_name' => array(true), 'sieve_utf8_folder_name' => array(true),
'owncloud_save_folder' => array('Attachments'),
'curl_proxy' => array(''), 'curl_proxy' => array(''),
'curl_proxy_auth' => array(''), 'curl_proxy_auth' => array(''),
'in_iframe' => array(false), 'in_iframe' => array(false),

View file

@ -362,7 +362,7 @@ class Account extends \RainLoop\Account // for backward compatibility
\RainLoop\Utils::GetShortToken(), // 7 \RainLoop\Utils::GetShortToken(), // 7
$this->sProxyAuthUser, // 8 $this->sProxyAuthUser, // 8
$this->sProxyAuthPassword, // 9 $this->sProxyAuthPassword, // 9
0 // 10 0 // 10 // timelife
)); ));
} }

View file

@ -116,6 +116,17 @@ class Files extends \RainLoop\Providers\AbstractProvider
$this->oDriver->CloseAllOpenedFiles() : false; $this->oDriver->CloseAllOpenedFiles() : false;
} }
/**
* @param \RainLoop\Model\Account $oAccount
* @param string $sKey
*
* @return string
*/
public function GenerateLocalFullFileName($oAccount, $sKey)
{
return $this->oDriver ? $this->oDriver->GenerateLocalFullFileName($oAccount, $sKey) : '';
}
/** /**
* @return bool * @return bool
*/ */

View file

@ -25,6 +25,17 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
$this->sDataPath = \rtrim(\trim($sStoragePath), '\\/'); $this->sDataPath = \rtrim(\trim($sStoragePath), '\\/');
} }
/**
* @param \RainLoop\Model\Account $oAccount
* @param string $sKey
*
* @return string
*/
public function GenerateLocalFullFileName($oAccount, $sKey)
{
return $this->generateFullFileName($oAccount, $sKey, true);
}
/** /**
* @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Model\Account $oAccount
* @param string $sKey * @param string $sKey
@ -37,7 +48,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
$bResult = false; $bResult = false;
if ($rSource) if ($rSource)
{ {
$rOpenOutput = @\fopen($this->generateFileName($oAccount, $sKey, true), 'w+b'); $rOpenOutput = @\fopen($this->generateFullFileName($oAccount, $sKey, true), 'w+b');
if ($rOpenOutput) if ($rOpenOutput)
{ {
$bResult = (false !== \MailSo\Base\Utils::MultipleStreamWriter($rSource, array($rOpenOutput))); $bResult = (false !== \MailSo\Base\Utils::MultipleStreamWriter($rSource, array($rOpenOutput)));
@ -57,7 +68,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
public function MoveUploadedFile($oAccount, $sKey, $sSource) public function MoveUploadedFile($oAccount, $sKey, $sSource)
{ {
return @\move_uploaded_file($sSource, return @\move_uploaded_file($sSource,
$this->generateFileName($oAccount, $sKey, true)); $this->generateFullFileName($oAccount, $sKey, true));
} }
/** /**
@ -72,7 +83,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
$mResult = false; $mResult = false;
$bCreate = !!\preg_match('/[wac]/', $sOpenMode); $bCreate = !!\preg_match('/[wac]/', $sOpenMode);
$sFileName = $this->generateFileName($oAccount, $sKey, $bCreate); $sFileName = $this->generateFullFileName($oAccount, $sKey, $bCreate);
if ($bCreate || \file_exists($sFileName)) if ($bCreate || \file_exists($sFileName))
{ {
$mResult = @\fopen($sFileName, $sOpenMode); $mResult = @\fopen($sFileName, $sOpenMode);
@ -95,7 +106,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
public function GetFileName($oAccount, $sKey) public function GetFileName($oAccount, $sKey)
{ {
$mResult = false; $mResult = false;
$sFileName = $this->generateFileName($oAccount, $sKey); $sFileName = $this->generateFullFileName($oAccount, $sKey);
if (\file_exists($sFileName)) if (\file_exists($sFileName))
{ {
$mResult = $sFileName; $mResult = $sFileName;
@ -113,7 +124,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
public function Clear($oAccount, $sKey) public function Clear($oAccount, $sKey)
{ {
$mResult = true; $mResult = true;
$sFileName = $this->generateFileName($oAccount, $sKey); $sFileName = $this->generateFullFileName($oAccount, $sKey);
if (\file_exists($sFileName)) if (\file_exists($sFileName))
{ {
if (isset($this->aResources[$sFileName]) && \is_resource($this->aResources[$sFileName])) if (isset($this->aResources[$sFileName]) && \is_resource($this->aResources[$sFileName]))
@ -136,7 +147,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
public function FileSize($oAccount, $sKey) public function FileSize($oAccount, $sKey)
{ {
$mResult = false; $mResult = false;
$sFileName = $this->generateFileName($oAccount, $sKey); $sFileName = $this->generateFullFileName($oAccount, $sKey);
if (\file_exists($sFileName)) if (\file_exists($sFileName))
{ {
$mResult = \filesize($sFileName); $mResult = \filesize($sFileName);
@ -153,7 +164,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
*/ */
public function FileExists($oAccount, $sKey) public function FileExists($oAccount, $sKey)
{ {
return @\file_exists($this->generateFileName($oAccount, $sKey)); return @\file_exists($this->generateFullFileName($oAccount, $sKey));
} }
/** /**
@ -198,7 +209,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
* *
* @return string * @return string
*/ */
private function generateFileName($oAccount, $sKey, $bMkDir = false) private function generateFullFileName($oAccount, $sKey, $bMkDir = false)
{ {
$sEmail = $sSubEmail = ''; $sEmail = $sSubEmail = '';
if ($oAccount instanceof \RainLoop\Model\Account) if ($oAccount instanceof \RainLoop\Model\Account)

View file

@ -4,6 +4,14 @@ namespace RainLoop\Providers\Files;
interface IFiles interface IFiles
{ {
/**
* @param \RainLoop\Model\Account $oAccount
* @param string $sKey
*
* @return string
*/
public function GenerateLocalFullFileName($oAccount, $sKey);
/** /**
* @param \RainLoop\Model\Account $oAccount * @param \RainLoop\Model\Account $oAccount
* @param string $sKey * @param string $sKey

View file

@ -18,13 +18,14 @@ class OwnCloudSuggestions implements \RainLoop\Providers\Suggestions\ISuggestion
*/ */
public function Process($oAccount, $sQuery, $iLimit = 20) public function Process($oAccount, $sQuery, $iLimit = 20)
{ {
$iInputLimit = $iLimit;
$aResult = array(); $aResult = array();
$sQuery = \trim($sQuery);
try try
{ {
if (!$oAccount || !\RainLoop\Utils::IsOwnCloud() || if ('' === $sQuery || !$oAccount || !\RainLoop\Utils::IsOwnCloudLoggedIn() ||
!\class_exists('\\OCP\\Contacts') || !\OCP\Contacts::isEnabled() || !\class_exists('\\OCP\\Contacts') || !\OCP\Contacts::isEnabled()
!\class_exists('\\OCP\\User') || !\OCP\User::isLoggedIn()
) )
{ {
return $aResult; return $aResult;
@ -33,7 +34,7 @@ class OwnCloudSuggestions implements \RainLoop\Providers\Suggestions\ISuggestion
$aSearchResult = \OCP\Contacts::search($sQuery, array('FN', 'EMAIL')); $aSearchResult = \OCP\Contacts::search($sQuery, array('FN', 'EMAIL'));
//$this->oLogger->WriteDump($aSearchResult); //$this->oLogger->WriteDump($aSearchResult);
$aPreResult = array(); $aHashes = array();
if (\is_array($aSearchResult) && 0 < \count($aSearchResult)) if (\is_array($aSearchResult) && 0 < \count($aSearchResult))
{ {
foreach ($aSearchResult as $aContact) foreach ($aSearchResult as $aContact)
@ -54,35 +55,23 @@ class OwnCloudSuggestions implements \RainLoop\Providers\Suggestions\ISuggestion
$mEmails = array($mEmails); $mEmails = array($mEmails);
} }
if (!isset($aPreResult[$sUid]))
{
$aPreResult[$sUid] = array();
}
foreach ($mEmails as $sEmail) foreach ($mEmails as $sEmail)
{ {
$sEmail = \trim($sEmail); $sHash = '"'.$sFullName.'" <'.$sEmail.'>';
if (!empty($sEmail)) if (!isset($aHashes[$sHash]))
{ {
$aHashes[$sHash] = true;
$aResult[] = array($sEmail, $sFullName);
$iLimit--; $iLimit--;
$aPreResult[$sUid][] = array($sEmail, $sFullName);
} }
} }
} }
} }
$aPreResult = \array_values($aPreResult); $aResult = \array_slice($aResult, 0, $iInputLimit);
// $this->oLogger->WriteDump($aPreResult);
foreach ($aPreResult as $aData)
{
foreach ($aData as $aSubData)
{
$aResult[] = $aSubData;
}
}
} }
unset($aSearchResult, $aPreResult); unset($aSearchResult, $aHashes);
} }
catch (\Exception $oException) catch (\Exception $oException)
{ {

View file

@ -315,6 +315,13 @@ class Utils
return isset($_ENV['RAINLOOP_OWNCLOUD']) && $_ENV['RAINLOOP_OWNCLOUD'] && return isset($_ENV['RAINLOOP_OWNCLOUD']) && $_ENV['RAINLOOP_OWNCLOUD'] &&
\class_exists('\\OC'); \class_exists('\\OC');
} }
/**
* @return bool
*/
public static function IsOwnCloudLoggedIn()
{
return self::IsOwnCloud() && \class_exists('\\OCP\\User') && \OCP\User::isLoggedIn();
}
/** /**
* @return string * @return string

View file

@ -66,7 +66,7 @@
.thm-border-radius(@login-border-radius); .thm-border-radius(@login-border-radius);
.thm-box-shadow(@login-box-shadow); .thm-box-shadow(@login-box-shadow);
.legend, .checkboxSignMe, .checkboxAdditionalCodeSignMe, .g-ui-link, .social-button, .language-button { .legend, .e-checkbox-icon, .g-ui-link, .social-button, .language-button {
color: @login-color !important; color: @login-color !important;
} }
} }
@ -85,7 +85,7 @@
color: @languages-color; color: @languages-color;
.flag-name { .flag-name {
color: @languages-color; color: @languages-color;
border-bottom: 1px dashed @languages-color; border-color: @languages-color;
} }
} }

View file

@ -1,14 +1,16 @@
<div class="b-login-content"> <div class="b-login-content">
<div class="loginFormWrapper"> <div class="loginFormWrapper" data-bind="css: {'afterLoginHide': formHidden}">
<center> <center>
<div class="alert alertError" data-bind="visible: '' !== submitError()"> <div class="alert alertError" data-bind="visible: '' !== submitError()">
<button type="button" class="close" data-bind="click: function () { submitError('') }">&times;</button> <button type="button" class="close" data-bind="click: function () { submitError('') }">&times;</button>
<span data-bind="text: submitError"></span> <span data-bind="text: submitError"></span>
</div> </div>
<form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm"> <form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError}">
<div class="controls" data-bind="css: {'error': loginError}"> <div class="controls" data-bind="css: {'error': loginError, 'animated': loginErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="text" class="input-block-level inputLogin checkAutocomplete" name="RainLoopAdminLogin" id="RainLoopAdminLogin" <input type="text" class="input-block-level inputLogin checkAutocomplete"
name="RainLoopAdminLogin" id="RainLoopAdminLogin"
style="padding-right: 35px;"
autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_LOGIN" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_LOGIN"
data-bind="textInput: login, hasFocus: loginFocus" /> data-bind="textInput: login, hasFocus: loginFocus" />
<span class="add-on"> <span class="add-on">
@ -16,9 +18,11 @@
</span> </span>
</div> </div>
</div> </div>
<div class="controls" data-bind="css: {'error': passwordError}"> <div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="password" class="input-block-level inputPassword checkAutocomplete" placeholder="Password" name="RainLoopAdminPassword" id="RainLoopAdminPassword" <input type="password" class="input-block-level inputPassword checkAutocomplete"
placeholder="Password" name="RainLoopAdminPassword" id="RainLoopAdminPassword"
style="padding-right: 35px;"
autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" autocorrect="off" autocapitalize="off" spellcheck="false" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD"
data-bind="textInput: password" /> data-bind="textInput: password" />
<span class="add-on"> <span class="add-on">

View file

@ -144,7 +144,7 @@
</div> </div>
</div> </div>
<div class="tab-pane active" id="branding-welcome-page-section-id"> <div class="tab-pane" id="branding-welcome-page-section-id">
<div class="form-horizontal" data-bind="css: {'disabled-form': !capa()}"> <div class="form-horizontal" data-bind="css: {'disabled-form': !capa()}">
<div class="control-group"> <div class="control-group">

View file

@ -26,9 +26,16 @@
<div class="control-group"> <div class="control-group">
<label class="control-label" data-i18n="TAB_CONTACTS/LABEL_STORAGE_TYPE"></label> <label class="control-label" data-i18n="TAB_CONTACTS/LABEL_STORAGE_TYPE"></label>
<div class="controls"> <div class="controls">
<select data-bind="options: contactsTypesOptions, value: mainContactsType, <div data-bind="component: {
optionsText: 'name', optionsValue: 'id', optionsAfterRender: defautOptionsAfterRender, saveTrigger: contactsTypeTrigger"></select> name: 'Select',
<div data-bind="saveTrigger: contactsTypeTrigger"></div> params: {
options: contactsTypesOptions,
value: mainContactsType,
trigger: contactsTypeTrigger,
optionsText: 'name',
optionsValue: 'id'
}
}"></div>
</div> </div>
</div> </div>
<br /> <br />

View file

@ -15,7 +15,7 @@
<span class="flag-wrapper"> <span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + language()" style=""></span> <span data-bind="css: 'flag flag-' + language()" style=""></span>
</span> </span>
<span class="flag-name" data-bind="text: languageFullName, click: selectLanguage"></span> <span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, keypress: selectLanguage"></span>
&nbsp;&nbsp; &nbsp;&nbsp;
<div data-bind="component: { <div data-bind="component: {
name: 'SaveTrigger', name: 'SaveTrigger',
@ -31,7 +31,7 @@
<span class="flag-wrapper"> <span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + languageAdmin()" style=""></span> <span data-bind="css: 'flag flag-' + languageAdmin()" style=""></span>
</span> </span>
<span class="flag-name" data-bind="text: languageAdminFullName, click: selectLanguageAdmin"></span> <span class="flag-name" tabindex="0" data-bind="text: languageAdminFullName, click: selectLanguageAdmin, keypress: selectLanguageAdmin"></span>
&nbsp;&nbsp; &nbsp;&nbsp;
<div data-bind="component: { <div data-bind="component: {
name: 'SaveTrigger', name: 'SaveTrigger',

View file

@ -42,12 +42,12 @@
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_CHECK_ALL"></td><td>Ctrl + A, Cmd + A</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_CHECK_ALL"></td><td>Ctrl + A, Cmd + A</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_ARCHIVE"></td><td>Z</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_ARCHIVE"></td><td>Z</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_DELETE"></td><td>Delete, Shift + Delete, #</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_DELETE"></td><td>Delete, Shift + Delete, #</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_OPEN_THREAD"></td><td>T</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_MOVE"></td><td>M</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_MOVE"></td><td>M</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_READ"></td><td>Q</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_READ"></td><td>Q</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_UNREAD"></td><td>U</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_UNREAD"></td><td>U</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_IMPORTANT"></td><td>I</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_IMPORTANT"></td><td>I</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_SEARCH"></td><td>/</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_SEARCH"></td><td>/</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_CANCEL_SEARCH"></td><td>Esc</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_FULLSCREEN_ENTER"></td><td>Enter</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_FULLSCREEN_ENTER"></td><td>Enter</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_VIEW_MESSAGE_ENTER"></td><td>Enter</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_VIEW_MESSAGE_ENTER"></td><td>Enter</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_SWITCH_TO_MESSAGE"></td><td>&rarr;, Tab</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_SWITCH_TO_MESSAGE"></td><td>&rarr;, Tab</td></tr>
@ -60,14 +60,12 @@
<table class="table table-striped table-bordered"> <table class="table table-striped table-bordered">
<tbody> <tbody>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_FULLSCREEN_TOGGLE">Toggle fullscreen mode</td><td>Enter</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_FULLSCREEN_TOGGLE"></td><td>Enter</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_BLOCKQUOTES_TOGGLE">Toggle message blockquotes</td><td>B</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_BLOCKQUOTES_TOGGLE"></td><td>B</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_THREAD_NEXT">Next message in thread</td><td>Ctrl + &larr;, Cmd + &larr;</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_PRINT"></td><td>Ctrl + P, Cmd + P</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_THREAD_PREV">Previous message in thread</td><td>Ctrl + &rarr;, Cmd + &rarr;</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_EXIT_FULLSCREEN"></td><td>Esc</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_PRINT">Print</td><td>Ctrl + P, Cmd + P</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_CLOSE_MESSAGE"></td><td>Esc</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_EXIT_FULLSCREEN">Exit fullscreen mode</td><td>Esc</td></tr> <tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_SWITCH_TO_LIST"></td><td>Tab, Shift + Tab, Esc</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_CLOSE_MESSAGE">Close message (No preview pane layout)</td><td>Esc</td></tr>
<tr><td class="i18n" data-i18n="SHORTCUTS_HELP/LABEL_SWITCH_TO_LIST">Switch focus back to message list</td><td>Tab, Shift + Tab, Esc</td></tr>
</tbody> </tbody>
</table> </table>

View file

@ -1,10 +1,10 @@
<label class="e-component e-checkbox inline" data-bind="click: click, css: { 'disabled': disable() || !enable() }"> <span class="e-component e-checkbox inline" tabindex="0" data-bind="click: click, keypress: keypress, css: { 'disabled': disable() || !enable() }">
<i data-bind="css: value() ? <i role="checkbox" class="e-checkbox-icon" data-bind="css: value() ?
(inverted ? 'icon-checkbox-unchecked' : 'icon-checkbox-checked') : (inverted ? 'icon-checkbox-unchecked' : 'icon-checkbox-checked') :
(inverted ? 'icon-checkbox-checked' : 'icon-checkbox-unchecked') (inverted ? 'icon-checkbox-checked' : 'icon-checkbox-unchecked')
"></i> "></i>
<!-- ko if: labeled --> <!-- ko if: labeled -->
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="sub-label i18n" data-bind="attr: {'data-i18n': label}"></span> <span class="sub-label i18n" data-bind="attr: {'data-i18n': label}, css: {'i18n-animation': labelAnimated}"></span>
<!-- /ko --> <!-- /ko -->
</label> </span>

View file

@ -2,6 +2,6 @@
<input type="checkbox" data-bind="checked: value, disable: disable() || !enable()" style="margin: 0 0 1px 0;" /> <input type="checkbox" data-bind="checked: value, disable: disable() || !enable()" style="margin: 0 0 1px 0;" />
<!-- ko if: labeled --> <!-- ko if: labeled -->
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="sub-label i18n" data-bind="attr: {'data-i18n': label}"></span> <span class="sub-label i18n" data-bind="attr: {'data-i18n': label}, css: {'i18n-animation': labelAnimated}"></span>
<!-- /ko --> <!-- /ko -->
</label> </label>

View file

@ -1,10 +1,10 @@
<label class="e-component e-checkbox material-design inline" data-bind="click: click, css: { 'disabled': disable() || !enable() }"> <span class="e-component e-checkbox material-design inline" tabindex="0" data-bind="click: click, keypress: keypress, css: { 'disabled': disable() || !enable() }">
<div class="sub-checkbox-container"> <div class="sub-checkbox-container" role="checkbox">
<div class="sub-checkbox" data-bind="css: {'checked': (value() && !inverted) || (!value() && inverted), <div class="sub-checkbox" data-bind="css: {'checked': (value() && !inverted) || (!value() && inverted),
'unchecked': (!value() && !inverted) || (value() && inverted), 'unchecked': (!value() && !inverted) || (value() && inverted),
'box': animationBox, 'checkmark': animationCheckmark}"></div> 'box': animationBox, 'checkmark': animationCheckmark}"></div>
</div> </div>
<!-- ko if: labeled --> <!-- ko if: labeled -->
<span class="sub-label i18n" data-bind="attr: {'data-i18n': label}"></span> <span class="sub-label i18n" data-bind="attr: {'data-i18n': label}, css: {'i18n-animation': labelAnimated}"></span>
<!-- /ko --> <!-- /ko -->
</label> </span>

View file

@ -1,6 +1,6 @@
<div class="e-component e-radio" data-bind="foreach: values"> <div class="e-component e-radio" data-bind="foreach: values">
<label data-bind="click: $parent.click, css: {'inline': $parent.inline}"> <label tabindex="0" data-bind="click: $parent.click, css: {'inline': $parent.inline}">
<i data-bind="css: $parent.value() === value ? 'icon-radio-checked' : 'icon-radio-unchecked'"></i> <i role="radio" data-bind="css: $parent.value() === value ? 'icon-radio-checked' : 'icon-radio-unchecked'"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="sub-label i18n" data-bind="attr: {'data-i18n': label}"></span> <span class="sub-label i18n" data-bind="attr: {'data-i18n': label}"></span>
</label> </label>

View file

@ -1,3 +1,4 @@
<div class="e-component e-select inline">
<!-- ko if: preLabeled --> <!-- ko if: preLabeled -->
<span class="i18n" data-bind="attr: {'data-i18n': preLabel}"></span> <span class="i18n" data-bind="attr: {'data-i18n': preLabel}"></span>
&nbsp;&nbsp; &nbsp;&nbsp;
@ -16,3 +17,4 @@
params: { value: trigger } params: { value: trigger }
}"></div> }"></div>
<!-- /ko --> <!-- /ko -->
</div>

View file

@ -1,5 +1,5 @@
<div class="b-login-content"> <div class="b-login-content">
<div class="loginFormWrapper"> <div class="loginFormWrapper" data-bind="css: {'afterLoginHide': formHidden}">
<center> <center>
{{INCLUDE/BeforeLogo/PLACE}} {{INCLUDE/BeforeLogo/PLACE}}
<!-- ko if: logoImg --> <!-- ko if: logoImg -->
@ -19,12 +19,13 @@
<span data-bind="text: submitErrorAddidional"></span> <span data-bind="text: submitErrorAddidional"></span>
</div> </div>
</div> </div>
<form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm"> <form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError}">
{{INCLUDE/TopControlGroup/PLACE}} {{INCLUDE/TopControlGroup/PLACE}}
<div class="controls" data-bind="css: {'error': emailError}"> <div class="controls" data-bind="css: {'error': emailError, 'animated': emailErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="email" class="i18n input-block-level inputEmail checkAutocomplete" <input type="email" class="i18n input-block-level inputEmail checkAutocomplete"
name="RainLoopEmail" id="RainLoopEmail" name="RainLoopEmail" id="RainLoopEmail"
style="padding-right: 35px;"
autocorrect="off" autocapitalize="off" spellcheck="false" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: email, hasFocus: emailFocus" data-i18n="[placeholder]LOGIN/LABEL_EMAIL" /> data-bind="textInput: email, hasFocus: emailFocus" data-i18n="[placeholder]LOGIN/LABEL_EMAIL" />
<span class="add-on"> <span class="add-on">
@ -32,9 +33,11 @@
</span> </span>
</div> </div>
</div> </div>
<div class="controls" data-bind="css: {'error': passwordError}"> <div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="password" class="i18n input-block-level inputPassword checkAutocomplete" name="RainLoopPassword" id="RainLoopPassword" <input type="password" class="i18n input-block-level inputPassword checkAutocomplete"
name="RainLoopPassword" id="RainLoopPassword"
style="padding-right: 35px;"
autocorrect="off" autocapitalize="off" spellcheck="false" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: password, hasFocus: passwordFocus" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" /> data-bind="textInput: password, hasFocus: passwordFocus" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" />
<span class="add-on"> <span class="add-on">
@ -42,9 +45,11 @@
</span> </span>
</div> </div>
</div> </div>
<div class="controls" data-bind="visible: additionalCode.visibility(), css: {'error': additionalCode.error}"> <div class="controls" data-bind="visible: additionalCode.visibility(), css: {'error': additionalCode.error, 'animated': additionalCode.errorAnimation}">
<div class="input-append"> <div class="input-append">
<input type="text" class="i18n input-block-level inputAdditionalCode" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" <input type="text" class="i18n input-block-level inputAdditionalCode"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
style="padding-right: 35px;"
data-bind="textInput: additionalCode, hasFocus: additionalCode.focused" data-i18n="[placeholder]LOGIN/LABEL_VERIFICATION_CODE" /> data-bind="textInput: additionalCode, hasFocus: additionalCode.focused" data-i18n="[placeholder]LOGIN/LABEL_VERIFICATION_CODE" />
<span class="add-on"> <span class="add-on">
<i class="icon-key"></i> <i class="icon-key"></i>
@ -52,11 +57,13 @@
</div> </div>
</div> </div>
<div class="controls" data-bind="visible: additionalCode.visibility()"> <div class="controls" data-bind="visible: additionalCode.visibility()">
<label class="additionalCodeSignMeLabel" data-bind="click: function () { additionalCodeSignMe(!additionalCodeSignMe()); }"> <div class="additionalCodeSignMeLabel" data-bind="component: {
<i data-bind="css: additionalCodeSignMe() ? 'checkboxAdditionalCodeSignMe icon-checkbox-checked' : 'checkboxAdditionalCodeSignMe icon-checkbox-unchecked'"></i> name: 'CheckboxSimple',
&nbsp;&nbsp; params: {
<span class="i18n" data-i18n="LOGIN/LABEL_DONT_ASK_VERIFICATION_CODE"></span> label: 'LOGIN/LABEL_DONT_ASK_VERIFICATION_CODE',
</label> value: additionalCodeSignMe
}
}"></div>
</div> </div>
{{INCLUDE/BottomControlGroup/PLACE}} {{INCLUDE/BottomControlGroup/PLACE}}
<div class="controls"> <div class="controls">
@ -80,11 +87,21 @@
<i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i> <i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i>
</a> </a>
</div> </div>
<label class="signMeLabel inline" data-bind="click: function () { signMe(!signMe()); }, visible: signMeVisibility">
<div class="signMeLabel" data-bind="component: {
name: 'CheckboxSimple',
params: {
label: 'LOGIN/LABEL_SIGN_ME',
labelAnimated: true,
value: signMe
}
}"></div>
<!-- <label class="signMeLabel inline" data-bind="click: function () { signMe(!signMe()); }, visible: signMeVisibility">
<i data-bind="css: signMe() ? 'checkboxSignMe icon-checkbox-checked' : 'checkboxSignMe icon-checkbox-unchecked'"></i> <i data-bind="css: signMe() ? 'checkboxSignMe icon-checkbox-checked' : 'checkboxSignMe icon-checkbox-unchecked'"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n i18n-animation" data-i18n="LOGIN/LABEL_SIGN_ME"></span> <span class="i18n i18n-animation" data-i18n="LOGIN/LABEL_SIGN_ME"></span>
</label> </label>-->
</div> </div>
<div class="controls clearfix" data-bind="visible: '' !== forgotPasswordLinkUrl || '' !== registrationLinkUrl"> <div class="controls clearfix" data-bind="visible: '' !== forgotPasswordLinkUrl || '' !== registrationLinkUrl">
<div class="forgot-link thm-forgot pull-left" data-bind="visible: '' !== forgotPasswordLinkUrl" style="text-align: center"> <div class="forgot-link thm-forgot pull-left" data-bind="visible: '' !== forgotPasswordLinkUrl" style="text-align: center">
@ -105,11 +122,12 @@
<label class="flag-selector"> <label class="flag-selector">
<i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i> <i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="flag-name" data-bind="text: languageFullName, click: selectLanguage"></span> <span class="flag-name" tabindex="0" data-bind="text: languageFullName, click: selectLanguage, keypress: selectLanguage, onTab: selectLanguageOnTab"></span>
</label> </label>
</div> </div>
{{INCLUDE/BottomFooter/PLACE}} {{INCLUDE/BottomFooter/PLACE}}
</center> </center>
</div> </div>
<a href="#" onclick="return false;"></a>
<div class="loginAfter"></div> <div class="loginAfter"></div>
</div> </div>

View file

@ -137,19 +137,21 @@
<div class="b-content" data-bind="nano: true, initDom: dragOverBodyArea"> <div class="b-content" data-bind="nano: true, initDom: dragOverBodyArea">
<div class="content g-scrollbox"> <div class="content g-scrollbox">
<div class="content-wrapper"> <div class="content-wrapper">
<div class="listThreadUidDesc" data-bind="visible: '' !== messageListEndThreadUid(), click: cancelThreadUid">
<i class="icon-left" data-bind="click: cancelThreadUid"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="MESSAGE_LIST/BACK_TO_MESSAGE_LIST"></span>
</div>
<div class="listSearchDesc" data-bind="visible: '' !== messageListSearchDesc()"> <div class="listSearchDesc" data-bind="visible: '' !== messageListSearchDesc()">
<a class="close-custom" data-bind="click: cancelSearch">&times;</a> <a class="close-custom" data-bind="click: cancelSearch">&times;</a>
<!-- <a class="btn btn-small pull-right searchCancelButton" data-bind="click: cancelSearch"> <span data-bind="text: messageListSearchDesc"></span>
<i class="icon-remove"></i>
</a>-->
<span data-bind="text: messageListSearchDesc"></span>:
</div> </div>
<div class="listDragOver" data-bind="css: {'viewAppendArea': dragOver() && '' === messageListError() && !popupVisibility(), 'dragOverEnter': dragOverEnter }, initDom: dragOverArea"> <div class="listDragOver" data-bind="css: {'viewAppendArea': dragOver() && '' === messageListError() && !popupVisibility(), 'dragOverEnter': dragOverEnter }, initDom: dragOverArea">
<i class="icon-down e-icon"></i> <i class="icon-down e-icon"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-i18n="MESSAGE_LIST/PUT_MESSAGE_HERE"></span> <span class="i18n" data-i18n="MESSAGE_LIST/PUT_MESSAGE_HERE"></span>
</div> </div>
<div class="listClear" data-bind="visible: '' === messageListSearchDesc() && '' === messageListError() && 0 < messageList().length && (isSpamFolder() || isTrashFolder())"> <div class="listClear" data-bind="visible: clearListIsVisible()">
<span class="g-ui-link i18n" data-i18n="MESSAGE_LIST/BUTTON_EMPTY_FOLDER" data-bind="command: clearCommand"></span> <span class="g-ui-link i18n" data-i18n="MESSAGE_LIST/BUTTON_EMPTY_FOLDER" data-bind="command: clearCommand"></span>
</div> </div>
<div class="listError" data-bind="visible: !dragOver() && '' !== messageListError()"> <div class="listError" data-bind="visible: !dragOver() && '' !== messageListError()">

View file

@ -18,15 +18,20 @@
<div class="dateParent actionHandle dragHandle"> <div class="dateParent actionHandle dragHandle">
<span class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></span> <span class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></span>
</div> </div>
<div class="threadsParent" data-bind="visible: 1 < threadsLen()">
<span class="threads-len">
<span class="threads-len-data">
<span data-bind="text: threadsLen"></span>
<i class="icon-right-mini"></i>
</span>
</span>
</div>
<div class="checkedParent"> <div class="checkedParent">
<i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i> <i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i>
</div> </div>
<div class="senderParent actionHandle dragHandle"> <div class="senderParent actionHandle dragHandle">
<span class="replyFlag"><i class="icon-reply"></i>&nbsp;</span> <span class="replyFlag"><i class="icon-reply"></i>&nbsp;</span>
<span class="forwardFlag"><i class="icon-forward"></i>&nbsp;</span> <span class="forwardFlag"><i class="icon-forward"></i>&nbsp;</span>
<span class="threads-len" data-bind="visible: 1 < threadsLen()">
<span class="threads-len-data" data-bind="text: threadsLen"></span>
</span>
<span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span> <span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span>
&nbsp; &nbsp;
</div> </div>

View file

@ -7,6 +7,14 @@
<div class="dateParent actionHandle dragHandle"> <div class="dateParent actionHandle dragHandle">
<span class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></span> <span class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></span>
</div> </div>
<div class="threadsParent" data-bind="visible: 1 < threadsLen()">
<span class="threads-len">
<span class="threads-len-data">
<span data-bind="text: threadsLen"></span>
<i class="icon-right-mini"></i>
</span>
</span>
</div>
<div class="checkedParent"> <div class="checkedParent">
<i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i> <i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i>
</div> </div>
@ -24,9 +32,6 @@
<div class="senderParent actionHandle dragHandle"> <div class="senderParent actionHandle dragHandle">
<span class="replyFlag"><i class="icon-reply"></i>&nbsp;</span> <span class="replyFlag"><i class="icon-reply"></i>&nbsp;</span>
<span class="forwardFlag"><i class="icon-forward"></i>&nbsp;</span> <span class="forwardFlag"><i class="icon-forward"></i>&nbsp;</span>
<span class="threads-len" data-bind="visible: 1 < threadsLen()">
<span class="threads-len-data" data-bind="text: threadsLen"></span>
</span>
<span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span> <span class="sender" data-bind="text: senderEmailsString, attr: {'title': senderClearEmailsString}"></span>
&nbsp; &nbsp;
</div> </div>

View file

@ -188,64 +188,6 @@
</a> </a>
</div> </div>
<div class="btn-group thread-controls pull-right g-ui-user-select-none" style="margin-right: 5px"
data-bind="visible: viewThreadsControlVisibility">
<a class="btn last btn-thin pull-right" data-tooltip-join="bottom"
data-bind="command: threadBackCommand, tooltip: 'MESSAGE/BUTTON_THREAD_PREV'">
<i class="icon-right-middle"></i>
</a>
<div class="btn-group pull-right" data-bind="registrateBootstrapDropdown: true, openDropdownTrigger: threadsDropdownTrigger">
<a class="btn btn-thin btn-dark-disabled-border dropdown-toggle" id="thread-list-view-dropdown-id" data-toggle="dropdown"
href="#" tabindex="-1" data-tooltip-join="bottom"
style="margin-left: -1px; margin-right: -1px;"
data-bind="command: threadListCommand, tooltip: 'MESSAGE/BUTTON_THREAD_LIST'"
>
<i class="icon-list"
data-bind="css: {'icon-list': !messageListOfThreadsLoading(), 'icon-spinner animated': messageListOfThreadsLoading()}"></i>
</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; padding: 0"
data-bind="css: {'hide-more': !viewThreadMessages.showMore() }"
>
<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 real-msg" role="presentation" data-bind="css: {'selected': selected, 'more-that': $parent.viewThreadMessages.limit < $index() }">
<a class="e-link menuitem" href="#" tabindex="-1" onclick="return false;">
<span class="thread-date pull-right" data-moment-format="SHORT" data-bind="moment: timestamp"></span>
<div style="text-overflow: ellipsis; overflow: hidden;">
<span class="thread-from" data-bind="text: fromAsString"></span>
&nbsp;
</div>
<div style="text-overflow: ellipsis; overflow: hidden;">
<span class="thread-subject" data-bind="text: subject"></span>
&nbsp;
</div>
</a>
</li>
</div>
<div data-bind="visible: !viewThreadMessages.showMore() && !messageListOfThreadsLoading()">
<li class="e-item thread-list-message" role="presentation">
<a class="e-link menuitem more-threads" href="#" tabindex="-1"
onclick="return false"
data-i18n="MESSAGE/BUTTON_THREAD_MORE"></a>
</li>
</div>
</ul>
</div>
<a class="btn first btn-thin pull-right" data-tooltip-join="bottom"
data-bind="command: threadForwardCommand, tooltip: 'MESSAGE/BUTTON_THREAD_NEXT'">
<i class="icon-left-middle"></i>
</a>
</div>
</nobr> </nobr>
</div> </div>
@ -258,9 +200,6 @@
<span class="flagParent"> <span class="flagParent">
<i class="icon-star-empty flagOff" data-bind="css: {'icon-star flagOn': viewIsFlagged, 'icon-star-empty flagOff': !viewIsFlagged()}"></i> <i class="icon-star-empty flagOff" data-bind="css: {'icon-star flagOn': viewIsFlagged, 'icon-star-empty flagOff': !viewIsFlagged()}"></i>
</span> </span>
<span class="thread-counter" data-bind="visible: viewThreadsControlVisibility"
style="font-weight: normal; margin-right: 5px">(<span
data-bind="text: viewThreadsControlDesc"></span>)</span>
<b style="color: red; margin-right: 5px" data-bind="visible: viewIsImportant">!</b> <b style="color: red; margin-right: 5px" data-bind="visible: viewIsImportant">!</b>
<span class="subject" data-bind="text: viewSubject, title: viewSubject"></span> <span class="subject" data-bind="text: viewSubject, title: viewSubject"></span>
<span class="i18n emptySubjectText" data-i18n="MESSAGE/EMPTY_SUBJECT_TEXT"></span> <span class="i18n emptySubjectText" data-i18n="MESSAGE/EMPTY_SUBJECT_TEXT"></span>
@ -380,7 +319,8 @@
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
style="margin-bottom: 0" data-i18n="[placeholder]MESSAGE/PGP_PASSWORD_INPUT_PLACEHOLDER" data-bind="value: viewPgpPassword, onEnter: function() { decryptPgpEncryptedMessage(message()); }" /> style="margin-bottom: 0" data-i18n="[placeholder]MESSAGE/PGP_PASSWORD_INPUT_PLACEHOLDER" data-bind="value: viewPgpPassword, onEnter: function() { decryptPgpEncryptedMessage(message()); }" />
</div> </div>
<div class="attachmentsPlace" data-bind="visible: message() && message().hasVisibleAttachments(), css: {'selection-mode' : showAttachmnetControls}"> <div class="attachmentsPlace" data-bind="visible: message() && message().hasVisibleAttachments(),
css: {'selection-mode' : showAttachmnetControls, 'unselectedAttachmentsError': highlightUnselectedAttachments}">
<ul class="attachmentList" data-bind="foreach: message() ? message().attachments() : []"> <ul class="attachmentList" data-bind="foreach: message() ? message().attachments() : []">
<li class="attachmentItem clearfix" draggable="true" data-tooltip-join="top" <li class="attachmentItem clearfix" draggable="true" data-tooltip-join="top"
data-bind="visible: !isLinked, event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }, css: {'checked': checked}"> data-bind="visible: !isLinked, event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }, css: {'checked': checked}">
@ -392,7 +332,7 @@
</div> </div>
</div> </div>
<div class="showPreview"> <div class="showPreview">
<a data-bind="css: {'attachmentImagePreview': isImage()}, attr: { 'title': fileName, 'href': linkPreviewMain(), 'data-index': $index }" target="_blank"> <a data-bind="css: {'attachmentImagePreview': isImage(), 'visible': !isLinked}, attr: { 'title': fileName, 'href': linkPreviewMain() }" target="_blank">
<div class="iconMain"> <div class="iconMain">
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i> <i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
<i class="attachmentIconText attachmentMainIconText" data-bind="text: iconText()"></i> <i class="attachmentIconText attachmentMainIconText" data-bind="text: iconText()"></i>
@ -431,13 +371,42 @@
</div> </div>
<div class="attachmentsControls" <div class="attachmentsControls"
data-bind="visible: showAttachmnetControls() && message() && message().hasVisibleAttachments()"> data-bind="visible: showAttachmnetControls() && message() && message().hasVisibleAttachments()">
<i class="icon-file-zip"></i>
<span data-bind="visible: downloadAsZipAllowed">
<i class="icon-remove iconcolor-red" data-bind="visible: downloadAsZipError"></i>
<i class="icon-file-zip" data-bind="visible: !downloadAsZipError(),
css: {'icon-file-zip': !downloadAsZipLoading(), 'icon-spinner animated': downloadAsZipLoading()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="g-ui-link">Download as zip</span> <span class="g-ui-link" data-bind="click: downloadAsZip">
<span class="i18n" data-i18n="MESSAGE/LINK_DOWNLOAD_AS_ZIP"></span>
</span>
</span>
<span data-bind="visible: saveToOwnCloudAllowed">
&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;
<i class="icon-cloud-up"></i> <i class="icon-remove iconcolor-red" data-bind="visible: saveToOwnCloudError"></i>
<i class="icon-ok iconcolor-green" data-bind="visible: saveToOwnCloudSuccess"></i>
<i class="icon-cloud-up" data-bind="visible: !saveToOwnCloudSuccess() && !saveToOwnCloudError(),
css: {'icon-cloud-up': !saveToOwnCloudLoading(), 'icon-spinner animated': saveToOwnCloudLoading()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="g-ui-link">Save to ownCloud</span> <span class="g-ui-link" data-bind="click: saveToOwnCloud">
<span class="i18n" data-i18n="MESSAGE/LINK_SAVE_TO_OWNCLOUD"></span>
</span>
</span>
<span data-bind="visible: saveToDropboxAllowed">
&nbsp;&nbsp;&nbsp;&nbsp;
<i class="icon-remove iconcolor-red" data-bind="visible: saveToDropboxError"></i>
<i class="icon-ok iconcolor-green" data-bind="visible: saveToDropboxSuccess"></i>
<i class="icon-dropbox" data-bind="visible: !saveToDropboxSuccess() && !saveToDropboxError(),
css: {'icon-dropbox': !saveToDropboxLoading(), 'icon-spinner animated': saveToDropboxLoading()}"></i>
&nbsp;&nbsp;
<span class="g-ui-link" data-bind="click: saveToDropbox">
<span class="i18n" data-i18n="MESSAGE/LINK_SAVE_TO_DROPBOX"></span>
</span>
</span>
<!-- // --->
<button type="button" class="close" style="margin-right: 5px;" <button type="button" class="close" style="margin-right: 5px;"
data-bind="click: function () { showAttachmnetControls(false); }">×</button> data-bind="click: function () { showAttachmnetControls(false); }">×</button>
</div> </div>

View file

@ -54,15 +54,15 @@
<span class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE"></span> <span class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE"></span>
</label> </label>
<div class="controls"> <div class="controls">
<select data-bind="value: selectedDateValue"> <div data-bind="component: {
<option value="-1" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_ALL"></option> name: 'Select',
<option value="3" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_3_DAYS"></option> params: {
<option value="7" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_7_DAYS"></option> options: selectedDates,
<option value="30" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_MONTH"></option> value: selectedDateValue,
<option value="90" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_3_MONTHS"></option> optionsText: 'name',
<option value="180" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_6_MONTHS"></option> optionsValue: 'id'
<option value="365" class="i18n" data-i18n="SEARCH/LABEL_ADV_DATE_YEAR"></option> }
</select> }"></div>
</div> </div>
</div> </div>
<div class="control-group"> <div class="control-group">

View file

@ -12,12 +12,12 @@
<span class="i18n" data-i18n="SETTINGS_FOLDERS/DELETING_ASK"></span> <span class="i18n" data-i18n="SETTINGS_FOLDERS/DELETING_ASK"></span>
</a> </a>
</td> </td>
<td> <td class="delete-folder-parent">
<span class="delete-folder" data-bind="visible: canBeDeleted() && !deleteAccess(), click: function (oFolder) { $root.folderForDeletion(oFolder); }"> <span class="delete-folder" data-bind="visible: canBeDeleted() && !deleteAccess(), click: function (oFolder) { $root.folderForDeletion(oFolder); }">
<i class="icon-trash"></i> <i class="icon-trash"></i>
</span> </span>
</td> </td>
<td> <td class="subscribe-folder-parent">
<span class="unsubscribe-folder" data-bind="visible: canBeSubScribed() && !subScribed(), click: function(oFolder) { $root.subscribeFolder(oFolder); }"> <span class="unsubscribe-folder" data-bind="visible: canBeSubScribed() && !subScribed(), click: function(oFolder) { $root.subscribeFolder(oFolder); }">
<i class="icon-eye"></i> <i class="icon-eye"></i>
</span> </span>
@ -25,5 +25,13 @@
<i class="icon-eye"></i> <i class="icon-eye"></i>
</span> </span>
</td> </td>
<td class="check-folder-parent">
<span class="uncheck-folder" data-bind="visible: canBeChecked() && subScribed() && !checkable(), click: function(oFolder) { $root.checkableTrueFolder(oFolder); }">
<i class="icon-check-mark-circle-two"></i>
</span>
<span class="check-folder" data-bind="visible: canBeChecked() && subScribed() && checkable(), click: function(oFolder) { $root.checkableFalseFolder(oFolder); }">
<i class="icon-check-mark-circle-two"></i>
</span>
</td>
</tr> </tr>
<!-- ko template: { name: 'SettingsFolderItem', foreach: subFolders } --><!-- /ko --> <!-- ko template: { name: 'SettingsFolderItem', foreach: subFolders } --><!-- /ko -->

View file

@ -1,4 +1,5 @@
<div class="b-settings-folders g-ui-user-select-none" data-bind="css: { 'ignore-folder-subscribe': !useImapSubscribe }"> <div class="b-settings-folders g-ui-user-select-none row" data-bind="css: { 'ignore-folder-subscribe': !useImapSubscribe }">
<div class="span8">
<div class="form-horizontal"> <div class="form-horizontal">
<div class="legend"> <div class="legend">
<span class="i18n" data-i18n="SETTINGS_FOLDERS/LEGEND_FOLDERS"></span> <span class="i18n" data-i18n="SETTINGS_FOLDERS/LEGEND_FOLDERS"></span>
@ -31,7 +32,14 @@
<col /> <col />
<col style="width: 1%" /> <col style="width: 1%" />
<col style="width: 1%" /> <col style="width: 1%" />
<col style="width: 1%" />
</colgroup> </colgroup>
<tbody data-bind="template: { name: 'SettingsFolderItem', foreach: folderList }"></tbody> <tbody data-bind="template: { name: 'SettingsFolderItem', foreach: folderList }"></tbody>
</table> </table>
</div> </div>
<div class="span4" style="position: relative" data-bind="visible: '' !== folderListHelp()">
<div class="alert alert-info" style="position: fixed">
<span data-bind="text: folderListHelp"></span>
</div>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show more