diff --git a/dev/App/Abstract.js b/dev/App/Abstract.js index 401cdac1f..06f43d7e8 100644 --- a/dev/App/Abstract.js +++ b/dev/App/Abstract.js @@ -73,7 +73,7 @@ }, 50)); - // TODO + // DEBUG // Events.sub({ // 'window.resize': function () { // window.console.log('window.resize'); diff --git a/dev/App/User.js b/dev/App/User.js index a67032d2c..d9bb971b4 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -211,7 +211,7 @@ /** * @param {Function} fResultFunc - * @returns {boolean} + * @return {boolean} */ AppUser.prototype.contactsSync = function (fResultFunc) { diff --git a/dev/Common/Audio.js b/dev/Common/Audio.js index d4e3046d0..8e437fc88 100644 --- a/dev/Common/Audio.js +++ b/dev/Common/Audio.js @@ -20,58 +20,90 @@ { var self = this; - this.obj = this.createNewObject(); - this.objForNotification = this.createNewObject(); +// this.userMedia = window.navigator.getUserMedia || window.navigator.webkitGetUserMedia || +// window.navigator.mozGetUserMedia || window.navigator.msGetUserMedia; +// +// this.audioContext = window.AudioContext || window.webkitAudioContext; +// if (!this.audioContext || !window.Float32Array) +// { +// this.audioContext = null; +// this.userMedia = null; +// } - this.supported = !Globals.bMobileDevice && !Globals.bSafari && - this.obj && '' !== this.obj.canPlayType('audio/mpeg'); + this.player = this.createNewObject(); - if (this.obj && this.supported) + this.supported = !Globals.bMobileDevice && !Globals.bSafari && !!this.player && !!this.player.play; + if (this.supported && this.player.canPlayType) { - this.objForNotification.src = Links.sound('new-mail.mp3'); + this.supportedMp3 = '' !== this.player.canPlayType('audio/mpeg;').replace(/no/, ''); + this.supportedWav = '' !== this.player.canPlayType('audio/wav; codecs="1"').replace(/no/, ''); + this.supportedOgg = '' !== this.player.canPlayType('audio/ogg; codecs="vorbis"').replace(/no/, ''); + this.supportedNotification = this.supported && this.supportedMp3; + } - $(this.obj).on('ended error', function () { + if (!this.player || (!this.supportedMp3 && !this.supportedOgg && !this.supportedWav)) + { + this.supported = false; + this.supportedMp3 = false; + this.supportedOgg = false; + this.supportedWav = false; + this.supportedNotification = false; + } + + if (this.supported) + { + $(this.player).on('ended error', function () { self.stop(); }); Events.sub('audio.api.stop', function () { self.stop(); }); - - Events.sub('audio.api.play', function (sUrl, sName) { - self.playMp3(sUrl, sName); - }); } } - Audio.prototype.obj = null; - Audio.prototype.objForNotification = null; + Audio.prototype.player = null; + Audio.prototype.notificator = null; + Audio.prototype.supported = false; + Audio.prototype.supportedMp3 = false; + Audio.prototype.supportedOgg = false; + Audio.prototype.supportedWav = false; + Audio.prototype.supportedNotification = false; + +// Audio.prototype.record = function () +// { +// this.getUserMedia({audio:true}, function () { +// window.console.log(arguments); +// }, function(oError) { +// window.console.log(arguments); +// }); +// }; Audio.prototype.createNewObject = function () { - var obj = window.Audio ? new window.Audio() : null; - if (obj && obj.canPlayType) + var player = window.Audio ? new window.Audio() : null; + if (player && player.canPlayType && player.pause && player.play) { - obj.preload = 'none'; - obj.loop = false; - obj.autoplay = false; - obj.muted = false; + player.preload = 'none'; + player.loop = false; + player.autoplay = false; + player.muted = false; } - return obj; + return player; }; Audio.prototype.paused = function () { - return this.supported ? !!this.obj.paused : true; + return this.supported ? !!this.player.paused : true; }; Audio.prototype.stop = function () { - if (this.supported && this.obj.pause) + if (this.supported && this.player.pause) { - this.obj.pause(); + this.player.pause(); } Events.pub('audio.stop'); @@ -79,33 +111,73 @@ Audio.prototype.pause = Audio.prototype.stop; + Audio.prototype.clearName = function (sName, sExt) + { + sExt = sExt || ''; + sName = Utils.isUnd(sName) ? '' : Utils.trim(sName); + if (sExt && '.' + sExt === sName.toLowerCase().substr((sExt.length + 1) * -1)) + { + sName = Utils.trim(sName.substr(0, sName.length - 4)); + } + + if ('' === sName) + { + sName = 'audio'; + } + + return sName; + }; + Audio.prototype.playMp3 = function (sUrl, sName) { - if (this.supported && this.obj.play) + if (this.supported && this.supportedMp3) { - this.obj.src = sUrl; - this.obj.play(); + this.player.src = sUrl; + this.player.play(); - sName = Utils.isUnd(sName) ? '' : Utils.trim(sName); - if ('.mp3' === sName.toLowerCase().substr(-4)) - { - sName = Utils.trim(sName.substr(0, sName.length - 4)); - } + Events.pub('audio.start', [this.clearName(sName, 'mp3'), 'mp3']); + } + }; - if ('' === sName) - { - sName = 'audio'; - } + Audio.prototype.playOgg = function (sUrl, sName) + { + if (this.supported && this.supportedOgg) + { + this.player.src = sUrl; + this.player.play(); - Events.pub('audio.start', [sName]); + sName = this.clearName(sName, 'oga'); + sName = this.clearName(sName, 'ogg'); + + Events.pub('audio.start', [sName, 'ogg']); + } + }; + + Audio.prototype.playWav = function (sUrl, sName) + { + if (this.supported && this.supportedWav) + { + this.player.src = sUrl; + this.player.play(); + + Events.pub('audio.start', [this.clearName(sName, 'wav'), 'wav']); } }; Audio.prototype.playNotification = function () { - if (this.supported && this.objForNotification.play) + if (this.supported && this.supportedMp3) { - this.objForNotification.play(); + if (!this.notificator) + { + this.notificator = this.createNewObject(); + this.notificator.src = Links.sound('new-mail.mp3'); + } + + if (this.notificator && this.notificator.play) + { + this.notificator.play(); + } } }; diff --git a/dev/Common/Enums.js b/dev/Common/Enums.js index 2a18a0d0d..36c56ad5e 100644 --- a/dev/Common/Enums.js +++ b/dev/Common/Enums.js @@ -5,6 +5,26 @@ var Enums = {}; + /** + * @enum {string} + */ + Enums.FileType = { + 'Unknown': 'unknown', + 'Text': 'text', + 'Html': 'html', + 'Code': 'code', + 'Eml': 'eml', + 'WordText': 'word-text', + 'Pdf': 'pdf', + 'Image': 'image', + 'Audio': 'audio', + 'Video': 'video', + 'Sheet': 'sheet', + 'Presentation': 'presentation', + 'Certificate': 'certificate', + 'Archive': 'archive' + }; + /** * @enum {string} */ diff --git a/dev/Common/Globals.js b/dev/Common/Globals.js index b408c073a..3a4bfb5c1 100644 --- a/dev/Common/Globals.js +++ b/dev/Common/Globals.js @@ -18,6 +18,7 @@ Globals.$win = $(window); Globals.$doc = $(window.document); Globals.$html = $('html'); + Globals.$body = $('body'); Globals.$div = $('
'); Globals.$win.__sizes = [0, 0]; @@ -268,7 +269,7 @@ }); Globals.keyScopeReal.subscribe(function (sValue) { -// window.console.log('keyScope=' + sValue); // TODO +// window.console.log('keyScope=' + sValue); // DEBUG key.setScope(sValue); }); diff --git a/dev/Common/HtmlEditor.js b/dev/Common/HtmlEditor.js index 27812bd7b..8e9f25c69 100644 --- a/dev/Common/HtmlEditor.js +++ b/dev/Common/HtmlEditor.js @@ -31,6 +31,9 @@ this.resize = _.throttle(_.bind(this.resize, this), 100); + this.__inited = false; + this.__initedData = null; + this.init(); } @@ -190,7 +193,7 @@ HtmlEditor.prototype.setHtml = function (sHtml, bFocus) { - if (this.editor) + if (this.editor && this.__inited) { this.modeToggle(true); @@ -203,11 +206,15 @@ this.focus(); } } + else + { + this.__initedData = [true, sHtml, bFocus]; + } }; 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) @@ -226,6 +233,10 @@ this.focus(); } } + else + { + this.__initedData = [false, sPlain, bFocus]; + } }; HtmlEditor.prototype.init = function () @@ -322,7 +333,20 @@ self.fOnReady(); self.__resizable = true; + self.__inited = true; self.resize(); + + if (self.__initedData) + { + if (self.__initedData[0]) + { + self.setHtml(self.__initedData[1], self.__initedData[2]); + } + else + { + self.setPlain(self.__initedData[1], self.__initedData[2]); + } + } }); } } diff --git a/dev/Common/Selector.js b/dev/Common/Selector.js index 05ebf76bf..0918e42b1 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) { 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/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..b2e1e4712 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -33,7 +33,7 @@ } }, fUpdateKoValue = function () { - if (oEditor) + if (oEditor && oEditor.__inited) { fValue(oEditor.getDataWithHtmlMark()); } @@ -49,6 +49,7 @@ fValue.__updateEditorValue = fUpdateEditorValue; fValue.subscribe(fUpdateEditorValue); + fUpdateEditorValue(); } } }; @@ -61,6 +62,7 @@ sValue = '', Translator = null, $oEl = $(oElement), + fValue = fValueAccessor(), bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'), Globals = require('Common/Globals') ; @@ -68,7 +70,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 +80,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 +124,7 @@ bi18n = true, sValue = '', $oEl = $(oElement), + fValue = fValueAccessor(), bMobile = 'on' === ($oEl.data('tooltip-mobile') || 'off'), Globals = require('Common/Globals') ; @@ -120,7 +132,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 +177,8 @@ var $oEl = $(oElement), - sValue = ko.unwrap(fValueAccessor()), + fValue = fValueAccessor(), + sValue = !ko.isObservable(fValue) && _.isFunction(fValue) ? fValue() : ko.unwrap(fValue), oOpenTips = oElement.__opentip ; diff --git a/dev/Model/Attachment.js b/dev/Model/Attachment.js index 3b126565d..d4dc6911e 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,7 @@ */ 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 +197,10 @@ */ AttachmentModel.prototype.hasPreplay = function () { - return this.isMp3(); + return (Audio.supportedMp3 && this.isMp3()) || + (Audio.supportedOgg && this.isOgg()) || + (Audio.supportedWav && this.isWav()) + ; }; /** @@ -229,7 +261,7 @@ switch (true) { case this.isImage(): - case this.isPdf(): + case this.isPdf() && Globals.bAllowPdfPreview: sResult = this.linkPreview(); break; case this.isText(): @@ -274,76 +306,85 @@ }; /** + * @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/pkcs7-signature', 'application/pgp-keys' + ]) || -1 < Utils.inArray(sExt, [ + 'asc', 'pem', 'ppk' + ]): + sResult = Enums.FileType.Certificate; + 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 +392,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 +404,140 @@ '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: + 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: + 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/Message.js b/dev/Model/Message.js index c9f89d51e..399e51912 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; 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/Promises/User/Ajax.js b/dev/Promises/User/Ajax.js index 4051aa901..6dec2b76f 100644 --- a/dev/Promises/User/Ajax.js +++ b/dev/Promises/User/Ajax.js @@ -112,6 +112,14 @@ }); }; + UserAjaxUserPromises.prototype.attachmentsActions = function (sAction, aHashes, fTrigger) + { + return this.postRequest('AttachmentsActions', fTrigger, { + 'Action': sAction, + 'Hashes': aHashes + }); + }; + UserAjaxUserPromises.prototype.welcomeClose = function () { return this.postRequest('WelcomeClose'); 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/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/Folder.js b/dev/Stores/User/Folder.js index dfa5c6b66..8b04773fe 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -182,7 +182,7 @@ /** * @param {boolean=} bBoot = false - * @returns {Array} + * @return {Array} */ FolderUserStore.prototype.getNextFolderNames = function (bBoot) { diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 2a93eaa24..b8d5691c4 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -714,7 +714,7 @@ /** * @param {Array} aList - * @returns {string} + * @return {string} */ MessageUserStore.prototype.calculateMessageListHash = function (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/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/Login.less b/dev/Styles/Login.less index 0fc1d0095..2a913bb65 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 { diff --git a/dev/Styles/MessageList.less b/dev/Styles/MessageList.less index 6d51ce18e..4c7dba60a 100644 --- a/dev/Styles/MessageList.less +++ b/dev/Styles/MessageList.less @@ -322,7 +322,7 @@ html.rl-no-preview-pane { &.e-single-line .attachmentParent { float: left; - margin: 0 5px 0 0; + margin: 0 8px 0 0; } .senderParent { diff --git a/dev/Styles/Ui.less b/dev/Styles/Ui.less index 94e44bad7..dbfa7b86b 100644 --- a/dev/Styles/Ui.less +++ b/dev/Styles/Ui.less @@ -230,4 +230,4 @@ html.cssanimations { .command.command-disabled.hide-on-disabled-command { display:none; -} \ No newline at end of file +} 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/View/Admin/Login.js b/dev/View/Admin/Login.js index bad88f134..e6a64c8ec 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())); @@ -67,6 +87,7 @@ { if (oData.Result) { + this.formHidden(true); require('App/Admin').loginAndLogoutReload(true); } else if (oData.ErrorCode) 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/Compose.js b/dev/View/Popup/Compose.js index 9640a981e..42b8bf4ab 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -995,7 +995,7 @@ * * @param {Array} aList * @param {boolean} bFriendly - * @returns {string} + * @return {string} */ ComposePopupView.prototype.emailArrayToStringLineHelper = function (aList, bFriendly) { diff --git a/dev/View/User/Login.js b/dev/View/User/Login.js index 406c1492d..f2ea8c45b 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; } @@ -172,10 +213,12 @@ } else if (oData.Admin) { + this.formHidden(true); require('App/User').redirectToAdminPanel(); } else { + this.formHidden(true); require('App/User').loginAndLogoutReload(false); } } @@ -339,6 +382,8 @@ if (0 === iErrorCode) { self.submitRequest(true); + self.formHidden(true); + require('App/User').loginAndLogoutReload(false); } else diff --git a/dev/View/User/MailBox/MessageList.js b/dev/View/User/MailBox/MessageList.js index 977895412..cd254167f 100644 --- a/dev/View/User/MailBox/MessageList.js +++ b/dev/View/User/MailBox/MessageList.js @@ -374,7 +374,7 @@ }; /** - * @returns {string} + * @return {string} */ MessageListMailBoxUserView.prototype.printableMessageCountForDeletion = function () { diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index ce4b99dc7..92315b3c1 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -82,6 +82,10 @@ this.allowAttachmnetControls = ko.observable(false); this.showAttachmnetControls = ko.observable(false); + this.downloadAsZipLoading = ko.observable(false); + this.saveToOwnCloudLoading = ko.observable(false); + this.saveToDropboxLoading = ko.observable(false); + this.showAttachmnetControls.subscribe(function (bV) { if (this.message()) { @@ -115,12 +119,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); @@ -759,9 +757,20 @@ } var oAttachment = ko.dataFor(this); - if (oAttachment && oAttachment.isMp3() && Audio.supported) + if (oAttachment && Audio.supported) { - Audio.playMp3(oAttachment.linkDownload(), oAttachment.fileName); + 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', '.thread-list .more-threads', function (e) { @@ -1107,6 +1116,40 @@ } }; + 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 aHashes = this.getAttachmentsHashes(); + if (0 < aHashes.length) + { + Promises.attachmentsActions('Zip', aHashes, this.downloadAsZipLoading); + } + }; + + MessageViewMailBoxUserView.prototype.saveToOwnCloud = function () + { + var aHashes = this.getAttachmentsHashes(); + if (0 < aHashes.length) + { + Promises.attachmentsActions('OwnCloud', aHashes, this.saveToOwnCloudLoading); + } + }; + + MessageViewMailBoxUserView.prototype.saveToDropbox = function () + { + var aHashes = this.getAttachmentsHashes(); + if (0 < aHashes.length) + { + Promises.attachmentsActions('Dropbox', aHashes, this.saveToDropboxLoading); + } + }; + /** * @param {MessageModel} oMessage */ @@ -1119,7 +1162,7 @@ }; /** - * @returns {string} + * @return {string} */ MessageViewMailBoxUserView.prototype.printableCheckedMessageCount = function () { diff --git a/package.json b/package.json index 405ddf101..dd25eb718 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "RainLoop", "title": "RainLoop Webmail", "version": "1.8.3", - "release": "298", + "release": "301", "description": "Simple, modern & fast web-based email client", "homepage": "http://rainloop.net", "main": "gulpfile.js", diff --git a/rainloop/v/0.0.0/app/libraries/MailSo/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/RainLoop/Actions.php b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php index 9e5b785db..713062acf 100644 --- a/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php +++ b/rainloop/v/0.0.0/app/libraries/RainLoop/Actions.php @@ -2630,6 +2630,17 @@ class Actions return $this->FalseResponse(__FUNCTION__); } + /** + * @return array + * + * @throws \MailSo\Base\Exceptions\Exception + */ + public function DoAttachmentsActions() + { + \sleep(1); + return $this->TrueResponse(__FUNCTION__); + } + /** * @return array * @@ -9079,28 +9090,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']); 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 @@