OpenPGP (#53) UNSTABLE

This commit is contained in:
RainLoop Team 2014-04-01 22:03:37 +04:00
parent 9d26fe049e
commit ee19a5cb41
17 changed files with 1010 additions and 582 deletions

View file

@ -306,10 +306,10 @@ module.exports = function (grunt) {
"dev/Settings/Identity.js",
"dev/Settings/Identities.js",
"dev/Settings/Social.js",
"dev/Settings/OpenPGP.js",
"dev/Settings/ChangePassword.js",
"dev/Settings/Folders.js",
"dev/Settings/Themes.js",
"dev/Settings/OpenPGP.js",
"dev/Storages/AbstractData.js",
"dev/Storages/WebMailData.js",

View file

@ -250,6 +250,16 @@ Enums.ContactScopeType = {
'ShareAll': 2
};
/**
* @enum {number}
*/
Enums.SignedVerifyStatus = {
'Unverified': -2,
'Error': -1,
'None': 0,
'Success': 1
};
/**
* @enum {number}
*/

View file

@ -4,5 +4,9 @@
/**
* @type {?RainLoopApp}
*/
var RL = null;
var
RL = null,
$proxyDiv = $('<div></div>')
;
/*jshint onevar: true*/

View file

@ -91,7 +91,8 @@ function MessageModel()
this.isPgpSigned = ko.observable(false);
this.isPgpEncrypted = ko.observable(false);
this.pgpSignature = ko.observable('');
this.pgpSignedVerifyStatus = ko.observable(Enums.SignedVerifyStatus.None);
this.pgpSignedVerifyUser = ko.observable('');
this.priority = ko.observable(Enums.MessagePriority.Normal);
this.readReceipt = ko.observable('');
@ -263,7 +264,8 @@ MessageModel.prototype.clear = function ()
this.isPgpSigned(false);
this.isPgpEncrypted(false);
this.pgpSignature('');
this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
this.pgpSignedVerifyUser('');
this.priority(Enums.MessagePriority.Normal);
this.readReceipt('');
@ -363,7 +365,6 @@ MessageModel.prototype.initUpdateByMessageJson = function (oJsonMessage)
{
this.isPgpSigned(!!oJsonMessage.PgpSigned);
this.isPgpEncrypted(!!oJsonMessage.PgpEncrypted);
this.pgpSignature(oJsonMessage.PgpSignature);
}
this.hasAttachments(!!oJsonMessage.HasAttachments);
@ -830,7 +831,6 @@ MessageModel.prototype.populateByMessageListItem = function (oMessage)
// this.isPgpSigned(false);
// this.isPgpEncrypted(false);
// this.pgpSignature('');
this.priority(Enums.MessagePriority.Normal);
this.aDraftInfo = [];
@ -897,12 +897,12 @@ MessageModel.prototype.showInternalImages = function (bLazy)
{
if (this.body && !this.body.data('rl-init-internal-images'))
{
this.body.data('rl-init-internal-images', true);
bLazy = Utils.isUnd(bLazy) ? false : bLazy;
var self = this;
this.body.data('rl-init-internal-images', true);
$('[data-x-src-cid]', this.body).each(function () {
var oAttachment = self.findAttachmentByCid($(this).attr('data-x-src-cid'));
@ -981,3 +981,128 @@ MessageModel.prototype.showInternalImages = function (bLazy)
Utils.windowResize(500);
}
};
MessageModel.prototype.storeDataToDom = function ()
{
if (this.body)
{
this.body.data('rl-is-rtl', !!this.isRtl());
this.body.data('rl-is-html', !!this.isHtml());
this.body.data('rl-has-images', !!this.hasImages());
this.body.data('rl-plain-raw', this.plainRaw);
if (RL.data().allowOpenPGP())
{
this.body.data('rl-plain-pgp-signed', !!this.isPgpSigned());
this.body.data('rl-plain-pgp-encrypted', !!this.isPgpEncrypted());
this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus());
this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser());
}
}
};
MessageModel.prototype.storePgpVerifyDataToDom = function ()
{
if (this.body && RL.data().allowOpenPGP())
{
this.body.data('rl-pgp-verify-status', this.pgpSignedVerifyStatus());
this.body.data('rl-pgp-verify-user', this.pgpSignedVerifyUser());
}
};
MessageModel.prototype.fetchDataToDom = function ()
{
if (this.body)
{
this.isRtl(!!this.body.data('rl-is-rtl'));
this.isHtml(!!this.body.data('rl-is-html'));
this.hasImages(!!this.body.data('rl-has-images'));
this.plainRaw = Utils.pString(this.body.data('rl-plain-raw'));
if (RL.data().allowOpenPGP())
{
this.isPgpSigned(!!this.body.data('rl-plain-pgp-signed'));
this.isPgpEncrypted(!!this.body.data('rl-plain-pgp-encrypted'));
this.pgpSignedVerifyStatus(this.body.data('rl-pgp-verify-status'));
this.pgpSignedVerifyUser(this.body.data('rl-pgp-verify-user'));
}
else
{
this.isPgpSigned(false);
this.isPgpEncrypted(false);
this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
this.pgpSignedVerifyUser('');
}
}
};
MessageModel.prototype.verifyPgpSignedClearMessage = function ()
{
if (this.isPgpSigned())
{
var
aRes = [],
mPgpMessage = null,
sFrom = this.from && this.from[0] && this.from[0].email ? this.from[0].email : '',
aPublicKey = RL.data().findPublicKeysByEmail(sFrom),
oValidKey = null,
oValidSysKey = null,
sPlain = ''
;
this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Error);
this.pgpSignedVerifyUser('');
try
{
mPgpMessage = window.openpgp.cleartext.readArmored(this.plainRaw);
if (mPgpMessage && mPgpMessage.getText)
{
this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Unverified);
aRes = mPgpMessage.verify(aPublicKey);
if (aRes && 0 < aRes.length)
{
oValidKey = _.find(aRes, function (oItem) {
return oItem && oItem.keyid && oItem.valid;
});
if (oValidKey)
{
oValidSysKey = RL.data().findPublicKeyByHex(oValidKey.keyid.toHex());
if (oValidSysKey)
{
sPlain = mPgpMessage.getText();
this.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.Success);
this.pgpSignedVerifyUser(oValidSysKey.user);
sPlain =
$proxyDiv.empty().append(
$('<pre class="b-plain-openpgp signed verified"></pre>').text(sPlain)
).html()
;
$proxyDiv.empty();
this.replacePlaneTextBody(sPlain);
}
}
}
}
}
catch (oExc) {}
this.storePgpVerifyDataToDom();
}
};
MessageModel.prototype.replacePlaneTextBody = function (sPlain)
{
if (this.body)
{
this.body.html(sPlain).addClass('b-text-part plain');
}
};

View file

@ -883,49 +883,43 @@ WebMailDataStorage.prototype.setMessage = function (oData, bCached)
RL.data().allowOpenPGP() &&
Utils.isNormal(oData.Result.PlainRaw))
{
bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oData.Result.PlainRaw);
oMessage.plainRaw = Utils.pString(oData.Result.PlainRaw);
bPgpEncrypted = /---BEGIN PGP MESSAGE---/.test(oMessage.plainRaw);
if (!bPgpEncrypted)
{
bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oData.Result.PlainRaw) &&
/-----BEGIN PGP SIGNATURE-----/.test(oData.Result.PlainRaw);
bPgpSigned = /-----BEGIN PGP SIGNED MESSAGE-----/.test(oMessage.plainRaw) &&
/-----BEGIN PGP SIGNATURE-----/.test(oMessage.plainRaw);
}
if (bPgpSigned && oMessage.isPgpSigned() && oMessage.pgpSignature())
$proxyDiv.empty();
if (bPgpSigned && oMessage.isPgpSigned())
{
sPlain = '<pre class="b-plain-openpgp signed">' + oData.Result.PlainRaw + '</pre>';
try
{
mPgpMessage = window.openpgp.cleartext.readArmored(oData.Result.PlainRaw);
}
catch (oExc) {}
if (mPgpMessage && mPgpMessage.getText)
{
sPlain = mPgpMessage.getText();
}
else
{
bPgpSigned = false;
}
sPlain =
$proxyDiv.append(
$('<pre class="b-plain-openpgp signed"></pre>').text(oMessage.plainRaw)
).html()
;
}
else if (bPgpEncrypted && oMessage.isPgpEncrypted())
{
try
{
mPgpMessage = window.openpgp.message.readArmored(oData.Result.PlainRaw);
}
catch (oExc) {}
// try
// {
// mPgpMessage = window.openpgp.message.readArmored(oMessage.plainRaw);
// }
// catch (oExc) {}
sPlain = '<pre class="b-plain-openpgp encrypted">' + oData.Result.PlainRaw + '</pre>';
sPlain =
$proxyDiv.append(
$('<pre class="b-plain-openpgp encrypted"></pre>').text(oMessage.plainRaw)
).html()
;
}
if (bPgpSigned || bPgpEncrypted)
{
oBody.data('rl-plain-raw', oData.Result.PlainRaw);
oBody.data('rl-plain-pgp-encrypted', bPgpEncrypted);
oBody.data('rl-plain-pgp-signed', bPgpSigned);
}
$proxyDiv.empty();
oMessage.isPgpSigned(bPgpSigned);
oMessage.isPgpEncrypted(bPgpEncrypted);
}
oBody.html(sPlain).addClass('b-text-part plain');
@ -935,9 +929,14 @@ WebMailDataStorage.prototype.setMessage = function (oData, bCached)
bIsHtml = false;
}
oMessage.isHtml(!!bIsHtml);
oMessage.hasImages(!!bHasExternals);
oMessage.pgpSignedVerifyStatus(Enums.SignedVerifyStatus.None);
oMessage.pgpSignedVerifyUser('');
if (oData.Result.Rtl)
{
oBody.data('rl-is-rtl', true);
this.isRtl(true);
oBody.addClass('rtl-text-part');
}
@ -945,14 +944,6 @@ WebMailDataStorage.prototype.setMessage = function (oData, bCached)
if (oMessage.body)
{
oMessagesBodiesDom.append(oMessage.body);
oMessage.body.data('rl-is-html', bIsHtml);
oMessage.body.data('rl-has-images', bHasExternals);
oMessage.isRtl(!!oMessage.body.data('rl-is-rtl'));
oMessage.isHtml(!!oMessage.body.data('rl-is-html'));
oMessage.hasImages(!!oMessage.body.data('rl-has-images'));
oMessage.plainRaw = Utils.pString(oMessage.body.data('rl-plain-raw'));
}
if (bHasInternals)
@ -965,6 +956,7 @@ WebMailDataStorage.prototype.setMessage = function (oData, bCached)
oMessage.showExternalImages(true);
}
oMessage.storeDataToDom();
this.purgeMessageBodyCacheThrottle();
}
else
@ -973,24 +965,10 @@ WebMailDataStorage.prototype.setMessage = function (oData, bCached)
if (oMessage.body)
{
oMessage.body.data('rl-cache-count', ++Globals.iMessageBodyCacheCount);
oMessage.isRtl(!!oMessage.body.data('rl-is-rtl'));
oMessage.isHtml(!!oMessage.body.data('rl-is-html'));
oMessage.hasImages(!!oMessage.body.data('rl-has-images'));
oMessage.plainRaw = Utils.pString(oMessage.body.data('rl-plain-raw'));
oMessage.fetchDataToDom();
}
}
if (oMessage.body && RL.data().allowOpenPGP())
{
oMessage.isPgpSigned(!!oMessage.body.data('rl-plain-pgp-signed'));
oMessage.isPgpEncrypted(!!oMessage.body.data('rl-plain-pgp-encrypted'));
}
else
{
oMessage.isPgpSigned(false);
oMessage.isPgpEncrypted(false);
}
this.messageActiveDom(oMessage.body);
this.hideMessageBodies();
@ -1148,3 +1126,66 @@ WebMailDataStorage.prototype.setMessageList = function (oData, bCached)
));
}
};
WebMailDataStorage.prototype.findPublicKeyByHex = function (sHash)
{
return _.find(this.openpgpkeysPublic(), function (oItem) {
return oItem && sHash === oItem.id;
});
};
WebMailDataStorage.prototype.findPublicKeysByEmail = function (sEmail)
{
return _.compact(_.map(this.openpgpkeysPublic(), function (oItem) {
var oKey = null;
if (oItem && sEmail === oItem.email)
{
try
{
oKey = window.openpgp.key.readArmored(oItem.armor);
if (oKey && !oKey.err && oKey.keys && oKey.keys[0])
{
return oKey.keys[0];
}
}
catch (e) {}
}
return null;
}));
};
WebMailDataStorage.prototype.findPrivateKeyByEmail = function (sEmail, sPass)
{
var
oPrivateKey = null,
oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email;
})
;
if (oKey)
{
try
{
oPrivateKey = window.openpgp.key.readArmored(oKey.armor);
if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
{
oPrivateKey = oPrivateKey.keys[0];
oPrivateKey.decrypt(sPass);
}
else
{
oPrivateKey = null;
}
}
catch (e)
{
oPrivateKey = null;
}
}
return oPrivateKey;
};

View file

@ -187,6 +187,16 @@ html.rl-no-preview-pane {
background-color: #eee;
}
.pgpInfo {
padding: 5px 15px;
border-bottom: 1px solid #ddd;
background-color: #fcf8e3;
&.success {
background-color: #e9f4ff;
}
}
.readReceipt {
background-color: #ffffd9;
}

View file

@ -36,6 +36,8 @@ function MailBoxMessageViewViewModel()
this.fullScreenMode = oData.messageFullScreenMode;
this.showFullInfo = ko.observable(false);
this.openPGPInformation = ko.observable('');
this.openPGPInformation.isError = ko.observable(false);
this.messageVisibility = ko.computed(function () {
return !this.messageLoadingThrottle() && !!this.message();
@ -105,6 +107,14 @@ function MailBoxMessageViewViewModel()
this.viewUserPic = ko.observable(Consts.DataImages.UserDotPic);
this.viewUserPicVisible = ko.observable(false);
this.viewPgpSignedVerifyStatus = ko.computed(function () {
return this.message() ? this.message().pgpSignedVerifyStatus() : Enums.SignedVerifyStatus.None;
}, this);
this.viewPgpSignedVerifyUser = ko.computed(function () {
return this.message() ? this.message().pgpSignedVerifyUser() : '';
}, this);
this.message.subscribe(function (oMessage) {
this.messageActiveDom(null);
@ -176,6 +186,41 @@ function MailBoxMessageViewViewModel()
Utils.extendAsViewModel('MailBoxMessageViewViewModel', MailBoxMessageViewViewModel);
MailBoxMessageViewViewModel.prototype.isPgpActionVisible = function ()
{
return Enums.SignedVerifyStatus.Success !== this.viewPgpSignedVerifyStatus();
};
MailBoxMessageViewViewModel.prototype.isPgpStatusVerifyVisible = function ()
{
return Enums.SignedVerifyStatus.None !== this.viewPgpSignedVerifyStatus();
};
MailBoxMessageViewViewModel.prototype.isPgpStatusVerifySuccess = function ()
{
return Enums.SignedVerifyStatus.Success === this.viewPgpSignedVerifyStatus();
};
MailBoxMessageViewViewModel.prototype.pgpStatusVerifyMessage = function ()
{
var sResult = '';
switch (this.viewPgpSignedVerifyStatus())
{
// TODO i18n
case Enums.SignedVerifyStatus.Unverified:
sResult = 'Unverified signature';
break;
case Enums.SignedVerifyStatus.Error:
sResult = 'OpenPGP decryption error';
break;
case Enums.SignedVerifyStatus.Success:
sResult = 'Good signature from ' + this.viewPgpSignedVerifyUser();
break;
}
return sResult;
};
MailBoxMessageViewViewModel.prototype.scrollToTop = function ()
{
var oCont = $('.messageItem.nano .content', this.viewModelDom);
@ -354,6 +399,28 @@ MailBoxMessageViewViewModel.prototype.showImages = function (oMessage)
}
};
/**
* @param {MessageModel} oMessage
*/
MailBoxMessageViewViewModel.prototype.verifyPgpSignedClearMessage = function (oMessage)
{
if (oMessage)
{
oMessage.verifyPgpSignedClearMessage();
}
};
/**
* @param {MessageModel} oMessage
*/
MailBoxMessageViewViewModel.prototype.decryptPgpEncryptedMessage = function (oMessage)
{
if (oMessage)
{
oMessage.decryptPgpEncryptedMessage();
}
};
/**
* @param {MessageModel} oMessage
*/

View file

@ -30,80 +30,26 @@ function PopupsComposeOpenPgpViewModel()
var
self = this,
bResult = true,
aOpenpgpkeysPublic = RL.data().openpgpkeysPublic(),
oKey = null,
oData = RL.data(),
oPrivateKey = null,
aPublicKeys = [],
fFindPublicKey = function (sEmail) {
var
oResult = null,
oKey = _.find(aOpenpgpkeysPublic, function (oItem) {
return oItem && sEmail === oItem.email;
})
;
if (oKey)
{
try
{
oResult = window.openpgp.key.readArmored(oKey.armor);
if (oResult && !oResult.err && oResult.keys && oResult.keys[0])
{
oResult = oResult.keys[0];
}
else
{
oResult = null;
}
}
catch (e)
{
oResult = null;
}
}
return oResult;
}
aPublicKeys = []
;
this.submitRequest(true);
if (bResult && this.sign() && '' === this.from())
{
this.notification('Please specify From email address');
// TODO i18n
this.notification('Please specify FROM email address');
bResult = false;
}
if (bResult && this.sign())
{
oKey = _.find(RL.data().openpgpkeysPrivate(), function (oItem) {
return oItem && self.from() === oItem.email;
});
if (oKey)
{
try
{
oPrivateKey = window.openpgp.key.readArmored(oKey.armor);
if (oPrivateKey && !oPrivateKey.err && oPrivateKey.keys && oPrivateKey.keys[0])
{
oPrivateKey = oPrivateKey.keys[0];
oPrivateKey.decrypt(this.password());
}
else
{
oPrivateKey = null;
}
}
catch (e)
{
oPrivateKey = null;
}
}
oPrivateKey = oData.findPrivateKeyByEmail(this.from(), this.password());
if (!oPrivateKey)
{
// TODO i18n
this.notification('No private key found for "' + this.from() + '" email');
bResult = false;
}
@ -111,25 +57,27 @@ function PopupsComposeOpenPgpViewModel()
if (bResult && this.encrypt() && 0 === this.to().length)
{
// TODO i18n
this.notification('Please specify at least one recipient');
bResult = false;
}
if (bResult && this.encrypt())
{
aPublicKeys = _.compact(_.map(this.to(), function (sEmail) {
var oKey = fFindPublicKey(sEmail);
if (!oKey && bResult)
aPublicKeys = _.compact(_.union(this.to(), function (sEmail) {
var aKeys = oData.findPublicKeysByEmail(sEmail);
if (0 === aKeys.length && bResult)
{
// TODO i18n
self.notification('No public key found for "' + sEmail + '" email');
bResult = false;
}
return oKey;
return aKeys;
}));
if (0 === aPublicKeys.length || this.to().length !== aPublicKeys.length)
if (bResult && (0 === aPublicKeys.length || this.to().length !== aPublicKeys.length))
{
bResult = false;
}
@ -162,6 +110,7 @@ function PopupsComposeOpenPgpViewModel()
}
catch (e)
{
// TODO i18n
self.notification('OpenPGP error: ' + e);
bResult = false;
}

View file

@ -22,8 +22,8 @@
</div>
<div class="btn-group">&nbsp;</div>
<div class="btn-group btn-group-custom-margin">
<a class="btn btn-danger buttonDelete" data-placement="bottom" data-bind="command: deleteCommand, tooltip: 'MESSAGE_LIST/BUTTON_DELETE'">
<i class="icon-trash icon-white"></i>
<a class="btn buttonDelete" data-placement="bottom" data-bind="command: deleteCommand, tooltip: 'MESSAGE_LIST/BUTTON_DELETE'">
<i class="icon-trash"></i>
<span data-bind="text: 1 < messageListCheckedOrSelectedUidsWithSubMails().length ? ' (' + messageListCheckedOrSelectedUidsWithSubMails().length + ')' : ''"></span>
</a>
<a class="btn buttonSpam" data-placement="bottom" data-bind="visible: !isSpamFolder() && !isSpamDisabled(), command: spamCommand, tooltip: 'MESSAGE_LIST/BUTTON_SPAM'">

View file

@ -2,58 +2,58 @@
<div class="messageView" data-bind="css: {'message-selected': isMessageSelected}">
<div class="toolbar g-ui-user-select-none">
<div class="messageButtons btn-toolbar">
<div class="btn-group" data-placement="bottom" data-bind="visible: !$root.usePreviewPane(), tooltip: 'MESSAGE/BUTTON_CLOSE'">
<a class="btn buttonClose" data-bind="command: $root.closeMessage">
<div class="btn-group" data-placement="bottom" data-bind="visible: !usePreviewPane(), tooltip: 'MESSAGE/BUTTON_CLOSE'">
<a class="btn buttonClose" data-bind="command: closeMessage">
<i class="icon-remove"></i>
</a>
</div>
<div class="btn-group">&nbsp;</div>
<div class="btn-group" data-placement="bottom" data-bind="visible: $root.isDraftFolder(), tooltip: 'MESSAGE/BUTTON_EDIT'">
<a class="btn btn-success buttonEdit" data-bind="command: $root.messageEditCommand">
<div class="btn-group" data-placement="bottom" data-bind="visible: isDraftFolder(), tooltip: 'MESSAGE/BUTTON_EDIT'">
<a class="btn btn-success buttonEdit" data-bind="command: messageEditCommand">
<i class="icon-pencil icon-white"></i>
</a>
</div>
<div class="btn-group" data-bind="visible: !$root.isDraftFolder()">
<a class="btn buttonReply" data-placement="bottom" data-bind="command: $root.replyCommand, tooltip: 'MESSAGE/BUTTON_REPLY'">
<div class="btn-group" data-bind="visible: !isDraftFolder()">
<a class="btn buttonReply" data-placement="bottom" data-bind="command: replyCommand, tooltip: 'MESSAGE/BUTTON_REPLY'">
<i class="icon-reply"></i>
</a>
<a class="btn buttonReplyAll" data-placement="bottom" data-bind="command: $root.replyAllCommand, tooltip: 'MESSAGE/BUTTON_REPLY_ALL'">
<a class="btn buttonReplyAll" data-placement="bottom" data-bind="command: replyAllCommand, tooltip: 'MESSAGE/BUTTON_REPLY_ALL'">
<i class="icon-reply-all"></i>
</a>
<a class="btn buttonForward" data-placement="bottom" data-bind=" command: $root.forwardCommand, tooltip: 'MESSAGE/BUTTON_FORWARD'">
<a class="btn buttonForward" data-placement="bottom" data-bind=" command: forwardCommand, tooltip: 'MESSAGE/BUTTON_FORWARD'">
<i class="icon-forward"></i>
</a>
</div>
<div class="btn-group">&nbsp;</div>
<div class="btn-group btn-group-custom-margin">
<a class="btn btn-danger buttonDelete" data-placement="bottom" data-bind="command: $root.deleteCommand, tooltip: 'MESSAGE/BUTTON_DELETE'">
<a class="btn btn-danger buttonDelete" data-placement="bottom" data-bind="command: deleteCommand, tooltip: 'MESSAGE/BUTTON_DELETE'">
<i class="icon-trash icon-white"></i>
</a>
<a class="btn buttonSpam" data-placement="bottom" data-bind="visible: !$root.isDraftFolder(), command: $root.spamCommand, tooltip: 'MESSAGE/BUTTON_SPAM'">
<a class="btn buttonSpam" data-placement="bottom" data-bind="visible: !isDraftFolder(), command: spamCommand, tooltip: 'MESSAGE/BUTTON_SPAM'">
<i class="icon-bug"></i>
</a>
</div>
<div class="btn-group">&nbsp;</div>
<div class="btn-group">
<a class="btn dropdown-toggle buttonMore" data-placement="bottom" data-toggle="dropdown" data-bind="command: $root.messageVisibilityCommand, tooltip: 'MESSAGE/BUTTON_MORE'">
<a class="btn dropdown-toggle buttonMore" data-placement="bottom" data-toggle="dropdown" data-bind="command: messageVisibilityCommand, tooltip: 'MESSAGE/BUTTON_MORE'">
<i class="icon-list"></i>
</a>
<ul class="dropdown-menu g-ui-menu">
<li class="e-item" data-bind="visible: !$root.isDraftFolder()">
<a class="e-link" data-bind="command: $root.forwardAsAttachmentCommand">
<li class="e-item" data-bind="visible: !isDraftFolder()">
<a class="e-link" data-bind="command: forwardAsAttachmentCommand">
<i class="icon-reply"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="MESSAGE/BUTTON_FORWARD_AS_ATTACHMENT"></span>
</a>
</li>
<li class="e-item" data-bind="visible: !$root.isDraftFolder()">
<a class="e-link" data-bind="command: $root.editAsNewCommand">
<li class="e-item" data-bind="visible: !isDraftFolder()">
<a class="e-link" data-bind="command: editAsNewCommand">
<i class="icon-pencil"></i>
&nbsp;&nbsp;
<span class="i18n" data-i18n-text="MESSAGE/BUTTON_EDIT_AS_NEW"></span>
</a>
</li>
<li class="divider" data-bind="visible: !$root.isDraftFolder()"></li>
<li class="divider" data-bind="visible: !isDraftFolder()"></li>
<li class="e-item">
<a target="_blank" class="e-link" data-bind="click: function () { if (message()) { message().viewPopupMessage(); }}">
<i class="icon-popup"></i>
@ -86,11 +86,11 @@
</ul>
</div>
<div class="btn-group">&nbsp;</div>
<div class="btn-group" data-bind="visible: !$root.usePreviewPane()">
<a class="btn buttonUp" data-bind="command: $root.goUpCommand">
<div class="btn-group" data-bind="visible: !usePreviewPane()">
<a class="btn buttonUp" data-bind="command: goUpCommand">
<i class="icon-left-middle"></i>
</a>
<a class="btn buttonDown" data-bind="command: $root.goDownCommand">
<a class="btn buttonDown" data-bind="command: goDownCommand">
<i class="icon-right-middle"></i>
</a>
</div>
@ -98,11 +98,11 @@
</div>
<div class="b-content thm-message-view-background-color">
<div>
<div class="b-message-view-desc" data-bind="visible: !message() && '' === $root.messageError()">
<div class="b-message-view-desc" data-bind="visible: !message() && '' === messageError()">
<span class="i18n" data-i18n-text="MESSAGE/MESSAGE_VIEW_DESC"></span>
</div>
<div class="b-message-view-desc error" data-bind="visible: !message() && '' !== $root.messageError()">
<span class="text" data-bind="text: $root.messageError()"></span>
<div class="b-message-view-desc error" data-bind="visible: !message() && '' !== messageError()">
<span class="text" data-bind="text: messageError()"></span>
</div>
<div data-bind="visible: message">
<div class="messageItem" data-bind="css: viewLineAsCcc(), nano: true">
@ -127,14 +127,14 @@
<span class="i18n emptySubjectText" data-i18n-text="MESSAGE/EMPTY_SUBJECT_TEXT"></span>
</div>
<div class="senderParent">
<div class="g-ui-user-select-none" style="float: left; cursor: pointer;" data-bind="click: function() { $root.showFullInfo(!$root.showFullInfo()); }">
<i class="icon-right-dir" data-bind="css: $root.showFullInfo() ? 'icon-down-dir' : 'icon-right-dir'"></i>
<div class="g-ui-user-select-none" style="float: left; cursor: pointer;" data-bind="click: function() { showFullInfo(!showFullInfo()); }">
<i class="icon-right-dir" data-bind="css: showFullInfo() ? 'icon-down-dir' : 'icon-right-dir'"></i>
</div>
<div class="informationShort" data-bind="event: { 'dblclick': toggleFullScreen }">
<span data-bind="visible: !$root.isDraftOrSentFolder()">
<span data-bind="visible: !isDraftOrSentFolder()">
<span class="from" data-bind="html: viewFromShort, title: viewFrom"></span>
</span>
<span data-bind="visible: $root.isDraftOrSentFolder()">
<span data-bind="visible: isDraftOrSentFolder()">
<span class="i18n uiLabel labelTo" data-i18n-text="MESSAGE/LABEL_TO"></span>:
<span class="to" data-bind="html: viewToShort, title: viewTo"></span>
</span>
@ -143,7 +143,7 @@
<div class="clearfix"></div>
</div>
</div>
<div class="informationFull" data-bind="visible: $root.showFullInfo()">
<div class="informationFull" data-bind="visible: showFullInfo()">
<div data-bind="visible: '' !== viewFrom()">
<span class="i18n uiLabel labelFrom" data-i18n-text="MESSAGE/LABEL_FROM"></span>:
&nbsp;
@ -175,33 +175,41 @@
</div>
</div>
<div class="line-loading e-strip-animation" data-bind="visible: $root.messageLoadingThrottle()"></div>
<div class="line-loading e-strip-animation" data-bind="visible: messageLoadingThrottle()"></div>
<div class="loading g-ui-min-height-300" data-bind="visible: $root.messageLoadingThrottle()">
<div class="loading g-ui-min-height-300" data-bind="visible: messageLoadingThrottle()">
<span class="i18n text" data-i18n-text="MESSAGE/MESSAGE_LOADING"></span><span class="textLoadingAnimationD1">.</span><span class="textLoadingAnimationD2">.</span><span class="textLoadingAnimationD3">.</span>
</div>
</div>
<div class="g-ui-min-height-300" data-bind="visible: !$root.messageLoadingThrottle()">
<div class="showImages" data-bind="visible: message() && message().hasImages(), click: function() { $root.showImages(message()); }">
<div class="g-ui-min-height-300" data-bind="visible: !messageLoadingThrottle()">
<div class="showImages" data-bind="visible: message() && message().hasImages(), click: function() { showImages(message()); }">
<i class="icon-image"></i>
&nbsp;&nbsp;
<span class="i18n text" data-i18n-text="MESSAGE/BUTTON_SHOW_IMAGES"></span>
</div>
<div class="readReceipt" data-bind="visible: message() && '' !== message().readReceipt() && !message().isReadReceipt(), click: function() { $root.readReceipt(message()); }">
<div class="readReceipt" data-bind="visible: message() && '' !== message().readReceipt() && !message().isReadReceipt(), click: function() { readReceipt(message()); }">
<i class="icon-mail"></i>
&nbsp;&nbsp;
<span class="i18n text" data-i18n-text="MESSAGE/BUTTON_NOTIFY_READ_RECEIPT"></span>
</div>
<div class="pgpSigned" data-bind="visible: message() && message().isPgpSigned()">
<div class="pgpInfo" data-bind="visible: isPgpStatusVerifyVisible(), css: {'success': isPgpStatusVerifySuccess()}">
<i class="icon-key"></i>
&nbsp;&nbsp;
<span data-bind="text: pgpStatusVerifyMessage()"></span>
</div>
<div class="pgpSigned" data-bind="visible: message() && message().isPgpSigned() && isPgpActionVisible(), click: function() { verifyPgpSignedClearMessage(message()); }">
<i class="icon-lock"></i>
&nbsp;&nbsp;
PGP signed
OpenPGP signed message (click to verify)
</div>
<div class="pgpEncrypted" data-bind="visible: message() && message().isPgpEncrypted()">
<i class="icon-lock"></i>
&nbsp;&nbsp;
PGP encrypted
<span data-bind="click: function() { decryptPgpEncryptedMessage(message()); }">
OpenPGP encrypted message (click to decrypt)
</span>
<input type="password" />
</div>
<div class="attachmentsPlace" data-bind="visible: message() && message().hasVisibleAttachments()">
<ul class="attachmentList" data-bind="foreach: message() ? message().attachments() : []">

View file

@ -637,7 +637,7 @@
border-radius: 8px;
}
/*! normalize.css 2012-03-11T12:53 UTC - http://github.com/necolas/normalize.css */
/* =============================================================================
@ -1142,7 +1142,7 @@ table {
border-collapse: collapse;
border-spacing: 0;
}
@charset "UTF-8";
@font-face {
@ -1474,7 +1474,7 @@ table {
.icon-mail:before {
content: "\e062";
}
/** initial setup **/
.nano {
/*
@ -1591,7 +1591,7 @@ table {
.nano > .pane2:hover > .slider2, .nano > .pane2.active > .slider2 {
background-color: rgba(0, 0, 0, 0.4);
}
/* Magnific Popup CSS */
.mfp-bg {
top: 0;
@ -1956,7 +1956,7 @@ img.mfp-img {
right: 0;
padding-top: 0; }
/* overlay at start */
.mfp-fade.mfp-bg {
@ -2002,7 +2002,7 @@ img.mfp-img {
-moz-transform: translateX(50px);
transform: translateX(50px);
}
.simple-pace {
-webkit-pointer-events: none;
pointer-events: none;
@ -2073,7 +2073,7 @@ img.mfp-img {
@keyframes simple-pace-stripe-animation {
0% { transform: none; transform: none; }
100% { transform: translate(-32px, 0); transform: translate(-32px, 0); }
}
}
.inputosaurus-container {
background-color:#fff;
border:1px solid #bcbec0;
@ -2141,7 +2141,7 @@ img.mfp-img {
box-shadow:none;
}
.inputosaurus-input-hidden { display:none; }
.flag-wrapper {
width: 24px;
height: 16px;
@ -2184,7 +2184,7 @@ img.mfp-img {
.flag.flag-pt-br {background-position: -192px -11px}
.flag.flag-cn, .flag.flag-zh-tw, .flag.flag-zh-cn, .flag.flag-zh-hk {background-position: -208px -22px}
/* RainLoop Webmail (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
.clearfix {
*zoom: 1;
@ -7226,6 +7226,14 @@ html.rl-no-preview-pane .messageView.message-selected {
border-bottom: 1px solid #ddd;
background-color: #eee;
}
.messageView .b-content .messageItem .pgpInfo {
padding: 5px 15px;
border-bottom: 1px solid #ddd;
background-color: #fcf8e3;
}
.messageView .b-content .messageItem .pgpInfo.success {
background-color: #e9f4ff;
}
.messageView .b-content .messageItem .readReceipt {
background-color: #ffffd9;
}

File diff suppressed because one or more lines are too long

View file

@ -1,5 +1,5 @@
/*! RainLoop Webmail Admin Module (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
(function (window, $, ko, crossroads, hasher, _) {
/*! RainLoop Webmail Admin Module (c) RainLoop Team | Licensed under CC BY-NC-SA 3.0 */
(function (window, $, ko, crossroads, hasher, _) {
'use strict';
@ -70,14 +70,14 @@ var
$document = $(window.document),
NotificationClass = window.Notification && window.Notification.requestPermission ? window.Notification : null
;
;
/*jshint onevar: false*/
/**
* @type {?AdminApp}
*/
var RL = null;
/*jshint onevar: true*/
/**
* @type {?}
*/
@ -221,7 +221,7 @@ if (Globals.bAllowPdfPreview && navigator && navigator.mimeTypes)
return oType && 'application/pdf' === oType.type;
});
}
Consts.Defaults = {};
Consts.Values = {};
Consts.DataImages = {};
@ -339,7 +339,7 @@ Consts.DataImages.UserDotPic = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA
* @type {string}
*/
Consts.DataImages.TranspPic = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQIW2NkAAIAAAoAAggA9GkAAAAASUVORK5CYII=';
/**
* @enum {string}
*/
@ -590,6 +590,16 @@ Enums.ContactScopeType = {
'ShareAll': 2
};
/**
* @enum {number}
*/
Enums.SignedVerifyStatus = {
'Unverified': -2,
'Error': -1,
'None': 0,
'Success': 1
};
/**
* @enum {number}
*/
@ -683,7 +693,7 @@ Enums.Notification = {
'UnknownNotification': 999,
'UnknownError': 999
};
Utils.trim = $.trim;
Utils.inArray = $.inArray;
Utils.isArray = _.isArray;
@ -2445,7 +2455,7 @@ Utils.openPgpImportPublicKeys = function (sPublicKeysArmored)
return false;
};
// Base64 encode / decode
// http://www.webtoolkit.info/
@ -2608,7 +2618,7 @@ Base64 = {
}
};
/*jslint bitwise: false*/
/*jslint bitwise: false*/
ko.bindingHandlers.tooltip = {
'init': function (oElement, fValueAccessor) {
if (!Globals.bMobileDevice)
@ -3257,7 +3267,7 @@ ko.observable.fn.validateFunc = function (fFunc)
return this;
};
/**
* @constructor
*/
@ -3555,7 +3565,7 @@ LinkBuilder.prototype.socialFacebook = function ()
{
return this.sServer + 'SocialFacebook' + ('' !== this.sSpecSuffix ? '/' + this.sSpecSuffix + '/' : '');
};
/**
* @type {Object}
*/
@ -3649,7 +3659,7 @@ Plugins.settingsGet = function (sPluginSection, sName)
};
/**
* @constructor
*/
@ -3723,7 +3733,7 @@ CookieDriver.prototype.get = function (sKey)
return mResult;
};
/**
* @constructor
*/
@ -3794,7 +3804,7 @@ LocalStorageDriver.prototype.get = function (sKey)
return mResult;
};
/**
* @constructor
*/
@ -3837,7 +3847,7 @@ LocalStorage.prototype.get = function (iKey)
{
return this.oDriver ? this.oDriver.get('p' + iKey) : null;
};
/**
* @constructor
*/
@ -3850,7 +3860,7 @@ KnoinAbstractBoot.prototype.bootstart = function ()
{
};
/**
* @param {string=} sPosition = ''
* @param {string=} sTemplate = ''
@ -3925,7 +3935,7 @@ KnoinAbstractViewModel.prototype.registerPopupEscapeKey = function ()
return true;
});
};
/**
* @param {string} sScreenName
* @param {?=} aViewModels = []
@ -4001,7 +4011,7 @@ KnoinAbstractScreen.prototype.__start = function ()
this.oCross = oRoute;
}
};
/**
* @constructor
*/
@ -4387,7 +4397,7 @@ Knoin.prototype.bootstart = function ()
};
kn = new Knoin();
/**
* @param {string=} sEmail
* @param {string=} sName
@ -4751,7 +4761,7 @@ EmailModel.prototype.inputoTagLine = function ()
{
return 0 < this.name.length ? this.name + ' (' + this.email + ')' : this.email;
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -4969,7 +4979,7 @@ PopupsDomainViewModel.prototype.clearForm = function ()
this.smtpAuth(true);
this.whiteList('');
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -5108,7 +5118,7 @@ PopupsPluginViewModel.prototype.onBuild = function ()
return bResult;
});
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -5224,7 +5234,7 @@ PopupsActivateViewModel.prototype.validateSubscriptionKey = function ()
{
var sValue = this.key();
return '' === sValue || !!/^RL[\d]+-[A-Z0-9\-]+Z$/.test(Utils.trim(sValue));
};
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -5284,7 +5294,7 @@ PopupsLanguagesViewModel.prototype.changeLanguage = function (sLang)
RL.data().mainLanguage(sLang);
this.cancelCommand();
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -5404,7 +5414,7 @@ PopupsAskViewModel.prototype.onBuild = function ()
});
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -5491,7 +5501,7 @@ AdminLoginViewModel.prototype.onHide = function ()
{
this.loginFocus(false);
};
/**
* @param {?} oScreen
*
@ -5513,7 +5523,7 @@ AdminMenuViewModel.prototype.link = function (sRoute)
{
return '#/' + sRoute;
};
/**
* @constructor
* @extends KnoinAbstractViewModel
@ -5535,7 +5545,7 @@ AdminPaneViewModel.prototype.logoutClick = function ()
RL.remote().adminLogout(function () {
RL.loginAndLogoutReload();
});
};
};
/**
* @constructor
*/
@ -5635,7 +5645,7 @@ AdminGeneral.prototype.selectLanguage = function ()
{
kn.showScreenPopup(PopupsLanguagesViewModel);
};
/**
* @constructor
*/
@ -5687,7 +5697,7 @@ AdminLogin.prototype.onBuild = function ()
}, 50);
};
/**
* @constructor
*/
@ -5756,7 +5766,7 @@ AdminBranding.prototype.onBuild = function ()
}, 50);
};
/**
* @constructor
*/
@ -5976,7 +5986,7 @@ AdminContacts.prototype.onBuild = function ()
}, 50);
};
/**
* @constructor
*/
@ -6065,7 +6075,7 @@ AdminDomains.prototype.onDomainListChangeRequest = function ()
{
RL.reloadDomainList();
};
/**
* @constructor
*/
@ -6153,7 +6163,7 @@ AdminSecurity.prototype.phpInfoLink = function ()
{
return RL.link().phpInfo();
};
/**
* @constructor
*/
@ -6269,7 +6279,7 @@ AdminSocial.prototype.onBuild = function ()
}, 50);
};
/**
* @constructor
*/
@ -6366,7 +6376,7 @@ AdminPlugins.prototype.onPluginDisableRequest = function (sResult, oData)
RL.reloadPluginList();
};
/**
* @constructor
*/
@ -6470,7 +6480,7 @@ AdminPackages.prototype.installPackage = function (oPackage)
RL.remote().packageInstall(this.requestHelper(oPackage, true), oPackage);
}
};
/**
* @constructor
*/
@ -6521,7 +6531,7 @@ AdminLicensing.prototype.licenseExpiredMomentValue = function ()
{
var oDate = moment.unix(this.licenseExpired());
return oDate.format('LL') + ' (' + oDate.from(moment()) + ')';
};
};
/**
* @constructor
*/
@ -6596,7 +6606,7 @@ AbstractData.prototype.populateDataOnStart = function()
this.contactsIsAllowed(!!RL.settingsGet('ContactsIsAllowed'));
};
/**
* @constructor
* @extends AbstractData
@ -6630,7 +6640,7 @@ _.extend(AdminDataStorage.prototype, AbstractData.prototype);
AdminDataStorage.prototype.populateDataOnStart = function()
{
AbstractData.prototype.populateDataOnStart.call(this);
};
};
/**
* @constructor
*/
@ -6904,7 +6914,7 @@ AbstractAjaxRemoteStorage.prototype.jsVersion = function (fCallback, sVersion)
'Version': sVersion
});
};
/**
* @constructor
* @extends AbstractAjaxRemoteStorage
@ -7148,7 +7158,7 @@ AdminAjaxRemoteStorage.prototype.adminPing = function (fCallback)
{
this.defaultRequest(fCallback, 'AdminPing');
};
/**
* @constructor
*/
@ -7214,7 +7224,7 @@ AbstractCacheStorage.prototype.setEmailsPicsHashesData = function (oData)
{
this.oEmailsPicsHashes = oData;
};
/**
* @constructor
* @extends AbstractCacheStorage
@ -7225,7 +7235,7 @@ function AdminCacheStorage()
}
_.extend(AdminCacheStorage.prototype, AbstractCacheStorage.prototype);
/**
* @param {Array} aViewModels
* @constructor
@ -7403,7 +7413,7 @@ AbstractSettings.prototype.routes = function ()
['', oRules]
];
};
/**
* @constructor
* @extends KnoinAbstractScreen
@ -7418,7 +7428,7 @@ _.extend(AdminLoginScreen.prototype, KnoinAbstractScreen.prototype);
AdminLoginScreen.prototype.onShow = function ()
{
RL.setTitle('');
};
};
/**
* @constructor
* @extends AbstractSettings
@ -7438,7 +7448,7 @@ AdminSettingsScreen.prototype.onShow = function ()
// AbstractSettings.prototype.onShow.call(this);
RL.setTitle('');
};
};
/**
* @constructor
* @extends KnoinAbstractBoot
@ -7758,7 +7768,7 @@ AbstractApp.prototype.bootstart = function ()
ssm.ready();
};
/**
* @constructor
* @extends AbstractApp
@ -7997,7 +8007,7 @@ AdminApp.prototype.bootstart = function ()
* @type {AdminApp}
*/
RL = new AdminApp();
$html.addClass(Globals.bMobileDevice ? 'mobile' : 'no-mobile');
$window.keydown(Utils.killCtrlAandS).keyup(Utils.killCtrlAandS);
@ -8044,9 +8054,9 @@ window['__RLBOOT'] = function (fCall) {
window['__RLBOOT'] = null;
});
};
if (window.SimplePace) {
window.SimplePace.add(10);
}
}
}(window, jQuery, ko, crossroads, hasher, _));

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long