diff --git a/README.md b/README.md index d5e5b2dcb..0d358d727 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/build/langs.php b/build/langs.php index 9d6248989..339c8bc88 100644 --- a/build/langs.php +++ b/build/langs.php @@ -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"; diff --git a/build/test.php b/build/test.php new file mode 100644 index 000000000..e5e1422dd --- /dev/null +++ b/build/test.php @@ -0,0 +1,19 @@ +'); 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); }); diff --git a/dev/Common/HtmlEditor.js b/dev/Common/HtmlEditor.js index 27812bd7b..5653c675d 100644 --- a/dev/Common/HtmlEditor.js +++ b/dev/Common/HtmlEditor.js @@ -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(); - }); - } + } + + }); } ; diff --git a/dev/Common/Links.js b/dev/Common/Links.js index da26296b0..ecabdc80b 100644 --- a/dev/Common/Links.js +++ b/dev/Common/Links.js @@ -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(/[\/]+$/, ''); diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 05ebf76bf..be7ed462c 100644 --- a/dev/Common/Selector.js +++ b/dev/Common/Selector.js @@ -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) { diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index dfa183e55..daf84d133 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -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(/&/gi, '&') ; - sText = splitPlainText(Utils.trim(sText)); + sText = Utils.splitPlainText(Utils.trim(sText)); iPos = 0; iLimit = 800; diff --git a/dev/Component/AbstracCheckbox.js b/dev/Component/AbstracCheckbox.js index 7bb6533b5..8101ab3bd 100644 --- a/dev/Component/AbstracCheckbox.js +++ b/dev/Component/AbstracCheckbox.js @@ -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; diff --git a/dev/External/Opentip.js b/dev/External/Opentip.js index cf7ad5d3f..fb6123067 100644 --- a/dev/External/Opentip.js +++ b/dev/External/Opentip.js @@ -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' }; diff --git a/dev/External/ko.js b/dev/External/ko.js index c0844364d..e8fab34f1 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -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) { diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index c8052eda5..42744c49f 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -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); diff --git a/dev/Model/Attachment.js b/dev/Model/Attachment.js index 3b126565d..0534494c1 100644 --- a/dev/Model/Attachment.js +++ b/dev/Model/Attachment.js @@ -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; diff --git a/dev/Model/ComposeAttachment.js b/dev/Model/ComposeAttachment.js index d9a470135..48d42e9d0 100644 --- a/dev/Model/ComposeAttachment.js +++ b/dev/Model/ComposeAttachment.js @@ -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; diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 63440bc2e..173ed2a80 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -66,7 +66,7 @@ }; /** - * @returns {boolean} + * @return {boolean} */ EmailModel.prototype.validate = function () { diff --git a/dev/Model/Folder.js b/dev/Model/Folder.js index 668fec202..68a71214e 100644 --- a/dev/Model/Folder.js +++ b/dev/Model/Folder.js @@ -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; diff --git a/dev/Model/Message.js b/dev/Model/Message.js index c9f89d51e..f05d1c3e5 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -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(); } }; diff --git a/dev/Model/MessageFull.js b/dev/Model/MessageFull.js index 92bfa5623..88c185ea6 100644 --- a/dev/Model/MessageFull.js +++ b/dev/Model/MessageFull.js @@ -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; } diff --git a/dev/Model/MessageSimple.js b/dev/Model/MessageSimple.js index ed994eebd..d4eb52d23 100644 --- a/dev/Model/MessageSimple.js +++ b/dev/Model/MessageSimple.js @@ -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 diff --git a/dev/Promises/User/Ajax.js b/dev/Promises/User/Ajax.js index 4051aa901..6cc9de4ba 100644 --- a/dev/Promises/User/Ajax.js +++ b/dev/Promises/User/Ajax.js @@ -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'); diff --git a/dev/Remote/User/Ajax.js b/dev/Remote/User/Ajax.js index 20ee13045..e5ae6c606 100644 --- a/dev/Remote/User/Ajax.js +++ b/dev/Remote/User/Ajax.js @@ -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 diff --git a/dev/Screen/AbstractSettings.js b/dev/Screen/AbstractSettings.js index 0727236f4..27d179cd2 100644 --- a/dev/Screen/AbstractSettings.js +++ b/dev/Screen/AbstractSettings.js @@ -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); diff --git a/dev/Screen/User/MailBox.js b/dev/Screen/User/MailBox.js index 64bb526cf..a08625507 100644 --- a/dev/Screen/User/MailBox.js +++ b/dev/Screen/User/MailBox.js @@ -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}] ]; }; diff --git a/dev/Settings/Admin/Branding.js b/dev/Settings/Admin/Branding.js index 748a68d79..b76661ebe 100644 --- a/dev/Settings/Admin/Branding.js +++ b/dev/Settings/Admin/Branding.js @@ -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; diff --git a/dev/Settings/Admin/Licensing.js b/dev/Settings/Admin/Licensing.js index f8cf74c71..15bd709f5 100644 --- a/dev/Settings/Admin/Licensing.js +++ b/dev/Settings/Admin/Licensing.js @@ -57,7 +57,7 @@ }; /** - * @returns {boolean} + * @return {boolean} */ LicensingAdminSettings.prototype.licenseIsUnlim = function () { @@ -65,7 +65,7 @@ }; /** - * @returns {string} + * @return {string} */ LicensingAdminSettings.prototype.licenseExpiredMomentValue = function () { diff --git a/dev/Settings/User/Folders.js b/dev/Settings/User/Folders.js index c5812c9ed..7886eb87a 100644 --- a/dev/Settings/User/Folders.js +++ b/dev/Settings/User/Folders.js @@ -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; }()); \ No newline at end of file diff --git a/dev/Stores/Language.js b/dev/Stores/Language.js index 2659f42a0..4b3b1aa9b 100644 --- a/dev/Stores/Language.js +++ b/dev/Stores/Language.js @@ -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 () diff --git a/dev/Stores/User/Account.js b/dev/Stores/User/Account.js index a03007817..daf93bee0 100644 --- a/dev/Stores/User/Account.js +++ b/dev/Stores/User/Account.js @@ -59,7 +59,7 @@ }; /** - * @returns {boolean} + * @return {boolean} */ AccountUserStore.prototype.isRootAccount = function () { diff --git a/dev/Stores/User/App.js b/dev/Stores/User/App.js index e42fb074d..80b295ed1 100644 --- a/dev/Stores/User/App.js +++ b/dev/Stores/User/App.js @@ -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'); }; diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index dfa5c6b66..68b87d794 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -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(); diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 2a93eaa24..b9e2331bb 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -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); diff --git a/dev/Stores/User/Notification.js b/dev/Stores/User/Notification.js index b35c59ebd..e17e10821 100644 --- a/dev/Stores/User/Notification.js +++ b/dev/Stores/User/Notification.js @@ -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(); } diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index 979ab8976..a6e62e6e0 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -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) { diff --git a/dev/Styles/AdminGeneral.less b/dev/Styles/AdminGeneral.less index 79bb229a1..68ee6dd23 100644 --- a/dev/Styles/AdminGeneral.less +++ b/dev/Styles/AdminGeneral.less @@ -6,8 +6,15 @@ } .flag-name { + border-bottom: 1px dashed #555; cursor: pointer; + padding: 2px; + + &:focus { + outline: 1px; + outline-style: dotted; + } } } diff --git a/dev/Styles/Animations.less b/dev/Styles/Animations.less index 968d2fa0a..fb9940869 100644 --- a/dev/Styles/Animations.less +++ b/dev/Styles/Animations.less @@ -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); }*/ diff --git a/dev/Styles/Attachmnets.less b/dev/Styles/Attachmnets.less index 9c9bf6d49..282a5cc05 100644 --- a/dev/Styles/Attachmnets.less +++ b/dev/Styles/Attachmnets.less @@ -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 { diff --git a/dev/Styles/Components.less b/dev/Styles/Components.less index cef5608ba..9bb5d03a3 100644 --- a/dev/Styles/Components.less +++ b/dev/Styles/Components.less @@ -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; diff --git a/dev/Styles/Login.less b/dev/Styles/Login.less index 0fc1d0095..504b1e4d2 100644 --- a/dev/Styles/Login.less +++ b/dev/Styles/Login.less @@ -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; } diff --git a/dev/Styles/MessageList.less b/dev/Styles/MessageList.less index 6d51ce18e..29ad90ad7 100644 --- a/dev/Styles/MessageList.less +++ b/dev/Styles/MessageList.less @@ -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; + } } } diff --git a/dev/Styles/MessageView.less b/dev/Styles/MessageView.less index 82b7ec020..83f9f00c8 100644 --- a/dev/Styles/MessageView.less +++ b/dev/Styles/MessageView.less @@ -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; diff --git a/dev/Styles/SettingsFolders.less b/dev/Styles/SettingsFolders.less index 734f0e026..8f18ef151 100644 --- a/dev/Styles/SettingsFolders.less +++ b/dev/Styles/SettingsFolders.less @@ -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 { diff --git a/dev/Styles/SettingsGeneral.less b/dev/Styles/SettingsGeneral.less index 7d4a236be..f8761567a 100644 --- a/dev/Styles/SettingsGeneral.less +++ b/dev/Styles/SettingsGeneral.less @@ -22,5 +22,11 @@ .flag-name { border-bottom: 1px dashed #555; cursor: pointer; + padding: 2px; + + &:focus { + outline: 1px; + outline-style: dotted; + } } } diff --git a/dev/Styles/Ui.less b/dev/Styles/Ui.less index 94e44bad7..067883e27 100644 --- a/dev/Styles/Ui.less +++ b/dev/Styles/Ui.less @@ -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; -} \ No newline at end of file +} diff --git a/dev/Styles/_BootstrapFix.less b/dev/Styles/_BootstrapFix.less index 9bb2619b8..add5ae9c9 100644 --- a/dev/Styles/_BootstrapFix.less +++ b/dev/Styles/_BootstrapFix.less @@ -7,7 +7,7 @@ label { cursor: pointer; } -label.inline { +label.inline, span.inline { display: inline-block; } diff --git a/dev/Styles/_End.less b/dev/Styles/_End.less index b6838cf11..5fcebedea 100644 --- a/dev/Styles/_End.less +++ b/dev/Styles/_End.less @@ -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; + } + } } diff --git a/dev/Styles/_FontasticToBoot.less b/dev/Styles/_FontasticToBoot.less index 9e61f787a..70b25dae9 100644 --- a/dev/Styles/_FontasticToBoot.less +++ b/dev/Styles/_FontasticToBoot.less @@ -142,7 +142,7 @@ border-color: transparent; border-top-color: #999; - animation: rotation .8s infinite linear; + animation: rotation .8s infinite ease-in-out; } &.big { diff --git a/dev/View/Admin/Login.js b/dev/View/Admin/Login.js index bad88f134..84d506e86 100644 --- a/dev/View/Admin/Login.js +++ b/dev/View/Admin/Login.js @@ -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())); diff --git a/dev/View/Popup/Activate.js b/dev/View/Popup/Activate.js index 2ea41f2d3..b48f3073a 100644 --- a/dev/View/Popup/Activate.js +++ b/dev/View/Popup/Activate.js @@ -137,7 +137,7 @@ }; /** - * @returns {boolean} + * @return {boolean} */ ActivatePopupView.prototype.validateSubscriptionKey = function () { diff --git a/dev/View/Popup/AdvancedSearch.js b/dev/View/Popup/AdvancedSearch.js index ad4eef02a..9200b02b2 100644 --- a/dev/View/Popup/AdvancedSearch.js +++ b/dev/View/Popup/AdvancedSearch.js @@ -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); } diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 9640a981e..567a3f42d 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -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); diff --git a/dev/View/Popup/Languages.js b/dev/View/Popup/Languages.js index cf32623c9..870d8850e 100644 --- a/dev/View/Popup/Languages.js +++ b/dev/View/Popup/Languages.js @@ -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) diff --git a/dev/View/User/Login.js b/dev/View/User/Login.js index 406c1492d..3fe1de3d8 100644 --- a/dev/View/User/Login.js +++ b/dev/View/User/Login.js @@ -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; }()); \ No newline at end of file diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index 977895412..c61332277 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -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 diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index ce4b99dc7..8ba822442 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -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': '
' + sErrorMessage + '
', @@ -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 () { diff --git a/package.json b/package.json index 405ddf101..e0fa36d9d 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/plugins/vesta-change-password/LICENSE b/plugins/vesta-change-password/LICENSE new file mode 100644 index 000000000..271342337 --- /dev/null +++ b/plugins/vesta-change-password/LICENSE @@ -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. diff --git a/plugins/vesta-change-password/README b/plugins/vesta-change-password/README new file mode 100644 index 000000000..4f2f06281 --- /dev/null +++ b/plugins/vesta-change-password/README @@ -0,0 +1 @@ +Plugin that adds functionality to change the email account password (Vesta Control Panel). \ No newline at end of file diff --git a/plugins/vesta-change-password/VERSION b/plugins/vesta-change-password/VERSION new file mode 100644 index 000000000..9f8e9b69a --- /dev/null +++ b/plugins/vesta-change-password/VERSION @@ -0,0 +1 @@ +1.0 \ No newline at end of file diff --git a/plugins/vesta-change-password/VestaChangePasswordDriver.php b/plugins/vesta-change-password/VestaChangePasswordDriver.php new file mode 100644 index 000000000..cbb8be5c1 --- /dev/null +++ b/plugins/vesta-change-password/VestaChangePasswordDriver.php @@ -0,0 +1,146 @@ +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; + } +} diff --git a/plugins/vesta-change-password/index.php b/plugins/vesta-change-password/index.php new file mode 100644 index 000000000..f79c8711a --- /dev/null +++ b/plugins/vesta-change-password/index.php @@ -0,0 +1,55 @@ +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('*') + ); + } +} diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Enumerations/Charset.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Enumerations/Charset.php index 8b09686e3..85132e6ea 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Enumerations/Charset.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Enumerations/Charset.php @@ -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'; } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php index 4294908da..f34e47214 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Base/Http.php @@ -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)) { 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 669f24dae..46e118c27 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 @@ -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 */ diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Log/Driver.php b/rainloop/v/0.0.0/app/libraries/MailSo/Log/Driver.php index 60a8d0021..0ac538b24 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Log/Driver.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Log/Driver.php @@ -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, diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php b/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php index 3d513f170..e5a0a37f4 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Log/Logger.php @@ -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 */ diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php index 0a603170a..5cc64b576 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/AttachmentCollection.php @@ -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; } } diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php index ce428e586..fa7c2261c 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MailClient.php @@ -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; diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php index 34c8cf884..ec176924a 100644 --- a/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php +++ b/rainloop/v/0.0.0/app/libraries/MailSo/Mail/MessageCollection.php @@ -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; 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 9e5b785db..c86096688 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -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 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 64eca8f7b..644d251e9 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 @@ -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), diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php index 82c463685..8540ac8f8 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Model/Account.php @@ -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 )); } diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files.php index c3539b9c8..31d72a8c4 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files.php @@ -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 */ diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php index bc2591523..3ae703a8a 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/FileStorage.php @@ -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) diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php index cb7aaceae..c31e231be 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Files/IFiles.php @@ -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 diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/OwnCloudSuggestions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/OwnCloudSuggestions.php index 1484597fa..a32e9e965 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/OwnCloudSuggestions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Providers/Suggestions/OwnCloudSuggestions.php @@ -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) { diff --git a/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php index b85f33c99..029415ff5 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Utils.php @@ -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 diff --git a/rainloop/v/0.0.0/app/templates/Themes/template.less b/rainloop/v/0.0.0/app/templates/Themes/template.less index d9e69c07a..bd4b0bec8 100644 --- a/rainloop/v/0.0.0/app/templates/Themes/template.less +++ b/rainloop/v/0.0.0/app/templates/Themes/template.less @@ -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; } } diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html index 1d4b36e3b..1dc30c7c2 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/AdminLogin.html @@ -1,14 +1,16 @@
-
+
-
+
\ No newline at end of file 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 dbc806cfe..ebe1fbaa1 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 @@ -137,19 +137,21 @@
+
+ +    + +
× - - : +
  
-
+
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html index c269f835c..a83231b3a 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItem.html @@ -18,15 +18,20 @@
+
+ + + + + + +
    - - -  
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html index e8a81aa9c..e16ec903b 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageListItemNoPreviewPane.html @@ -7,6 +7,14 @@
+
+ + + + + + +
@@ -24,9 +32,6 @@
    - - -  
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html index e6d84221d..fbdfa0580 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/MailMessageView.html @@ -188,64 +188,6 @@
-
- - - - -
- - - - -
- - - - -
@@ -258,9 +200,6 @@ - () ! @@ -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()); }" />
-
+
  • @@ -392,7 +332,7 @@
diff --git a/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolderItem.html b/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolderItem.html index a82905374..a32889f01 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolderItem.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolderItem.html @@ -12,12 +12,12 @@ - + - + @@ -25,5 +25,13 @@ + + + + + + + + \ No newline at end of file diff --git a/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolders.html b/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolders.html index 5f0ca4ef5..e83c412aa 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolders.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/SettingsFolders.html @@ -1,37 +1,45 @@ -
-
-
- -     - +
+
+
+
+ +     + +
+
+ + +    + + +    + + +    + + +
+ +
+ +
+
+ + +
+ + + + + + + + +
+
+
+
+
- - -    - - -    - - -    - - -
- -
- -
-
- - -
- - - - - - - -
\ No newline at end of file diff --git a/rainloop/v/0.0.0/app/templates/Views/User/SettingsGeneral.html b/rainloop/v/0.0.0/app/templates/Views/User/SettingsGeneral.html index 6af2aefee..0bae61a34 100644 --- a/rainloop/v/0.0.0/app/templates/Views/User/SettingsGeneral.html +++ b/rainloop/v/0.0.0/app/templates/Views/User/SettingsGeneral.html @@ -12,7 +12,7 @@ - +   
-    - +   +
diff --git a/rainloop/v/0.0.0/langs/admin/pt-br.ini b/rainloop/v/0.0.0/langs/admin/pt-br.ini new file mode 100644 index 000000000..dfca120f9 --- /dev/null +++ b/rainloop/v/0.0.0/langs/admin/pt-br.ini @@ -0,0 +1,321 @@ + +[LOGIN] +LABEL_LOGIN = "Usuário" +LABEL_PASSWORD = "Senha" +BUTTON_LOGIN = "Entrar no painel administrativo" + +[TOP_PANEL] +LABEL_PREMIUM = "Premium" +LABEL_ADMIN_PANEL = "Painel Administrativo" + +[TABS_LABELS] +LABEL_GENERAL_NAME = "Geral" +LABEL_LOGIN_NAME = "Login" +LABEL_BRANDING_NAME = "Personalizar" +LABEL_CONTACTS_NAME = "Contatos" +LABEL_DOMAINS_NAME = "Domínios" +LABEL_SECURITY_NAME = "Segurança" +LABEL_INTEGRATION_NAME = "Integrações" +LABEL_PLUGINS_NAME = "Plugins" +LABEL_PACKAGES_NAME = "Pacotes" +LABEL_LICENSING_NAME = "Licença" +LABEL_ABOUT_NAME = "Sobre" + +[TAB_GENERAL] +LEGEND_INTERFACE = "Interface" +LABEL_LANGUAGE = "Idioma" +LABEL_LANGUAGE_ADMIN = "Idioma (admin)" +LABEL_THEME = "Tema Padrão" +LABEL_ALLOW_LANGUAGES_ON_SETTINGS = "Permitir escolha do idioma na tela de configurações" +LABEL_ALLOW_THEMES_ON_SETTINGS = "Permitir escolha do tema na tela de configurações" +LABEL_ALLOW_BACKGROUND_ON_SETTINGS = "Permitir escolha de Imagem de Fundo na tela de configurações" +LABEL_SHOW_THUMBNAILS = "Mostrar imagem miniatura (Anexos)" +LABEL_ALLOW_GRAVATAR = "Permitir Gravatar" +LEGEND_MAIN = "Principal" +LABEL_ATTACHMENT_SIZE_LIMIT = "Tamanho limite de anexo" +LABEL_ALLOW_ADDITIONAL_ACCOUNTS = "Permitir contas adicionais" +LABEL_ALLOW_TEMPLATES = "Permitir templates" +ALERT_WARNING = "Aviso!" +HTML_ALERT_WEAK_PASSWORD = "Você está usando a senha administrativa padrão. +
+Por motivo de segurança por favor +troque a senha +agora." + +[TAB_LOGIN] +LEGEND_LOGIN_SCREEN = "Tela de entrada" +LABEL_DEFAULT_DOMAIN = "Domínio padrão" +LABEL_DETERMINE_USER_DOMAIN = "Tentar determinar o domínio do usuário" +LABEL_ALLOW_LANGUAGES_ON_LOGIN = "Permitir escolha do idioma na tela de entrada" +LABEL_DETERMINE_USER_LANGUAGE = "Tentar determinar o idioma do usuário" + +[TAB_BRANDING] +LEGEND_BRANDING = "Personalização" +LABEL_PAGE_TITLE = "Título da página" +LABEL_LOADING_DESCRIPTION = "Descrição do 'carregando'" +LEGEND_LOGIN = "Tela de Entrada" +LABEL_LOGIN_LOGO = "Logo" +LABEL_LOGIN_DESCRIPTION = "Descrição" +LABEL_LOGIN_BACKGROUND = "Imagem de fundo" +LABEL_LOGIN_CUSTOM_CSS = "CSS personalizado" +LABEL_LOGIN_SHOW_POWERED_LINK = "Mostrar link \"Powered by RainLoop\"" +LEGEND_USER = "Internas" +LABEL_USER_LOGO = "Logo" +LABEL_USER_CUSTOM_CSS = "CSS personalizado" +LEGEND_WELCOME_PAGE = "Página de boas-vindas" +LABEL_WELCOME_PAGE_TITLE = "Título" +LABEL_WELCOME_PAGE_URL = "URL" +LABEL_WELCOME_PAGE_DISPLAY = "Mostrar" +OPTION_WELCOME_PAGE_DISPLAY_NONE = "Nunca" +OPTION_WELCOME_PAGE_DISPLAY_ONCE = "Única vez" +OPTION_WELCOME_PAGE_DISPLAY_ALWAYS = "Sempre" +HTML_ALERT_PREMIUM = "Essa funcionalidade está disponível apenas para usuários Premium." + +[TAB_CONTACTS] +LEGEND_CONTACTS = "Contatos" +LEGEND_STORAGE = "Armazenamento (PDO)" +LABEL_ENABLE_CONTACTS = "Habilitar contatos" +LABEL_ALLOW_SYNC = "Permitir sincronização de contatos (com servidor externo CardDAV)" +LABEL_STORAGE_TYPE = "Tipo" +LABEL_STORAGE_DSN = "Dsn" +LABEL_STORAGE_USER = "Usuário" +LABEL_STORAGE_PASSWORD = "Senha" +BUTTON_TEST = "Testar" +ALERT_NOTICE = "Aviso!" +HTML_ALERT_DO_NOT_USE_THIS_DATABASE = "Não use este tipo de banco de dados com um grande número de usuários ativos." +HTML_ALERT_DOES_NOT_SUPPORTED = "O seu sistema não suporta o uso dos contatos. +
+Você precisa instalar ou habilitar a extensão PDO (SQLite / MySQL / PostgreSQL) em seu servidor." + +[TAB_DOMAINS] +LEGEND_DOMAINS = "Domínios" +BUTTON_ADD_DOMAIN = "Adicionar Domínio" +DELETE_ARE_YOU_SURE = "Você tem certeza?" +HTML_DOMAINS_HELPER = "Lista dos domínios com acesso permitido ao webmail. +
Clique no domínio para configurá-lo." + +[TAB_SECURITY] +LEGEND_SECURITY = "Segurança" +LABEL_ALLOW_TWO_STEP = "Permitir verificação em duas etapas (Login 2-Step)" +LABEL_FORCE_TWO_STEP = "Obrigar o uso da verificação em duas etapas" +LABEL_USE_IMAGE_PROXY = "Usar proxy local para imagens externas" +LABEL_ALLOW_OPEN_PGP = "Permitir OpenPGP" +LABEL_SHOW_PHP_INFO = "Mostar informações PHP" +LEGEND_ADMIN_PANEL_ACCESS_CREDENTIALS = "Credenciais de acesso ao Painel Administrativo" +LABEL_CURRENT_PASSWORD = "Senha atual" +LABEL_NEW_LOGIN = "Usuário" +LABEL_NEW_PASSWORD = "Nova senha" +LABEL_REPEAT_PASSWORD = "Confirmar nova senha" +BUTTON_UPDATE_PASSWORD = "Atualizar" +LEGEND_SSL = "SSL" +LABEL_REQUIRE_VERIFICATION = "Exigir verificação do certificado SSL (IMAP/SMTP)" +LABEL_ALLOW_SELF_SIGNED = "Permitir certificados auto-assinados" + +[TAB_INTEGRATIONS] +LEGEND_GOOGLE = "Google" +LABEL_ENABLE_GOOGLE = "Habilitar integração com o Google" +LABEL_GOOGLE_AUTH = "Autenticação" +LABEL_GOOGLE_DRIVE = "Integração com Google Drive (Anexos)" +LABEL_GOOGLE_PREVIEW = "Integração com Leitor Google (Pré-visualização para Microsoft Word, Excel e PowerPoint)" +LABEL_GOOGLE_CLIENT_ID = "Client ID" +LABEL_GOOGLE_CLIENT_SECRET = "Client Secret" +LABEL_GOOGLE_API_KEY = "Api Key" +HINT_GOOGLE_API_KEY = "Requerido para o uso da seleção de arquivos do Google Drive" +LEGEND_FACEBOOK = "Facebook" +LABEL_ENABLE_FACEBOOK = "Habilitar integração com o Facebook (Autenticação)" +LABEL_FACEBOOK_APP_ID = "App ID" +LABEL_FACEBOOK_APP_SECRET = "App Secret" +LEGEND_TWITTER = "Twitter" +LABEL_ENABLE_TWITTER = "Habilitar integração com o Twitter (Autenticação)" +LABEL_TWITTER_CONSUMER_KEY = "Consumer Key" +LABEL_TWITTER_CONSUMER_SECRET = "Consumer Secret" +LEGEND_DROPBOX = "Dropbox" +LABEL_ENABLE_DROPBOX = "Habilitar integração com o Dropbox (Anexos)" +LABEL_DROPBOX_API_KEY = "Api Key" +TOP_ALERT = "Informações detalhadas sobre as integrações em" + +[TAB_PLUGINS] +LEGEND_PLUGINS = "Plugins" +LABEL_ENABLE_PLUGINS = "Habilitar uso de plugins" +ALERT_NO_PLUGINS = "Nenhum plugin instalado." +LINK_INSTALL_NEW = "Clique aqui para instalar!" +HINT_CLICK_NAME = "Clique no nome para configurar o plugin." + +[TAB_PACKAGES] +LEGEND_AVAILABLE_FOR_UPDATE = "Atualização disponível" +LEGEND_AVAILABLE_FOR_INSTALLATION = "Disponíveis para isntalação" +LEGEND_INSTALLED_PACKAGES = "Pacotes já instalados" +ALERT_CANNOT_ACCESS_REPOSITORY = "Não foi possível acessar o repositório no momento." + +[TAB_LICENSING] +LABEL_YOUR_DOMAIN = "Seu domínio" +LABEL_VERSION = "Versão" +LABEL_CHECKING = "Checando" +TYPE_BASIC = "Básico" +TYPE_BASIC_HINT = "Esse domínio não está licenciado para uso comercial (com recursos adicionais)." +TYPE_BASIC_HINT_2 = "Esse domínio não pode ser licenciado." +HTML_ALERT_TOP_1 = "RainLoop Webmail é licenciado sob" +HTML_ALERT_TOP_2 = "Você é livre para usá-lo em seus projetos pessoais." +HTML_ALERT_TOP_3 = "Uso comercial (com recursos adicionais) do RainLoop Webmail +requer uma assinatura." +TYPE_PREMIUM = "Premium" +TYPE_PREMIUM_LIFETIME = "Lifetime" +TYPE_PREMIUM_HINT = "Esse domínio está licenciado para uso comercial (com recursos adicionais)." +LABEL_SUB_EXPIRES = "A assinatura expira em:" +BUTTON_ACTIVATE = "Ative a chave de assinatura para este domínio" +BUTTON_PURCHASE = "Comprar assinatura" +BUTTON_TRIAL = "Trial" + +[TAB_ABOUT] +LEGEND_ABOUT = "Sobre" +LABEL_TAG_HINT = "Leitor de email web simples, moderno & rápido." +LABEL_ALL_RIGHTS_RESERVED = "Todos os direitos reservados." +HINT_READ_CHANGE_LOG = "Por favor, leia o registro de alterações antes de atualizar." +HINT_IS_UP_TO_DATE = "RainLoop está atualizado." +HTML_NEW_VERSION = "Está disponível uma nova versão: %VERSION%." +LABEL_UPDATING = "Atualizando" +LABEL_CHECKING = "Checar por atualizações" +BUTTON_UPDATE = "Atualizar" +BUTTON_DOWNLOAD = "Baixar" +BUTTON_CHANGELOG = "Registro de alterações" + +[POPUPS_ACTIVATE] +TITLE_ACTIVATE = "Ativar chave de assinatura?" +TITLE_ACTIVATION = "Ativação..." +LABEL_DOMAIN = "Domínio" +LABEL_SUB_KEY = "Chave de assinatura" +BUTTON_ACTIVATE = "Ativar" +LABEL_ACTIVATED = "Ativado" +ERROR_INVALID_SUBS_KEY = "Chave de assinatura inválida" +SUBS_KEY_ACTIVATED = "Chave de assinatura ativada com sucesso" +HTML_DESC = "Após a ativação, a assinatura premium para %DOMAIN% será extendida. +
+Note que chave de assinatura é ativada apenas para um único domínio. +

+Uma vez iniciado, o processo de ativação não poderá ser interrompido ou cancelado." + +[POPUPS_DOMAIN] +TITLE_ADD_DOMAIN = "Adicionar Domínio" +TITLE_ADD_DOMAIN_WITH_NAME = "Adicionar Domínio \"%NAME%\"" +TITLE_EDIT_DOMAIN = "Editar Domínio \"%NAME%\"" +LABEL_NAME = "Domínio" +NAME_HELPER = "curinga (wildcard) suportado" +LABEL_IMAP = "IMAP" +LABEL_SMTP = "SMTP" +LABEL_SIEVE = "SIEVE" +LABEL_SERVER = "Servidor" +LABEL_PORT = "Porta" +LABEL_SECURE = "Criptografia" +LABEL_WHITE_LIST = "Lista branca" +SECURE_OPTION_NONE = "Nenhuma" +SECURE_OPTION_SSL = "SSL/TLS" +SECURE_OPTION_STARTTLS = "STARTTLS" +LABEL_ALLOW_SIEVE_SCRIPTS = "Permitir scripts sieve" +LABEL_ALLOW_USER_SCRIPT = "Permitir scripts personalizados por usuários" +LABEL_USE_SHORT_LOGIN = "Usar login curto" +LABEL_USE_AUTH = "Usar autenticação" +LABEL_USE_PHP_MAIL = "Usar função php mail()" +BUTTON_TEST = "Testar" +BUTTON_WHITE_LIST = "Lista branca" +BUTTON_SIEVE_CONFIGURATION = "Configurações Sieve" +BUTTON_BACK_TO_IMAP = "Voltar para as configurações IMAP" +BUTTON_BACK = "Voltar" +BUTTON_CLOSE = "Fechar" +BUTTON_ADD = "Adicionar" +BUTTON_UPDATE = "Atualizar" +WHITE_LIST_ALERT = "Filtrar com lista dos únicos usuários do domínio que terão acesso ao webmail. +Use um espaço como delimitador." + +[POPUPS_PLUGIN] +TITLE_PLUGIN = "Plugin" +DESC_NOTHING_TO_CONFIGURE = "Nada para configurar" +BUTTON_CLOSE = "Fechar" +BUTTON_SAVE = "Salvar" +TOOLTIP_ABOUT_TITLE = "Sobre" + +[POPUPS_ASK] +BUTTON_YES = "Sim" +BUTTON_NO = "Não" +DESC_WANT_CLOSE_THIS_WINDOW = "Você tem certeza que deseja fechar esta janela?" +DESC_WANT_DELETE_MESSAGES = "Você tem certeza que deseja apagar a(s) mensagem(s)?" + +[POPUPS_LANGUAGES] +TITLE_LANGUAGES = "Selecionar idioma" + +[HINTS] +BETA = "beta" +UNSTABLE = "instável" +WARNING = "Aviso!" +NOT_SUPPORTED = "não suportado" +REQUIRES_PHP_54 = "requer PHP 5.4 ou superior" + +[ERRORS] +DOMAIN_ALREADY_EXISTS = "Domínio já existente" +UNKNOWN_ERROR = "Erro desconhecido" + +[NOTIFICATIONS] +INVALID_TOKEN = "Senha inválida" +AUTH_ERROR = "Falha na autenticação" +ACCESS_ERROR = "Erro ao acessar" +CONNECTION_ERROR = "Não foi possível conectar ao servidor" +CAPTCHA_ERROR = "Verificação CAPTCHA inválido." +SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE = "Esse usuário Facebook não está associado a nenhuma conta de email ainda. Entre utilizando seu email e senha e habilite essa função em configurações de conta." +SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE = "Esse usuário Twitter não está associado a nenhuma conta de email ainda. Entre utilizando seu email e senha e habilite essa função em configurações de conta." +SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE = "Esse usuário Google não está associado a nenhuma conta de email ainda. Entre utilizando seu email e senha e habilite essa função em configurações de conta." +DOMAIN_NOT_ALLOWED = "Este domínio não é permitido" +ACCOUNT_NOT_ALLOWED = "Conta não permitida" +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Verificação de duas etapas requerida" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Erro na verificação de duas etapas" +COULD_NOT_SAVE_NEW_PASSWORD = "Não foi possível salvar a nova senha" +CURRENT_PASSWORD_INCORRECT = "Senha atual incorreta" +NEW_PASSWORD_SHORT = "A senha é muito curta" +NEW_PASSWORD_WEAK = "A senha é muito fácil" +NEW_PASSWORD_FORBIDDENT = "A senha contém caracteres não permitidos" +CONTACTS_SYNC_ERROR = "Erro na sincronização dos contatos" +CANT_GET_MESSAGE_LIST = "Não foi possível obter a lista de mensagens" +CANT_GET_MESSAGE = "Não foi possível obter a mensagem" +CANT_DELETE_MESSAGE = "Não foi possível excluir a mensagem" +CANT_MOVE_MESSAGE = "Não foi possível mover a mensagem" +CANT_SAVE_MESSAGE = "Não foi possível salvar a mensagem" +CANT_SEND_MESSAGE = "Não foi possível enviar a mensagem" +INVALID_RECIPIENTS = "Destinatário inválido" +CANT_SAVE_FILTERS = "Não é possível salvar os filtros" +CANT_GET_FILTERS = "Não é possível obter os filtros" +FILTERS_ARE_NOT_CORRECT = "Os filtros não estão corretos" +CANT_CREATE_FOLDER = "Não foi possível criar a pasta" +CANT_RENAME_FOLDER = "Não foi possível renomear a pasta" +CANT_DELETE_FOLDER = "Não foi possível exluir a pasta" +CANT_DELETE_NON_EMPTY_FOLDER = "Não é possível excluir diretório não vazio" +CANT_SUBSCRIBE_FOLDER = "Não foi possível inscrever esta pasta" +CANT_UNSUBSCRIBE_FOLDER = "Não foi possível desinscrever esta pasta" +CANT_SAVE_SETTINGS = "Não foi possível salvar as configurações" +CANT_SAVE_PLUGIN_SETTINGS = "Não foi possível salvar as configurações" +DOMAIN_ALREADY_EXISTS = "O domínio já existe" +CANT_INSTALL_PACKAGE = "Erro ao instalar pacote de plugin" +CANT_DELETE_PACKAGE = "Erro ao remover pacote de plugin" +INVALID_PLUGIN_PACKAGE = "Pacote de plugins inválido" +UNSUPPORTED_PLUGIN_PACKAGE = "Pacote de plugins insuportados" +LICENSING_SERVER_IS_UNAVAILABLE = "A assinatura do servidor está indisponível" +LICENSING_DOMAIN_EXPIRED = "A assinatura para este domínio expirou." +LICENSING_DOMAIN_BANNED = "A assinatura para este domínio é proibida." +DEMO_SEND_MESSAGE_ERROR = "Por motivos de segurança, esta conta demo não tem permissão para enviar mensagens para endereços de e-mail externo!" +DEMO_ACCOUNT_ERROR = "Por motivos de segurança, esta conta não tem permição para esta ação!" +ACCOUNT_ALREADY_EXISTS = "Esta conta já existe" +MAIL_SERVER_ERROR = "Ocorreu um erro ao acessar o servidor de e-mail" +INVALID_INPUT_ARGUMENT = "Argumento de entrada inválido" +UNKNOWN_ERROR = "Erro desconhecido" + +[STATIC] +BACK_LINK = "Recarregar" +DOMAIN_LIST_DESC = "A lista de domínios de webmail tem permissão para acessar." +PHP_EXSTENSIONS_ERROR_DESC = "Extensões necessárias PHP não estão disponíveis na sua configuração do PHP!" +PHP_VERSION_ERROR_DESC = "Sua versão PHP (%VERSION%) é menor do que o mínimo exigido 5.3.0!" +NO_SCRIPT_TITLE = "JavaScript é necessário para esta aplicação." +NO_SCRIPT_DESC = "O JavaScript não está disponível em seu navegador. +Por favor ative o suporte a JavaScript nas configurações do seu navegador e tente novamente." +NO_COOKIE_TITLE = "É necessário suporte a cookies para esta aplicação." +NO_COOKIE_DESC = "Os Cookies não estão disponíveis em seu navegador. +Por favor ative o suporte a Cookies nas configurações do seu navegador e tente novamente." +BAD_BROWSER_TITLE = "Seu navegador está desatualizado." +BAD_BROWSER_DESC = "Para usar todos os recursos do aplicativo, +baixe e instale um desses navegadores:" \ No newline at end of file diff --git a/rainloop/v/0.0.0/langs/bg.ini b/rainloop/v/0.0.0/langs/bg.ini index c959c0e0a..722515d0b 100644 --- a/rainloop/v/0.0.0/langs/bg.ini +++ b/rainloop/v/0.0.0/langs/bg.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "С премахнат флаг" EMPTY_LIST = "Празен списък." EMPTY_SEARCH_LIST = "Няма съобщения, които да отговарят на търсенето." SEARCH_RESULT_FOR = "Резултати от търсенето за \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Зареждане" EMPTY_SUBJECT_TEXT = "(Няма заглавие)" PUT_MESSAGE_HERE = "Пуснете съобщението тук, за да го видите в списъка" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Изберете съобщение, за да го вид PGP_PASSWORD_INPUT_PLACEHOLDER = "Парола" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Обратна разписка (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renaming a folder" DELETING_ASK = "Are you sure?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Списък с акаунти" @@ -607,6 +614,7 @@ LABEL_HELP = "Помощ" LABEL_CHECK_ALL = "Провери всички съобщения" LABEL_ARCHIVE = "Архивирай" LABEL_DELETE = "Изтрий" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Премести" LABEL_READ = "Прочети избраните съобщения" LABEL_UNREAD = "Маркирай като непрочетени избраните съобщения" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Поддръжката на Бисквитки не е нал Моля, разрешете поддръжката на Бисквитки в настройките на вашия браузър и опитайте отново." BAD_BROWSER_TITLE = "Вашият браузър е остарял." BAD_BROWSER_DESC = "За да използвате всички функции на приложението, -изтеглете и инсталирайте един от тези браузъри:" \ No newline at end of file +изтеглете и инсталирайте един от тези браузъри:" diff --git a/rainloop/v/0.0.0/langs/cs.ini b/rainloop/v/0.0.0/langs/cs.ini index 9b722039c..76b3bf98c 100644 --- a/rainloop/v/0.0.0/langs/cs.ini +++ b/rainloop/v/0.0.0/langs/cs.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Neoznačené" EMPTY_LIST = "Seznam je prázdny." EMPTY_SEARCH_LIST = "Vašemu vyhledávání neodpovídají žádné zprávy." SEARCH_RESULT_FOR = "Výsledky vyhledávání pro \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Načítám" EMPTY_SUBJECT_TEXT = "(Bez předmětu)" PUT_MESSAGE_HERE = "Přesuňte zprávu sem pro její zobrazení v seznamu" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Vyberte zprávu ze seznamu pro její zobrazení zde." PGP_PASSWORD_INPUT_PLACEHOLDER = "Heslo" PGP_SIGNED_MESSAGE_DESC = "Zpráva podepsaná OpenPGP (klikněte pro ověření)" PGP_ENCRYPTED_MESSAGE_DESC = "Zpráva šifrovaná OpenPGP (klikněte pro dešifraci)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Potvrzení o zobrazení zprávy - %SUBJECT%" @@ -550,6 +554,9 @@ RENAMING_PROCESS = "Přejmenovávam složku" DELETING_ASK = "Opravdu to chcete?" TO_MANY_FOLDERS_DESC_1 = "Máte moc složek!" TO_MANY_FOLDERS_DESC_2 = "Můžeme zobrazit jen část z nich, abychom zabránili problémům s výkonem." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Seznam účtů" @@ -606,6 +613,7 @@ LABEL_HELP = "Nápověda" LABEL_CHECK_ALL = "Zjistit nové zprávy" LABEL_ARCHIVE = "Archivovat" LABEL_DELETE = "Smazat" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Přesunout" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -707,4 +715,4 @@ NO_COOKIE_DESC = "Váš internetový prohlížeč nepodporuje Cookies. Prosím povolte podporu Cookies v nastavení vašeho internetového prohlížeče a zkuste to znovu." BAD_BROWSER_TITLE = "Používáte zastaralý internetový prohlížeč." BAD_BROWSER_DESC = "Abyste mohli využívat všchny funkcie této aplikace, -použijte některý z následujících internetových prohlížečů:" \ No newline at end of file +použijte některý z následujících internetových prohlížečů:" diff --git a/rainloop/v/0.0.0/langs/de.ini b/rainloop/v/0.0.0/langs/de.ini index 401d239ab..f2b222f6f 100644 --- a/rainloop/v/0.0.0/langs/de.ini +++ b/rainloop/v/0.0.0/langs/de.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Nicht markiert" EMPTY_LIST = "Liste ist leer." EMPTY_SEARCH_LIST = "Keine Treffer für Ihre Suche." SEARCH_RESULT_FOR = "Suchergebnisse für \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "Zurück zur Nachrichtenliste" LIST_LOADING = "Die Liste wird geladen" EMPTY_SUBJECT_TEXT = "(Kein Betreff)" PUT_MESSAGE_HERE = "Ziehen Sie die Nachricht hierhin, um sie in der Liste anzuzeigen" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Wählen Sie eine Nachricht aus der Liste aus, um sie anzuze PGP_PASSWORD_INPUT_PLACEHOLDER = "Passwort" PGP_SIGNED_MESSAGE_DESC = "OpenPGP-signierte Nachricht (klicken, um zu überprüfen)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP-verschlüsselte Nachricht (klicken, um zu entschlüsseln)" +LINK_DOWNLOAD_AS_ZIP = "Herunterladen als ZIP" +LINK_SAVE_TO_OWNCLOUD = "Speichern in ownCloud" +LINK_SAVE_TO_DROPBOX = "Speichern in Dropbox" [READ_RECEIPT] SUBJECT = "Empfangsbestätigung (angezeigt) - %SUBJECT%" @@ -240,7 +244,7 @@ BUTTON_OPEN_PGP = "OpenPGP (nur bei unformatiertem Text)" BUTTON_REQUEST_DSN = "Übermittlungsstatus anfordern" [POPUPS_WELCOME_PAGE] -BUTTON_CLOSE = "Close" +BUTTON_CLOSE = "Schließen" [POPUPS_ASK] BUTTON_YES = "Ja" @@ -381,27 +385,27 @@ Falls Sie gelöschte Nachrichten endgültig löschen möchten, wählen Sie die O NOTIFICATION_ARCHIVE = "Sie haben keinen \"Archiv\"-Systemordner gewählt, in dem die archivierten Nachrichten abgelegt werden." [POPUPS_TWO_FACTOR_CFG] -LEGEND_TWO_FACTOR_AUTH = "2-Step Verification" -LABEL_ENABLE_TWO_FACTOR = "Enable 2-Step verification" -LABEL_TWO_FACTOR_USER = "User" +LEGEND_TWO_FACTOR_AUTH = "Zwei-Faktor-Authentifizierung" +LABEL_ENABLE_TWO_FACTOR = "Zwei-Faktor-Authentifizierung aktivieren" +LABEL_TWO_FACTOR_USER = "Benutzer" LABEL_TWO_FACTOR_STATUS = "Status" -LABEL_TWO_FACTOR_SECRET = "Secret" -LABEL_TWO_FACTOR_BACKUP_CODES = "Backup codes" -BUTTON_CREATE = "Create New Secret" -BUTTON_ACTIVATE = "Activate" -BUTTON_CLEAR = "Clear" -BUTTON_LOGOUT = "Logout" -BUTTON_DONE = "Done" +LABEL_TWO_FACTOR_SECRET = "Geheimnis" +LABEL_TWO_FACTOR_BACKUP_CODES = "Sicherungscodes" +BUTTON_CREATE = "Neues Geheimnis erstellen" +BUTTON_ACTIVATE = "Aktivieren" +BUTTON_CLEAR = "Löschen" +BUTTON_LOGOUT = "Abmelden" +BUTTON_DONE = "Erledigt" BUTTON_TEST = "Test" LINK_TEST = "test" -BUTTON_SHOW_SECRET = "Show Secret" -BUTTON_HIDE_SECRET = "Hide Secret" -TWO_FACTOR_REQUIRE_DESC = "Your account requires 2-Step verification configuration." -TWO_FACTOR_SECRET_CONFIGURED_DESC = "Configured" -TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Not configured" -TWO_FACTOR_SECRET_DESC = "Import this info into your Google Authenticator client (or other TOTP client) using the provided QR code below or by entering the code manually." -TWO_FACTOR_BACKUP_CODES_DESC = "If you can't receive codes via Google Authenticator, you can use backup codes to sign in. After you’ve used a backup code to sign in, it will become inactive." -TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "You can't change this setting before test." +BUTTON_SHOW_SECRET = "Geheimnis einblenden" +BUTTON_HIDE_SECRET = "Geheminis ausblenden" +TWO_FACTOR_REQUIRE_DESC = "Ihr Benutzerkonto erfordert die Einrichtung der Zwei-Faktor-Authentifizierung." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Konfiguriert" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Nicht konfiguriert" +TWO_FACTOR_SECRET_DESC = "Importieren Sie diese Information in Ihre Google Authenticator Anwendung (oder andere TOTP-Anwendung), in dem Sie den unten bereitgestellten QR-Code verwenden oder den Code manuell eingeben." +TWO_FACTOR_BACKUP_CODES_DESC = "Sollten Sie keine Codes über den Google Authenticator erhalten, können Sie einen Sicherungscode zur Anmeldung verwenden. Der Sicherungscode wird inaktiv, sobald Sie ihn zur Anmeldung verwendet haben." +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "Sie können diese Einstellung nicht ohne vorherigen Test verändern." [TITLES] LOADING = "Wird geladen" @@ -467,7 +471,7 @@ LABEL_ADD_SIGNATURE_TO_ALL = "Signatur zu allen ausgehenden Nachrichten hinzufü [SETTINGS_SECURITY] LEGEND_SECURITY = "Sicherheit" -LABEL_CONFIGURATE_TWO_FACTOR = "Configurate 2-Step verification" +LABEL_CONFIGURATE_TWO_FACTOR = "Zwei-Faktor-Authentifizierung konfigurieren" LABEL_AUTOLOGOUT = "Automatische Abmeldung" AUTOLOGIN_NEVER_OPTION_NAME = "Nie" AUTOLOGIN_MINUTES_OPTION_NAME = "%MINUTES% Minute(n)" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Ordner wird umbenannt" DELETING_ASK = "Sind Sie sicher?" TO_MANY_FOLDERS_DESC_1 = "Sie haben zu viele Ordner!" TO_MANY_FOLDERS_DESC_2 = "Aus Leistungsgründen zeigen wir nur einen Teil davon an." +HELP_DELETE_FOLDER = "Lösche Ordner" +HELP_SHOW_HIDE_FOLDER = "Zeige/verstecke Ordner" +HELP_SHOW_HIDE_FOLDER_COUNT = "Zeige/verstecke Ordner Nachrichtenzahl" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Konten" @@ -607,6 +614,7 @@ LABEL_HELP = "Hilfe" LABEL_CHECK_ALL = "Alle Nachrichten auswählen" LABEL_ARCHIVE = "Archivieren" LABEL_DELETE = "Löschen" +LABEL_OPEN_THREAD = "Ausgewählte Unterhaltung öffnen" LABEL_MOVE = "Verschieben" LABEL_READ = "Ausgewählte Nachrichten als gelesen markieren" LABEL_UNREAD = "Ausgewählte Nachrichten als ungelesen markieren" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Ihr Browser unterstützt Cookies nicht. Aktivieren Sie bitte die Cookie-Unterstützung in Ihrem Browser und versuchen Sie es erneut." BAD_BROWSER_TITLE = "Ihr Browser ist veraltet." BAD_BROWSER_DESC = "Um alle Funktionen dieser Anwendung nutzen zu können, -sollten Sie einen der folgenden Browser herunterladen und installieren:" \ No newline at end of file +sollten Sie einen der folgenden Browser herunterladen und installieren:" diff --git a/rainloop/v/0.0.0/langs/en-gb.ini b/rainloop/v/0.0.0/langs/en-gb.ini index b701e6963..13a7691e2 100644 --- a/rainloop/v/0.0.0/langs/en-gb.ini +++ b/rainloop/v/0.0.0/langs/en-gb.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Unflagged" EMPTY_LIST = "Empty list." EMPTY_SEARCH_LIST = "No messages matched your search." SEARCH_RESULT_FOR = "Search results for \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Loading" EMPTY_SUBJECT_TEXT = "(No subject)" PUT_MESSAGE_HERE = "Drop message here to view it in the list" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Select message in list to view it here." PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renaming a folder" DELETING_ASK = "Are you sure?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Accounts" @@ -607,6 +614,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Cookies support is not available in your browser. Please enable Cookie support in your browser settings and retry." BAD_BROWSER_TITLE = "Your browser is outdated." BAD_BROWSER_DESC = "To use all the features of the application, -download and install one of these browsers:" \ No newline at end of file +download and install one of these browsers:" diff --git a/rainloop/v/0.0.0/langs/en.ini b/rainloop/v/0.0.0/langs/en.ini index b701e6963..13a7691e2 100644 --- a/rainloop/v/0.0.0/langs/en.ini +++ b/rainloop/v/0.0.0/langs/en.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Unflagged" EMPTY_LIST = "Empty list." EMPTY_SEARCH_LIST = "No messages matched your search." SEARCH_RESULT_FOR = "Search results for \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Loading" EMPTY_SUBJECT_TEXT = "(No subject)" PUT_MESSAGE_HERE = "Drop message here to view it in the list" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Select message in list to view it here." PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renaming a folder" DELETING_ASK = "Are you sure?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Accounts" @@ -607,6 +614,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Cookies support is not available in your browser. Please enable Cookie support in your browser settings and retry." BAD_BROWSER_TITLE = "Your browser is outdated." BAD_BROWSER_DESC = "To use all the features of the application, -download and install one of these browsers:" \ No newline at end of file +download and install one of these browsers:" diff --git a/rainloop/v/0.0.0/langs/es.ini b/rainloop/v/0.0.0/langs/es.ini index 76db7ee5c..dfaef38c1 100644 --- a/rainloop/v/0.0.0/langs/es.ini +++ b/rainloop/v/0.0.0/langs/es.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "No Destacado" EMPTY_LIST = "No hay mensajes." EMPTY_SEARCH_LIST = "La búsqueda no ha producido resultados." SEARCH_RESULT_FOR = "Resultados de búsqueda para \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Cargando" EMPTY_SUBJECT_TEXT = "(Sin asunto)" PUT_MESSAGE_HERE = "Arrastre su mensaje aquí para verlo en la lista" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Seleccione un mensaje en la lista para verlo aquí." PGP_PASSWORD_INPUT_PLACEHOLDER = "Contraseña" PGP_SIGNED_MESSAGE_DESC = "Mensaje firmado mediante OpenPGP (click para verificar)" PGP_ENCRYPTED_MESSAGE_DESC = "Mensaje cifrado mediante OpenPGP (click para desencriptar)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Acuse de recibo (se visualiza) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renombrando carpeta" DELETING_ASK = "¿Está usted seguro?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Lista de cuentas" @@ -607,6 +614,7 @@ LABEL_HELP = "Ayuda" LABEL_CHECK_ALL = "Comprobar todos los mensajes" LABEL_ARCHIVE = "Archivo" LABEL_DELETE = "Eliminar" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Mover" LABEL_READ = "Marcar los mensajes seleccionados como leídos" LABEL_UNREAD = "Marcar los mensajes seleccionados como no leídos" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "El soporte de Cookies no esta disponible en su navegador. Por favor habilite los Cookie en la configuración de su navegador e inténtelo de nuevo." BAD_BROWSER_TITLE = "Su navegador no está actualizado." BAD_BROWSER_DESC = "Para usar todas las características de esta aplicación, -descargue e instale uno de estos navegadores:" \ No newline at end of file +descargue e instale uno de estos navegadores:" diff --git a/rainloop/v/0.0.0/langs/fr.ini b/rainloop/v/0.0.0/langs/fr.ini index dc990eb9a..8ff9d6123 100644 --- a/rainloop/v/0.0.0/langs/fr.ini +++ b/rainloop/v/0.0.0/langs/fr.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Non marqué" EMPTY_LIST = "Liste vide." EMPTY_SEARCH_LIST = "Aucun message ne correspond à votre recherche." SEARCH_RESULT_FOR = "Résultats de recherche pour \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Chargement" EMPTY_SUBJECT_TEXT = "(Pas de sujet)" PUT_MESSAGE_HERE = "Glisser un message ici pour l'afficher dans la liste" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Sélectionner un message dans la liste pour l'afficher ici. PGP_PASSWORD_INPUT_PLACEHOLDER = "Mot de passe" PGP_SIGNED_MESSAGE_DESC = "Message signé par OpenPGP (cliquer pour vérifier)" PGP_ENCRYPTED_MESSAGE_DESC = "Message chiffré par OpenPGP (cliquer pour déchiffrer)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Accusé de réception (affiché) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renommer un dossier" DELETING_ASK = "Êtes-vous sûr ?" TO_MANY_FOLDERS_DESC_1 = "Vous avez trop de dossiers !" TO_MANY_FOLDERS_DESC_2 = "Seulement une partie d'entre eux ont été affichés afin de limiter les problèmes de performance." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Liste des comptes" @@ -607,6 +614,7 @@ LABEL_HELP = "Aide" LABEL_CHECK_ALL = "Marquer tous les messages comme lus" LABEL_ARCHIVE = "Archiver" LABEL_DELETE = "Supprimer" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Déplacer" LABEL_READ = "Marquer les messages sélectionnés comme lus" LABEL_UNREAD = "Marquer les messages sélectionnés comme non-lus" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Le support des cookies n'est pas disponible dans votre navigat S'il vous plaît activer le support des cookies dans les paramètres de votre navigateur et réessayez." BAD_BROWSER_TITLE = "Votre navigateur n'est pas à jour." BAD_BROWSER_DESC = "Pour utiliser toutes les fonctionnalités de l'application, -télécharger et installer l'un de ces navigateurs :" \ No newline at end of file +télécharger et installer l'un de ces navigateurs :" diff --git a/rainloop/v/0.0.0/langs/hu.ini b/rainloop/v/0.0.0/langs/hu.ini index 2cade922e..0782a5404 100644 --- a/rainloop/v/0.0.0/langs/hu.ini +++ b/rainloop/v/0.0.0/langs/hu.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Jelöletlen" EMPTY_LIST = "Lista üres." EMPTY_SEARCH_LIST = "Nincs a keresésnek megfelelő levél." SEARCH_RESULT_FOR = "Eredmény: \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Betöltés" EMPTY_SUBJECT_TEXT = "(Nincs tárgy)" PUT_MESSAGE_HERE = "Drop message here to view it in the list" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Select message in list to view it here." PGP_PASSWORD_INPUT_PLACEHOLDER = "Jelszó" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renaming a folder" DELETING_ASK = "Biztosak vagyunk benne?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Fiók lista" @@ -607,6 +614,7 @@ LABEL_HELP = "Súgó" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archiválás" LABEL_DELETE = "Tőrlés" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Cookies support is not available in your browser. Please enable Cookie support in your browser settings and retry." BAD_BROWSER_TITLE = "A böngésző elavult." BAD_BROWSER_DESC = "To use all the features of the application, -download and install one of these browsers:" \ No newline at end of file +download and install one of these browsers:" diff --git a/rainloop/v/0.0.0/langs/is.ini b/rainloop/v/0.0.0/langs/is.ini index a716bfb76..b427e163d 100644 --- a/rainloop/v/0.0.0/langs/is.ini +++ b/rainloop/v/0.0.0/langs/is.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Afflaggað" EMPTY_LIST = "Tómur listi." EMPTY_SEARCH_LIST = "Engin skilaboð stemmdu við leitina þína." SEARCH_RESULT_FOR = "Leitarniðurstöður fyrir \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Hleð" EMPTY_SUBJECT_TEXT = "(Ekkert viðfangsefni)" PUT_MESSAGE_HERE = "Settu skilaboðið hér til að skoða það í listanum" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Veldu bréf úr listanum til að skoða hér." PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Endurnefna möppu" DELETING_ASK = "Ertu viss?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Aðgangslisti" @@ -607,6 +614,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Köku stuðningur er ekki til staðar í vafranum þínum. Vinsamlegast gerðu JavaScript virkt í vafranum þínum og reyndu aftur." BAD_BROWSER_TITLE = "Vafrinn þinn er úreltur." BAD_BROWSER_DESC = "Til að geta notað alla valmöguleika í þessu forriti, -niðurhalaðu og settu upp einn af þessum vöfrum:" \ No newline at end of file +niðurhalaðu og settu upp einn af þessum vöfrum:" diff --git a/rainloop/v/0.0.0/langs/it.ini b/rainloop/v/0.0.0/langs/it.ini index ea795c3fb..fed36010d 100644 --- a/rainloop/v/0.0.0/langs/it.ini +++ b/rainloop/v/0.0.0/langs/it.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Non preferiti" EMPTY_LIST = "Lista vuota" EMPTY_SEARCH_LIST = "Nessun messaggio corrisponde alla tua ricerca" SEARCH_RESULT_FOR = "Risultati di ricerca per \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Caricamento..." EMPTY_SUBJECT_TEXT = "(Nessun oggetto)" PUT_MESSAGE_HERE = "Trascina il messaggio qui per vederlo nella lista" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Seleziona un messaggio dalla lista per visualizzarlo qui" PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "Messaggio firmato con OpenPGP (clicca qui per verificarlo)" PGP_ENCRYPTED_MESSAGE_DESC = "Messaggio cifrato con OpenPGP (clicca qui per decifrarlo)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Avviso di lettura - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Rinomina cartella..." DELETING_ASK = "Sei sicuro?" TO_MANY_FOLDERS_DESC_1 = "Ci sono troppe cartelle!" TO_MANY_FOLDERS_DESC_2 = "Vengono visualizzate solo una parte per evitare problemi di performance." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Lista degli account" @@ -607,6 +614,7 @@ LABEL_HELP = "Aiuto" LABEL_CHECK_ALL = "Seleziona tutti i messaggi" LABEL_ARCHIVE = "Archivia messaggio" LABEL_DELETE = "Elimina messaggio" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Sposta messaggio" LABEL_READ = "Marca come letti i messaggi selezionati" LABEL_UNREAD = "Marca come non letti i messaggi selezionati" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Il supporto ai Cookie è disabilitato. Abilitare il supporto ai Cookie e riprovare." BAD_BROWSER_TITLE = "Il tuo browser è troppo vecchio" BAD_BROWSER_DESC = "Per usare tutte le funzionalità dell'applicazione, -scarica ed installa uno dei seguenti browser:" \ No newline at end of file +scarica ed installa uno dei seguenti browser:" diff --git a/rainloop/v/0.0.0/langs/ja-jp.ini b/rainloop/v/0.0.0/langs/ja-jp.ini index ebb6a2279..cf78e315f 100644 --- a/rainloop/v/0.0.0/langs/ja-jp.ini +++ b/rainloop/v/0.0.0/langs/ja-jp.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Unflagged" EMPTY_LIST = "メールは空です。おめでとう!" EMPTY_SEARCH_LIST = "検索条件に一致するメールは見つかりませんでした。高度な検索を試してみてください。" SEARCH_RESULT_FOR = "Search results for \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "読み込み中" EMPTY_SUBJECT_TEXT = "(件名なし)" PUT_MESSAGE_HERE = "Drop message here to view it in the list" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "選択したメールがここに表示されます。" PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "開封確認メッセージ - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renaming a folder" DELETING_ASK = "Are you sure?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Account List" @@ -607,6 +614,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Cookies support is not available in your browser. Please enable Cookie support in your browser settings and retry." BAD_BROWSER_TITLE = "Your browser is outdated." BAD_BROWSER_DESC = "To use all the features of the application, -download and install one of these browsers:" \ No newline at end of file +download and install one of these browsers:" diff --git a/rainloop/v/0.0.0/langs/ko-kr.ini b/rainloop/v/0.0.0/langs/ko-kr.ini index 1d922c4e9..451615767 100644 --- a/rainloop/v/0.0.0/langs/ko-kr.ini +++ b/rainloop/v/0.0.0/langs/ko-kr.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "일반" EMPTY_LIST = "Empty list." EMPTY_SEARCH_LIST = "검색 조건에 맞는 결과가 없습니다.." SEARCH_RESULT_FOR = "\"%SEARCH%\"에 대한 검색결과" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "로딩 중" EMPTY_SUBJECT_TEXT = "(제목 없음)" PUT_MESSAGE_HERE = "목록에서 표시할 메시지를 여기로 끌어 넣으세요." @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "목록에서 선택한 메시지가 여기에 표시됩니 PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -548,6 +552,9 @@ RENAMING_PROCESS = "폴더명을 변경하고 있습니다." DELETING_ASK = "정말로 삭제하시겠습니까?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "계정 목록" @@ -604,6 +611,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -702,4 +710,4 @@ NO_SCRIPT_DESC = "사용 중인 브라우저가 자바스크립트를 지원하 NO_COOKIE_TITLE = "이 어플리케이션을 사용하려면 쿠키 사용이 지원되야 합니다." NO_COOKIE_DESC = "사용 중인 브라우저가 쿠키를 지원하지 않습니다. 브라우저 설정을 확인하여 쿠키 사용을 활성화하고 다시 시도해주세요." BAD_BROWSER_TITLE = "사용 중인 브라우저가 구버전입니다." -BAD_BROWSER_DESC = "이 어플리케이션을 정상적으로 사용하시려면 아래의 브라우저들 중 하나를 설치해주세요:" \ No newline at end of file +BAD_BROWSER_DESC = "이 어플리케이션을 정상적으로 사용하시려면 아래의 브라우저들 중 하나를 설치해주세요:" diff --git a/rainloop/v/0.0.0/langs/lt.ini b/rainloop/v/0.0.0/langs/lt.ini index c55b5203b..ebdc9c617 100644 --- a/rainloop/v/0.0.0/langs/lt.ini +++ b/rainloop/v/0.0.0/langs/lt.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Nepažymėta" EMPTY_LIST = "Tuščias sąrašas." EMPTY_SEARCH_LIST = "Nėra pranešimų, atitinkančių jūsų paiešką." SEARCH_RESULT_FOR = "Ieškoti \"%SEARCH%\" rezultatuose" +BACK_TO_MESSAGE_LIST = "grįžti į sąrašą" LIST_LOADING = "Kraunasi" EMPTY_SUBJECT_TEXT = "(Be temos)" PUT_MESSAGE_HERE = "Įkelkite žinutę čia, kad matytumėte ją sąraše" @@ -120,7 +121,7 @@ BUTTON_IN_NEW_WINDOW = "Peržiūrėti atskirame lange" BUTTON_THREAD_LIST = "Temų sąrašas" BUTTON_THREAD_PREV = "Ankstesnis" BUTTON_THREAD_NEXT = "Kitas" -BUTTON_THREAD_MORE = "More messages" +BUTTON_THREAD_MORE = "Daugiau žinučių" MENU_HEADERS = "Rodyti pranešimų antraštės" MENU_VIEW_ORIGINAL = "Rodyti šaltinį" MENU_DOWNLOAD_ORIGINAL = "Atsisiųsti .eml failą" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Pasirinkite žinutę sąraše, kad matytumėte jos turinį PGP_PASSWORD_INPUT_PLACEHOLDER = "Slaptažodis" PGP_SIGNED_MESSAGE_DESC = "OpenPGP pasirašytas pranešimas (spustelkite patikrinimui)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP šifruotas pranešimas (spustelkite iššifravimui)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Pristatymo patvirtinimas (laiškas parodytas) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Pervardinamas katalogas" DELETING_ASK = "Ar Jūs įsitikinę?" TO_MANY_FOLDERS_DESC_1 = "Pas jus per daug katalogų!" TO_MANY_FOLDERS_DESC_2 = "Mes rodome ne visus juos, kad išvengti našumo problemų." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Paskyrų sąrašas" @@ -607,6 +614,7 @@ LABEL_HELP = "Pagalba" LABEL_CHECK_ALL = "Žymėti visus laiškus" LABEL_ARCHIVE = "Archyvuoti" LABEL_DELETE = "Šalinti" +LABEL_OPEN_THREAD = "Atidaryti pasirinktą giją" LABEL_MOVE = "Perkelti" LABEL_READ = "Pasirinktus žymėti kaip skaitytus" LABEL_UNREAD = "Pasirinktus žymėti kaip neskaitytus" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Slapukų paialikymas negalimas jūsų naršyklėje. Įjunkite slapukų palaikymą naršyklėje ir bandykite vėl." BAD_BROWSER_TITLE = "Jūsų naršyklė pasenusi." BAD_BROWSER_DESC = "Norint naudotis visais šios aplikacijos privalumais, -parsisiųskite ir įsidiekite vieną iš šių naršyklių:" \ No newline at end of file +parsisiųskite ir įsidiekite vieną iš šių naršyklių:" diff --git a/rainloop/v/0.0.0/langs/lv.ini b/rainloop/v/0.0.0/langs/lv.ini index 025b7fa95..9576571b8 100644 --- a/rainloop/v/0.0.0/langs/lv.ini +++ b/rainloop/v/0.0.0/langs/lv.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Noņemtās atzīmes" EMPTY_LIST = "Tukšs saraksts." EMPTY_SEARCH_LIST = "Neviens ziņojums neatbilda jūsu meklētajiem kritērijiem." SEARCH_RESULT_FOR = "Meklēšanas rezultāti: \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Lādējas" EMPTY_SUBJECT_TEXT = "(Nav virsraksta)" PUT_MESSAGE_HERE = "Ievelciet ziņojumus šeit lai tos apskatītu." @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Izvēlaties ziņojumu no saraksta lai to apskatītu." PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Pārsaukt mapi" DELETING_ASK = "Tiešām?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Kontu saraksts" @@ -607,6 +614,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Cookies atbalsts nav pieejams šai pārlūkprogrammai. Lūdzu ieslēdziet Cookies atbalstu." BAD_BROWSER_TITLE = "Jūsu pārlūkprogramma ir novecojusi." BAD_BROWSER_DESC = "Lai izmantotu pilnas šis aplikācijas iespējas, -lejupielādējat un uzinstalējas kādu no šīm pārlukprogrammām:" \ No newline at end of file +lejupielādējat un uzinstalējas kādu no šīm pārlukprogrammām:" diff --git a/rainloop/v/0.0.0/langs/nl.ini b/rainloop/v/0.0.0/langs/nl.ini index 04242608c..1a1302724 100644 --- a/rainloop/v/0.0.0/langs/nl.ini +++ b/rainloop/v/0.0.0/langs/nl.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Zonder Vlag" EMPTY_LIST = "Geen berichten" EMPTY_SEARCH_LIST = "Geen items gevonden" SEARCH_RESULT_FOR = "Zoekresultaten voor\"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "terug naar berichtenlijst" LIST_LOADING = "Ophalen..." EMPTY_SUBJECT_TEXT = "(Geen onderwerp)" PUT_MESSAGE_HERE = "Sleep bericht hier om ze te bekijken in de lijst" @@ -135,12 +136,12 @@ LABEL_TO = "Naar" LABEL_TO_SHORT = "naar" LABEL_CC = "CC" LABEL_BCC = "BCC" -LABEL_REPLY_TO = "Reply-To" +LABEL_REPLY_TO = "Antwoordadres" PRINT_LABEL_FROM = "Van" PRINT_LABEL_TO = "Naar" PRINT_LABEL_CC = "CC" PRINT_LABEL_BCC = "BCC" -PRINT_LABEL_REPLY_TO = "Reply-To" +PRINT_LABEL_REPLY_TO = "Antwoordadres" PRINT_LABEL_DATE = "Datum" PRINT_LABEL_SUBJECT = "Onderwerp" PRINT_LABEL_ATTACHMENTS = "Bijlagen" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Selecteer een bericht in de lijst om hier te bekijken" PGP_PASSWORD_INPUT_PLACEHOLDER = "Wachtwoord" PGP_SIGNED_MESSAGE_DESC = "OpenPGP ondertekend bericht (klik om te verifiëren)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP versleuteld bericht (klik om te ontsleutelen)" +LINK_DOWNLOAD_AS_ZIP = "Download als zip" +LINK_SAVE_TO_OWNCLOUD = "Sla op in ownCloud" +LINK_SAVE_TO_DROPBOX = "Sla op in Dropbox" [READ_RECEIPT] SUBJECT = "Ontvangstbevestiging (weergegeven) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Map hernoemen" DELETING_ASK = "Weet u het zeker?" TO_MANY_FOLDERS_DESC_1 = "U heeft te veel mappen!" TO_MANY_FOLDERS_DESC_2 = "Er worden er maar een deel weergegeven om prestatieproblemen te voorkomen." +HELP_DELETE_FOLDER = "Verwijder map" +HELP_SHOW_HIDE_FOLDER = "Toon/verberg map" +HELP_SHOW_HIDE_FOLDER_COUNT = "Toon/verberg ongelezen teller" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Account Lijst" @@ -595,7 +602,7 @@ DELETING_ASK = "Weet u het zeker?" [SHORTCUTS_HELP] LEGEND_SHORTCUTS_HELP = "Hulp bij toetsenbord sneltoetsen" TAB_MAILBOX = "Mailbox" -TAB_MESSAGE_LIST = "Berichten lijst" +TAB_MESSAGE_LIST = "Berichtenlijst" TAB_MESSAGE_VIEW = "Bericht leesvenster" TAB_COMPOSE = "Opstellen" LABEL_OPEN_USER_DROPDOWN = "Open gebruikers menu" @@ -607,16 +614,17 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Selecteer alle berichten" LABEL_ARCHIVE = "Archiveren" LABEL_DELETE = "Verwijderen" +LABEL_OPEN_THREAD = "Open geselecteerde berichtenlijst" LABEL_MOVE = "Verplaatsen" LABEL_READ = "Markeer geselecteerde berichten als gelezen" LABEL_UNREAD = "Markeer geselecteerde berichten als ongelezen" LABEL_IMPORTANT = "Belangrijk, vlag geselecteerde berichten" LABEL_SEARCH = "Zoeken" LABEL_CANCEL_SEARCH = "Zoekopdracht afbreken" -LABEL_FULLSCREEN_ENTER = "Bericht in volledig scherm lezen (leesvenster ingeschakeld)" +LABEL_FULLSCREEN_ENTER = "Bericht in volledig scherm lezen(leesvenster ingeschakeld)" LABEL_VIEW_MESSAGE_ENTER = "Bericht lezen (leesvenster uitgeschakeld)" LABEL_SWITCH_TO_MESSAGE = "Schakel focus naar bericht" -LABEL_SWITCH_TO_FOLDER_LIST = "Schakel focus naar berichten lijst" +LABEL_SWITCH_TO_FOLDER_LIST = "Schakel focus naar berichtenlijst" LABEL_FULLSCREEN_TOGGLE = "Schakel tussen normaal en lezen in volledig scherm" LABEL_BLOCKQUOTES_TOGGLE = "Schakel blok citaten aan of uit" LABEL_THREAD_NEXT = "Volgende bericht in groep" @@ -624,7 +632,7 @@ LABEL_THREAD_PREV = "Vorige bericht in groep" LABEL_PRINT = "Afdrukken" LABEL_EXIT_FULLSCREEN = "Volledig scherm sluiten" LABEL_CLOSE_MESSAGE = "Bericht sluiten (leesvenster uitgeschakeld)" -LABEL_SWITCH_TO_LIST = "Schakel focus terug naar berichten lijst" +LABEL_SWITCH_TO_LIST = "Schakel focus terug naar berichtenlijst" LABEL_OPEN_COMPOSE_POPUP = "Open nieuw bericht venster" LABEL_OPEN_IDENTITIES_DROPDOWN = "Open identiteiten menu" LABEL_SAVE_MESSAGE = "Concept opslaan" @@ -662,7 +670,7 @@ NEW_PASSWORD_SHORT = "Wachtwoord is te kort" NEW_PASSWORD_WEAK = "Wachtwoord is te makkelijk" NEW_PASSWORD_FORBIDDENT = "Wachtwoord bevat verboden tekens" CONTACTS_SYNC_ERROR = "Contactpersonen synchronisatie fout" -CANT_GET_MESSAGE_LIST = "Berichten lijst kan niet worden opgehaald" +CANT_GET_MESSAGE_LIST = "Berichtenlijst kan niet worden opgehaald" CANT_GET_MESSAGE = "Bericht kan niet worden opgehaald" CANT_DELETE_MESSAGE = "Kan bericht niet verwijderen" CANT_MOVE_MESSAGE = "Kan bericht niet verplaatsen" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Cookies ondersteuning is niet beschikbaar in uw browser. Gelieve Cookies in te schakelen en opnieuw te proberen." BAD_BROWSER_TITLE = "Uw browser is verouderd" BAD_BROWSER_DESC = "Om alle functies van deze applicatie te gebruiken, -download en installeer een van de volgende browsers:" \ No newline at end of file +download en installeer een van de volgende browsers:" diff --git a/rainloop/v/0.0.0/langs/no.ini b/rainloop/v/0.0.0/langs/no.ini index a91b414d8..f1be023a9 100644 --- a/rainloop/v/0.0.0/langs/no.ini +++ b/rainloop/v/0.0.0/langs/no.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Ikke markert" EMPTY_LIST = "Tom liste" EMPTY_SEARCH_LIST = "Ingen meldinger matchet søket ditt." SEARCH_RESULT_FOR = "Søkeresultater for \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Laster" EMPTY_SUBJECT_TEXT = "(tomt emne)" PUT_MESSAGE_HERE = "Dra melding her for å vise det i listen" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Klikk på en e-post for å lese" PGP_PASSWORD_INPUT_PLACEHOLDER = "Passord" PGP_SIGNED_MESSAGE_DESC = "OpenPGP-signert melding (klikk for å verifisere)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP-kryptert melding (klikk for å dekryptere)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Lesebekreftelse (meldingen er lest) - %SUBJECT%" @@ -550,6 +554,9 @@ RENAMING_PROCESS = "Endre navn på en mappe" DELETING_ASK = "Er du sikker?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Kontoer" @@ -606,6 +613,7 @@ LABEL_HELP = "Hjelp" LABEL_CHECK_ALL = "Merk alle meldinger" LABEL_ARCHIVE = "Arkiver" LABEL_DELETE = "Slett" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Flytt" LABEL_READ = "Merk som lest" LABEL_UNREAD = "Merk som ulest" @@ -707,4 +715,4 @@ NO_COOKIE_DESC = "Cookies-støtte er ikke tilgjengelig i nettleseren din . Vennligst aktiver Cookie-støtte i nettleserens innstillinger, og prøv på nytt ." BAD_BROWSER_TITLE = "Nettleseren din er utdatert." BAD_BROWSER_DESC = "Hvis du vil bruke alle funksjonene i programmet, -last ned og installerer en av disse nettleserne:" \ No newline at end of file +last ned og installerer en av disse nettleserne:" diff --git a/rainloop/v/0.0.0/langs/pl.ini b/rainloop/v/0.0.0/langs/pl.ini index 6b3af5a81..a3223a577 100644 --- a/rainloop/v/0.0.0/langs/pl.ini +++ b/rainloop/v/0.0.0/langs/pl.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Oznaczone jako nieważne" EMPTY_LIST = "Pusta lista" EMPTY_SEARCH_LIST = "Brak wiadomości spełniających kryteria wyszukiwania" SEARCH_RESULT_FOR = "Wyniki wyszukiwania dla: \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "powrót do listy wiadomości" LIST_LOADING = "Ładowanie" EMPTY_SUBJECT_TEXT = "(Brak tematu)" PUT_MESSAGE_HERE = "Przeciągnij tutaj wiadomość, żeby zobaczyć ją na liście" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Wybierz wiadomość z listy w celu jej wyświetlenia" PGP_PASSWORD_INPUT_PLACEHOLDER = "Hasło" PGP_SIGNED_MESSAGE_DESC = "Wiadomość podpisana za pomocą OpenPGP (kliknij, aby zweryfikować)" PGP_ENCRYPTED_MESSAGE_DESC = "Wiadomość zaszyfrowana za pomocą OpenPGP (kliknij, aby odszyfrować)" +LINK_DOWNLOAD_AS_ZIP = "Pobierz jako zip" +LINK_SAVE_TO_OWNCLOUD = "Zapisz na ownCloud" +LINK_SAVE_TO_DROPBOX = "Zapisz na Dropbox" [READ_RECEIPT] SUBJECT = "Potwierdzenie odbioru (wyświetlenia) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Zmiana nazwy folderu" DELETING_ASK = "Czy na pewno usunąć?" TO_MANY_FOLDERS_DESC_1 = "Masz za dużo folderów!" TO_MANY_FOLDERS_DESC_2 = "Pokazaliśmy tylko część z nich aby uniknąć problemów z wydajnością." +HELP_DELETE_FOLDER = "Usuń folder" +HELP_SHOW_HIDE_FOLDER = "Pokaż/ukryj folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Pokaż/ukryj liczbę wiadomoci w folderze" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Lista kont" @@ -607,6 +614,7 @@ LABEL_HELP = "Pomoc" LABEL_CHECK_ALL = "Sprawdź wszystkie wiadomości" LABEL_ARCHIVE = "Archiwum" LABEL_DELETE = "Usuń" +LABEL_OPEN_THREAD = "Otwórz wybrany wątek" LABEL_MOVE = "Przenieś" LABEL_READ = "Oznacz zaznaczone wiadomości, jako przeczytane" LABEL_UNREAD = "Oznacz zaznaczone wiadomości, jako nieprzeczytane" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Obsługa Cookies jest wyłączona w twojej przeglądarce Proszę o umożliwienie obsługi plików Cookie w przeglądarce oraz ponowne uruchomienie tej strony." BAD_BROWSER_TITLE = "Twoja przeglądarka jest przestarzała!" BAD_BROWSER_DESC = "W celu wykorzystania wszystkich funkcji tej aplikacji, -zaktualizuj aktualnie używaną lub pobierz i zainstaluj jedną z poniższych przeglądarek:" \ No newline at end of file +zaktualizuj aktualnie używaną lub pobierz i zainstaluj jedną z poniższych przeglądarek:" diff --git a/rainloop/v/0.0.0/langs/pt-br.ini b/rainloop/v/0.0.0/langs/pt-br.ini index 68e4045c5..4c858a38b 100644 --- a/rainloop/v/0.0.0/langs/pt-br.ini +++ b/rainloop/v/0.0.0/langs/pt-br.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Não Sinalizadas" EMPTY_LIST = "Lista Vazia." EMPTY_SEARCH_LIST = "Nenhuma mensagem corresponde a sua pesquisa." SEARCH_RESULT_FOR = "Resultados de pesquisa para \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "Voltar para a lista de mensagens" LIST_LOADING = "Carregando" EMPTY_SUBJECT_TEXT = "(Sem assunto)" PUT_MESSAGE_HERE = "Coloque a mensagem aqui para vê-la na lista" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Selecione uma mensagem para visualizá-la aqui." PGP_PASSWORD_INPUT_PLACEHOLDER = "Senha" PGP_SIGNED_MESSAGE_DESC = "Mensagem assinada com OpenPGP (clique para verificar)" PGP_ENCRYPTED_MESSAGE_DESC = "Mensagem criptografada com OpenPGP (clique para descriptografar)" +LINK_DOWNLOAD_AS_ZIP = "Baixar como zip" +LINK_SAVE_TO_OWNCLOUD = "Salvar no ownCloud" +LINK_SAVE_TO_DROPBOX = "Salvar no Dropbox" [READ_RECEIPT] SUBJECT = "Aviso de recepção (Visualizada) - %SUBJECT%" @@ -467,10 +471,10 @@ LABEL_ADD_SIGNATURE_TO_ALL = "Adicionar sua assinatura em todas as mensagens de [SETTINGS_SECURITY] LEGEND_SECURITY = "Segurança" -LABEL_CONFIGURATE_TWO_FACTOR = "Configurate 2-Step verification" +LABEL_CONFIGURATE_TWO_FACTOR = "Configurar verificação de 2 etapas" LABEL_AUTOLOGOUT = "Deslogar Automaticamente" AUTOLOGIN_NEVER_OPTION_NAME = "Nunca" -AUTOLOGIN_MINUTES_OPTION_NAME = "%MINUTES% minuto(s)" +AUTOLOGIN_MINUTES_OPTION_NAME = "%MINUTES% minutos" [SETTINGS_GENERAL] LEGEND_GENERAL = "Geral" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renomeando a pasta" DELETING_ASK = "Você tem certeza?" TO_MANY_FOLDERS_DESC_1 = "Você possui muitas pastas!" TO_MANY_FOLDERS_DESC_2 = "Mostraremos apenas uma parte delas, para evitar problemas de desempenho." +HELP_DELETE_FOLDER = "Deletar pasta" +HELP_SHOW_HIDE_FOLDER = "Mostrar/esconder pasta" +HELP_SHOW_HIDE_FOLDER_COUNT = "Mostrar/esconder contagem de mensagens na pasta" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Lista de contas" @@ -607,6 +614,7 @@ LABEL_HELP = "Ajuda" LABEL_CHECK_ALL = "Selecionar todas as mensagens" LABEL_ARCHIVE = "Arquivar" LABEL_DELETE = "Excluir" +LABEL_OPEN_THREAD = "Abrir conversa selecionada" LABEL_MOVE = "Mover" LABEL_READ = "Marcar mensagens selecionadas como lidas" LABEL_UNREAD = "Marcar mensagens selecionadas como não lidas" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Os Cookies não estão disponíveis em seu navegador. Por favor ative o suporte a Cookies nas configurações do seu navegador e tente novamente." BAD_BROWSER_TITLE = "Seu navegador está desatualizado." BAD_BROWSER_DESC = "Para usar todos os recursos do aplicativo, -baixe e instale um desses navegadores:" \ No newline at end of file +baixe e instale um desses navegadores:" diff --git a/rainloop/v/0.0.0/langs/pt-pt.ini b/rainloop/v/0.0.0/langs/pt-pt.ini index a20ff536a..22fb83de1 100644 --- a/rainloop/v/0.0.0/langs/pt-pt.ini +++ b/rainloop/v/0.0.0/langs/pt-pt.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Não sinalizada" EMPTY_LIST = "Lista vazia." EMPTY_SEARCH_LIST = "Nenhuma mensagem corresponde à sua pesquisa." SEARCH_RESULT_FOR = "Resultados de pesquisa para \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "voltar à lista de mensagens" LIST_LOADING = "A carregar" EMPTY_SUBJECT_TEXT = "(Sem assunto)" PUT_MESSAGE_HERE = "Coloque a mensagem aqui para vê-la na lista" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Selecione uma mensagem na lista para ver o conteúdo aqui." PGP_PASSWORD_INPUT_PLACEHOLDER = "Palavra-passe" PGP_SIGNED_MESSAGE_DESC = "Mensagem assinada com OpenPGP (clique para verificar)" PGP_ENCRYPTED_MESSAGE_DESC = "Mensagem encriptada com OpenPGP (clique para desencriptar)" +LINK_DOWNLOAD_AS_ZIP = "Transferir em arquivo zip" +LINK_SAVE_TO_OWNCLOUD = "Guardar no ownCloud" +LINK_SAVE_TO_DROPBOX = "Guardar no Dropbox" [READ_RECEIPT] SUBJECT = "Recibo de leitura (apresentado) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Renomear a pasta" DELETING_ASK = "Tem a certeza?" TO_MANY_FOLDERS_DESC_1 = "Você tem demasiadas pastas!" TO_MANY_FOLDERS_DESC_2 = "Apenas apresentámos parte delas, para evitar problemas de desempenho." +HELP_DELETE_FOLDER = "Eliminar pasta" +HELP_SHOW_HIDE_FOLDER = "Mostrar/ocultar pasta" +HELP_SHOW_HIDE_FOLDER_COUNT = "Mostrar/ocultar contador de mensagens por pasta" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Lista de contas" @@ -607,6 +614,7 @@ LABEL_HELP = "Ajuda" LABEL_CHECK_ALL = "Selecionar todas as mensagens" LABEL_ARCHIVE = "Arquivar" LABEL_DELETE = "Eliminar" +LABEL_OPEN_THREAD = "Abrir tópico selecionado" LABEL_MOVE = "Mover" LABEL_READ = "Ler as mensagens selecionadas" LABEL_UNREAD = "Marcar as mensagens selecionadas como não lidas" @@ -644,14 +652,14 @@ SPECIFY_FROM_EMAIL = "Por favor especifique o endereço de email DE" SPECIFY_AT_LEAST_ONE_RECIPIENT = "Por favor especifique pelo menos um destinatário" [NOTIFICATIONS] -INVALID_TOKEN = "Palavra-passe inválida" +INVALID_TOKEN = "Token inválido" AUTH_ERROR = "Falha na autenticação" -ACCESS_ERROR = "Erro ao aceder" -CONNECTION_ERROR = "Não doi possível ligar ao servidor" -CAPTCHA_ERROR = "CAPTCHA incorreto." -SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE = "Esta ID social ainda não foi atribuída a nenhuma conta de email. Inicie sessão com credenciais de email e ative este recurso nas definições de conta." -SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE = "Esta ID social ainda não foi atribuída a nenhuma conta de email. Inicie sessão com credenciais de email e ative este recurso nas definições de conta." -SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE = "Esta ID social ainda não foi atribuída a nenhuma conta de email. Inicie sessão com credenciais de email e ative este recurso nas definições de conta." +ACCESS_ERROR = "Erro de acesso" +CONNECTION_ERROR = "Não foi possível conectar ao servidor" +CAPTCHA_ERROR = "Verificação CAPTCHA inválido." +SOCIAL_FACEBOOK_LOGIN_ACCESS_DISABLE = "Esse usuário Facebook não está associado a nenhuma conta de email ainda. Entre utilizando seu email e senha e habilite essa função em suas configurações de conta." +SOCIAL_TWITTER_LOGIN_ACCESS_DISABLE = "Esse usuário Twitter não está associado a nenhuma conta de email ainda. Entre utilizando seu email e senha e habilite essa função em suas configurações de conta." +SOCIAL_GOOGLE_LOGIN_ACCESS_DISABLE = "Esse usuário Google não está associado a nenhuma conta de email ainda. Entre utilizando seu email e senha e habilite essa função em suas configurações de conta." DOMAIN_NOT_ALLOWED = "Este domínio não é permitido" ACCOUNT_NOT_ALLOWED = "Esta conta não é permitida" ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "É necessária a verificação em duas etapas" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "O suport de Cookies não está disponível no seu navegador. Por favor ative o suporte de Cookies nas definições do seu navegador e tente novamente." BAD_BROWSER_TITLE = "O seu navegador está desatualizado." BAD_BROWSER_DESC = "Para usar todas as funções da aplicação, -transfira e instale um destes navegadores:" \ No newline at end of file +transfira e instale um destes navegadores:" diff --git a/rainloop/v/0.0.0/langs/ro.ini b/rainloop/v/0.0.0/langs/ro.ini index 157555e6f..f265a452d 100644 --- a/rainloop/v/0.0.0/langs/ro.ini +++ b/rainloop/v/0.0.0/langs/ro.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Neimportant" EMPTY_LIST = "Nimic aici, deocamdată" EMPTY_SEARCH_LIST = "Nu am găsit nimic." SEARCH_RESULT_FOR = "Rezultat căutare \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Caut..." EMPTY_SUBJECT_TEXT = "(nici un subiect)" PUT_MESSAGE_HERE = "EROARE CONTACTATI ADIMISTRATORUL!(trimite-ti un prt.scr)" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Selectați un mesaj pentru al vizualiza." PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Mesajul dumneavoastră a fost citit - %SUBJECT%" @@ -550,6 +554,9 @@ RENAMING_PROCESS = "Redenumesc dosarul" DELETING_ASK = "Sunteți sigur?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Lista de Conturi" @@ -606,6 +613,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -707,4 +715,4 @@ NO_COOKIE_DESC = "Aparent, Cookie sau nu este acceptat de browser-ul dvs. sau op Întoarceți Cookie, schimband optiunile browserului dvs., apoi încercați din nou." BAD_BROWSER_TITLE = "Browser-ul dvs. este depășit." BAD_BROWSER_DESC = "Pentru a utiliza toate caracteristicile cererii, -descărcați și instalați unul dintre aceste browsere:" \ No newline at end of file +descărcați și instalați unul dintre aceste browsere:" diff --git a/rainloop/v/0.0.0/langs/ru.ini b/rainloop/v/0.0.0/langs/ru.ini index fe867ba1b..1626f5eb0 100644 --- a/rainloop/v/0.0.0/langs/ru.ini +++ b/rainloop/v/0.0.0/langs/ru.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Без пометок" EMPTY_LIST = "В папке нет писем." EMPTY_SEARCH_LIST = "Письма не найдены." SEARCH_RESULT_FOR = "Результат поиска \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Загрузка" EMPTY_SUBJECT_TEXT = "(Без темы)" PUT_MESSAGE_HERE = "Киньте сообщение сюда, чтобы посмотреть его в списке" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Выберите сообщение для просмотр PGP_PASSWORD_INPUT_PLACEHOLDER = "Пароль" PGP_SIGNED_MESSAGE_DESC = "OpenPGP подписанное сообщение (нажмите, чтобы подтвердить)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP шифрованное сообщение (нажмите, чтобы расшифровать)" +LINK_DOWNLOAD_AS_ZIP = "Сохранить как zip" +LINK_SAVE_TO_OWNCLOUD = "Сохранить в ownCloud" +LINK_SAVE_TO_DROPBOX = "Сохранить в Dropbox" [READ_RECEIPT] SUBJECT = "Уведомление о прочтении письма - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Переименование папки" DELETING_ASK = "Точно?" TO_MANY_FOLDERS_DESC_1 = "У вас слишком много папок!" TO_MANY_FOLDERS_DESC_2 = "Мы показали только часть из них, чтобы избежать проблем с производительностью." +HELP_DELETE_FOLDER = "Удалить папку" +HELP_SHOW_HIDE_FOLDER = "Показывать/прятать папку" +HELP_SHOW_HIDE_FOLDER_COUNT = "Показывать/прятать количество сообщений в папке" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Список Аккаунтов" @@ -607,6 +614,7 @@ LABEL_HELP = "Помощь" LABEL_CHECK_ALL = "Выбрать все сообщения" LABEL_ARCHIVE = "Архивировать" LABEL_DELETE = "Удалить" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Переместить" LABEL_READ = "Пометить выбранные письма прочитанными" LABEL_UNREAD = "Пометить выбранные письма непрочитанными" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "По-видимому, Cookie либо не поддержив Включите Cookie, изменив настройки браузера, затем повторите попытку." BAD_BROWSER_TITLE = "Ваш браузер устарел." BAD_BROWSER_DESC = "Чтобы использовать все возможности приложения, -загрузите и установите один из этих браузеров:" \ No newline at end of file +загрузите и установите один из этих браузеров:" diff --git a/rainloop/v/0.0.0/langs/sk.ini b/rainloop/v/0.0.0/langs/sk.ini index ec6f1512b..3b43c2c58 100644 --- a/rainloop/v/0.0.0/langs/sk.ini +++ b/rainloop/v/0.0.0/langs/sk.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Neoznačené" EMPTY_LIST = "Zoznam je prázdny." EMPTY_SEARCH_LIST = "Vášmu vyhľadávaniu nezodpovedajú žiadne správy." SEARCH_RESULT_FOR = "Výsledky vyhľadávania pre \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Načítavam" EMPTY_SUBJECT_TEXT = "(Bez predmetu)" PUT_MESSAGE_HERE = "Pusťte správu tu pre jej zobrazenie v zozname" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Vyberte správu zo zoznamu pre jej zobrazenie tu." PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Premenovávam prečinok" DELETING_ASK = "Ste si istý?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Zoznam účtov" @@ -607,6 +614,7 @@ LABEL_HELP = "Help" LABEL_CHECK_ALL = "Select all messages" LABEL_ARCHIVE = "Archive" LABEL_DELETE = "Delete" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Move" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Váš internetový prehliadač nepodporuje Cookies. Prosím povoľte podporu Cookies v nastaveniach vášho internetového prehliadača a skúste to znova." BAD_BROWSER_TITLE = "Používate zastaralý internetový prehliadač." BAD_BROWSER_DESC = "Aby ste mohli využívať všetky funkcie tejto aplikácie, -použite niektorý z nasledujúcich internetových prehliadačov:" \ No newline at end of file +použite niektorý z nasledujúcich internetových prehliadačov:" diff --git a/rainloop/v/0.0.0/langs/sv.ini b/rainloop/v/0.0.0/langs/sv.ini index bee76c80f..eff3f163e 100644 --- a/rainloop/v/0.0.0/langs/sv.ini +++ b/rainloop/v/0.0.0/langs/sv.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Avflaggad" EMPTY_LIST = "Tom lista" EMPTY_SEARCH_LIST = "Inga meddelanden matchar din sökning." SEARCH_RESULT_FOR = "Sök resultat för \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Laddar" EMPTY_SUBJECT_TEXT = "(Inget ämne)" PUT_MESSAGE_HERE = "Släpp meddelandet för att se det i listan" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Välj meddelande i listan att visa här." PGP_PASSWORD_INPUT_PLACEHOLDER = "Lösenord" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signerat meddelande (klicka för att verifiera)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP krypterat meddelande (klicka för att dekryptera)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Mottagar kvitto (visad) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Byter namn på en mapp" DELETING_ASK = "Är du säker?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Kontolista" @@ -607,6 +614,7 @@ LABEL_HELP = "Hjälp" LABEL_CHECK_ALL = "Kontrollera alla meddelanden" LABEL_ARCHIVE = "Arkiv" LABEL_DELETE = "Ta bort" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Flytta" LABEL_READ = "Markera valda meddelanden som lästa" LABEL_UNREAD = "Markera valda meddelanden som olästa" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Kakstöd är inte tillgängligt i webbläsaren. Aktivera kakstöd och försök igen." BAD_BROWSER_TITLE = "Webbläsaren är för gammal." BAD_BROWSER_DESC = "För att använda funktioner i denna applikation, -ladda ner och installera någon av följande webbläsare:" \ No newline at end of file +ladda ner och installera någon av följande webbläsare:" diff --git a/rainloop/v/0.0.0/langs/tr.ini b/rainloop/v/0.0.0/langs/tr.ini index 710626341..017ec19fb 100644 --- a/rainloop/v/0.0.0/langs/tr.ini +++ b/rainloop/v/0.0.0/langs/tr.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "İşaretlenmemiş" EMPTY_LIST = "Herhangi Bir Sonuç Bulunamadı." EMPTY_SEARCH_LIST = "Arama sonuçlarına uyan herhangi bir mesaj bulunamadı." SEARCH_RESULT_FOR = "\"%SEARCH%\" için arama sonuçları" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Yükleniyor" EMPTY_SUBJECT_TEXT = "(Konu Yok)" PUT_MESSAGE_HERE = "Listede görüntülemek istediğiniz mesajı buraya sürükleyin." @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Burada görüntülemek için listeden mesaj seçin." PGP_PASSWORD_INPUT_PLACEHOLDER = "Şifre" PGP_SIGNED_MESSAGE_DESC = "OpenPGP imzalı mesaj (onay için tıklayın)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP şifreli mesaj (çözmek için tıklayın)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -549,6 +553,9 @@ RENAMING_PROCESS = "Klasör değiştiriliyor" DELETING_ASK = "Eminmisiniz?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Hesap Listesi" @@ -605,6 +612,7 @@ LABEL_HELP = "Yardım" LABEL_CHECK_ALL = "Tüm mesajları seç" LABEL_ARCHIVE = "Arşiv" LABEL_DELETE = "Sil" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Taşı" LABEL_READ = "Seçili mesajları oku" LABEL_UNREAD = "Seçili mesajları okuma" @@ -706,4 +714,4 @@ NO_COOKIE_DESC = "Çerezler destek tarayıcınızda mevcut değil. Tarayıcı ayarlarında Cookie desteği etkinleştirin ve yeniden deneyin." BAD_BROWSER_TITLE = "Tarayıcınız çok eski." BAD_BROWSER_DESC = "Uygulamanın tüm özelliklerini kullanmak için, -Bu tarayıcılardan birini indirmek ve yükleyin:" \ No newline at end of file +Bu tarayıcılardan birini indirmek ve yükleyin:" diff --git a/rainloop/v/0.0.0/langs/ua.ini b/rainloop/v/0.0.0/langs/ua.ini index 7968a966d..6d696cb48 100644 --- a/rainloop/v/0.0.0/langs/ua.ini +++ b/rainloop/v/0.0.0/langs/ua.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "Без поміток" EMPTY_LIST = "В папці немає листів." EMPTY_SEARCH_LIST = "Листи не знайдено." SEARCH_RESULT_FOR = "Результат пошуку \"%SEARCH%\"" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "Завантаження" EMPTY_SUBJECT_TEXT = "(Без теми)" PUT_MESSAGE_HERE = "Киньте повідомлення сюди, щоб подивитися його в списку" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "Выберіть повідомлення для перег PGP_PASSWORD_INPUT_PLACEHOLDER = "Пароль" PGP_SIGNED_MESSAGE_DESC = "OpenPGP підписане повідомлення (натисніть, щоб підтвердити)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP шифроване повідомлення (натисніть, щоб розшифрувати)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Сповіщення про прочитання цього повідомлення - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "Перейменування теки" DELETING_ASK = "Впевнені?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "Список акаунтів" @@ -607,6 +614,7 @@ LABEL_HELP = "Допомога" LABEL_CHECK_ALL = "Вибрати всі повідомлення" LABEL_ARCHIVE = "Архівувати" LABEL_DELETE = "Видалити" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "Перемістити" LABEL_READ = "Відмітити вибрані листи прочитаними" LABEL_UNREAD = "Відмітити вибрані листи непрочитаними" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "Здається, Cookie або не підтримуютьс Увімкніть Cookie, змінив налаштівання браузера, і спробуйте знову." BAD_BROWSER_TITLE = "Ваш браузер застарів." BAD_BROWSER_DESC = "Щоб використовувати всі можливості додатку, -завантажте і встановіть один з цих браузерів:" \ No newline at end of file +завантажте і встановіть один з цих браузерів:" diff --git a/rainloop/v/0.0.0/langs/zh-cn.ini b/rainloop/v/0.0.0/langs/zh-cn.ini index 21ee3af13..bf69baaba 100644 --- a/rainloop/v/0.0.0/langs/zh-cn.ini +++ b/rainloop/v/0.0.0/langs/zh-cn.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "未加星标" EMPTY_LIST = "空列表。" EMPTY_SEARCH_LIST = "未找到您想要的。" SEARCH_RESULT_FOR = "搜索 \"%SEARCH%\" 的结果" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "加载中" EMPTY_SUBJECT_TEXT = "(无主题)" PUT_MESSAGE_HERE = "在这里查看邮件" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "在此查看列表中选中的邮件。" PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "文件夹重命名" DELETING_ASK = "确定?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "账户列表" @@ -607,6 +614,7 @@ LABEL_HELP = "帮助" LABEL_CHECK_ALL = "检查所有消息" LABEL_ARCHIVE = "存档" LABEL_DELETE = "删除" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "移动" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "您的浏览器不支持Cookies。 请启用浏览器的Cookies支持并重试。" BAD_BROWSER_TITLE = "您的浏览器过时了" BAD_BROWSER_DESC = "为了使用所有特性, -下载并安装其中一款浏览器:" \ No newline at end of file +下载并安装其中一款浏览器:" diff --git a/rainloop/v/0.0.0/langs/zh-tw.ini b/rainloop/v/0.0.0/langs/zh-tw.ini index f6b6ff4bd..a2a03f391 100644 --- a/rainloop/v/0.0.0/langs/zh-tw.ini +++ b/rainloop/v/0.0.0/langs/zh-tw.ini @@ -90,6 +90,7 @@ MENU_SELECT_UNFLAGGED = "未加星標" EMPTY_LIST = "空列表。" EMPTY_SEARCH_LIST = "未找到您想要的。" SEARCH_RESULT_FOR = "搜索 \"%SEARCH%\" 的結果" +BACK_TO_MESSAGE_LIST = "back to message list" LIST_LOADING = "載入中" EMPTY_SUBJECT_TEXT = "(無主題)" PUT_MESSAGE_HERE = "在這裡查看郵件" @@ -149,6 +150,9 @@ MESSAGE_VIEW_DESC = "在此查看列表中選中的郵件。" PGP_PASSWORD_INPUT_PLACEHOLDER = "Password" PGP_SIGNED_MESSAGE_DESC = "OpenPGP signed message (click to verify)" PGP_ENCRYPTED_MESSAGE_DESC = "OpenPGP encrypted message (click to decrypt)" +LINK_DOWNLOAD_AS_ZIP = "Download as zip" +LINK_SAVE_TO_OWNCLOUD = "Save to ownCloud" +LINK_SAVE_TO_DROPBOX = "Save to Dropbox" [READ_RECEIPT] SUBJECT = "Return Receipt (displayed) - %SUBJECT%" @@ -551,6 +555,9 @@ RENAMING_PROCESS = "資料夾重命名" DELETING_ASK = "確定?" TO_MANY_FOLDERS_DESC_1 = "You have too many folders!" TO_MANY_FOLDERS_DESC_2 = "We have shown only a part of them, to avoid performance problems." +HELP_DELETE_FOLDER = "Delete folder" +HELP_SHOW_HIDE_FOLDER = "Show/hide folder" +HELP_SHOW_HIDE_FOLDER_COUNT = "Show/hide folder message count" [SETTINGS_ACCOUNTS] LEGEND_ACCOUNTS = "帳戶列表" @@ -607,6 +614,7 @@ LABEL_HELP = "幫助" LABEL_CHECK_ALL = "檢查所有消息" LABEL_ARCHIVE = "存檔" LABEL_DELETE = "刪除" +LABEL_OPEN_THREAD = "Open selected thread" LABEL_MOVE = "移動" LABEL_READ = "Read selected messages" LABEL_UNREAD = "Unread selected messages" @@ -708,4 +716,4 @@ NO_COOKIE_DESC = "您的瀏覽器不支持Cookies。 請啟用瀏覽器的Cookies支持並重試。" BAD_BROWSER_TITLE = "您的瀏覽器過時了" BAD_BROWSER_DESC = "為了使用所有特性, -下載並安裝其中一款瀏覽器:" \ No newline at end of file +下載並安裝其中一款瀏覽器:" diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.eot b/rainloop/v/0.0.0/static/css/fonts/rainloop.eot index cb64d4537..d2bf0d541 100644 Binary files a/rainloop/v/0.0.0/static/css/fonts/rainloop.eot and b/rainloop/v/0.0.0/static/css/fonts/rainloop.eot differ diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.svg b/rainloop/v/0.0.0/static/css/fonts/rainloop.svg index 3357f3364..cc66a0e77 100644 --- a/rainloop/v/0.0.0/static/css/fonts/rainloop.svg +++ b/rainloop/v/0.0.0/static/css/fonts/rainloop.svg @@ -122,4 +122,5 @@ + diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf b/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf index 3cc1d79cc..09186ead5 100644 Binary files a/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf and b/rainloop/v/0.0.0/static/css/fonts/rainloop.ttf differ diff --git a/rainloop/v/0.0.0/static/css/fonts/rainloop.woff b/rainloop/v/0.0.0/static/css/fonts/rainloop.woff index 3ff6793d9..a7d4ab38c 100644 Binary files a/rainloop/v/0.0.0/static/css/fonts/rainloop.woff and b/rainloop/v/0.0.0/static/css/fonts/rainloop.woff differ diff --git a/vendors/fontastic/fonts/rainloop.eot b/vendors/fontastic/fonts/rainloop.eot index cb64d4537..d2bf0d541 100644 Binary files a/vendors/fontastic/fonts/rainloop.eot and b/vendors/fontastic/fonts/rainloop.eot differ diff --git a/vendors/fontastic/fonts/rainloop.svg b/vendors/fontastic/fonts/rainloop.svg index 3357f3364..cc66a0e77 100644 --- a/vendors/fontastic/fonts/rainloop.svg +++ b/vendors/fontastic/fonts/rainloop.svg @@ -122,4 +122,5 @@ + diff --git a/vendors/fontastic/fonts/rainloop.ttf b/vendors/fontastic/fonts/rainloop.ttf index 3cc1d79cc..09186ead5 100644 Binary files a/vendors/fontastic/fonts/rainloop.ttf and b/vendors/fontastic/fonts/rainloop.ttf differ diff --git a/vendors/fontastic/fonts/rainloop.woff b/vendors/fontastic/fonts/rainloop.woff index 3ff6793d9..a7d4ab38c 100644 Binary files a/vendors/fontastic/fonts/rainloop.woff and b/vendors/fontastic/fonts/rainloop.woff differ diff --git a/vendors/fontastic/icons-reference.html b/vendors/fontastic/icons-reference.html index 7132f5af8..94b3b7e95 100644 --- a/vendors/fontastic/icons-reference.html +++ b/vendors/fontastic/icons-reference.html @@ -505,6 +505,10 @@ h2{font-size:18px;padding:0 0 21px 5px;margin:45px 0 0 0;text-transform:uppercas
+
  • +
    + +
  • Character mapping

      @@ -968,6 +972,10 @@ h2{font-size:18px;padding:0 0 21px 5px;margin:45px 0 0 0;text-transform:uppercas
      +
    • +
      + +