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
==================
[![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.
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";

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));
// TODO
// DEBUG
// Events.sub({
// 'window.resize': function () {
// window.console.log('window.resize');
@ -284,14 +284,17 @@
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('CheckboxSimple', require('Component/Checkbox'));
}
else
{
// 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('CheckboxSimple', require('Component/Checkbox'));
}
Translator.initOnStartOrLangChange(Translator.initNotificationLanguage, Translator);

View file

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

View file

@ -20,58 +20,90 @@
{
var self = this;
this.obj = this.createNewObject();
this.objForNotification = this.createNewObject();
// this.userMedia = window.navigator.getUserMedia || window.navigator.webkitGetUserMedia ||
// 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.obj && '' !== this.obj.canPlayType('audio/mpeg');
this.player = this.createNewObject();
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();
});
Events.sub('audio.api.stop', function () {
self.stop();
});
Events.sub('audio.api.play', function (sUrl, sName) {
self.playMp3(sUrl, sName);
});
}
}
Audio.prototype.obj = null;
Audio.prototype.objForNotification = null;
Audio.prototype.player = null;
Audio.prototype.notificator = null;
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 ()
{
var obj = window.Audio ? new window.Audio() : null;
if (obj && obj.canPlayType)
var player = window.Audio ? new window.Audio() : null;
if (player && player.canPlayType && player.pause && player.play)
{
obj.preload = 'none';
obj.loop = false;
obj.autoplay = false;
obj.muted = false;
player.preload = 'none';
player.loop = false;
player.autoplay = false;
player.muted = false;
}
return obj;
return player;
};
Audio.prototype.paused = function ()
{
return this.supported ? !!this.obj.paused : true;
return this.supported ? !!this.player.paused : true;
};
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');
@ -79,33 +111,73 @@
Audio.prototype.pause = Audio.prototype.stop;
Audio.prototype.clearName = function (sName, sExt)
{
sExt = sExt || '';
sName = Utils.isUnd(sName) ? '' : Utils.trim(sName);
if (sExt && '.' + sExt === sName.toLowerCase().substr((sExt.length + 1) * -1))
{
sName = Utils.trim(sName.substr(0, sName.length - 4));
}
if ('' === sName)
{
sName = 'audio';
}
return sName;
};
Audio.prototype.playMp3 = function (sUrl, sName)
{
if (this.supported && this.obj.play)
if (this.supported && this.supportedMp3)
{
this.obj.src = sUrl;
this.obj.play();
this.player.src = sUrl;
this.player.play();
sName = Utils.isUnd(sName) ? '' : Utils.trim(sName);
if ('.mp3' === sName.toLowerCase().substr(-4))
{
sName = Utils.trim(sName.substr(0, sName.length - 4));
}
Events.pub('audio.start', [this.clearName(sName, 'mp3'), 'mp3']);
}
};
if ('' === sName)
{
sName = 'audio';
}
Audio.prototype.playOgg = function (sUrl, sName)
{
if (this.supported && this.supportedOgg)
{
this.player.src = sUrl;
this.player.play();
Events.pub('audio.start', [sName]);
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 ()
{
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,12 +302,16 @@
if (aFlags && 0 < aFlags.length)
{
oMessage.unseen(!!aFlags[0]);
oMessage.flagged(!!aFlags[1]);
oMessage.answered(!!aFlags[2]);
oMessage.forwarded(!!aFlags[3]);
oMessage.isReadReceipt(!!aFlags[4]);
oMessage.deletedMark(!!aFlags[5]);
if (!oMessage.__simple_message__)
{
oMessage.unseen(!!aFlags[0]);
oMessage.answered(!!aFlags[2]);
oMessage.forwarded(!!aFlags[3]);
oMessage.isReadReceipt(!!aFlags[4]);
oMessage.deletedMark(!!aFlags[5]);
}
}
if (0 < oMessage.threads().length)

View file

@ -5,6 +5,27 @@
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}
*/

View file

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

View file

@ -31,6 +31,8 @@
this.resize = _.throttle(_.bind(this.resize, this), 100);
this.__inited = false;
this.init();
}
@ -190,7 +192,7 @@
HtmlEditor.prototype.setHtml = function (sHtml, bFocus)
{
if (this.editor)
if (this.editor && this.__inited)
{
this.modeToggle(true);
@ -207,7 +209,7 @@
HtmlEditor.prototype.setPlain = function (sPlain, bFocus)
{
if (this.editor)
if (this.editor && this.__inited)
{
this.modeToggle(false);
if ('plain' === this.editor.mode && this.editor.plugins.plain && this.editor.__plain)
@ -259,7 +261,7 @@
}
oConfig.enterMode = window.CKEDITOR.ENTER_BR;
oConfig.shiftEnterMode = window.CKEDITOR.ENTER_BR;
oConfig.shiftEnterMode = window.CKEDITOR.ENTER_P;
oConfig.language = Globals.oHtmlEditorLangsMap[sLanguage] || 'en';
if (window.CKEDITOR.env)
@ -267,17 +269,6 @@
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;
self.editor = window.CKEDITOR.appendTo(self.$element[0], oConfig);
@ -307,24 +298,29 @@
self.focusTrigger();
});
if (self.fOnReady)
{
self.editor.on('instanceReady', function () {
self.editor.on('instanceReady', function () {
if (self.editor.removeMenuItem)
{
self.editor.removeMenuItem('cut');
self.editor.removeMenuItem('copy');
self.editor.removeMenuItem('paste');
}
if (self.editor.removeMenuItem)
{
self.editor.removeMenuItem('cut');
self.editor.removeMenuItem('copy');
self.editor.removeMenuItem('paste');
}
self.editor.setKeystroke(window.CKEDITOR.CTRL + 65 /* A */, 'selectAll');
self.editor.setKeystroke(window.CKEDITOR.CTRL + 65 /* A */, 'selectAll');
self.__resizable = true;
self.__inited = true;
self.resize();
if (self.fOnReady)
{
self.fOnReady();
self.__resizable = true;
self.resize();
});
}
}
});
}
;

View file

@ -59,47 +59,62 @@
/**
* @param {string} sDownload
* @param {string=} sCustomSpecSuffix
* @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=} sCustomSpecSuffix
* @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=} sCustomSpecSuffix
* @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=} sCustomSpecSuffix
* @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=} sCustomSpecSuffix
* @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 {number=} iPage = 1
* @param {string=} sSearch = ''
* @param {string=} sThreadUid = ''
* @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;
sSearch = Utils.pString(sSearch);
var sResult = this.sBase + 'mailbox/';
var
sResult = this.sBase + 'mailbox/',
iThreadUid = Utils.pInt(sThreadUid)
;
if ('' !== sFolder)
{
sResult += encodeURI(sFolder);
sResult += encodeURI(sFolder) + (0 < iThreadUid ? '~' + iThreadUid : '');
}
if (1 < iPage)
{
sResult = sResult.replace(/[\/]+$/, '');
sResult += '/p' + iPage;
}
if ('' !== sSearch)
{
sResult = sResult.replace(/[\/]+$/, '');

View file

@ -435,7 +435,7 @@
};
/**
* @returns {boolean}
* @return {boolean}
*/
Selector.prototype.autoSelect = function ()
{
@ -452,7 +452,7 @@
/**
* @param {Object} oItem
* @returns {string}
* @return {string}
*/
Selector.prototype.getItemUid = function (oItem)
{
@ -633,13 +633,20 @@
var
iOffset = 20,
aList = this.list(),
oFocused = $(this.sItemFocusedSelector, this.oContentScrollable),
oPos = oFocused.position(),
iVisibleHeight = this.oContentVisible.height(),
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)
{

View file

@ -144,7 +144,7 @@
/**
* @param {string} sMailToUrl
* @param {Function} PopupComposeVoreModel
* @returns {boolean}
* @return {boolean}
*/
Utils.mailToHelper = function (sMailToUrl, PopupComposeVoreModel)
{
@ -598,7 +598,7 @@
* @param {string} sTheme
* @return {string}
*/
Utils.convertThemeName = function (sTheme)
Utils.convertThemeName = _.memoize(function (sTheme)
{
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, ' '));
};
});
/**
* @param {string} sName
@ -822,40 +822,6 @@
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) {
sText = Utils.trim(sText);
sText = '> ' + sText.replace(/\n/gm, '\n> ');
@ -929,7 +895,7 @@
.replace(/&amp;/gi, '&')
;
sText = splitPlainText(Utils.trim(sText));
sText = Utils.splitPlainText(Utils.trim(sText));
iPos = 0;
iLimit = 800;

View file

@ -48,6 +48,7 @@
this.inverted = Utils.isUnd(oParams.inverted) ? false : !!oParams.inverted;
this.labeled = !Utils.isUnd(oParams.label);
this.labelAnimated = !!oParams.labelAnimated;
}
_.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;
module.exports = AbstracCheckbox;

View file

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

79
dev/External/ko.js vendored
View file

@ -24,31 +24,41 @@
'init': function (oElement, fValueAccessor) {
var
fValue = fValueAccessor(),
oEditor = null,
fValue = fValueAccessor(),
fUpdateEditorValue = function () {
if (oEditor)
if (fValue && fValue.__editor)
{
oEditor.setHtmlOrPlain(fValue());
fValue.__editor.setHtmlOrPlain(fValue());
}
},
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')
;
if (fValue)
if (ko.isObservable(fValue) && HtmlEditor)
{
oEditor = new HtmlEditor(oElement, fUpdateKoValue, fUpdateEditorValue, fUpdateKoValue);
fValue.__editor = oEditor;
oEditor = new HtmlEditor(oElement, fUpdateKoValue, fOnReady, fUpdateKoValue);
fValue.__fetchEditorValue = fUpdateKoValue;
fValue.__updateEditorValue = fUpdateEditorValue;
fValue.subscribe(fUpdateEditorValue);
// ko.utils.domNodeDisposal.addDisposeCallback(oElement, function () {
// });
}
}
};
@ -61,6 +71,7 @@
sValue = '',
Translator = null,
$oEl = $(oElement),
fValue = fValueAccessor(),
bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'),
Globals = require('Common/Globals')
;
@ -68,7 +79,7 @@
if (!Globals.bMobileDevice || bMobile)
{
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, {
'style': 'rainloopTip',
@ -78,12 +89,21 @@
Globals.dropdownVisibility.subscribe(function (bV) {
if (bV) {
oElement.__opentip.deactivate();
} else {
oElement.__opentip.activate();
oElement.__opentip.hide();
}
});
if ('' === sValue)
{
oElement.__opentip.hide();
oElement.__opentip.deactivate();
oElement.__opentip.setContent('');
}
else
{
oElement.__opentip.activate();
}
if (bi18n)
{
Translator = require('Common/Translator');
@ -113,6 +133,7 @@
bi18n = true,
sValue = '',
$oEl = $(oElement),
fValue = fValueAccessor(),
bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'),
Globals = require('Common/Globals')
;
@ -120,7 +141,7 @@
if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip)
{
bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
sValue = ko.unwrap(fValueAccessor());
sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue);
if (sValue)
{
@ -165,7 +186,8 @@
var
$oEl = $(oElement),
sValue = ko.unwrap(fValueAccessor()),
fValue = fValueAccessor(),
sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue),
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 = {
'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
$(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 = {
'init': function (oElement, fValueAccessor, fAllBindingsAccessor, oViewModel) {
$(oElement).on('keypress.koOnEsc', function (oEvent) {

View file

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

View file

@ -8,6 +8,7 @@
_ = require('_'),
ko = require('ko'),
Enums = require('Common/Enums'),
Globals = require('Common/Globals'),
Utils = require('Common/Utils'),
Links = require('Common/Links'),
@ -27,6 +28,8 @@
this.mimeType = '';
this.fileName = '';
this.fileNameExt = '';
this.fileType = Enums.FileType.Unknown;
this.estimatedSize = 0;
this.friendlySize = '';
this.isInline = false;
@ -57,6 +60,8 @@
AttachmentModel.prototype.mimeType = '';
AttachmentModel.prototype.fileName = '';
AttachmentModel.prototype.fileType = '';
AttachmentModel.prototype.fileNameExt = '';
AttachmentModel.prototype.estimatedSize = 0;
AttachmentModel.prototype.friendlySize = '';
AttachmentModel.prototype.isInline = false;
@ -97,6 +102,9 @@
this.friendlySize = Utils.friendlySize(this.estimatedSize);
this.cidWithOutTags = this.cid.replace(/^<+/, '').replace(/>+$/, '');
this.fileNameExt = Utils.getFileExtension(this.fileName);
this.fileType = AttachmentModel.staticFileType(this.fileNameExt, this.mimeType);
bResult = true;
}
@ -108,9 +116,7 @@
*/
AttachmentModel.prototype.isImage = function ()
{
return -1 < Utils.inArray(this.mimeType.toLowerCase(),
['image/png', 'image/jpg', 'image/jpeg', 'image/gif']
);
return Enums.FileType.Image === this.fileType;
};
/**
@ -118,7 +124,26 @@
*/
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 ()
{
return -1 < Utils.inArray(this.mimeType, ['application/pgp-signature', 'message/delivery-status', 'message/rfc822']) ||
('text/' === this.mimeType.substr(0, 5) && -1 === Utils.inArray(this.mimeType, ['text/html']));
return Enums.FileType.Text === this.fileType ||
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 ()
{
return Globals.bAllowPdfPreview && 'application/pdf' === this.mimeType;
return Enums.FileType.Pdf === this.fileType;
};
/**
@ -152,7 +181,7 @@
AttachmentModel.prototype.isFramed = function ()
{
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 ()
{
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 ()
{
return this.isMp3();
return (Audio.supportedMp3 && this.isMp3()) ||
(Audio.supportedOgg && this.isOgg()) ||
(Audio.supportedWav && this.isWav())
;
};
/**
@ -229,7 +262,7 @@
switch (true)
{
case this.isImage():
case this.isPdf():
case this.isPdf() && Globals.bAllowPdfPreview:
sResult = this.linkPreview();
break;
case this.isText():
@ -274,76 +307,90 @@
};
/**
* @param {string} sExt
* @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();
var
sText = '',
sClass = 'icon-file',
aParts = sMimeType.split('/')
sResult = Enums.FileType.Unknown,
aMimeTypeParts = sMimeType.split('/')
;
if (aParts && aParts[1])
switch (true)
{
if ('image' === aParts[0])
{
sClass = 'icon-file-image';
}
else if ('text' === aParts[0])
{
sClass = 'icon-file-text';
}
else if ('audio' === aParts[0])
{
sClass = 'icon-file-music';
}
else if ('video' === aParts[0])
{
sClass = 'icon-file-movie';
}
else if (-1 < Utils.inArray(aParts[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']))
{
sClass = 'icon-file-zip';
}
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, [
case 'image' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [
'png', 'jpg', 'jpeg', 'gif', 'bmp'
]):
sResult = Enums.FileType.Image;
break;
case 'audio' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [
'mp3', 'ogg', 'oga', 'wav'
]):
sResult = Enums.FileType.Audio;
break;
case 'video' === aMimeTypeParts[0] || -1 < Utils.inArray(sExt, [
'mkv', 'avi'
]):
sResult = Enums.FileType.Video;
break;
case -1 < Utils.inArray(sExt, [
'php', 'js', 'css'
]):
sResult = Enums.FileType.Code;
break;
case 'eml' === sExt || -1 < Utils.inArray(sMimeType, [
'message/delivery-status', 'message/rfc822'
]))
{
sClass = 'icon-file-text';
}
else if (-1 < Utils.inArray(aParts[1], [
]):
sResult = Enums.FileType.Eml;
break;
case ('text' === aMimeTypeParts[0] && 'html' !== aMimeTypeParts[1]) || -1 < Utils.inArray(sExt, [
'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',
'vnd.openxmlformats-officedocument.wordprocessingml.template',
'vnd.ms-word.document.macroEnabled.12',
'vnd.ms-word.template.macroEnabled.12'
]))
{
sClass = 'icon-file-text';
}
else if (-1 < Utils.inArray(aParts[1], [
]):
sResult = Enums.FileType.WordText;
break;
case -1 < Utils.inArray(aMimeTypeParts[1], [
'excel', 'ms-excel', 'vnd.ms-excel',
'vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'vnd.openxmlformats-officedocument.spreadsheetml.template',
@ -351,11 +398,10 @@
'vnd.ms-excel.template.macroEnabled.12',
'vnd.ms-excel.addin.macroEnabled.12',
'vnd.ms-excel.sheet.binary.macroEnabled.12'
]))
{
sClass = 'icon-file-excel';
}
else if (-1 < Utils.inArray(aParts[1], [
]):
sResult = Enums.FileType.Sheet;
break;
case -1 < Utils.inArray(aMimeTypeParts[1], [
'powerpoint', 'ms-powerpoint', 'vnd.ms-powerpoint',
'vnd.openxmlformats-officedocument.presentationml.presentation',
'vnd.openxmlformats-officedocument.presentationml.template',
@ -364,29 +410,142 @@
'vnd.ms-powerpoint.presentation.macroEnabled.12',
'vnd.ms-powerpoint.template.macroEnabled.12',
'vnd.ms-powerpoint.slideshow.macroEnabled.12'
]))
{
]):
sResult = Enums.FileType.Presentation;
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];
});
/**
* @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 ()
{
return AttachmentModel.staticIconClassHelper(this.mimeType)[0];
return AttachmentModel.staticIconClass(this.fileType)[0];
};
/**
* @returns {string}
* @return {string}
*/
AttachmentModel.prototype.iconText = function ()
{
return AttachmentModel.staticIconClassHelper(this.mimeType)[1];
return AttachmentModel.staticIconClass(this.fileType)[1];
};
module.exports = AttachmentModel;

View file

@ -70,7 +70,11 @@
return Utils.mimeContentType(this.fileName());
}, 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);
@ -108,7 +112,8 @@
*/
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 ()
{
return AttachmentModel.staticIconClassHelper(this.mimeType())[1];
return AttachmentModel.staticIconClass(
AttachmentModel.staticFileType(this.fileExt(), this.mimeType()))[1];
};
module.exports = ComposeAttachmentModel;

View file

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

View file

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

View file

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

View file

@ -29,8 +29,6 @@
MessageFullModel.prototype.requestHash = '';
MessageFullModel.prototype.proxy = false;
MessageFullModel.prototype.hasAttachments = false;
MessageFullModel.prototype.attachmentsMainType = '';
MessageFullModel.prototype.attachmentsClass = '';
MessageFullModel.prototype.clear = function ()
{
@ -43,37 +41,6 @@
this.proxy = 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.hasAttachments = !!oJson.HasAttachments;
this.attachmentsMainType = Utils.pString(oJson.AttachmentsMainType);
this.attachmentsClass = this.getAttachmentsClass();
bResult = true;
}

View file

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

View file

@ -16,8 +16,6 @@
// AppStore = require('Stores/User/App'),
// SettingsStore = require('Stores/User/Settings'),
MessageSimpleModel = require('Model/MessageSimple'),
PromisesPopulator = require('Promises/User/Populator'),
AbstractAjaxPromises = require('Promises/AbstractAjax')
;
@ -29,44 +27,10 @@
function UserAjaxUserPromises()
{
AbstractAjaxPromises.call(this);
this.messageListSimpleHash = '';
this.messageListSimpleCache = null;
}
_.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)
{
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 ()
{
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 {string} sFolderFullNameRaw
* @param {number=} iOffset = 0
* @param {number=} iLimit = 20
* @param {string=} sSearch = ''
* @param {string=} sThreadUid = ''
* @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);
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;
iOffset = Utils.isUnd(iOffset) ? 0 : Utils.pInt(iOffset);
iLimit = Utils.isUnd(iOffset) ? 20 : Utils.pInt(iLimit);
sSearch = Utils.pString(sSearch);
sThreadUid = Utils.pString(sThreadUid);
if ('' !== sFolderHash && ('' === sSearch || -1 === sSearch.indexOf('is:')))
{
@ -358,9 +349,9 @@
sSearch,
AppStore.projectHash(),
sFolderHash,
Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0',
''
sInboxUidNext,
bUseThreads ? '1' : '0',
bUseThreads ? sThreadUid : ''
].join(String.fromCharCode(0))), bSilent ? [] : ['MessageList']);
}
else
@ -370,9 +361,11 @@
'Offset': iOffset,
'Limit': iLimit,
'Search': sSearch,
'UidNext': Cache.getFolderInboxName() === sFolderFullNameRaw ? Cache.getFolderUidNext(sFolderFullNameRaw) : '',
'UseThreads': AppStore.threadsAllowed() && SettingsStore.useThreads() ? '1' : '0'
}, '' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout, '', bSilent ? [] : ['MessageList']);
'UidNext': sInboxUidNext,
'UseThreads': bUseThreads ? '1' : '0',
'ThreadUid': bUseThreads ? sThreadUid : ''
}, '' === sSearch ? Consts.Defaults.DefaultAjaxTimeout : Consts.Defaults.SearchAjaxTimeout,
'', bSilent ? [] : ['MessageList']);
}
};
@ -414,35 +407,6 @@
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 {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 {string} sFolder

View file

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

View file

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

View file

@ -144,29 +144,27 @@
}, 50);
}
else
{
_.delay(function () {
var
f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self),
f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self)
;
_.delay(function () {
self.title.subscribe(function (sValue) {
Remote.saveAdminConfig(f1, {
'Title': Utils.trim(sValue)
});
var
f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self),
f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self)
;
self.title.subscribe(function (sValue) {
Remote.saveAdminConfig(f1, {
'Title': Utils.trim(sValue)
});
});
self.loadingDesc.subscribe(function (sValue) {
Remote.saveAdminConfig(f2, {
'LoadingDescription': Utils.trim(sValue)
});
self.loadingDesc.subscribe(function (sValue) {
Remote.saveAdminConfig(f2, {
'LoadingDescription': Utils.trim(sValue)
});
});
}, 50);
}
}, 50);
};
module.exports = BrandingAdminSettings;

View file

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

View file

@ -28,6 +28,8 @@
{
this.folderList = FolderStore.folderList;
this.folderListHelp = ko.observable('').extend({'throttle': 100});
this.loading = ko.computed(function () {
var
@ -95,6 +97,25 @@
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 ()
{
require('Knoin/Knoin').showScreenPopup(require('View/Popup/FolderCreate'));
@ -160,6 +181,20 @@
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;
}());

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -6,8 +6,15 @@
}
.flag-name {
border-bottom: 1px dashed #555;
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;}
}
@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 {
/*transform: scale(1.1);*/
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 {
.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 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 {
opacity: 0.6;
@ -120,6 +120,15 @@
color: #aaa;
}
.attachmentIcon {
&.icon-none {
display: none;
}
&.icon-file-certificate {
margin-left: 15px;
}
}
.attachmentIconText {
display: inline-block;
font-size: 28px;
@ -131,15 +140,13 @@
font-style: normal;
}
.attachmentIcon.icon-none {
display: none;
}
/*
.attachmentIconParent.hasPreview.isImage {
.iconMain {
display: none;
}
}
*/
.attachmentIconParent.hasPreview:hover {
.iconPreview {

View file

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

View file

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

View file

@ -178,6 +178,17 @@ html.rl-no-preview-pane {
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 {
display: block;
height: 1px;
@ -313,6 +324,12 @@ html.rl-no-preview-pane {
&.e-single-line .dateParent {
}
.threadsParent {
display: inline-block;
float: right;
position: relative;
}
.attachmentParent {
display: inline-block;
float: right;
@ -322,7 +339,7 @@ html.rl-no-preview-pane {
&.e-single-line .attachmentParent {
float: left;
margin: 0 5px 0 0;
margin: 0 8px 0 0;
}
.senderParent {
@ -395,11 +412,25 @@ html.rl-no-preview-pane {
.threads-len {
.threads-len-data {
background-color: #999;
color: #fff;
background-color: #eee;
color: #666;
border-radius: 6px;
padding: 1px 6px 1px 5px;
margin-right: 5px;
padding: 2px 0px 1px 6px;
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;
}
.flagParent {
cursor: pointer;
margin-right: 5px;
.flagOn {
color: orange;
}
.flagOff {
opacity: 0.5;
&:hover {
opacity: 1;
}
}
}
.messageItemHeader {
position: absolute;
@ -120,23 +137,6 @@ html.rl-no-preview-pane {
margin-top: 5px;
}
.flagParent {
cursor: pointer;
margin-right: 5px;
.flagOn {
color: orange;
}
.flagOff {
opacity: 0.5;
&:hover {
opacity: 1;
}
}
}
.informationShort {
margin-left: 22px;
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 {
position: absolute;
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;
opacity: 0.6;
}
@ -94,6 +94,10 @@
.unsubscribe-folder {
opacity: 0.2;
}
.uncheck-folder {
opacity: 0.1;
}
}
.folder-padding.deep-1 {

View file

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

View file

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

View file

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

View file

@ -33,6 +33,7 @@
}
.opentip-container {
z-index: 2001 !important;
.ot-content {
font-size: 13px;
@ -41,4 +42,30 @@
&.style-rainloopErrorTip .ot-content {
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-top-color: #999;
animation: rotation .8s infinite linear;
animation: rotation .8s infinite ease-in-out;
}
&.big {

View file

@ -34,8 +34,17 @@
this.loginError = 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.formHidden = ko.observable(false);
this.formError = ko.computed(function () {
return this.loginErrorAnimation() || this.passwordErrorAnimation();
}, this);
this.login.subscribe(function () {
this.loginError(false);
}, this);
@ -44,6 +53,14 @@
this.passwordError(false);
}, 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.submitError = ko.observable('');
@ -51,6 +68,9 @@
Utils.triggerAutocompleteInputChange();
this.loginError(false);
this.passwordError(false);
this.loginError('' === Utils.trim(this.login()));
this.passwordError('' === Utils.trim(this.password()));

View file

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

View file

@ -8,6 +8,7 @@
ko = require('ko'),
Utils = require('Common/Utils'),
Translator = require('Common/Translator'),
MessageStore = require('Stores/User/Message'),
@ -46,6 +47,19 @@
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);
}

View file

@ -1,4 +1,6 @@
/* global require, module */
(function () {
'use strict';
@ -523,7 +525,6 @@
if (window.Dropbox)
{
window.Dropbox.choose({
//'iframe': true,
'success': function(aFiles) {
if (aFiles && aFiles[0] && aFiles[0]['link'])
@ -995,7 +996,7 @@
*
* @param {Array} aList
* @param {boolean} bFriendly
* @returns {string}
* @return {string}
*/
ComposePopupView.prototype.emailArrayToStringLineHelper = function (aList, bFriendly)
{
@ -1097,6 +1098,26 @@
oText.find('.rlBlockquoteSwitcher').off('.rlBlockquoteSwitcher').remove();
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();
}
@ -1414,11 +1435,11 @@
Events.sub('window.resize.real', this.resizerTrigger);
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.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());
window.document.body.appendChild(oScript);

View file

@ -24,15 +24,16 @@
var self = this;
this.fLang = null;
this.sUserLanguage = '';
this.userLanguage = ko.observable('');
this.langs = ko.observableArray([]);
this.languages = ko.computed(function () {
var sUserLanguage = self.userLanguage();
return _.map(self.langs(), function (sLanguage) {
return {
'key': sLanguage,
'user': sLanguage === self.sUserLanguage,
'user': sLanguage === sUserLanguage,
'selected': ko.observable(false),
'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)
{
this.fLang = fLanguage;
this.sUserLanguage = sUserLanguage || '';
this.userLanguage(sUserLanguage || '');
this.langs(aLangs);
};
LanguagesPopupView.prototype.onHide = function ()
{
this.fLang = null;
this.sUserLanguage = '';
this.langs([]);
};
LanguagesPopupView.prototype.changeLanguage = function (sLang)
{
if (this.fLang)

View file

@ -42,6 +42,7 @@
this.additionalCode = ko.observable('');
this.additionalCode.error = ko.observable(false);
this.additionalCode.errorAnimation = ko.observable(false).extend({'falseTimeout': 500});
this.additionalCode.focused = ko.observable(false);
this.additionalCode.visibility = ko.observable(false);
this.additionalCodeSignMe = ko.observable(false);
@ -56,6 +57,16 @@
this.emailError = 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.passwordFocus = ko.observable(false);
this.submitFocus = ko.observable(false);
@ -78,6 +89,18 @@
this.additionalCode.error(false);
}, 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.submitError = ko.observable('');
this.submitErrorAddidional = ko.observable('');
@ -115,16 +138,34 @@
Utils.triggerAutocompleteInputChange();
this.emailError(false);
this.passwordError(false);
this.emailError('' === Utils.trim(this.email()));
this.passwordError('' === Utils.trim(this.password()));
if (this.additionalCode.visibility())
{
this.additionalCode.error(false);
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;
}
@ -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;
}());

View file

@ -62,6 +62,7 @@
this.selectorMessageFocused = MessageStore.selectorMessageFocused;
this.isMessageSelected = MessageStore.isMessageSelected;
this.messageListSearch = MessageStore.messageListSearch;
this.messageListThreadUid = MessageStore.messageListThreadUid;
this.messageListError = MessageStore.messageListError;
this.folderMenuForMove = FolderStore.folderMenuForMove;
@ -69,6 +70,7 @@
this.mainMessageListSearch = MessageStore.mainMessageListSearch;
this.messageListEndFolder = MessageStore.messageListEndFolder;
this.messageListEndThreadUid = MessageStore.messageListEndThreadUid;
this.messageListChecked = MessageStore.messageListChecked;
this.messageListCheckedOrSelected = MessageStore.messageListCheckedOrSelected;
@ -374,7 +376,7 @@
};
/**
* @returns {string}
* @return {string}
*/
MessageListMailBoxUserView.prototype.printableMessageCountForDeletion = function ()
{
@ -388,6 +390,15 @@
this.inputMessageListSearchFocus(false);
};
MessageListMailBoxUserView.prototype.cancelThreadUid = function ()
{
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
MessageStore.messageListPageBeforeThread(),
MessageStore.messageListSearch()
));
};
/**
* @param {string} sToFolderFullNameRaw
* @param {boolean} bCopy
@ -611,11 +622,34 @@
kn.setHash(Links.mailBox(
FolderStore.currentFolderFullNameHash(),
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)
{
var self = this;
@ -641,6 +675,12 @@
.on('click', '.messageList .messageListItem .flagParent', function () {
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();
@ -723,6 +763,22 @@
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
key('m', Enums.KeyState.MessageList, function () {
self.moveDropdownTrigger(true);
@ -759,6 +815,11 @@
self.cancelSearch();
return false;
}
else if ('' !== self.messageListEndThreadUid())
{
self.cancelThreadUid();
return false;
}
});
// change focused state

View file

@ -4,6 +4,7 @@
'use strict';
var
window = require('window'),
_ = require('_'),
$ = require('$'),
ko = require('ko'),
@ -19,9 +20,11 @@
Events = require('Common/Events'),
Translator = require('Common/Translator'),
Audio = require('Common/Audio'),
Links = require('Common/Links'),
Cache = require('Common/Cache'),
SocialStore = require('Stores/Social'),
AppStore = require('Stores/User/App'),
SettingsStore = require('Stores/User/Settings'),
AccountStore = require('Stores/User/Account'),
@ -56,11 +59,14 @@
}
;
this.oDom = null;
this.oHeaderDom = null;
this.oMessageScrollerDom = null;
this.pswp = null;
this.attahcmentsActions = AppStore.attahcmentsActions;
this.message = MessageStore.message;
this.messageListChecked = MessageStore.messageListChecked;
this.hasCheckedMessages = MessageStore.hasCheckedMessages;
@ -79,9 +85,65 @@
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.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) {
if (this.message())
{
@ -115,12 +177,6 @@
this.moreDropdownTrigger = ko.observable(false);
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 () {
return !this.messageLoadingThrottle() && !!this.message();
}, this);
@ -204,6 +260,9 @@
}
}, this.messageVisibility);
this.dropboxEnabled = SocialStore.dropbox.enabled;
this.dropboxApiKey = SocialStore.dropbox.apiKey;
// viewer
this.viewBodyTopValue = ko.observable(0);
@ -230,140 +289,6 @@
this.viewIsImportant = 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
this.viewPgpPassword = ko.observable('');
this.viewPgpSignedVerifyStatus = ko.computed(function () {
@ -449,9 +374,6 @@
this.viewIsImportant(oMessage.isImportant());
this.viewIsFlagged(oMessage.flagged());
this.viewThreads(oMessage.threads());
this.viewThreads.trigger(!this.viewThreads.trigger());
sLastEmail = oMessage.fromAsSingleEmail();
Cache.getUserPic(sLastEmail, function (sPic, sEmail) {
if (sPic !== self.viewUserPic() && sLastEmail === sEmail)
@ -472,8 +394,6 @@
this.viewUid = '';
this.viewHash = '';
this.viewThreads([]);
this.scrollMessageToTop();
}
@ -532,24 +452,6 @@
kn.extendAsViewModel(['View/User/MailBox/MessageView', 'View/App/MailBox/MessageView', 'MailBoxMessageViewViewModel'], MessageViewMailBoxUserView);
_.extend(MessageViewMailBoxUserView.prototype, AbstractView.prototype);
MessageViewMailBoxUserView.prototype.updateViewFlagsFromCache = function ()
{
var aFlags = this.getMessageFlagsFromCache(this.viewFolder, this.viewUid);
if (aFlags)
{
this.viewIsFlagged(!!aFlags[1]);
}
};
MessageViewMailBoxUserView.prototype.openThreadMessage = function (sUid)
{
var oMessage = this.message();
if (oMessage && sUid)
{
MessageStore.selectThreadMessage(oMessage.folderFullNameRaw, sUid);
}
};
MessageViewMailBoxUserView.prototype.isPgpActionVisible = function ()
{
return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
@ -633,10 +535,13 @@
{
var
self = this,
oScript = null,
sErrorMessage = Translator.i18n('PREVIEW_POPUP/IMAGE_ERROR'),
fCheckHeaderHeight = _.bind(this.checkHeaderHeight, this)
;
this.oDom = oDom;
this.fullScreenMode.subscribe(function (bValue) {
if (bValue && self.message())
{
@ -659,6 +564,16 @@
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 = this.oHeaderDom[0] ? this.oHeaderDom : null;
@ -667,9 +582,10 @@
if (this.pswpDom)
{
oDom
.on('click', '.attachmentImagePreview[data-index]', function (oEvent) {
.on('click', '.attachmentImagePreview.visible', function (oEvent) {
var
iIndex = 0,
oPs = null,
oEl = oEvent.currentTarget || null,
aItems = []
@ -686,14 +602,18 @@
// }
;
oDom.find('.attachmentImagePreview[data-index]').each(function (index, oSubElement) {
oDom.find('.attachmentImagePreview.visible').each(function (index, oSubElement) {
var
$oItem = $(oSubElement)
;
if (oEl === oSubElement)
{
iIndex = index;
}
aItems.push({
// 'el': $oItem,
'w': 600, 'h': 400,
'src': $oItem.attr('href'),
'title': $oItem.attr('title') || ''
@ -705,7 +625,7 @@
Globals.useKeyboardShortcuts(false);
oPs = new PhotoSwipe(self.pswpDom, PhotoSwipeUI_Default, aItems, {
'index': Utils.pInt($(oEl).data('index')),
'index': iIndex,
'bgOpacity': 0.85,
'loadingIndicatorDelay': 500,
'errorMsg': '<div class="pswp__error-msg">' + sErrorMessage + '</div>',
@ -759,35 +679,20 @@
}
var oAttachment = ko.dataFor(this);
if (oAttachment && oAttachment.isMp3() && Audio.supported)
if (oAttachment && Audio.supported)
{
Audio.playMp3(oAttachment.linkDownload(), oAttachment.fileName);
}
})
.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);
switch (true)
{
case Audio.supportedMp3 && oAttachment.isMp3():
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', '.attachmentsPlace .attachmentItem .attachmentNameParent', function () {
@ -801,8 +706,26 @@
require('App/User').download(oAttachment.linkDownload());
}
})
.on('click', '.messageItemHeader .flagParent', function () {
self.flagViewMessage();
.on('click', '.messageItemHeader .subjectParent .flagParent', function () {
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();
};
/**
* @return {boolean}
*/
MessageViewMailBoxUserView.prototype.flagViewMessage = function ()
MessageViewMailBoxUserView.prototype.selectSelectedThreadMessage = function ()
{
var oMessage = this.message();
if (oMessage)
{
require('App/User').messageListAction(oMessage.folderFullNameRaw, oMessage.uid,
oMessage.flagged() ? Enums.MessageSetAction.UnsetFlag : Enums.MessageSetAction.SetFlag, [oMessage]);
}
var self = this;
_.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])
{
oLast.focus();
}
}, 30);
};
/**
@ -926,35 +848,16 @@
}
});
key('t', [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 () {
key('ctrl+up, command+up, ctrl+left, command+left', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
self.goUpCommand();
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();
return false;
});
key('ctrl+left, command+left', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
self.threadForwardCommand();
return false;
});
key('ctrl+right, command+right', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () {
self.threadBackCommand();
return false;
});
// print
key('ctrl+p, command+p', Enums.KeyState.MessageView, function () {
if (self.message())
@ -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
*/
@ -1119,7 +1137,7 @@
};
/**
* @returns {string}
* @return {string}
*/
MessageViewMailBoxUserView.prototype.printableCheckedMessageCount = function ()
{

View file

@ -1,8 +1,8 @@
{
"name": "RainLoop",
"title": "RainLoop Webmail",
"version": "1.8.3",
"release": "298",
"version": "1.8.4",
"release": "312",
"description": "Simple, modern & fast web-based email client",
"homepage": "http://rainloop.net",
"main": "gulpfile.js",
@ -36,7 +36,7 @@
"plugins"
],
"readmeFilename": "README.md",
"ownCloudPackageVersion": "3.2",
"ownCloudPackageVersion": "3.3",
"engines": {
"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_I = 'iso-8859-8-i';
const ISO_2022_JP = 'iso-2022-jp';
const CP858 = 'cp858';
}

View file

@ -567,6 +567,12 @@ class Http
return false;
}
$sUrl = \trim($sUrl);
if ('//' === substr($sUrl, 0, 2))
{
$sUrl = 'http:'.$sUrl;
}
$aOptions = array(
CURLOPT_URL => $sUrl,
CURLOPT_HEADER => false,
@ -679,10 +685,10 @@ class Http
{
if ($bSetCacheHeader)
{
\header('Cache-Control: public', true);
\header('Pragma: public', 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('Cache-Control: public', true);
@\header('Pragma: public', 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);
if (0 < strlen($sEtag))
{

View file

@ -235,6 +235,56 @@ END;
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
*/

View file

@ -83,6 +83,8 @@ abstract class Driver
$this->bTypedPrefix = true;
$this->bGuidPrefix = true;
$this->iTimeOffset = 0;
$this->iWriteOnTimeoutOnly = 0;
$this->bWriteOnErrorOnly = 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
*/
@ -217,7 +230,7 @@ abstract class Driver
protected function getTimeWithMicroSec()
{
$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);
}
@ -247,7 +260,7 @@ abstract class Driver
if (!$this->bFlushCache && ($this->bWriteOnErrorOnly || $this->bWriteOnPhpErrorOnly || 0 < $this->iWriteOnTimeoutOnly))
{
$bErrorPhp = false;
$bError = $this->bWriteOnErrorOnly && \in_array($iType, array(
\MailSo\Log\Enumerations\Type::NOTICE,
\MailSo\Log\Enumerations\Type::NOTICE_PHP,

View file

@ -91,6 +91,21 @@ class Logger extends \MailSo\Base\Collection
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
*/

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 $oAttachment && $oAttachment->IsImage();
return $this->MapList(function ($oAttachment) {
if ($oAttachment)
{
return array($oAttachment->FileName(true), $oAttachment->MimeType());
}
return null;
});
return \is_array($aList) ? \count($aList) : 0;
}
/**
* @return int
*/
public function ArchiveCount()
{
$aList = $this->FilterList(function ($oAttachment) {
return $oAttachment && $oAttachment->IsArchive();
});
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();
}
/**
* @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 int $iOffset = 0
@ -1921,6 +2004,7 @@ class MailClient
* @param bool $bUseSortIfSupported = false
* @param bool $bUseThreadSortIfSupported = false
* @param bool $bUseESearchOrESortRequest = false
* @param string $sThreadUid = ''
*
* @return \MailSo\Mail\MessageCollection
*
@ -1929,7 +2013,7 @@ class MailClient
* @throws \MailSo\Imap\Exceptions\Exception
*/
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);
if (!\MailSo\Base\Validator::RangeInt($iOffset, 0) ||
@ -1945,10 +2029,14 @@ class MailClient
$oMessageCollection->Offset = $iOffset;
$oMessageCollection->Limit = $iLimit;
$oMessageCollection->Search = $sSearch;
$oMessageCollection->ThreadUid = $sThreadUid;
$aThreads = array();
$aUids = array();
$mAllSortedUids = null;
$mAllThreads = null;
$iThreadUid = empty($sThreadUid) ? 0 : (int) $sThreadUid;
$iMessageCount = 0;
$iMessageRealCount = 0;
$iMessageUnseenCount = 0;
$sUidNext = '0';
@ -1958,17 +2046,26 @@ class MailClient
$bUseThreadSortIfSupported = $bUseThreadSortIfSupported ?
($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))
{
$oCacher = null;
}
$this->initFolderValues($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $sUidNext);
$iMessageCount = $iMessageRealCount;
$oMessageCollection->FolderHash = $this->GenerateFolderHash($sFolderName, $iMessageRealCount, $iMessageUnseenCount, $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;
$bMessageListOptimization = 0 < \MailSo\Config::$MessageListCountLimitTrigger &&
@ -1978,98 +2075,119 @@ class MailClient
{
$bUseSortIfSupported = false;
$bUseThreadSortIfSupported = false;
$bUseESearchOrESortRequest = false;
}
if (0 < $iMessageRealCount)
{
$bIndexAsUid = false;
$aIndexOrUids = array();
$mAllSortedUids = $this->GetUids($oCacher, '',
$oMessageCollection->FolderName, $oMessageCollection->FolderHash, $bUseSortIfSupported);
if (0 < \strlen($sSearch))
{
$aIndexOrUids = $this->getSearchUidsResult($sSearch,
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
$mAllThreads = $bUseThreadSortIfSupported ? $this->MessageListThreadsMap(
$oMessageCollection->FolderName, $oMessageCollection->FolderHash, $mAllSortedUids, $oCacher) : null;
$bIndexAsUid = true;
}
else
if ($bUseThreadSortIfSupported && 0 < $iThreadUid && \is_array($mAllThreads))
{
if ($bUseThreadSortIfSupported && 1 < $iMessageCount)
$aUids = array();
$iResultRootUid = 0;
if (isset($mAllThreads[$iThreadUid]))
{
$aIndexOrUids = $this->getSearchUidsResult('',
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
$aThreads = $this->MessageListThreadsMap(
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$aIndexOrUids, $oCacher);
$aNewIndexOrUids = array();
foreach ($aIndexOrUids as $iUid)
$iResultRootUid = $iThreadUid;
if (\is_array($mAllThreads[$iThreadUid]))
{
if (isset($aThreads[$iUid]))
{
$aNewIndexOrUids[] = $iUid;
}
$aUids = $mAllThreads[$iThreadUid];
}
$aIndexOrUids = $aNewIndexOrUids;
unset($aNewIndexOrUids);
$iMessageCount = \count($aIndexOrUids);
$bIndexAsUid = true;
}
else
{
$aIndexOrUids = array(1);
$bIndexAsUid = false;
if (1 < $iMessageCount)
foreach ($mAllThreads as $iRootUid => $mSubUids)
{
if (0 === \MailSo\Config::$MessageListDateFilter &&
($bMessageListOptimization || !$bUseSortIfSupported))
if (\is_array($mSubUids) && \in_array($iThreadUid, $mSubUids))
{
$aIndexOrUids = \array_reverse(\range(1, $iMessageCount));
}
else
{
$aIndexOrUids = $this->getSearchUidsResult('',
$oMessageCollection->FolderName, $oMessageCollection->FolderHash,
$bUseSortIfSupported, $bUseESearchOrESortRequest, $oCacher);
$bIndexAsUid = true;
$iResultRootUid = $iRootUid;
$aUids = $mSubUids;
continue;
}
}
}
if (0 < $iResultRootUid && \in_array($iResultRootUid, $mAllSortedUids))
{
\array_unshift($aUids, $iResultRootUid);
}
}
else if ($bUseThreadSortIfSupported && \is_array($mAllThreads))
{
$aUids = \array_keys($mAllThreads);
}
else
{
$bUseThreadSortIfSupported = false;
$aUids = $mAllSortedUids;
}
if (\is_array($aIndexOrUids))
if (0 < \strlen($sSearch) && \is_array($aUids))
{
$aSearchedUids = $this->GetUids($oCacher, $sSearch, $oMessageCollection->FolderName, $oMessageCollection->FolderHash);
if (\is_array($aSearchedUids) && 0 < \count($aSearchedUids))
{
$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
{
$aUids = array();
}
}
if (\is_array($aUids))
{
$oMessageCollection->MessageCount = $iMessageRealCount;
$oMessageCollection->MessageUnseenCount = $iMessageUnseenCount;
$oMessageCollection->MessageResultCount = 0 === \strlen($sSearch)
? $iMessageCount : \count($aIndexOrUids);
$oMessageCollection->MessageResultCount = \count($aUids);
if (0 < \count($aIndexOrUids))
if (0 < \count($aUids))
{
$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();
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);
$oMessage->SetThreads(\array_map('trim', $aSubThreads));
@ -2263,7 +2381,10 @@ class MailClient
{
$aSubscribedFolders = $this->oImapClient->FolderSubscribeList($sParent, $sListPattern);
}
catch (\Exception $oException) {}
catch (\Exception $oException)
{
unset($oException);
}
}
$aImapSubscribedFoldersHelper = null;

View file

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

View file

@ -407,8 +407,9 @@ class Actions
{
if (false !== \strpos($sLine, '{date:'))
{
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) {
return \gmdate($aMatch[1]);
$iTimeOffset = (int) $this->Config()->Get('logs', 'time_offset', 0);
$sLine = \preg_replace_callback('/\{date:([^}]+)\}/', function ($aMatch) use ($iTimeOffset) {
return \MailSo\Log\Logger::DateHelper($aMatch[1], $iTimeOffset);
}, $sLine);
$sLine = \preg_replace('/\{date:([^}]*)\}/', 'date', $sLine);
@ -939,6 +940,8 @@ class Actions
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));
$sLogFileFullPath = \APP_PRIVATE_DATA.'logs/'.$this->compileLogFileName(
@ -956,6 +959,7 @@ class Actions
->WriteOnErrorOnly($this->Config()->Get('logs', 'write_on_error_only', false))
->WriteOnPhpErrorOnly($this->Config()->Get('logs', 'write_on_php_error_only', false))
->WriteOnTimeoutOnly($this->Config()->Get('logs', 'write_on_timeout_only', 0))
->SetTimeOffset($iTimeOffset)
);
if (!$this->Config()->Get('debug', 'enable', false))
@ -967,7 +971,10 @@ class Actions
$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:'.
(\MailSo\Base\Utils::FunctionExistsAndEnabled('getmypid') ? \getmypid() : 'unknown').']['.
$oHttp->GetServer('SERVER_SOFTWARE', '~').']['.
@ -1315,12 +1322,34 @@ class Actions
'AllowAppendMessage' => (bool) $oConfig->Get('labs', 'allow_message_append', false),
'MaterialDesign' => (bool) $oConfig->Get('labs', 'use_material_design', true),
'PremType' => $this->PremType(),
'ZipSupported' => !!\class_exists('ZipArchive'),
'Admin' => array(),
'Capa' => array(),
'AttahcmentsActions' => 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'] &&
\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['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').'/'.
($bAdmin ? $aResult['LanguageAdmin'] : $aResult['Language']).'/'.$sStaticCache.'/';
@ -1755,14 +1785,30 @@ class Actions
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()
{
$iDelay = (int) $this->Config()->Get('labs', 'login_fault_delay', 0);
if (0 < $iDelay)
{
\sleep($iDelay);
$this->requestSleep(1, $iDelay);
}
}
/**
* @param \RainLoop\Model\Account $oAccount
*/
@ -2630,6 +2676,209 @@ class Actions
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
*
@ -3531,7 +3780,6 @@ class Actions
*/
public function DoAdminLicensing()
{
$iStart = \time();
$this->IsAdminLoggined();
$bForce = '1' === (string) $this->GetActionParam('Force', '0');
@ -3543,10 +3791,7 @@ class Actions
{
$sValue = $this->licenseHelper($bForce);
if ($iStart === \time())
{
\sleep(1);
}
$this->requestSleep();
$iExpired = 0;
if ($this->licenseParser($sValue, $iExpired))
@ -3583,7 +3828,6 @@ class Actions
*/
public function DoAdminLicensingActivate()
{
$iStart = \time();
$this->IsAdminLoggined();
$sDomain = (string) $this->GetActionParam('Domain', '');
@ -3599,7 +3843,6 @@ class Actions
$oHttp = \MailSo\Base\Http::SingletonInstance();
\sleep(1);
$sValue = $oHttp->GetUrlAsString(APP_API_PATH.'activate/'.\urlencode($sDomain).'/'.\urlencode($sKey),
'RainLoop/'.APP_VERSION, $sContentType, $iCode, $this->Logger(), 10,
$this->Config()->Get('labs', 'curl_proxy', ''), $this->Config()->Get('labs', 'curl_proxy_auth', ''),
@ -3611,10 +3854,7 @@ class Actions
$sValue = '';
}
if ($iStart + 2 > \time())
{
\sleep(1);
}
$this->requestSleep();
$aMatch = array();
if ('OK' === $sValue)
@ -5240,12 +5480,12 @@ class Actions
{
$this->initMailClientConnection();
$sFolderNameInUtf = $this->GetActionParam('Folder', '');
$sFolderFullNameRaw = $this->GetActionParam('Folder', '');
$bSubscribe = '1' === (string) $this->GetActionParam('Subscribe', '0');
try
{
$this->MailClient()->FolderSubscribe($sFolderNameInUtf, !!$bSubscribe);
$this->MailClient()->FolderSubscribe($sFolderFullNameRaw, !!$bSubscribe);
}
catch (\Exception $oException)
{
@ -5262,6 +5502,51 @@ class Actions
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
*
@ -5443,45 +5728,6 @@ class Actions
return $this->DefaultResponse(__FUNCTION__, $aResult);
}
/**
* @return array
*
* @throws \MailSo\Base\Exceptions\Exception
*/
public function DoMessageThreadsFromCache()
{
$sFolder = $this->GetActionParam('Folder', '');
$sFolderHash = $this->GetActionParam('FolderHash', '');
$sUid = (string) $this->GetActionParam('Uid', '');
if (0 === \strlen($sFolder) || 0 === \strlen($sUid))
{
throw new \RainLoop\Exceptions\ClientException(\RainLoop\Notifications::InvalidInputArgument);
}
$aResult = array(
'Folder' => $sFolder,
'Uid' => $sUid,
'FolderHash' => $sFolderHash,
'ThreadUids' => null
);
$oCache = $this->cacherForUids();
if ($oCache && $this->Config()->Get('labs', 'use_imap_thread', false))
{
$aThreadUids = $this->MailClient()->MessageThreadUidsFromCache(
$sFolder, $sFolderHash, $sUid, $oCache
);
if (\is_array($aThreadUids) && 1 < \count($aThreadUids))
{
$aResult['ThreadUids'] = $aThreadUids;
}
}
return $this->DefaultResponse(__FUNCTION__, $aResult);
}
/**
* @return array
*
@ -5498,11 +5744,12 @@ class Actions
$sSearch = '';
$sUidNext = '';
$bUseThreads = false;
$sThreadUid = '';
$sRawKey = $this->GetActionParam('RawKey', '');
$aValues = $this->getDecodedClientRawKeyValue($sRawKey, 9);
if (\is_array($aValues) && 9 === \count($aValues))
if (\is_array($aValues) && 7 < \count($aValues))
{
$sFolder =(string) $aValues[0];
$iOffset = (int) $aValues[1];
@ -5511,6 +5758,11 @@ class Actions
$sUidNext = (string) $aValues[6];
$bUseThreads = (bool) $aValues[7];
if ($bUseThreads)
{
$sThreadUid = isset($aValues[8]) ? (string) $aValues[8] : '';
}
$this->verifyCacheByKey($sRawKey);
}
else
@ -5521,6 +5773,11 @@ class Actions
$sSearch = $this->GetActionParam('Search', '');
$sUidNext = $this->GetActionParam('UidNext', '');
$bUseThreads = '1' === (string) $this->GetActionParam('UseThreads', '0');
if ($bUseThreads)
{
$sThreadUid = (string) $this->GetActionParam('ThreadUid', '');
}
}
if (0 === strlen($sFolder))
@ -5542,7 +5799,7 @@ class Actions
$this->cacherForUids(),
!!$this->Config()->Get('labs', 'use_imap_sort', false),
$bUseThreads,
!!$this->Config()->Get('labs', 'use_imap_esearch_esort', false)
$sThreadUid
);
}
catch (\Exception $oException)
@ -5558,40 +5815,6 @@ class Actions
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 bool $bWithDraftInfo = true
@ -6565,7 +6788,8 @@ class Actions
))
);
\sleep(1);
$this->requestSleep();
return $this->DefaultResponse(__FUNCTION__,
$this->getTwoFactorInfo($oAccount));
}
@ -6649,7 +6873,8 @@ class Actions
// $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode)
// ));
\sleep(1);
$this->requestSleep();
return $this->DefaultResponse(__FUNCTION__,
$this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode));
}
@ -7713,10 +7938,10 @@ class Actions
$iUid = (int) (isset($aValues['Uid']) ? $aValues['Uid'] : 0);
$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) {
if (is_resource($rResource))
if (\is_resource($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 $bThumbnail = false
@ -7956,15 +8232,50 @@ class Actions
$aValues = $this->getDecodedRawKeyValue($sRawKey);
$sFolder = isset($aValues['Folder']) ? $aValues['Folder'] : '';
$iUid = (int) isset($aValues['Uid']) ? $aValues['Uid'] : 0;
$sMimeIndex = (string) isset($aValues['MimeIndex']) ? $aValues['MimeIndex'] : '';
$iUid = isset($aValues['Uid']) ? (int) $aValues['Uid'] : 0;
$sMimeIndex = isset($aValues['MimeIndex']) ? (string) $aValues['MimeIndex'] : '';
$sContentTypeIn = (string) isset($aValues['MimeType']) ? $aValues['MimeType'] : '';
$sFileNameIn = (string) isset($aValues['FileName']) ? $aValues['FileName'] : '';
$sContentTypeIn = isset($aValues['MimeType']) ? (string) $aValues['MimeType'] : '';
$sFileNameIn = isset($aValues['FileName']) ? (string) $aValues['FileName'] : '';
$sFileHashIn = isset($aValues['FileHash']) ? (string) $aValues['FileHash'] : '';
if (!empty($sFolder) && 0 < $iUid)
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)
{
$this->verifyCacheByKey($sRawKey);
}
}
$oAccount = $this->initMailClientConnection();
@ -8358,10 +8669,11 @@ class Actions
* @param string $sDefault = ''
* @param bool $bAdmin = false
* @param bool $bSearchShortName = false
* @param bool $bAllowEmptyResult = false
*
* @return string
*/
public function ValidateLanguage($sLanguage, $sDefault = '', $bAdmin = false, $bSearchShortName = false)
public function ValidateLanguage($sLanguage, $sDefault = '', $bAdmin = false, $bSearchShortName = false, $bAllowEmptyResult = false)
{
$sResult = '';
$aLang = $this->GetLanguages($bAdmin);
@ -8387,7 +8699,7 @@ class Actions
$sResult = $sDefault;
}
if (empty($sResult))
if (empty($sResult) && !$bAllowEmptyResult)
{
$sResult = $this->Config()->Get('webmail', $bAdmin ? 'language_admin' : 'language', 'en');
$sResult = \in_array($sResult, $aLang) ? $sResult : 'en';
@ -9039,17 +9351,49 @@ class Actions
if (\is_object($mResponse))
{
$bHook = true;
$self = $this;
$sClassName = \get_class($mResponse);
$bHasSimpleJsonFunc = \method_exists($mResponse, 'ToSimpleJSON');
$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)
{
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), $mResponse->ToSimpleJSON(true));
}
else if ('MailSo\Mail\Message' === $sClassName)
{
$oAccount = $this->getAccountFromToken(false);
$oAccount = call_user_func($fGetAccount);
$mResult = \array_merge($this->objectData($mResponse, $sParent, $aParameters), array(
'Folder' => $mResponse->Folder(),
@ -9079,28 +9423,7 @@ class Actions
$iAttachmentsCount = $oAttachments ? $oAttachments->Count() : 0;
$mResult['HasAttachments'] = 0 < $iAttachmentsCount;
$mResult['AttachmentsMainType'] = '';
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;
}
}
$mResult['AttachmentsSpecData'] = $mResult['HasAttachments'] ? $oAttachments->SpecData() : array();
$sSubject = $mResult['Subject'];
$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(
'Name' => $mResponse->Name(),
'FullName' => $mResponse->FullName(),
@ -9356,6 +9685,7 @@ class Actions
'IsExists' => $mResponse->IsExists(),
'IsSelectable' => $mResponse->IsSelectable(),
'Flags' => $mResponse->FlagsLowerCase(),
'Checkable' => \in_array($mResponse->FullNameRaw(), $aCheckableFolder),
'Extended' => $aExtended,
'SubFolders' => $this->responseObject($mResponse->SubFolders(), $sParent, $aParameters)
));
@ -9369,8 +9699,8 @@ class Actions
'Folder' => $mResponse->FolderName,
'FolderHash' => $mResponse->FolderHash,
'UidNext' => $mResponse->UidNext,
'ThreadUid' => $mResponse->ThreadUid,
'NewMessages' => $this->responseObject($mResponse->NewMessages),
// 'LastCollapsedThreadUids' => $mResponse->LastCollapsedThreadUids,
'Offset' => $mResponse->Offset,
'Limit' => $mResponse->Limit,
'Search' => $mResponse->Search

View file

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

View file

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

View file

@ -116,6 +116,17 @@ class Files extends \RainLoop\Providers\AbstractProvider
$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
*/

View file

@ -25,6 +25,17 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
$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 string $sKey
@ -37,7 +48,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
$bResult = false;
if ($rSource)
{
$rOpenOutput = @\fopen($this->generateFileName($oAccount, $sKey, true), 'w+b');
$rOpenOutput = @\fopen($this->generateFullFileName($oAccount, $sKey, true), 'w+b');
if ($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)
{
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;
$bCreate = !!\preg_match('/[wac]/', $sOpenMode);
$sFileName = $this->generateFileName($oAccount, $sKey, $bCreate);
$sFileName = $this->generateFullFileName($oAccount, $sKey, $bCreate);
if ($bCreate || \file_exists($sFileName))
{
$mResult = @\fopen($sFileName, $sOpenMode);
@ -95,7 +106,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
public function GetFileName($oAccount, $sKey)
{
$mResult = false;
$sFileName = $this->generateFileName($oAccount, $sKey);
$sFileName = $this->generateFullFileName($oAccount, $sKey);
if (\file_exists($sFileName))
{
$mResult = $sFileName;
@ -113,7 +124,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
public function Clear($oAccount, $sKey)
{
$mResult = true;
$sFileName = $this->generateFileName($oAccount, $sKey);
$sFileName = $this->generateFullFileName($oAccount, $sKey);
if (\file_exists($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)
{
$mResult = false;
$sFileName = $this->generateFileName($oAccount, $sKey);
$sFileName = $this->generateFullFileName($oAccount, $sKey);
if (\file_exists($sFileName))
{
$mResult = \filesize($sFileName);
@ -153,7 +164,7 @@ class FileStorage implements \RainLoop\Providers\Files\IFiles
*/
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
*/
private function generateFileName($oAccount, $sKey, $bMkDir = false)
private function generateFullFileName($oAccount, $sKey, $bMkDir = false)
{
$sEmail = $sSubEmail = '';
if ($oAccount instanceof \RainLoop\Model\Account)

View file

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

View file

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

View file

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

View file

@ -66,7 +66,7 @@
.thm-border-radius(@login-border-radius);
.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;
}
}
@ -85,7 +85,7 @@
color: @languages-color;
.flag-name {
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="loginFormWrapper">
<div class="loginFormWrapper" data-bind="css: {'afterLoginHide': formHidden}">
<center>
<div class="alert alertError" data-bind="visible: '' !== submitError()">
<button type="button" class="close" data-bind="click: function () { submitError('') }">&times;</button>
<span data-bind="text: submitError"></span>
</div>
<form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm">
<div class="controls" data-bind="css: {'error': loginError}">
<form class="wrapper loginForm thm-login" action="#/" data-bind="submit: submitForm, css: {'errorAnimated': formError}">
<div class="controls" data-bind="css: {'error': loginError, 'animated': loginErrorAnimation}">
<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"
data-bind="textInput: login, hasFocus: loginFocus" />
<span class="add-on">
@ -16,9 +18,11 @@
</span>
</div>
</div>
<div class="controls" data-bind="css: {'error': passwordError}">
<div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<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"
data-bind="textInput: password" />
<span class="add-on">

View file

@ -144,7 +144,7 @@
</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="control-group">

View file

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

View file

@ -15,7 +15,7 @@
<span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + language()" style=""></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;
<div data-bind="component: {
name: 'SaveTrigger',
@ -31,7 +31,7 @@
<span class="flag-wrapper">
<span data-bind="css: 'flag flag-' + languageAdmin()" style=""></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;
<div data-bind="component: {
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_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_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_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_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_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_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>
@ -60,14 +60,12 @@
<table class="table table-striped table-bordered">
<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_BLOCKQUOTES_TOGGLE">Toggle message blockquotes</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_THREAD_PREV">Previous message in thread</td><td>Ctrl + &rarr;, Cmd + &rarr;</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_EXIT_FULLSCREEN">Exit fullscreen mode</td><td>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>
<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"></td><td>B</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_EXIT_FULLSCREEN"></td><td>Esc</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_SWITCH_TO_LIST"></td><td>Tab, Shift + Tab, Esc</td></tr>
</tbody>
</table>

View file

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

View file

@ -2,6 +2,6 @@
<input type="checkbox" data-bind="checked: value, disable: disable() || !enable()" style="margin: 0 0 1px 0;" />
<!-- ko if: labeled -->
&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 -->
</label>

View file

@ -1,10 +1,10 @@
<label class="e-component e-checkbox material-design inline" data-bind="click: click, css: { 'disabled': disable() || !enable() }">
<div class="sub-checkbox-container">
<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" role="checkbox">
<div class="sub-checkbox" data-bind="css: {'checked': (value() && !inverted) || (!value() && inverted),
'unchecked': (!value() && !inverted) || (value() && inverted),
'box': animationBox, 'checkmark': animationCheckmark}"></div>
</div>
<!-- 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 -->
</label>
</span>

View file

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

View file

@ -1,18 +1,20 @@
<!-- ko if: preLabeled -->
<span class="i18n" data-bind="attr: {'data-i18n': preLabel}"></span>
&nbsp;&nbsp;
<!-- /ko -->
<select data-bind="options: options, value: value, enable: enable, optionsText: optionsText, optionsValue: optionsValue,
css: className, optionsAfterRender: defautOptionsAfterRender"></select>
<!-- ko if: labeled -->
&nbsp;
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
&nbsp;
<!-- /ko -->
<!-- ko if: triggered -->
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: trigger }
}"></div>
<!-- /ko -->
<div class="e-component e-select inline">
<!-- ko if: preLabeled -->
<span class="i18n" data-bind="attr: {'data-i18n': preLabel}"></span>
&nbsp;&nbsp;
<!-- /ko -->
<select data-bind="options: options, value: value, enable: enable, optionsText: optionsText, optionsValue: optionsValue,
css: className, optionsAfterRender: defautOptionsAfterRender"></select>
<!-- ko if: labeled -->
&nbsp;
<span class="i18n" data-bind="attr: {'data-i18n': label}"></span>
&nbsp;
<!-- /ko -->
<!-- ko if: triggered -->
&nbsp;
<div data-bind="component: {
name: 'SaveTrigger',
params: { value: trigger }
}"></div>
<!-- /ko -->
</div>

View file

@ -1,5 +1,5 @@
<div class="b-login-content">
<div class="loginFormWrapper">
<div class="loginFormWrapper" data-bind="css: {'afterLoginHide': formHidden}">
<center>
{{INCLUDE/BeforeLogo/PLACE}}
<!-- ko if: logoImg -->
@ -19,12 +19,13 @@
<span data-bind="text: submitErrorAddidional"></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}">
{{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">
<input type="email" class="i18n input-block-level inputEmail checkAutocomplete"
name="RainLoopEmail" id="RainLoopEmail"
style="padding-right: 35px;"
autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="textInput: email, hasFocus: emailFocus" data-i18n="[placeholder]LOGIN/LABEL_EMAIL" />
<span class="add-on">
@ -32,9 +33,11 @@
</span>
</div>
</div>
<div class="controls" data-bind="css: {'error': passwordError}">
<div class="controls" data-bind="css: {'error': passwordError, 'animated': passwordErrorAnimation}">
<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"
data-bind="textInput: password, hasFocus: passwordFocus" data-i18n="[placeholder]LOGIN/LABEL_PASSWORD" />
<span class="add-on">
@ -42,9 +45,11 @@
</span>
</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">
<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" />
<span class="add-on">
<i class="icon-key"></i>
@ -52,11 +57,13 @@
</div>
</div>
<div class="controls" data-bind="visible: additionalCode.visibility()">
<label class="additionalCodeSignMeLabel" data-bind="click: function () { additionalCodeSignMe(!additionalCodeSignMe()); }">
<i data-bind="css: additionalCodeSignMe() ? 'checkboxAdditionalCodeSignMe icon-checkbox-checked' : 'checkboxAdditionalCodeSignMe icon-checkbox-unchecked'"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="LOGIN/LABEL_DONT_ASK_VERIFICATION_CODE"></span>
</label>
<div class="additionalCodeSignMeLabel" data-bind="component: {
name: 'CheckboxSimple',
params: {
label: 'LOGIN/LABEL_DONT_ASK_VERIFICATION_CODE',
value: additionalCodeSignMe
}
}"></div>
</div>
{{INCLUDE/BottomControlGroup/PLACE}}
<div class="controls">
@ -80,11 +87,21 @@
<i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i>
</a>
</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>
&nbsp;&nbsp;
<span class="i18n i18n-animation" data-i18n="LOGIN/LABEL_SIGN_ME"></span>
</label>
</label>-->
</div>
<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">
@ -105,11 +122,12 @@
<label class="flag-selector">
<i data-bind="css: langRequest() ? 'icon-spinner animated' : 'icon-world'"></i>
&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>
</div>
{{INCLUDE/BottomFooter/PLACE}}
</center>
</div>
<a href="#" onclick="return false;"></a>
<div class="loginAfter"></div>
</div>

View file

@ -137,19 +137,21 @@
<div class="b-content" data-bind="nano: true, initDom: dragOverBodyArea">
<div class="content g-scrollbox">
<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()">
<a class="close-custom" data-bind="click: cancelSearch">&times;</a>
<!-- <a class="btn btn-small pull-right searchCancelButton" data-bind="click: cancelSearch">
<i class="icon-remove"></i>
</a>-->
<span data-bind="text: messageListSearchDesc"></span>:
<span data-bind="text: messageListSearchDesc"></span>
</div>
<div class="listDragOver" data-bind="css: {'viewAppendArea': dragOver() && '' === messageListError() && !popupVisibility(), 'dragOverEnter': dragOverEnter }, initDom: dragOverArea">
<i class="icon-down e-icon"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="MESSAGE_LIST/PUT_MESSAGE_HERE"></span>
</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>
</div>
<div class="listError" data-bind="visible: !dragOver() && '' !== messageListError()">

View file

@ -18,15 +18,20 @@
<div class="dateParent actionHandle dragHandle">
<span class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></span>
</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">
<i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i>
</div>
<div class="senderParent actionHandle dragHandle">
<span class="replyFlag"><i class="icon-reply"></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>
&nbsp;
</div>

View file

@ -7,6 +7,14 @@
<div class="dateParent actionHandle dragHandle">
<span class="date" data-moment-format="SHORT" data-moment-format-title="FULL" data-bind="moment: dateTimeStampInUTC"></span>
</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">
<i class="checkboxMessage" data-bind="css: checked() ? 'checkboxMessage icon-checkbox-checked' : 'checkboxMessage icon-checkbox-unchecked'"></i>
</div>
@ -24,9 +32,6 @@
<div class="senderParent actionHandle dragHandle">
<span class="replyFlag"><i class="icon-reply"></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>
&nbsp;
</div>

View file

@ -188,64 +188,6 @@
</a>
</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>
</div>
@ -258,9 +200,6 @@
<span class="flagParent">
<i class="icon-star-empty flagOff" data-bind="css: {'icon-star flagOn': viewIsFlagged, 'icon-star-empty flagOff': !viewIsFlagged()}"></i>
</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>
<span class="subject" data-bind="text: viewSubject, title: viewSubject"></span>
<span class="i18n emptySubjectText" data-i18n="MESSAGE/EMPTY_SUBJECT_TEXT"></span>
@ -380,7 +319,8 @@
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()); }" />
</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() : []">
<li class="attachmentItem clearfix" draggable="true" data-tooltip-join="top"
data-bind="visible: !isLinked, event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }, css: {'checked': checked}">
@ -392,7 +332,7 @@
</div>
</div>
<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">
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
<i class="attachmentIconText attachmentMainIconText" data-bind="text: iconText()"></i>
@ -430,14 +370,43 @@
<i class="icon-cog controls-handle" data-bind="visible: allowAttachmnetControls() && !showAttachmnetControls(), click: function () { showAttachmnetControls(true) }"></i>
</div>
<div class="attachmentsControls"
data-bind="visible: showAttachmnetControls() && message() && message().hasVisibleAttachments()">
<i class="icon-file-zip"></i>
&nbsp;&nbsp;
<span class="g-ui-link">Download as zip</span>
&nbsp;&nbsp;&nbsp;&nbsp;
<i class="icon-cloud-up"></i>
&nbsp;&nbsp;
<span class="g-ui-link">Save to ownCloud</span>
data-bind="visible: showAttachmnetControls() && message() && message().hasVisibleAttachments()">
<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;
<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;
<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;
<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;"
data-bind="click: function () { showAttachmnetControls(false); }">×</button>
</div>

View file

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

View file

@ -12,12 +12,12 @@
<span class="i18n" data-i18n="SETTINGS_FOLDERS/DELETING_ASK"></span>
</a>
</td>
<td>
<td class="delete-folder-parent">
<span class="delete-folder" data-bind="visible: canBeDeleted() && !deleteAccess(), click: function (oFolder) { $root.folderForDeletion(oFolder); }">
<i class="icon-trash"></i>
</span>
</td>
<td>
<td class="subscribe-folder-parent">
<span class="unsubscribe-folder" data-bind="visible: canBeSubScribed() && !subScribed(), click: function(oFolder) { $root.subscribeFolder(oFolder); }">
<i class="icon-eye"></i>
</span>
@ -25,5 +25,13 @@
<i class="icon-eye"></i>
</span>
</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>
<!-- ko template: { name: 'SettingsFolderItem', foreach: subFolders } --><!-- /ko -->

View file

@ -1,37 +1,45 @@
<div class="b-settings-folders g-ui-user-select-none" data-bind="css: { 'ignore-folder-subscribe': !useImapSubscribe }">
<div class="form-horizontal">
<div class="legend">
<span class="i18n" data-i18n="SETTINGS_FOLDERS/LEGEND_FOLDERS"></span>
&nbsp;&nbsp;&nbsp;
<i class="icon-spinner animated" style="margin-top: 5px" data-bind="visible: loading"></i>
<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="legend">
<span class="i18n" data-i18n="SETTINGS_FOLDERS/LEGEND_FOLDERS"></span>
&nbsp;&nbsp;&nbsp;
<i class="icon-spinner animated" style="margin-top: 5px" data-bind="visible: loading"></i>
</div>
</div>
<a class="btn" data-bind="click: createFolder">
<i class="icon-folder-add"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="SETTINGS_FOLDERS/BUTTON_CREATE"></span>
</a>
&nbsp;&nbsp;
<a class="btn" data-bind="click: systemFolder">
<i class="icon-wrench"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="SETTINGS_FOLDERS/BUTTON_SYSTEM"></span>
</a>
<div class="alert folders-list-error" data-bind="visible: folderList.optimized">
<b class="i18n" data-i18n="SETTINGS_FOLDERS/TO_MANY_FOLDERS_DESC_1"></b>
<br />
<span class="i18n" data-i18n="SETTINGS_FOLDERS/TO_MANY_FOLDERS_DESC_2"></span>
</div>
<div class="alert folders-list-error" data-bind="visible: '' !== folderList.error()">
<button type="button" class="close" data-bind="click: function () { folderList.error(''); }">&times;</button>
<span data-bind="text: folderList.error"></span>
</div>
<table class="table table-hover list-table" data-bind="i18nUpdate: folderList" style="margin-top: 40px">
<colgroup>
<col />
<col style="width: 1%" />
<col style="width: 1%" />
<col style="width: 1%" />
</colgroup>
<tbody data-bind="template: { name: 'SettingsFolderItem', foreach: folderList }"></tbody>
</table>
</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>
<a class="btn" data-bind="click: createFolder">
<i class="icon-folder-add"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="SETTINGS_FOLDERS/BUTTON_CREATE"></span>
</a>
&nbsp;&nbsp;
<a class="btn" data-bind="click: systemFolder">
<i class="icon-wrench"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n="SETTINGS_FOLDERS/BUTTON_SYSTEM"></span>
</a>
<div class="alert folders-list-error" data-bind="visible: folderList.optimized">
<b class="i18n" data-i18n="SETTINGS_FOLDERS/TO_MANY_FOLDERS_DESC_1"></b>
<br />
<span class="i18n" data-i18n="SETTINGS_FOLDERS/TO_MANY_FOLDERS_DESC_2"></span>
</div>
<div class="alert folders-list-error" data-bind="visible: '' !== folderList.error()">
<button type="button" class="close" data-bind="click: function () { folderList.error(''); }">&times;</button>
<span data-bind="text: folderList.error"></span>
</div>
<table class="table table-hover list-table" data-bind="i18nUpdate: folderList" style="margin-top: 40px">
<colgroup>
<col />
<col style="width: 1%" />
<col style="width: 1%" />
</colgroup>
<tbody data-bind="template: { name: 'SettingsFolderItem', foreach: folderList }"></tbody>
</table>
</div>

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