Simple mp3 player

This commit is contained in:
RainLoop Team 2015-04-10 12:17:49 +04:00
parent 351c12c002
commit 1d943356f5
23 changed files with 336 additions and 121 deletions

110
dev/Common/Audio.js Normal file
View file

@ -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();
}());

View file

@ -10,10 +10,10 @@
Opentip.styles.rainloop = { Opentip.styles.rainloop = {
'extends': 'standard', 'extends': 'standard',
'group': 'rainloopTips',
'fixed': true, 'fixed': true,
'target': true, 'target': true,
'showOn': 'mouseover click',
'removeElementsOnHide': true, 'removeElementsOnHide': true,
'background': '#fff', 'background': '#fff',
@ -26,12 +26,14 @@
Opentip.styles.rainloopTip = { Opentip.styles.rainloopTip = {
'extends': 'rainloop', 'extends': 'rainloop',
'stemLength': 3,
'stemBase': 5,
'group': 'rainloopTips' 'group': 'rainloopTips'
}; };
Opentip.styles.rainloopTestTip = { Opentip.styles.rainloopErrorTip = {
'extends': 'rainloop', 'extends': 'rainloop',
'className': 'rainloopTestTip' 'className': 'rainloopErrorTip'
}; };
module.exports = Opentip; module.exports = Opentip;

29
dev/External/ko.js vendored
View file

@ -68,20 +68,14 @@
if (!Globals.bMobileDevice || bMobile) if (!Globals.bMobileDevice || bMobile)
{ {
bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
sValue = bi18n ? ko.unwrap(fValueAccessor()) : fValueAccessor()(); sValue = ko.unwrap(fValueAccessor());
if (sValue)
{
oElement.__opentip = new Opentip(oElement, { oElement.__opentip = new Opentip(oElement, {
'style': 'rainloopTip', 'style': 'rainloopTip',
'showOn': 'mouseover click',
'element': oElement, 'element': oElement,
'tipJoint': $oEl.data('tooltip-join') || 'bottom' 'tipJoint': $oEl.data('tooltip-join') || 'bottom'
}); });
oElement.__opentip.setContent(
bi18n ? require('Common/Translator').i18n(sValue) : sValue);
Globals.dropdownVisibility.subscribe(function (bV) { Globals.dropdownVisibility.subscribe(function (bV) {
if (bV) { if (bV) {
oElement.__opentip.deactivate(); oElement.__opentip.deactivate();
@ -111,9 +105,6 @@
{ {
oElement.__opentip.setContent(sValue); oElement.__opentip.setContent(sValue);
} }
fDisposalTooltipHelper(oElement);
}
} }
}, },
'update': function (oElement, fValueAccessor) { 'update': function (oElement, fValueAccessor) {
@ -129,31 +120,31 @@
if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip) if ((!Globals.bMobileDevice || bMobile) && oElement.__opentip)
{ {
bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on'); bi18n = 'on' === ($oEl.data('tooltip-i18n') || 'on');
sValue = bi18n ? ko.unwrap(fValueAccessor()) : fValueAccessor()(); sValue = ko.unwrap(fValueAccessor());
if (sValue) if (sValue)
{ {
oElement.__opentip.activate();
oElement.__opentip.setContent( oElement.__opentip.setContent(
bi18n ? require('Common/Translator').i18n(sValue) : sValue); bi18n ? require('Common/Translator').i18n(sValue) : sValue);
oElement.__opentip.activate();
} }
else else
{ {
oElement.__opentip.setContent(''); oElement.__opentip.hide();
oElement.__opentip.deactivate(); oElement.__opentip.deactivate();
oElement.__opentip.setContent('');
} }
} }
} }
}; };
ko.bindingHandlers.tooltipForTest = { ko.bindingHandlers.tooltipErrorTip = {
'init': function (oElement) { 'init': function (oElement) {
var $oEl = $(oElement); var $oEl = $(oElement);
oElement.__opentip = new Opentip(oElement, { oElement.__opentip = new Opentip(oElement, {
'style': 'rainloopTestTip', 'style': 'rainloopErrorTip',
'showOn': 'mouseover click',
'hideOn': 'mouseout click', 'hideOn': 'mouseout click',
'element': oElement, 'element': oElement,
'tipJoint': $oEl.data('tooltip-join') || 'top' 'tipJoint': $oEl.data('tooltip-join') || 'top'
@ -183,23 +174,23 @@
if ('' === sValue) if ('' === sValue)
{ {
oOpenTips.hide(); oOpenTips.hide();
oOpenTips.setContent('');
oOpenTips.deactivate(); oOpenTips.deactivate();
oOpenTips.setContent('');
} }
else else
{ {
_.delay(function () { _.delay(function () {
if ($oEl.is(':visible')) if ($oEl.is(':visible'))
{ {
oOpenTips.activate();
oOpenTips.setContent(sValue); oOpenTips.setContent(sValue);
oOpenTips.activate();
oOpenTips.show(); oOpenTips.show();
} }
else else
{ {
oOpenTips.hide(); oOpenTips.hide();
oOpenTips.setContent('');
oOpenTips.deactivate(); oOpenTips.deactivate();
oOpenTips.setContent('');
} }
}, 100); }, 100);
} }

View file

@ -10,6 +10,7 @@
Globals = require('Common/Globals'), Globals = require('Common/Globals'),
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
Links = require('Common/Links'), Links = require('Common/Links'),
Audio = require('Common/Audio'),
AbstractModel = require('Knoin/AbstractModel') 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} * @return {boolean}
*/ */
@ -151,6 +160,14 @@
return this.isImage() || this.isPdf() || this.isText() || this.isFramed(); return this.isImage() || this.isPdf() || this.isText() || this.isFramed();
}; };
/**
* @return {boolean}
*/
AttachmentModel.prototype.hasPreplay = function ()
{
return this.isMp3();
};
/** /**
* @return {string} * @return {string}
*/ */
@ -223,14 +240,6 @@
return sResult; return sResult;
}; };
/**
* @return {boolean}
*/
AttachmentModel.prototype.hasPreview = function ()
{
return this.isImage() || this.isPdf() || this.isText() || this.isFramed();
};
/** /**
* @return {string} * @return {string}
*/ */

View file

@ -67,19 +67,17 @@
} }
BrandingAdminSettings.prototype.onBuild = function () BrandingAdminSettings.prototype.onBuild = function ()
{
if (this.capa)
{ {
var var
self = this, self = this,
Remote = require('Remote/Admin/Ajax') Remote = require('Remote/Admin/Ajax')
; ;
if (this.capa())
{
_.delay(function () { _.delay(function () {
var var
f1 = Utils.settingsSaveHelperSimpleFunction(self.title.trigger, self),
f2 = Utils.settingsSaveHelperSimpleFunction(self.loadingDesc.trigger, self),
f3 = Utils.settingsSaveHelperSimpleFunction(self.loginLogo.trigger, self), f3 = Utils.settingsSaveHelperSimpleFunction(self.loginLogo.trigger, self),
f4 = Utils.settingsSaveHelperSimpleFunction(self.loginDescription.trigger, self), f4 = Utils.settingsSaveHelperSimpleFunction(self.loginDescription.trigger, self),
f5 = Utils.settingsSaveHelperSimpleFunction(self.loginCss.trigger, self), f5 = Utils.settingsSaveHelperSimpleFunction(self.loginCss.trigger, self),
@ -90,18 +88,6 @@
f10 = Utils.settingsSaveHelperSimpleFunction(self.welcomePageDisplay.trigger, self) 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) { self.loginLogo.subscribe(function (sValue) {
Remote.saveAdminConfig(f3, { Remote.saveAdminConfig(f3, {
'LoginLogo': Utils.trim(sValue) 'LoginLogo': Utils.trim(sValue)
@ -158,6 +144,29 @@
}, 50); }, 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; module.exports = BrandingAdminSettings;

View file

@ -21,6 +21,8 @@
{ {
AppStore.call(this); AppStore.call(this);
this.currentAudio = ko.observable('');
this.focusedState = ko.observable(Enums.Focused.None); this.focusedState = ko.observable(Enums.Focused.None);
this.focusedState.subscribe(function (mValue) { this.focusedState.subscribe(function (mValue) {

View file

@ -6,10 +6,9 @@
var var
window = require('window'), window = require('window'),
ko = require('ko'), ko = require('ko'),
buzz = require('buzz'),
Enums = require('Common/Enums'), Enums = require('Common/Enums'),
Links = require('Common/Links'), Audio = require('Common/Audio'),
Settings = require('Storage/Settings') Settings = require('Storage/Settings')
; ;
@ -21,8 +20,6 @@
{ {
var self = this; var self = this;
this.buzz = null;
this.enableSoundNotification = ko.observable(false); this.enableSoundNotification = ko.observable(false);
this.soundNotificationIsSupported = ko.observable(false); this.soundNotificationIsSupported = ko.observable(false);
@ -145,14 +142,9 @@
NotificationUserStore.prototype.initNotificationPlayer = function () NotificationUserStore.prototype.initNotificationPlayer = function ()
{ {
if (buzz && buzz.isSupported() && (buzz.isOGGSupported() || buzz.isMP3Supported())) if (Audio && Audio.supported)
{ {
this.soundNotificationIsSupported(true); this.soundNotificationIsSupported(true);
this.buzz = new buzz.sound(Links.sound('new-mail'), {
'preload': 'none',
'formats': ['mp3', 'ogg']
});
} }
else else
{ {
@ -163,9 +155,9 @@
NotificationUserStore.prototype.playSoundNotification = function (bSkipSetting) 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();
} }
}; };

View file

@ -126,15 +126,34 @@
} }
} }
.showPreview { .attachmentIconParent.hasPreplay:hover {
.iconPreview {
display: inline-block;
}
.iconMain {
display: none; display: none;
} }
}
.showPreview, .showPreplay {
display: none;
cursor: pointer;
}
.attachmentIconParent.hasPreview { .attachmentIconParent.hasPreview {
.showPreview { .showPreview {
display: inline; display: inline;
} }
.hidePreview {
display: none;
}
}
.attachmentIconParent.hasPreplay {
.showPreplay {
display: inline;
}
.hidePreview { .hidePreview {
display: none; display: none;
} }

View file

@ -57,8 +57,8 @@
color: #fff; color: #fff;
border-color: #eee; border-color: #eee;
font-size: 28px; font-size: 24px;
line-height: 28px; line-height: 24px;
} }
.btn.disabled { .btn.disabled {
@ -130,6 +130,11 @@
color: red; color: red;
} }
.error-to {
color: red;
font-weight: bold;
}
.b-appachments { .b-appachments {
.b-attacment { .b-attacment {

View file

@ -360,7 +360,7 @@ html.rl-no-preview-pane {
} }
&.message-focused .b-content { &.message-focused .b-content {
z-index: 102; z-index: 101;
box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.2); box-shadow: 0px 2px 8px rgba(0, 0, 0, 0.2);
border-radius: @rlLowBorderRadius; border-radius: @rlLowBorderRadius;
border-color: darken(@rlMainDarkColor, 5%); border-color: darken(@rlMainDarkColor, 5%);

View file

@ -34,6 +34,24 @@
vertical-align: middle; 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 { .accountPlace {
background-color: #000; background-color: #000;
background-color: rgba(0, 0, 0, 0.5); background-color: rgba(0, 0, 0, 0.5);

View file

@ -38,7 +38,7 @@
font-size: 13px; font-size: 13px;
} }
&.style-rainloopTestTip .ot-content { &.style-rainloopErrorTip .ot-content {
color: red; color: red;
} }
} }

View file

@ -170,6 +170,23 @@
this.attachmentsInProcessError = ko.observable(false); this.attachmentsInProcessError = ko.observable(false);
this.attachmentsInErrorError = 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.showCc = ko.observable(false);
this.showBcc = ko.observable(false); this.showBcc = ko.observable(false);
this.showReplyTo = ko.observable(false); this.showReplyTo = ko.observable(false);
@ -326,6 +343,10 @@
aFlagsCache = [] aFlagsCache = []
; ;
this.attachmentsInProcessError(false);
this.attachmentsInErrorError(false);
this.emptyToError(false);
if (0 < this.attachmentsInProcess().length) if (0 < this.attachmentsInProcess().length)
{ {
this.attachmentsInProcessError(true); this.attachmentsInProcessError(true);
@ -336,11 +357,13 @@
this.attachmentsInErrorError(true); this.attachmentsInErrorError(true);
this.attachmentsPlace(true); this.attachmentsPlace(true);
} }
else if (0 === sTo.length)
if (0 === sTo.length)
{ {
this.emptyToError(true); this.emptyToError(true);
} }
else
if (!this.emptyToError() && !this.attachmentsInErrorError() && !this.attachmentsInProcessError())
{ {
if (SettingsStore.replySameFolder()) if (SettingsStore.replySameFolder())
{ {

View file

@ -11,7 +11,9 @@
Enums = require('Common/Enums'), Enums = require('Common/Enums'),
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
Links = require('Common/Links'), Links = require('Common/Links'),
Events = require('Common/Events'),
AppStore = require('Stores/User/App'),
AccountStore = require('Stores/User/Account'), AccountStore = require('Stores/User/Account'),
MessageStore = require('Stores/User/Message'), MessageStore = require('Stores/User/Message'),
@ -28,6 +30,8 @@
{ {
AbstractView.call(this, 'Right', 'SystemDropDown'); AbstractView.call(this, 'Right', 'SystemDropDown');
this.currentAudio = AppStore.currentAudio;
this.accountEmail = AccountStore.email; this.accountEmail = AccountStore.email;
this.accounts = AccountStore.accounts; this.accounts = AccountStore.accounts;
@ -37,10 +41,25 @@
this.capaAdditionalAccounts = ko.observable(Settings.capa(Enums.Capa.AdditionalAccounts)); this.capaAdditionalAccounts = ko.observable(Settings.capa(Enums.Capa.AdditionalAccounts));
this.accountClick = _.bind(this.accountClick, this); 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); _.extend(AbstractSystemDropDownUserView.prototype, AbstractView.prototype);
AbstractSystemDropDownUserView.prototype.stopPlay = function ()
{
Events.pub('audio.api.stop');
};
AbstractSystemDropDownUserView.prototype.accountClick = function (oAccount, oEvent) AbstractSystemDropDownUserView.prototype.accountClick = function (oAccount, oEvent)
{ {
if (oAccount && oEvent && !Utils.isUnd(oEvent.which) && 1 === oEvent.which) if (oAccount && oEvent && !Utils.isUnd(oEvent.which) && 1 === oEvent.which)

View file

@ -18,6 +18,7 @@
Utils = require('Common/Utils'), Utils = require('Common/Utils'),
Events = require('Common/Events'), Events = require('Common/Events'),
Translator = require('Common/Translator'), Translator = require('Common/Translator'),
Audio = require('Common/Audio'),
Cache = require('Common/Cache'), Cache = require('Common/Cache'),
@ -734,6 +735,18 @@
oEvent.stopPropagation(); 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) { .on('click', '.thread-list .more-threads', function (e) {
var oLast = null; var oLast = null;

View file

@ -168,7 +168,6 @@ cfg.paths.js = {
'vendors/knockout-sortable/knockout-sortable.min.js', 'vendors/knockout-sortable/knockout-sortable.min.js',
'vendors/ssm/ssm.min.js', 'vendors/ssm/ssm.min.js',
'vendors/jua/jua.min.js', 'vendors/jua/jua.min.js',
'vendors/buzz/buzz.min.js',
'vendors/Q/q.min.js', 'vendors/Q/q.min.js',
'vendors/opentip/opentip-jquery.min.js', 'vendors/opentip/opentip-jquery.min.js',
'vendors/Autolinker/Autolinker.min.js', 'vendors/Autolinker/Autolinker.min.js',

View file

@ -22,12 +22,12 @@
<div class="row"> <div class="row">
<div class="span5"> <div class="span5">
<div class="legend imap-header" data-bind="visible: !sieveSettings(), css: { 'testing-done': testingDone, 'testing-error': testingImapError }"> <div class="legend imap-header" data-bind="visible: !sieveSettings(), css: { 'testing-done': testingDone, 'testing-error': testingImapError }">
<span data-placement="bottom" data-bind="tooltipForTest: testingImapErrorDesc"> <span data-placement="bottom" data-bind="tooltipErrorTip: testingImapErrorDesc">
<span data-i18n="POPUPS_DOMAIN/LABEL_IMAP"></span> <span data-i18n="POPUPS_DOMAIN/LABEL_IMAP"></span>
</span> </span>
</div> </div>
<div class="legend sieve-header" data-bind="visible: sieveSettings(), css: { 'testing-done': testingDone, 'testing-error': testingSieveError }"> <div class="legend sieve-header" data-bind="visible: sieveSettings(), css: { 'testing-done': testingDone, 'testing-error': testingSieveError }">
<span data-placement="bottom" data-bind="tooltipForTest: testingSieveErrorDesc"> <span data-placement="bottom" data-bind="tooltipErrorTip: testingSieveErrorDesc">
<span data-i18n="POPUPS_DOMAIN/LABEL_SIEVE"></span> <span data-i18n="POPUPS_DOMAIN/LABEL_SIEVE"></span>
</span> </span>
</div> </div>
@ -153,7 +153,7 @@
</div> </div>
<div class="span5"> <div class="span5">
<div class="legend smtp-header" data-bind="css: { 'testing-done': testingDone, 'testing-error': testingSmtpError }"> <div class="legend smtp-header" data-bind="css: { 'testing-done': testingDone, 'testing-error': testingSmtpError }">
<span data-placement="bottom" data-bind="tooltipForTest: testingSmtpErrorDesc"> <span data-placement="bottom" data-bind="tooltipErrorTip: testingSmtpErrorDesc">
<span data-i18n="POPUPS_DOMAIN/LABEL_SMTP"></span> <span data-i18n="POPUPS_DOMAIN/LABEL_SMTP"></span>
</span> </span>
</div> </div>

View file

@ -2,7 +2,7 @@
<span class="i18n" data-bind="attr: {'data-i18n': preLabel}"></span> <span class="i18n" data-bind="attr: {'data-i18n': preLabel}"></span>
&nbsp;&nbsp; &nbsp;&nbsp;
<!-- /ko --> <!-- /ko -->
<select data-bind="options: options, value: value, optionsText: optionsText, optionsValue: optionsValue, <select data-bind="options: options, value: value, enable: enable, optionsText: optionsText, optionsValue: optionsValue,
css: className, optionsAfterRender: defautOptionsAfterRender"></select> css: className, optionsAfterRender: defautOptionsAfterRender"></select>
<!-- ko if: labeled --> <!-- ko if: labeled -->
&nbsp; &nbsp;

View file

@ -384,7 +384,7 @@
<ul class="attachmentList" data-bind="foreach: message() ? message().attachments() : []"> <ul class="attachmentList" data-bind="foreach: message() ? message().attachments() : []">
<li class="attachmentItem clearfix" draggable="true" data-tooltip-join="top" <li class="attachmentItem clearfix" draggable="true" data-tooltip-join="top"
data-bind="visible: !isLinked, event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }"> data-bind="visible: !isLinked, event: { 'dragstart': eventDragStart }, attr: { 'title': fileName }">
<div class="attachmentIconParent pull-left" data-bind="css: { 'hasPreview': hasPreview() }"> <div class="attachmentIconParent pull-left" data-bind="css: { 'hasPreview': hasPreview(), 'hasPreplay': hasPreplay() }">
<div class="hidePreview"> <div class="hidePreview">
<div class="iconMain"> <div class="iconMain">
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i> <i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
@ -403,6 +403,14 @@
</div> </div>
</a> </a>
</div> </div>
<div class="showPreplay">
<div class="iconMain">
<i class="attachmentIcon attachmentMainIcon" data-bind="css: iconClass()"></i>
</div>
<div class="iconPreview">
<i class="attachmentIcon icon-right-dir show-hover"></i>
</div>
</div>
</div> </div>
<div class="attachmentNameParent"> <div class="attachmentNameParent">
<div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;"> <div style="white-space: nowrap; text-overflow: ellipsis; overflow: hidden;">

View file

@ -1,12 +1,12 @@
<div class="popups"> <div class="popups">
<div class="modal hide b-compose" data-backdrop="static" data-bind="modal: modalVisibility, css: {'loading': saving() || sending()}"> <div class="modal hide b-compose" data-backdrop="static" data-bind="modal: modalVisibility, css: {'loading': saving() || sending()}">
<div class="modal-header b-header-toolbar g-ui-user-select-none"> <div class="modal-header b-header-toolbar g-ui-user-select-none">
<a class="btn btn-large button-send" data-bind="command: sendCommand, tooltipForTest: sendErrorDesc, css: {'btn-danger': sendError, 'btn-warning': sendSuccessButSaveError }"> <a class="btn btn-large button-send" data-bind="command: sendCommand, tooltipErrorTip: sendErrorDesc, css: {'btn-danger': sendError, 'btn-warning': sendSuccessButSaveError }">
<i data-bind="css: {'icon-paper-plane': !sending(), 'icon-spinner animated big': sending(), 'icon-white': sendError() || sendSuccessButSaveError()}"></i> <i data-bind="css: {'icon-paper-plane': !sending(), 'icon-spinner animated big': sending(), 'icon-white': sendError() || sendSuccessButSaveError()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-i18n="COMPOSE/BUTTON_SEND"></span> <span class="i18n" data-i18n="COMPOSE/BUTTON_SEND"></span>
</a> </a>
<a class="btn button-save" data-bind="command: saveCommand, tooltipForTest: savedErrorDesc, css: {'btn-danger': savedError }"> <a class="btn button-save" data-bind="command: saveCommand, tooltipErrorTip: savedErrorDesc, css: {'btn-danger': savedError }">
<i data-bind="css: {'icon-floppy': !saving(), 'icon-spinner animated': saving(), 'icon-white': savedError()}"></i> <i data-bind="css: {'icon-floppy': !saving(), 'icon-spinner animated': saving(), 'icon-white': savedError()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-i18n="COMPOSE/BUTTON_SAVE"></span> <span class="i18n" data-i18n="COMPOSE/BUTTON_SAVE"></span>
@ -108,15 +108,13 @@
</div> </div>
<div class="e-row"> <div class="e-row">
<div class="e-cell e-label"> <div class="e-cell e-label">
<label class="control-label"> <label class="control-label" data-bind="css: {'error-to': emptyToError}">
<span class="i18n" data-i18n="COMPOSE/TITLE_TO"></span> <span class="i18n" data-i18n="COMPOSE/TITLE_TO" data-tooltip-join="top"
data-bind="tooltipErrorTip: emptyToErrorTooltip"></span>
</label> </label>
</div> </div>
<div class="e-cell e-value"> <div class="e-cell e-value">
<input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" data-bind="emailsTags: to, emailsTagsFocus: to.focused, autoCompleteSource: emailsSource" /> <input type="text" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" data-bind="emailsTags: to, emailsTagsFocus: to.focused, autoCompleteSource: emailsSource" />
<span class="help-block error-desc" data-bind="visible: emptyToError">
<span class="i18n" data-i18n="COMPOSE/EMPTY_TO_ERROR_DESC"></span>
</span>
</div> </div>
</div> </div>
<div class="e-row cc-row" data-bind="visible: showCc"> <div class="e-row cc-row" data-bind="visible: showCc">
@ -161,8 +159,9 @@
css: { 'active': !attachmentsPlace() }"> css: { 'active': !attachmentsPlace() }">
<i class="icon-file-text"></i> <i class="icon-file-text"></i>
</button> </button>
<button type="button" class="btn last" data-bind="click: function () { attachmentsPlace(true); }, <button type="button" class="btn last" data-tooltip-join="left" data-bind="click: function () { attachmentsPlace(true); },
css: { 'btn-danger': 0 < attachmentsInErrorCount(), 'active': attachmentsPlace() }"> css: { 'btn-danger': 0 < attachmentsInErrorCount(), 'active': attachmentsPlace() },
tooltipErrorTip: attachmentsErrorTooltip">
<span data-bind="visible: 0 < attachmentsCount()"> <span data-bind="visible: 0 < attachmentsCount()">
<b data-bind="text: attachmentsCount"></b> <b data-bind="text: attachmentsCount"></b>
&nbsp;&nbsp; &nbsp;&nbsp;
@ -170,12 +169,6 @@
<i data-bind="css: { 'icon-attachment': 0 === attachmentsInProcessCount(), 'icon-spinner animated': 0 < attachmentsInProcessCount(), 'icon-white': 0 < attachmentsInErrorCount() }"></i> <i data-bind="css: { 'icon-attachment': 0 === attachmentsInProcessCount(), 'icon-spinner animated': 0 < attachmentsInProcessCount(), 'icon-white': 0 < attachmentsInErrorCount() }"></i>
</button> </button>
</div> </div>
<span class="help-block error-desc" data-bind="visible: attachmentsInProcessError">
<span class="i18n" data-i18n="COMPOSE/ATTACHMENTS_UPLOAD_ERROR_DESC"></span>
</span>
<span class="help-block error-desc" data-bind="visible: !attachmentsInProcessError() && attachmentsInErrorError()">
<span class="i18n" data-i18n="COMPOSE/ATTACHMENTS_ERROR_DESC"></span>
</span>
</div> </div>
<div class="pull-right" style="margin-right: 4px;"> <div class="pull-right" style="margin-right: 4px;">
<div class="btn-group pull-right"> <div class="btn-group pull-right">

View file

@ -20,7 +20,7 @@
</a> </a>
&nbsp;&nbsp; &nbsp;&nbsp;
<a class="btn hide-on-disabled-command" data-placement="bottom" data-join="top" <a class="btn hide-on-disabled-command" data-placement="bottom" data-join="top"
data-bind="command: saveChanges, tooltipForTest: saveErrorText, css: {'btn-danger': '' !== saveErrorText()}"> data-bind="command: saveChanges, tooltipErrorTip: saveErrorText, css: {'btn-danger': '' !== saveErrorText()}">
<i data-bind="css: {'icon-floppy': !filters.saving(), 'icon-spinner animated': filters.saving()}"></i> <i data-bind="css: {'icon-floppy': !filters.saving(), 'icon-spinner animated': filters.saving()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-i18n="SETTINGS_FILTERS/BUTTON_SAVE"></span> <span class="i18n" data-i18n="SETTINGS_FILTERS/BUTTON_SAVE"></span>

View file

@ -68,6 +68,10 @@
</ul> </ul>
</div> </div>
<div class="accountPlace pull-right" data-bind="text: emailTitle()"></div> <div class="accountPlace pull-right" data-bind="text: emailTitle()"></div>
<div class="audioPlace pull-right" data-tooltip-i18n="off" data-tooltip-mobile="on" data-tooltip-join="right top"
data-bind="visible: '' !== currentAudio(), tooltip: currentAudio, click: stopPlay">
<i class="playIcon icon-right-dir"></i>
</div>
</div> </div>
</div> </div>
</div> </div>

View file

@ -40,7 +40,6 @@ module.exports = {
'Jua': 'window.Jua', 'Jua': 'window.Jua',
'Autolinker': 'window.Autolinker', 'Autolinker': 'window.Autolinker',
'Tinycon': 'window.Tinycon', 'Tinycon': 'window.Tinycon',
'buzz': 'window.buzz',
'ssm': 'window.ssm', 'ssm': 'window.ssm',
'key': 'window.key', 'key': 'window.key',
'_': 'window._', '_': 'window._',