diff --git a/README.md b/README.md index d5e5b2dcb..0d358d727 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ RainLoop Webmail ================== +[](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': '