diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js index 5eb1ebd46..6fe033604 100644 --- a/dev/App/Abstract.js +++ b/dev/App/Abstract.js @@ -92,6 +92,10 @@ Globals.$html.removeClass('rl-ctrl-key-pressed'); } }); + + Globals.$doc.on('mousemove keypress click', _.debounce(function () { + Events.pub('rl.auto-logout-refresh'); + }, 5000)); } _.extend(AbstractApp.prototype, AbstractBoot.prototype); diff --git a/dev/App/User.js b/dev/App/User.js index bce396df3..2e0892ea6 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -1398,6 +1398,15 @@ }); }; + AppUser.prototype.logout = function () + { + var self = this; + Remote.logout(function () { + self.loginAndLogoutReload(true, + Settings.settingsGet('ParentEmail') && 0 < Settings.settingsGet('ParentEmail').length); + }); + }; + AppUser.prototype.bootstartLoginScreen = function () { Globals.$html.removeClass('rl-user-auth').addClass('rl-user-no-auth'); @@ -1568,6 +1577,10 @@ Remote.appDelayStart(Utils.emptyFunction); }, 35000); + Events.sub('rl.auto-logout', function () { + self.logout(); + }); + Plugins.runHook('rl-start-user-screens'); Events.pub('rl.bootstart-user-screens'); diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index 77851e32e..a732ffd5a 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -263,7 +263,8 @@ Enums.Layout = { 'NoPreview': 0, 'SidePreview': 1, - 'BottomPreview': 2 + 'BottomPreview': 2, + 'Mobile': 3 }; /** diff --git a/dev/Screen/User/Settings.js b/dev/Screen/User/Settings.js index 259cc2a72..0f80ee3cb 100644 --- a/dev/Screen/User/Settings.js +++ b/dev/Screen/User/Settings.js @@ -63,11 +63,8 @@ 'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters'); } - if (Settings.capa(Enums.Capa.TwoFactor)) - { - kn.addSettingsViewModel(require('Settings/User/Security'), - 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security'); - } + kn.addSettingsViewModel(require('Settings/User/Security'), + 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security'); if ((Settings.settingsGet('AllowGoogleSocial') && Settings.settingsGet('AllowGoogleSocialAuth')) || Settings.settingsGet('AllowFacebookSocial') || diff --git a/dev/Settings/User/Security.js b/dev/Settings/User/Security.js index e8d4cf8a2..e4f119a36 100644 --- a/dev/Settings/User/Security.js +++ b/dev/Settings/User/Security.js @@ -11,6 +11,10 @@ Utils = require('Common/Utils'), Translator = require('Common/Translator'), + SettinsStore = require('Stores/User/Settings'), + + Settings = require('Storage/Settings'), + Remote = require('Storage/User/Remote') ; @@ -19,6 +23,22 @@ */ function SecurityUserSettings() { + this.capaTwoFactor = Settings.capa(Enums.Capa.TwoFactor); + + this.autoLogout = SettinsStore.autoLogout; + this.autoLogout.trigger = ko.observable(Enums.SaveSettingsStep.Idle); + + this.autoLogoutOptions = ko.computed(function () { + Translator.trigger(); + return [ + {'id': 0, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_NEVER_OPTION_NAME')}, + {'id': 5, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 5})}, + {'id': 10, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 10})}, + {'id': 30, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 30})}, + {'id': 60, 'name': Translator.i18n('SETTINGS_SECURITY/AUTOLOGIN_MINUTES_OPTION_NAME', {'MINUTES': 60})} + ]; + }); + this.processing = ko.observable(false); this.clearing = ko.observable(false); this.secreting = ko.observable(false); @@ -186,8 +206,27 @@ SecurityUserSettings.prototype.onBuild = function () { - this.processing(true); - Remote.getTwoFactor(this.onResult); + if (this.capaTwoFactor) + { + this.processing(true); + Remote.getTwoFactor(this.onResult); + } + + var self = this; + + _.delay(function () { + + var + f0 = Utils.settingsSaveHelperSimpleFunction(self.autoLogout.trigger, self) + ; + + self.autoLogout.subscribe(function (sValue) { + Remote.saveSettings(f0, { + 'AutoLogout': Utils.pInt(sValue) + }); + }); + + }); }; module.exports = SecurityUserSettings; diff --git a/dev/Stores/User/Account.js b/dev/Stores/User/Account.js index d562ccde6..02544f715 100644 --- a/dev/Stores/User/Account.js +++ b/dev/Stores/User/Account.js @@ -24,6 +24,11 @@ this.accounts = ko.observableArray([]); this.accounts.loading = ko.observable(false).extend({'throttle': 100}); + this.computers(); + } + + AccountUserStore.prototype.computers = function () + { this.accountsEmails = ko.computed(function () { return _.compact(_.map(this.accounts(), function (oItem) { return oItem ? oItem.email : null; @@ -44,7 +49,7 @@ return iResult; }, this); - } + }; AccountUserStore.prototype.populate = function () { diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index 55fc3f117..6c6fa5894 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -23,18 +23,18 @@ this.trashFolder = ko.observable(''); this.archiveFolder = ko.observable(''); - this.computed(); - this.subscribe(); + this.computers(); + this.subscribers(); } - FolderUserStore.prototype.computed = function () + FolderUserStore.prototype.computers = function () { this.draftFolderNotEnabled = ko.computed(function () { return '' === this.draftFolder() || Consts.Values.UnuseOptionValue === this.draftFolder(); }, this); }; - FolderUserStore.prototype.subscribe = function () + FolderUserStore.prototype.subscribers = function () { var fRemoveSystemFolderType = function (observable) { diff --git a/dev/Stores/User/Notification.js b/dev/Stores/User/Notification.js index f6be376a7..08dff8ce8 100644 --- a/dev/Stores/User/Notification.js +++ b/dev/Stores/User/Notification.js @@ -126,12 +126,12 @@ }; } - this.computedProperies(); + this.computers(); this.initNotificationPlayer(); } - NotificationUserStore.prototype.computedProperies = function () + NotificationUserStore.prototype.computers = function () { this.isDesktopNotificationSupported = ko.computed(function () { return Enums.DesktopNotification.NotSupported !== this.desktopNotificationPermissions(); diff --git a/dev/Stores/User/Settings.js b/dev/Stores/User/Settings.js index 1e566af96..0534f3914 100644 --- a/dev/Stores/User/Settings.js +++ b/dev/Stores/User/Settings.js @@ -4,6 +4,7 @@ 'use strict'; var + window = require('window'), ko = require('ko'), Consts = require('Common/Consts'), @@ -20,6 +21,8 @@ */ function SettingsUserStore() { + this.iAutoLogoutTimer = 0; + this.layout = ko.observable(Enums.Layout.SidePreview) .extend({'limitedList': [ Enums.Layout.SidePreview, Enums.Layout.BottomPreview, Enums.Layout.NoPreview @@ -39,24 +42,27 @@ this.useThreads = ko.observable(false); this.replySameFolder = ko.observable(false); - this.computedProperies(); - this.subscribes(); + this.autoLogout = ko.observable(30); + + this.computers(); + this.subscribers(); } - SettingsUserStore.prototype.computedProperies = function () + SettingsUserStore.prototype.computers = function () { this.usePreviewPane = ko.computed(function () { return Enums.Layout.NoPreview !== this.layout(); }, this); }; - SettingsUserStore.prototype.subscribes = function () + SettingsUserStore.prototype.subscribers = function () { this.layout.subscribe(function (nValue) { Globals.$html.toggleClass('rl-no-preview-pane', Enums.Layout.NoPreview === nValue); Globals.$html.toggleClass('rl-side-preview-pane', Enums.Layout.SidePreview === nValue); Globals.$html.toggleClass('rl-bottom-preview-pane', Enums.Layout.BottomPreview === nValue); + Globals.$html.toggleClass('rl-mobile-layout', Enums.Layout.Mobile === nValue); Events.pub('layout', [nValue]); }); @@ -67,12 +73,27 @@ this.layout(Utils.pInt(Settings.settingsGet('Layout'))); this.editorDefaultType(Settings.settingsGet('EditorDefaultType')); + this.autoLogout(Utils.pInt(Settings.settingsGet('AutoLogout'))); this.messagesPerPage(Settings.settingsGet('MPP')); this.showImages(!!Settings.settingsGet('ShowImages')); this.useCheckboxesInList(!!Settings.settingsGet('UseCheckboxesInList')); this.useThreads(!!Settings.settingsGet('UseThreads')); this.replySameFolder(!!Settings.settingsGet('ReplySameFolder')); + + var self = this; + + Events.sub('rl.auto-logout-refresh', function () { + window.clearTimeout(self.iAutoLogoutTimer); + if (0 < self.autoLogout()) + { + self.iAutoLogoutTimer = window.setTimeout(function () { + Events.pub('rl.auto-logout'); + }, self.autoLogout() * 1000 * 60); + } + }); + + Events.pub('rl.auto-logout-refresh'); }; module.exports = new SettingsUserStore(); diff --git a/dev/Stores/User/Template.js b/dev/Stores/User/Template.js index cfaff8986..0e123a0ea 100644 --- a/dev/Stores/User/Template.js +++ b/dev/Stores/User/Template.js @@ -21,6 +21,11 @@ this.templatesNames = ko.observableArray([]).extend({'throttle': 1000}); this.templatesNames.skipFirst = true; + this.subscribers(); + } + + TemplateUserStore.prototype.subscribers = function () + { this.templates.subscribe(function (aList) { this.templatesNames(_.compact(_.map(aList, function (oItem) { return oItem ? oItem.name : null; @@ -37,7 +42,7 @@ // Remote.templatesSortOrder(null, aList); // } // }, this); - } + }; module.exports = new TemplateUserStore(); diff --git a/dev/Styles/SettingsAccounts.less b/dev/Styles/SettingsAccounts.less index 3d1dbee49..7c6cd63f6 100644 --- a/dev/Styles/SettingsAccounts.less +++ b/dev/Styles/SettingsAccounts.less @@ -36,6 +36,11 @@ line-height: 22px; } + .identity-default { + cursor: pointer; + color: #ccc; + } + .identity-name { display: inline-block; word-break: break-all; diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 5b61227f0..c72aec14b 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -1321,7 +1321,7 @@ return false; }); - if (!!Settings.settingsGet('AllowСtrlEnterOnCompose')) + if (!!Settings.settingsGet('AllowCtrlEnterOnCompose')) { key('ctrl+enter, command+enter', Enums.KeyState.Compose, function () { self.sendCommand(); diff --git a/dev/View/User/AbstractSystemDropDown.js b/dev/View/User/AbstractSystemDropDown.js index 6fbd735b8..e8a5ac5f6 100644 --- a/dev/View/User/AbstractSystemDropDown.js +++ b/dev/View/User/AbstractSystemDropDown.js @@ -15,7 +15,6 @@ AccountStore = require('Stores/User/Account'), Settings = require('Storage/Settings'), - Remote = require('Storage/User/Remote'), AbstractView = require('Knoin/AbstractView') ; @@ -80,10 +79,7 @@ AbstractSystemDropDownUserView.prototype.logoutClick = function () { - Remote.logout(function () { - require('App/User').loginAndLogoutReload(true, - Settings.settingsGet('ParentEmail') && 0 < Settings.settingsGet('ParentEmail').length); - }); + require('App/User').logout(); }; AbstractSystemDropDownUserView.prototype.onBuild = function () 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 19b3cd331..207da5bb9 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -1269,7 +1269,7 @@ class Actions 'AllowAdminPanel' => (bool) $oConfig->Get('security', 'allow_admin_panel', true), 'AllowHtmlEditorSourceButton' => (bool) $oConfig->Get('labs', 'allow_html_editor_source_button', false), 'AllowHtmlEditorBitiButtons' => (bool) $oConfig->Get('labs', 'allow_html_editor_biti_buttons', false), - 'AllowСtrlEnterOnCompose' => (bool) $oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false), + 'AllowCtrlEnterOnCompose' => (bool) $oConfig->Get('labs', 'allow_ctrl_enter_on_compose', false), 'UseRsaEncryption' => (bool) $oConfig->Get('security', 'use_rsa_encryption', false), 'RsaPublicKey' => '', 'HideDangerousActions' => $oConfig->Get('labs', 'hide_dangerous_actions', false), @@ -1546,10 +1546,10 @@ class Actions $aResult['Layout'] = (int) $oConfig->Get('defaults', 'view_layout', \RainLoop\Enumerations\Layout::SIDE_PREVIEW); $aResult['EditorDefaultType'] = (string) $oConfig->Get('defaults', 'view_editor_type', ''); $aResult['UseCheckboxesInList'] = (bool) $oConfig->Get('defaults', 'view_use_checkboxes', true); + $aResult['AutoLogout'] = (int) $oConfig->Get('defaults', 'autologout', 30); $aResult['UseThreads'] = (bool) $oConfig->Get('defaults', 'mail_use_threads', false); $aResult['ReplySameFolder'] = (bool) $oConfig->Get('defaults', 'mail_reply_same_folder', false); $aResult['ContactsAutosave'] = (bool) $oConfig->Get('defaults', 'contacts_autosave', true); - $aResult['Signature'] = ''; $aResult['EnableTwoFactor'] = false; $aResult['ParentEmail'] = ''; $aResult['InterfaceAnimation'] = true; @@ -1583,13 +1583,12 @@ class Actions $aResult['SoundNotification'] = (bool) $oSettings->GetConf('SoundNotification', $aResult['SoundNotification']); $aResult['DesktopNotifications'] = (bool) $oSettings->GetConf('DesktopNotifications', $aResult['DesktopNotifications']); $aResult['UseCheckboxesInList'] = (bool) $oSettings->GetConf('UseCheckboxesInList', $aResult['UseCheckboxesInList']); + $aResult['AutoLogout'] = (int) $oSettings->GetConf('AutoLogout', $aResult['AutoLogout']); $aResult['Layout'] = (int) $oSettings->GetConf('Layout', $aResult['Layout']); $aResult['UseThreads'] = (bool) $oSettingsLocal->GetConf('UseThreads', $aResult['UseThreads']); $aResult['ReplySameFolder'] = (bool) $oSettingsLocal->GetConf('ReplySameFolder', $aResult['ReplySameFolder']); - $aResult['Signature'] = $oSettingsLocal->GetConf('Signature', $aResult['Signature']); - if ($this->GetCapa(false, \RainLoop\Enumerations\Capa::USER_BACKGROUND, $oAccount)) { $aResult['UserBackgroundName'] = (string) $oSettings->GetConf('UserBackgroundName', $aResult['UserBackgroundName']); @@ -4681,14 +4680,13 @@ class Actions $this->setSettingsFromParams($oSettings, 'DesktopNotifications', 'bool'); $this->setSettingsFromParams($oSettings, 'SoundNotification', 'bool'); $this->setSettingsFromParams($oSettings, 'UseCheckboxesInList', 'bool'); + $this->setSettingsFromParams($oSettings, 'AutoLogout', 'int'); $this->setSettingsFromParams($oSettings, 'EnableTwoFactor', 'bool'); $this->setSettingsFromParams($oSettingsLocal, 'UseThreads', 'bool'); $this->setSettingsFromParams($oSettingsLocal, 'ReplySameFolder', 'bool'); - $this->setSettingsFromParams($oSettingsLocal, 'Signature', 'string'); - return $this->DefaultResponse(__FUNCTION__, $this->SettingsProvider()->Save($oAccount, $oSettings) && $this->SettingsProvider(true)->Save($oAccount, $oSettingsLocal)); diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php index 571926be2..d8eccb6d9 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Config/Application.php @@ -164,6 +164,7 @@ Values: 'view_editor_type' => array('Html', 'Editor mode used by default (Plain, Html, HtmlForced or PlainForced)'), 'view_layout' => array(1, 'layout: 0 - no preview, 1 - side preview, 3 - bottom preview'), 'view_use_checkboxes' => array(true), + 'autologout' => array(30), 'show_images' => array(false), 'contacts_autosave' => array(true), 'mail_use_threads' => array(false), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php index ff541c4ab..a72d8fdec 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/ServiceActions.php @@ -1225,6 +1225,29 @@ class ServiceActions return $bJsOutput ? 'window.rainloopTEMPLATES='.\MailSo\Base\Utils::Php2js(array($sHtml), $this->Logger()).';' : $sHtml; } + /** + * @param string $sLanguage + * + * @return string + */ + private function convertLanguageNameToMomentLanguageName($sLanguage) + { + switch ($sLanguage) + { + case 'pt-pt': + $sLanguage = 'pt'; + break; + case 'ja-jp': + $sLanguage = 'ja'; + break; + case 'ko-kr': + $sLanguage = 'ko'; + break; + } + + return $sLanguage; + } + /** * @param string $sLanguage * @param bool $bWrapByScriptTag = true @@ -1236,7 +1259,9 @@ class ServiceActions $aResultLang = array(); $sMoment = 'window.moment && window.moment.lang && window.moment.lang(\'en\');'; - $sMomentFileName = APP_VERSION_ROOT_PATH.'app/i18n/moment/'.$sLanguage.'.js'; + $sMomentFileName = APP_VERSION_ROOT_PATH.'app/i18n/moment/'. + $this->convertLanguageNameToMomentLanguageName($sLanguage).'.js'; + if (\file_exists($sMomentFileName)) { $sMoment = \file_get_contents($sMomentFileName); diff --git a/rainloop/v/0.0.0/app/templates/Views/User/SettingsAccounts.html b/rainloop/v/0.0.0/app/templates/Views/User/SettingsAccounts.html index 8e797050a..ff0b2f7aa 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/SettingsAccounts.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/SettingsAccounts.html @@ -72,6 +72,10 @@ + + + () +