diff --git a/dev/Common/Globals.js b/dev/Common/Globals.js index c70e1839b..f3ed2cad6 100644 --- a/dev/Common/Globals.js +++ b/dev/Common/Globals.js @@ -7,7 +7,6 @@ Globals = {}, window = require('window'), - is = require('is'), _ = require('_'), $ = require('$'), ko = require('ko'), @@ -69,14 +68,21 @@ Globals.bUnload = false; /** - * @type {boolean} + * @type {string} */ - Globals.bTabletDevice = is.tablet(); + Globals.sUserAgent = 'navigator' in window && 'userAgent' in window.navigator && + window.navigator.userAgent.toLowerCase() || ''; /** * @type {boolean} */ - Globals.bMobileDevice = is.mobile() || is.tablet(); + Globals.bMobileDevice = + /android/i.test(Globals.sUserAgent) || + /iphone/i.test(Globals.sUserAgent) || + /ipod/i.test(Globals.sUserAgent) || + /ipad/i.test(Globals.sUserAgent) || + /blackberry/i.test(Globals.sUserAgent) + ; /** * @type {boolean} diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 16f817244..eacc5c324 100644 --- a/dev/Common/Selector.js +++ b/dev/Common/Selector.js @@ -58,7 +58,7 @@ this.selectedItem(null); } } - else if (this.bAutoSelect && this.focusedItem()) + else if (this.autoSelect() && this.focusedItem()) { this.selectedItem(this.focusedItem()); } @@ -124,10 +124,10 @@ this.sItemFocusedSelector = sItemFocusedSelector; this.sLastUid = ''; - this.bAutoSelect = true; this.oCallbacks = {}; this.emptyFunction = function () {}; + this.emptyTrueFunction = function () { return true; }; this.focusedItem.subscribe(function (oItem) { if (oItem) @@ -221,7 +221,7 @@ this.selectedItemUseCallback = true; - if (!bChecked && !bSelected && this.bAutoSelect) + if (!bChecked && !bSelected && this.autoSelect()) { if (self.focusedItem()) { @@ -392,9 +392,12 @@ } }; - Selector.prototype.autoSelect = function (bValue) + /** + * @returns {boolean} + */ + Selector.prototype.autoSelect = function () { - this.bAutoSelect = !!bValue; + return !!(this.oCallbacks['onAutoSelect'] || this.emptyTrueFunction)(); }; /** @@ -540,7 +543,7 @@ } } - if ((this.bAutoSelect || !!bForceSelect) && + if ((this.autoSelect() || !!bForceSelect) && !this.isListChecked() && Enums.EventKeyCode.Space !== iEventKeyCode) { this.selectedItem(oResult); diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index 6c6f8e8ab..fd3621467 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -244,9 +244,7 @@ */ Utils.encodeHtml = function (sText) { - return Utils.isNormal(sText) ? sText.toString() - .replace(/&/g, '&').replace(//g, '>') - .replace(/"/g, '"').replace(/'/g, ''') : ''; + return Utils.isNormal(sText) ? _.escape(sText.toString()) : ''; }; /** @@ -512,7 +510,7 @@ /** * @param {?} oEvent */ - Utils.killCtrlAandS = function (oEvent) + Utils.kill_CtrlA_CtrlS = function (oEvent) { oEvent = oEvent || window.event; if (oEvent && oEvent.ctrlKey && !oEvent.shiftKey && !oEvent.altKey) @@ -944,7 +942,7 @@ fixAttibuteValue = function () { return (arguments && 1 < arguments.length) ? - '' + arguments[1] + arguments[2].replace(//g, '>') : ''; + '' + arguments[1] + _.escape(arguments[2]) : ''; }, convertLinks = function () { @@ -1550,6 +1548,36 @@ } }; + /** + * @param {string} sStr + * @return {string} + */ + Utils.FullHtmlEncode = function (sStr) + { + var + iIndex = 0, + sChr = '', + iChrCode = 0, + sResult = ''; + ; + + for (iIndex = 0; iIndex < sStr.length; iIndex++) + { + sChr = sStr[iIndex]; + if (-1 < Utils.inArray(sChr, ['&', '<', '>', '"', '`', '\''])) + { + sResult += _.escape(sChr); + } + else + { + iChrCode = sChr.charCodeAt(0); + sResult += (iChrCode > 128) ? '' + iChrCode + ';' : sChr; + } + } + + return sResult; + }; + /** * @param {Object|Array} mObjectOrObjects */ diff --git a/dev/Remote/AbstractAjax.js b/dev/Remote/AbstractAjax.js index b4a22123f..20811ce43 100644 --- a/dev/Remote/AbstractAjax.js +++ b/dev/Remote/AbstractAjax.js @@ -63,7 +63,7 @@ if (Consts.Values.TokenErrorLimit < Globals.iTokenErrorCount) { - if (Globals.__APP__) + if (Globals.__APP__ && Globals.__APP__.loginAndLogoutReload) { Globals.__APP__.loginAndLogoutReload(true); } @@ -71,11 +71,11 @@ if (oData.ClearAuth || oData.Logout || Consts.Values.AjaxErrorLimit < Globals.iAjaxErrorCount) { - if (Globals.__APP__) + if (Globals.__APP__ && Globals.__APP__.clearClientSideToken) { Globals.__APP__.clearClientSideToken(); - if (!oData.ClearAuth) + if (!oData.ClearAuth && Globals.__APP__.loginAndLogoutReload) { Globals.__APP__.loginAndLogoutReload(true); } diff --git a/dev/View/Popup/Account.js b/dev/View/Popup/Account.js index c8ddaa296..58af7fc5a 100644 --- a/dev/View/Popup/Account.js +++ b/dev/View/Popup/Account.js @@ -43,6 +43,7 @@ this.submitRequest = ko.observable(false); this.submitError = ko.observable(''); + this.submitErrorAdditional = ko.observable(''); this.emailFocus = ko.observable(false); @@ -61,21 +62,28 @@ Remote.accountSetup(_.bind(function (sResult, oData) { this.submitRequest(false); - if (Enums.StorageResultType.Success === sResult && oData && 'AccountSetup' === oData.Action) + if (Enums.StorageResultType.Success === sResult && oData) { if (oData.Result) { require('App/User').accountsAndIdentities(); this.cancelCommand(); } - else if (oData.ErrorCode) + else { - this.submitError(Translator.getNotification(oData.ErrorCode)); + this.submitError(oData.ErrorCode ? Translator.getNotification(oData.ErrorCode) : + Translator.getNotification(Enums.Notification.UnknownError)); + + if (oData.ErrorMessageAdditional) + { + this.submitErrorAdditional(oData.ErrorMessageAdditional); + } } } else { this.submitError(Translator.getNotification(Enums.Notification.UnknownError)); + this.submitErrorAdditional(''); } }, this), this.email(), this.password(), this.isNew()); @@ -104,6 +112,7 @@ this.submitRequest(false); this.submitError(''); + this.submitErrorAdditional(''); }; AccountPopupView.prototype.onShow = function (oAccount) diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index fff46919e..8111776e6 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -251,15 +251,9 @@ return oMessage ? oMessage.generateUid() : ''; }); - MessageStore.messageListEndHash.subscribe(function () { - this.selector.scrollToTop(); - }, this); - - SettingsStore.layout.subscribe(function (mValue) { - this.selector.autoSelect(Enums.Layout.NoPreview !== mValue); - }, this); - - SettingsStore.layout.valueHasMutated(); + this.selector.on('onAutoSelect', _.bind(function () { + return this.useAutoSelect(); + }, this)); Events .sub('mailbox.message-list.selector.go-down', function () { @@ -270,6 +264,10 @@ }, this) ; + MessageStore.messageListEndHash.subscribe(function () { + this.selector.scrollToTop(); + }, this); + kn.constructorEnd(this); } @@ -281,6 +279,16 @@ */ MessageListMailBoxUserView.prototype.emptySubjectValue = ''; + MessageListMailBoxUserView.prototype.useAutoSelect = function () + { + if (/is:unseen/.test(this.mainMessageListSearch())) + { + return false; + } + + return Enums.Layout.NoPreview !== SettingsStore.layout(); + }; + MessageListMailBoxUserView.prototype.searchEnterAction = function () { this.mainMessageListSearch(this.sLastSearchValue); @@ -721,6 +729,14 @@ return false; }); + key('enter', Enums.KeyState.MessageList, function () { + if (self.message() && self.useAutoSelect()) + { + Events.pub('mailbox.message-view.toggle-full-screen'); + return false; + } + }); + // archive (zip) key('z', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { self.archiveCommand(); diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 921b14cf4..42582fa8e 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -323,6 +323,10 @@ Events.pub('mailbox.message-list.selector.go-down'); }); + Events.sub('mailbox.message-view.toggle-full-screen', function () { + this.toggleFullScreen(); + }, this); + kn.constructorEnd(this); } @@ -581,6 +585,11 @@ if (this.fullScreenMode()) { this.fullScreenMode(false); + + if (Enums.Layout.NoPreview !== this.layout()) + { + this.message.focused(false); + } } else if (Enums.Layout.NoPreview === this.layout()) { @@ -610,14 +619,6 @@ return false; }); - key('enter', Enums.KeyState.MessageList, function () { - if (Enums.Layout.NoPreview !== self.layout() && self.message()) - { - self.toggleFullScreen(); - return false; - } - }); - // reply key('r', [Enums.KeyState.MessageList, Enums.KeyState.MessageView], function () { if (MessageStore.message()) diff --git a/dev/bootstrap.js b/dev/bootstrap.js index 8bc540e19..2572f95fa 100644 --- a/dev/bootstrap.js +++ b/dev/bootstrap.js @@ -22,8 +22,8 @@ Globals.__APP__ = App; Globals.$win - .keydown(Utils.killCtrlAandS) - .keyup(Utils.killCtrlAandS) + .keydown(Utils.kill_CtrlA_CtrlS) + .keyup(Utils.kill_CtrlA_CtrlS) .unload(function () { Globals.bUnload = true; }) @@ -45,7 +45,7 @@ window['rl']['createCommand'] = Utils.createCommand; window['rl']['addSettingsViewModel'] = _.bind(Plugins.addSettingsViewModel, Plugins); - + window['rl']['pluginRemoteRequest'] = _.bind(Plugins.remoteRequest, Plugins); window['rl']['pluginSettingsGet'] = _.bind(Plugins.settingsGet, Plugins); diff --git a/package.json b/package.json index bd3a765f6..9787cf277 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "RainLoop", "title": "RainLoop Webmail", "version": "1.8.1", - "release": "272", + "release": "274", "description": "Simple, modern & fast web-based email client", "homepage": "http://rainloop.net", "main": "gulpfile.js", diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php index edf5cd91d..e8d41c384 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Utils.php @@ -17,6 +17,22 @@ namespace MailSo\Base; */ class Utils { + /** + * @var string + */ + static $sValidUtf8Regexp = <<<'END' +/ + ( + (?: [\x00-\x7F] # single-byte sequences 0xxxxxxx + | [\xC0-\xDF][\x80-\xBF] # double-byte sequences 110xxxxx 10xxxxxx + | [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences 1110xxxx 10xxxxxx * 2 + | [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3 + ){1,100} # ...one or more times + ) +| . # anything else +/x +END; + /** * @var array */ @@ -1432,15 +1448,7 @@ class Utils return $sUtfString; } - $sUtfString = \preg_replace( - '/[\x00-\x08\x10\x0B\x0C\x0E-\x1F\x7F]'. - '|[\x00-\x7F][\x80-\xBF]+'. - '|([\xC0\xC1]|[\xF0-\xFF])[\x80-\xBF]*'. - '|[\xC2-\xDF]((?![\x80-\xBF])|[\x80-\xBF]{2,})'. - '|[\xE0-\xEF](([\x80-\xBF](?![\x80-\xBF]))|(?![\x80-\xBF]{2})|[\x80-\xBF]{3,})/S', - $sReplaceOn, - $sUtfString - ); + $sUtfString = \preg_replace(\MailSo\Base\Utils::$sValidUtf8Regexp, '$1', $sUtfString); $sUtfString = \preg_replace( '/\xE0[\x80-\x9F][\x80-\xBF]'. @@ -1450,15 +1458,16 @@ class Utils $sUtfString = \preg_replace('/\xEF\xBF\xBD/', '?', $sUtfString); $sNewUtfString = false; - if (\MailSo\Base\Utils::IsIconvSupported()) - { - $sNewUtfString = \MailSo\Base\Utils::IconvConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); - } - else if (\MailSo\Base\Utils::IsMbStringSupported()) + if (false === $sNewUtfString && \MailSo\Base\Utils::IsMbStringSupported()) { $sNewUtfString = \MailSo\Base\Utils::MbConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); } + if (false === $sNewUtfString && \MailSo\Base\Utils::IsIconvSupported()) + { + $sNewUtfString = \MailSo\Base\Utils::IconvConvertEncoding($sUtfString, 'UTF-8', 'UTF-8'); + } + if (false !== $sNewUtfString) { $sUtfString = $sNewUtfString; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php index a6619b7d6..df84926c5 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Imap/ImapClient.php @@ -1185,7 +1185,7 @@ class ImapClient extends \MailSo\Net\NetClient foreach ($aResponse as /* @var $oImapResponse \MailSo\Imap\Response */ $oImapResponse) { if (\MailSo\Imap\Enumerations\ResponseType::UNTAGGED === $oImapResponse->ResponseType - && 'ESEARCH' === $oImapResponse->StatusOrIndex + && ('ESEARCH' === $oImapResponse->StatusOrIndex || 'ESORT' === $oImapResponse->StatusOrIndex) && \is_array($oImapResponse->ResponseList) && isset($oImapResponse->ResponseList[2], $oImapResponse->ResponseList[2][0], $oImapResponse->ResponseList[2][1]) && 'TAG' === $oImapResponse->ResponseList[2][0] && $sRequestTag === $oImapResponse->ResponseList[2][1] diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index fb016c164..ecacf8f1c 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -2156,6 +2156,9 @@ class Actions 1 < \count($aOrder['Accounts'])) { $aAccounts = \array_merge(\array_flip($aOrder['Accounts']), $aAccounts); + $aAccounts = \array_filter($aAccounts, function ($sHash) { + return 5 < \strlen($sHash); + }); } } diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageList.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageList.html index 1e84b635c..648e6b60f 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageList.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageList.html @@ -131,9 +131,10 @@