diff --git a/dev/Common/Audio.js b/dev/Common/Audio.js new file mode 100644 index 000000000..1db610e77 --- /dev/null +++ b/dev/Common/Audio.js @@ -0,0 +1,110 @@ + +(function () { + + 'use strict'; + + var + window = require('window'), + $ = require('$'), + + Globals = require('Common/Globals'), + Utils = require('Common/Utils'), + Links = require('Common/Links'), + Events = require('Common/Events') + ; + + /** + * @constructor + */ + function Audio() + { + var self = this; + + this.obj = window.Audio ? new window.Audio() : null; + this.objForNotification = window.Audio ? new window.Audio() : null; + + this.supported = !Globals.bMobileDevice && + this.obj && this.obj.canPlayType && this.obj.play && + '' !== this.obj.canPlayType('audio/mpeg;'); + + if (this.obj && this.supported) + { + this.obj.preload = 'none'; + this.obj.loop = false; + this.obj.autoplay = false; + this.obj.muted = false; + + this.objForNotification.preload = 'none'; + this.objForNotification.loop = false; + this.objForNotification.autoplay = false; + this.objForNotification.muted = false; + this.objForNotification.src = Links.sound('new-mail.mp3'); + + $(this.obj).on('ended', 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.supported = false; + + Audio.prototype.paused = function () + { + return this.supported ? !!this.obj.paused : true; + }; + + Audio.prototype.stop = function () + { + if (this.supported && this.obj.pause) + { + this.obj.pause(); + } + + Events.pub('audio.stop'); + }; + + Audio.prototype.pause = Audio.prototype.stop; + + Audio.prototype.playMp3 = function (sUrl, sName) + { + if (this.supported && this.obj.play) + { + this.obj.src = sUrl; + this.obj.play(); + + sName = Utils.isUnd(sName) ? '' : Utils.trim(sName); + if ('.mp3' === sName.toLowerCase().substr(-4)) + { + sName = Utils.trim(sName.substr(0, sName.length - 4)); + } + + if ('' === sName) + { + sName = 'audio'; + } + + Events.pub('audio.start', [sName]); + } + }; + + Audio.prototype.playNotification = function () + { + if (this.supported && this.objForNotification.play) + { + this.objForNotification.play(); + } + }; + + module.exports = new Audio(); + +}()); diff --git a/dev/External/Opentip.js b/dev/External/Opentip.js index d4530d84d..cf7ad5d3f 100644 --- a/dev/External/Opentip.js +++ b/dev/External/Opentip.js @@ -10,10 +10,10 @@ Opentip.styles.rainloop = { 'extends': 'standard', - 'group': 'rainloopTips', 'fixed': true, 'target': true, + 'showOn': 'mouseover click', 'removeElementsOnHide': true, 'background': '#fff', @@ -26,12 +26,14 @@ Opentip.styles.rainloopTip = { 'extends': 'rainloop', + 'stemLength': 3, + 'stemBase': 5, 'group': 'rainloopTips' }; - Opentip.styles.rainloopTestTip = { + Opentip.styles.rainloopErrorTip = { 'extends': 'rainloop', - 'className': 'rainloopTestTip' + 'className': 'rainloopErrorTip' }; module.exports = Opentip; diff --git a/dev/External/ko.js b/dev/External/ko.js index 612f0b1e3..c0844364d 100644 --- a/dev/External/ko.js +++ b/dev/External/ko.js @@ -68,51 +68,42 @@ if (!Globals.bMobileDevice || bMobile) { bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); - sValue = bi18n ? ko.unwrap(fValueAccessor()) : fValueAccessor()(); + sValue = ko.unwrap(fValueAccessor()); - if (sValue) + oElement.__opentip = new Opentip(oElement, { + 'style': 'rainloopTip', + 'element': oElement, + 'tipJoint': $oEl.data('tooltip-join') || 'bottom' + }); + + Globals.dropdownVisibility.subscribe(function (bV) { + if (bV) { + oElement.__opentip.deactivate(); + } else { + oElement.__opentip.activate(); + } + }); + + if (bi18n) { - oElement.__opentip = new Opentip(oElement, { - 'style': 'rainloopTip', - 'showOn': 'mouseover click', - 'element': oElement, - 'tipJoint': $oEl.data('tooltip-join') || 'bottom' + Translator = require('Common/Translator'); + + oElement.__opentip.setContent(Translator.i18n(sValue)); + + Translator.trigger.subscribe(function () { + oElement.__opentip.setContent(Translator.i18n(sValue)); }); - oElement.__opentip.setContent( - bi18n ? require('Common/Translator').i18n(sValue) : sValue); - - Globals.dropdownVisibility.subscribe(function (bV) { - if (bV) { - oElement.__opentip.deactivate(); - } else { - oElement.__opentip.activate(); + Globals.dropdownVisibility.subscribe(function () { + if (oElement && oElement.__opentip) + { + oElement.__opentip.setContent(require('Common/Translator').i18n(sValue)); } }); - - if (bi18n) - { - Translator = require('Common/Translator'); - - oElement.__opentip.setContent(Translator.i18n(sValue)); - - Translator.trigger.subscribe(function () { - oElement.__opentip.setContent(Translator.i18n(sValue)); - }); - - Globals.dropdownVisibility.subscribe(function () { - if (oElement && oElement.__opentip) - { - oElement.__opentip.setContent(require('Common/Translator').i18n(sValue)); - } - }); - } - else - { - oElement.__opentip.setContent(sValue); - } - - fDisposalTooltipHelper(oElement); + } + else + { + oElement.__opentip.setContent(sValue); } } }, @@ -129,31 +120,31 @@ if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip) { bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); - sValue = bi18n ? ko.unwrap(fValueAccessor()) : fValueAccessor()(); + sValue = ko.unwrap(fValueAccessor()); if (sValue) { - oElement.__opentip.activate(); oElement.__opentip.setContent( bi18n ? require('Common/Translator').i18n(sValue) : sValue); + oElement.__opentip.activate(); } else { - oElement.__opentip.setContent(''); + oElement.__opentip.hide(); oElement.__opentip.deactivate(); + oElement.__opentip.setContent(''); } } } }; - ko.bindingHandlers.tooltipForTest = { + ko.bindingHandlers.tooltipErrorTip = { 'init': function (oElement) { var $oEl = $(oElement); oElement.__opentip = new Opentip(oElement, { - 'style': 'rainloopTestTip', - 'showOn': 'mouseover click', + 'style': 'rainloopErrorTip', 'hideOn': 'mouseout click', 'element': oElement, 'tipJoint': $oEl.data('tooltip-join') || 'top' @@ -183,23 +174,23 @@ if ('' === sValue) { oOpenTips.hide(); - oOpenTips.setContent(''); oOpenTips.deactivate(); + oOpenTips.setContent(''); } else { _.delay(function () { if ($oEl.is(':visible')) { - oOpenTips.activate(); oOpenTips.setContent(sValue); + oOpenTips.activate(); oOpenTips.show(); } else { oOpenTips.hide(); - oOpenTips.setContent(''); oOpenTips.deactivate(); + oOpenTips.setContent(''); } }, 100); } diff --git a/dev/Model/Attachment.js b/dev/Model/Attachment.js index c66654661..a8c4ccce9 100644 --- a/dev/Model/Attachment.js +++ b/dev/Model/Attachment.js @@ -10,6 +10,7 @@ Globals = require('Common/Globals'), Utils = require('Common/Utils'), Links = require('Common/Links'), + Audio = require('Common/Audio'), AbstractModel = require('Knoin/AbstractModel') ; @@ -109,6 +110,14 @@ ); }; + /** + * @return {boolean} + */ + AttachmentModel.prototype.isMp3 = function () + { + return Audio.supported && '.mp3' === this.fileName.toLowerCase().substr(-4); + }; + /** * @return {boolean} */ @@ -151,6 +160,14 @@ return this.isImage() || this.isPdf() || this.isText() || this.isFramed(); }; + /** + * @return {boolean} + */ + AttachmentModel.prototype.hasPreplay = function () + { + return this.isMp3(); + }; + /** * @return {string} */ @@ -223,14 +240,6 @@ return sResult; }; - /** - * @return {boolean} - */ - AttachmentModel.prototype.hasPreview = function () - { - return this.isImage() || this.isPdf() || this.isText() || this.isFramed(); - }; - /** * @return {string} */ diff --git a/dev/Settings/Admin/Branding.js b/dev/Settings/Admin/Branding.js index a5f99570a..748a68d79 100644 --- a/dev/Settings/Admin/Branding.js +++ b/dev/Settings/Admin/Branding.js @@ -68,18 +68,16 @@ BrandingAdminSettings.prototype.onBuild = function () { - if (this.capa) - { - var - self = this, - Remote = require('Remote/Admin/Ajax') - ; + var + self = this, + Remote = require('Remote/Admin/Ajax') + ; + if (this.capa()) + { _.delay(function () { var - f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self), - f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self), f3 = Utils.settingsSaveHelperSimpleFunction(self.loginLogo.trigger, self), f4 = Utils.settingsSaveHelperSimpleFunction(self.loginDescription.trigger, self), f5 = Utils.settingsSaveHelperSimpleFunction(self.loginCss.trigger, self), @@ -90,18 +88,6 @@ f10 = Utils.settingsSaveHelperSimpleFunction(self.welcomePageDisplay.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.loginLogo.subscribe(function (sValue) { Remote.saveAdminConfig(f3, { 'LoginLogo': Utils.trim(sValue) @@ -158,6 +144,29 @@ }, 50); } + else + { + _.delay(function () { + + 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) + }); + }); + + }, 50); + } }; module.exports = BrandingAdminSettings; diff --git a/dev/Stores/User/App.js b/dev/Stores/User/App.js index fb4a994f6..e42fb074d 100644 --- a/dev/Stores/User/App.js +++ b/dev/Stores/User/App.js @@ -21,6 +21,8 @@ { AppStore.call(this); + this.currentAudio = ko.observable(''); + this.focusedState = ko.observable(Enums.Focused.None); this.focusedState.subscribe(function (mValue) { diff --git a/dev/Stores/User/Notification.js b/dev/Stores/User/Notification.js index 08dff8ce8..b35c59ebd 100644 --- a/dev/Stores/User/Notification.js +++ b/dev/Stores/User/Notification.js @@ -6,10 +6,9 @@ var window = require('window'), ko = require('ko'), - buzz = require('buzz'), Enums = require('Common/Enums'), - Links = require('Common/Links'), + Audio = require('Common/Audio'), Settings = require('Storage/Settings') ; @@ -21,8 +20,6 @@ { var self = this; - this.buzz = null; - this.enableSoundNotification = ko.observable(false); this.soundNotificationIsSupported = ko.observable(false); @@ -145,14 +142,9 @@ NotificationUserStore.prototype.initNotificationPlayer = function () { - if (buzz && buzz.isSupported() && (buzz.isOGGSupported() || buzz.isMP3Supported())) + if (Audio && Audio.supported) { this.soundNotificationIsSupported(true); - - this.buzz = new buzz.sound(Links.sound('new-mail'), { - 'preload': 'none', - 'formats': ['mp3', 'ogg'] - }); } else { @@ -163,9 +155,9 @@ NotificationUserStore.prototype.playSoundNotification = function (bSkipSetting) { - if (this.buzz && (bSkipSetting ? true : this.enableSoundNotification())) + if (Audio && Audio.supported && (bSkipSetting ? true : this.enableSoundNotification())) { - this.buzz.play(); + Audio.playNotification(); } }; diff --git a/dev/Styles/Attachmnets.less b/dev/Styles/Attachmnets.less index 941464487..ab907046b 100644 --- a/dev/Styles/Attachmnets.less +++ b/dev/Styles/Attachmnets.less @@ -126,8 +126,18 @@ } } - .showPreview { + .attachmentIconParent.hasPreplay:hover { + .iconPreview { + display: inline-block; + } + .iconMain { + display: none; + } + } + + .showPreview, .showPreplay { display: none; + cursor: pointer; } .attachmentIconParent.hasPreview { @@ -135,6 +145,15 @@ display: inline; } + .hidePreview { + display: none; + } + } + .attachmentIconParent.hasPreplay { + .showPreplay { + display: inline; + } + .hidePreview { display: none; } diff --git a/dev/Styles/Compose.less b/dev/Styles/Compose.less index 8c808a67f..06f26b599 100644 --- a/dev/Styles/Compose.less +++ b/dev/Styles/Compose.less @@ -57,8 +57,8 @@ color: #fff; border-color: #eee; - font-size: 28px; - line-height: 28px; + font-size: 24px; + line-height: 24px; } .btn.disabled { @@ -130,6 +130,11 @@ color: red; } + .error-to { + color: red; + font-weight: bold; + } + .b-appachments { .b-attacment { diff --git a/dev/Styles/MessageView.less b/dev/Styles/MessageView.less index 168970bfc..ecbcc203b 100644 --- a/dev/Styles/MessageView.less +++ b/dev/Styles/MessageView.less @@ -360,7 +360,7 @@ html.rl-no-preview-pane { } &.message-focused .b-content { - z-index: 102; + z-index: 101; box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.2); border-radius: @rlLowBorderRadius; border-color: darken(@rlMainDarkColor, 5%); diff --git a/dev/Styles/SystemDropDown.less b/dev/Styles/SystemDropDown.less index fb1b750dc..8bd018651 100644 --- a/dev/Styles/SystemDropDown.less +++ b/dev/Styles/SystemDropDown.less @@ -34,6 +34,24 @@ vertical-align: middle; } + .audioPlace { + + font-size: 25px; + line-height: 30px; + margin-right: 15px; + + .playIcon { + + font-size: 30px; + line-height: 30px; + + cursor: pointer; + + color: orange; + text-shadow: 0 1px 0 #fff; + } + } + .accountPlace { background-color: #000; background-color: rgba(0, 0, 0, 0.5); diff --git a/dev/Styles/_End.less b/dev/Styles/_End.less index 73eb20bbe..b6838cf11 100644 --- a/dev/Styles/_End.less +++ b/dev/Styles/_End.less @@ -38,7 +38,7 @@ font-size: 13px; } - &.style-rainloopTestTip .ot-content { + &.style-rainloopErrorTip .ot-content { color: red; } } diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 5e07444d8..9640a981e 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -170,6 +170,23 @@ this.attachmentsInProcessError = ko.observable(false); this.attachmentsInErrorError = ko.observable(false); + this.attachmentsErrorTooltip = ko.computed(function () { + + var sResult = ''; + switch (true) + { + case this.attachmentsInProcessError(): + sResult = Translator.i18n('COMPOSE/ATTACHMENTS_UPLOAD_ERROR_DESC'); + break; + case this.attachmentsInErrorError(): + sResult = Translator.i18n('COMPOSE/ATTACHMENTS_ERROR_DESC'); + break; + } + + return sResult; + + }, this); + this.showCc = ko.observable(false); this.showBcc = ko.observable(false); this.showReplyTo = ko.observable(false); @@ -326,6 +343,10 @@ aFlagsCache = [] ; + this.attachmentsInProcessError(false); + this.attachmentsInErrorError(false); + this.emptyToError(false); + if (0 < this.attachmentsInProcess().length) { this.attachmentsInProcessError(true); @@ -336,11 +357,13 @@ this.attachmentsInErrorError(true); this.attachmentsPlace(true); } - else if (0 === sTo.length) + + if (0 === sTo.length) { this.emptyToError(true); } - else + + if (!this.emptyToError() && !this.attachmentsInErrorError() && !this.attachmentsInProcessError()) { if (SettingsStore.replySameFolder()) { diff --git a/dev/View/User/AbstractSystemDropDown.js b/dev/View/User/AbstractSystemDropDown.js index 8344185ac..76c1d5e9e 100644 --- a/dev/View/User/AbstractSystemDropDown.js +++ b/dev/View/User/AbstractSystemDropDown.js @@ -11,7 +11,9 @@ Enums = require('Common/Enums'), Utils = require('Common/Utils'), Links = require('Common/Links'), + Events = require('Common/Events'), + AppStore = require('Stores/User/App'), AccountStore = require('Stores/User/Account'), MessageStore = require('Stores/User/Message'), @@ -28,6 +30,8 @@ { AbstractView.call(this, 'Right', 'SystemDropDown'); + this.currentAudio = AppStore.currentAudio; + this.accountEmail = AccountStore.email; this.accounts = AccountStore.accounts; @@ -37,10 +41,25 @@ this.capaAdditionalAccounts = ko.observable(Settings.capa(Enums.Capa.AdditionalAccounts)); this.accountClick = _.bind(this.accountClick, this); + + this.accountClick = _.bind(this.accountClick, this); + + Events.sub('audio.stop', function () { + AppStore.currentAudio(''); + }); + + Events.sub('audio.start', function (sName) { + AppStore.currentAudio(sName); + }); } _.extend(AbstractSystemDropDownUserView.prototype, AbstractView.prototype); + AbstractSystemDropDownUserView.prototype.stopPlay = function () + { + Events.pub('audio.api.stop'); + }; + AbstractSystemDropDownUserView.prototype.accountClick = function (oAccount, oEvent) { if (oAccount && oEvent && !Utils.isUnd(oEvent.which) && 1 === oEvent.which) diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index 011e66dfc..99ab43c27 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -18,6 +18,7 @@ Utils = require('Common/Utils'), Events = require('Common/Events'), Translator = require('Common/Translator'), + Audio = require('Common/Audio'), Cache = require('Common/Cache'), @@ -734,6 +735,18 @@ oEvent.stopPropagation(); } }) + .on('click', '.attachmentsPlace .showPreplay', function (oEvent) { + if (oEvent && oEvent.stopPropagation) + { + oEvent.stopPropagation(); + } + + var oAttachment = ko.dataFor(this); + if (oAttachment && oAttachment.isMp3() && Audio.supported) + { + Audio.playMp3(oAttachment.linkDownload(), oAttachment.fileName); + } + }) .on('click', '.thread-list .more-threads', function (e) { var oLast = null; diff --git a/gulpfile.js b/gulpfile.js index 9f4902d65..4612b4f6d 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -168,7 +168,6 @@ cfg.paths.js = { 'vendors/knockout-sortable/knockout-sortable.min.js', 'vendors/ssm/ssm.min.js', 'vendors/jua/jua.min.js', - 'vendors/buzz/buzz.min.js', 'vendors/Q/q.min.js', 'vendors/opentip/opentip-jquery.min.js', 'vendors/Autolinker/Autolinker.min.js', diff --git a/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html b/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html index 4f50b2af4..1c1f52a51 100644 --- a/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html +++ b/rainloop/v/0.0.0/app/templates/Views/Admin/PopupsDomain.html @@ -22,12 +22,12 @@