Merge pull request #999 from esroyo/gpg-key-multiple-addresses

Gpg key multiple addresses
This commit is contained in:
RainLoop Team 2016-04-19 20:26:36 +03:00
commit dd824179f1
12 changed files with 198 additions and 77 deletions

View file

@ -426,25 +426,36 @@ class AppUser extends AbstractApp
if (oItem && oItem.primaryKey) if (oItem && oItem.primaryKey)
{ {
var var
aEmails = [],
aUsers = [],
oPrimaryUser = oItem.getPrimaryUser(), oPrimaryUser = oItem.getPrimaryUser(),
sUser = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid sUser = (oPrimaryUser && oPrimaryUser.user) ? oPrimaryUser.user.userId.userid
: (oItem.users && oItem.users[0] ? oItem.users[0].userId.userid : '') : (oItem.users && oItem.users[0] ? oItem.users[0].userId.userid : '')
; ;
oEmail.clear(); if (oItem.users) {
oEmail.mailsoParse(sUser); _.each(oItem.users, (oItem) => {
oEmail.clear();
oEmail.mailsoParse(oItem.userId.userid);
if (oEmail.validate())
{
aEmails.push(oEmail.email);
aUsers.push(oItem.userId.userid);
}
});
}
if (oEmail.validate()) if (aEmails.length)
{ {
aKeys.push(new OpenPgpKeyModel( aKeys.push(new OpenPgpKeyModel(
iIndex, iIndex,
oItem.primaryKey.getFingerprint(), oItem.primaryKey.getFingerprint(),
oItem.primaryKey.getKeyId().toHex().toLowerCase(), oItem.primaryKey.getKeyId().toHex().toLowerCase(),
sUser, aUsers,
oEmail.email, aEmails,
oItem.isPrivate(), oItem.isPrivate(),
oItem.armor()) oItem.armor(),
sUser)
); );
} }
} }

View file

@ -201,7 +201,19 @@
*/ */
MessageModel.prototype.getRecipientsEmails = function () MessageModel.prototype.getRecipientsEmails = function ()
{ {
return _.compact(_.uniq(_.map(this.to.concat(this.cc), function (oItem) { return this.getEmails(['to', 'cc']);
};
/**
* @param {Array} aProperties
* @return {Array}
*/
MessageModel.prototype.getEmails = function (aProperties)
{
var self = this;
return _.compact(_.uniq(_.map(_.reduce(aProperties, function (aCarry, sProperty) {
return aCarry.concat(self[sProperty]);
}, []), function (oItem) {
return oItem ? oItem.email : ''; return oItem ? oItem.email : '';
}))); })));
}; };

View file

@ -18,23 +18,25 @@
* @param {string} iIndex * @param {string} iIndex
* @param {string} sGuID * @param {string} sGuID
* @param {string} sID * @param {string} sID
* @param {string} sUserID * @param {array} aUserIDs
* @param {string} sEmail * @param {array} aEmails
* @param {boolean} bIsPrivate * @param {boolean} bIsPrivate
* @param {string} sArmor * @param {string} sArmor
* @param {string} sUserID
* @constructor * @constructor
*/ */
function OpenPgpKeyModel(iIndex, sGuID, sID, sUserID, sEmail, bIsPrivate, sArmor) function OpenPgpKeyModel(iIndex, sGuID, sID, aUserIDs, aEmails, bIsPrivate, sArmor, sUserID)
{ {
AbstractModel.call(this, 'OpenPgpKeyModel'); AbstractModel.call(this, 'OpenPgpKeyModel');
this.index = iIndex; this.index = iIndex;
this.id = sID; this.id = sID;
this.guid = sGuID; this.guid = sGuID;
this.user = sUserID; this.users = aUserIDs;
this.email = sEmail; this.emails = aEmails;
this.armor = sArmor; this.armor = sArmor;
this.isPrivate = !!bIsPrivate; this.isPrivate = !!bIsPrivate;
this.selectUser(sUserID);
this.deleteAccess = ko.observable(false); this.deleteAccess = ko.observable(false);
} }
@ -45,7 +47,9 @@
OpenPgpKeyModel.prototype.id = ''; OpenPgpKeyModel.prototype.id = '';
OpenPgpKeyModel.prototype.guid = ''; OpenPgpKeyModel.prototype.guid = '';
OpenPgpKeyModel.prototype.user = ''; OpenPgpKeyModel.prototype.user = '';
OpenPgpKeyModel.prototype.users = [];
OpenPgpKeyModel.prototype.email = ''; OpenPgpKeyModel.prototype.email = '';
OpenPgpKeyModel.prototype.emails = [];
OpenPgpKeyModel.prototype.armor = ''; OpenPgpKeyModel.prototype.armor = '';
OpenPgpKeyModel.prototype.isPrivate = false; OpenPgpKeyModel.prototype.isPrivate = false;
@ -74,6 +78,26 @@
return oKey && oKey.keys ? oKey.keys : null; return oKey && oKey.keys ? oKey.keys : null;
}; };
OpenPgpKeyModel.prototype.select = function (sPattern, sProperty)
{
var index = this[sProperty].indexOf(sPattern);
if (index !== -1) {
this.user = this.users[index];
this.email = this.emails[index];
}
}
OpenPgpKeyModel.prototype.selectUser = function (sUser)
{
this.select(sUser, 'users');
}
OpenPgpKeyModel.prototype.selectEmail = function (sEmail)
{
this.select(sEmail, 'emails');
}
module.exports = OpenPgpKeyModel; module.exports = OpenPgpKeyModel;
}()); }());

View file

@ -59,7 +59,7 @@
PgpUserStore.prototype.findPublicKeysByEmail = function (sEmail) PgpUserStore.prototype.findPublicKeysByEmail = function (sEmail)
{ {
return _.compact(_.flatten(_.map(this.openpgpkeysPublic(), function (oItem) { return _.compact(_.flatten(_.map(this.openpgpkeysPublic(), function (oItem) {
var oKey = oItem && sEmail === oItem.email ? oItem : null; var oKey = oItem && -1 !== oItem.emails.indexOf(sEmail) ? oItem : null;
return oKey ? oKey.getNativeKeys() : [null]; return oKey ? oKey.getNativeKeys() : [null];
}), true)); }), true));
}; };
@ -99,7 +99,7 @@
PgpUserStore.prototype.findPublicKeyByEmailNotNative = function (sEmail) PgpUserStore.prototype.findPublicKeyByEmailNotNative = function (sEmail)
{ {
return _.find(this.openpgpkeysPublic(), function (oItem) { return _.find(this.openpgpkeysPublic(), function (oItem) {
return oItem && sEmail === oItem.email; return oItem && -1 !== oItem.emails.indexOf(sEmail);
}) || null; }) || null;
}; };
@ -110,7 +110,7 @@
PgpUserStore.prototype.findPrivateKeyByEmailNotNative = function (sEmail) PgpUserStore.prototype.findPrivateKeyByEmailNotNative = function (sEmail)
{ {
return _.find(this.openpgpkeysPrivate(), function (oItem) { return _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email; return oItem && -1 !== oItem.emails.indexOf(sEmail);
}) || null; }) || null;
}; };
@ -125,7 +125,7 @@
oPrivateKeys = [], oPrivateKeys = [],
oPrivateKey = null, oPrivateKey = null,
oKey = _.find(this.openpgpkeysPrivate(), function (oItem) { oKey = _.find(this.openpgpkeysPrivate(), function (oItem) {
return oItem && sEmail === oItem.email; return oItem && -1 !== oItem.emails.indexOf(sEmail);
}) })
; ;
@ -292,7 +292,7 @@
bEncrypted = mDom.hasClass('encrypted'), bEncrypted = mDom.hasClass('encrypted'),
bSigned = mDom.hasClass('signed'), bSigned = mDom.hasClass('signed'),
oVerControl = null, oVerControl = null,
aRecipients = oRainLoopMessage ? oRainLoopMessage.getRecipientsEmails() : [], aRecipients = oRainLoopMessage ? oRainLoopMessage.getEmails(['from', 'to', 'cc']) : [],
sData = '' sData = ''
; ;

View file

@ -33,8 +33,14 @@
min-height: 40px; min-height: 40px;
&-wrp { &-wrp {
overflow: hidden;
text-overflow: ellipsis; &:hover {
overflow: auto;
}
&:hover .key-list__item-name {
overflow: visible;
}
&.empty { &.empty {
text-align: center; text-align: center;
@ -48,33 +54,39 @@
color: #333; color: #333;
white-space: nowrap; white-space: nowrap;
padding-bottom: 4px;
&-delete { &:last-child {
display: inline; padding-bottom: 0;
cursor: pointer;
margin-right: 5px;
} }
&-name { &-delete {
margin-right: 5px; cursor: pointer;
&.disabled {
cursor: not-allowed;
}
}
&-names {
color: #333; color: #333;
display: inline;
&.empty { &.empty {
color: red; color: red;
} }
} }
&-name {
overflow: hidden;
text-overflow: ellipsis;
}
&-error { &-error {
margin-right: 5px;
color: red; color: red;
display: inline;
} }
&-hash { &-hash {
margin-right: 5px;
color: #aaa; color: #aaa;
display: inline;
} }
} }
} }
@ -82,6 +94,10 @@
.key-actions { .key-actions {
margin-top: 10px; margin-top: 10px;
min-height: 40px; min-height: 40px;
select option.even {
background-color: #f5f5f5;
}
} }
} }
@ -103,12 +119,18 @@
white-space: nowrap; white-space: nowrap;
&__radio { &__radio {
padding-right: 5px; padding: 3px 5px 0 0;
vertical-align: top;
} }
&__name { &__name {
border-bottom: 1px solid transparent;
}
&:hover { &__names {
display: inline-block;
&:hover .key-list__item__name {
border-bottom: 1px dashed #555; border-bottom: 1px dashed #555;
} }
} }

View file

@ -19,6 +19,7 @@
.open-pgp-key-img { .open-pgp-key-img {
margin-right: 10px; margin-right: 10px;
vertical-align: top;
.svg-icon { .svg-icon {
width: 12px; width: 12px;
@ -33,6 +34,11 @@
line-height: 22px; line-height: 22px;
cursor: default; cursor: default;
} }
.open-pgp-key-user-address:first-child {
line-height: 30px;
margin-bottom: -4px;
}
} }
.open-pgp-key-item { .open-pgp-key-item {

View file

@ -7,6 +7,11 @@
line-height: 17px; line-height: 17px;
font-size: 16px; font-size: 16px;
vertical-align: text-top; vertical-align: text-top;
&.disabled,
.disabled & {
color: grey;
}
} }
.icon-folder, .icon-folder-add, .icon-list { .icon-folder, .icon-folder-add, .icon-list {

View file

@ -31,6 +31,14 @@
var self = this; var self = this;
this.optionsCaption = Translator.i18n('PGP_NOTIFICATIONS/ADD_A_PUBLICK_KEY'); this.optionsCaption = Translator.i18n('PGP_NOTIFICATIONS/ADD_A_PUBLICK_KEY');
this.addOptionClass = function (oDomOption, oItem)
{
self.defautOptionsAfterRender(oDomOption, oItem);
if (oItem) {
oDomOption.classList.add(oItem['class']);
}
};
this.notification = ko.observable(''); this.notification = ko.observable('');
@ -54,13 +62,17 @@
}, this); }, this);
this.publicKeysOptions = ko.computed(function () { this.publicKeysOptions = ko.computed(function () {
return _.compact(_.map(PgpStore.openpgpkeysPublic(), function (oKey) { return _.compact(_.flatten(_.map(PgpStore.openpgpkeysPublic(), function (oKey, iIndex) {
return -1 < Utils.inArray(oKey, self.encryptKeysView()) ? null : { return -1 < Utils.inArray(oKey, self.encryptKeysView()) ? null :
'id': oKey.guid, _.map(oKey.users, function (sUser) {
'name': '(' + oKey.id.substr(-8).toUpperCase() + ') ' + oKey.user, return {
'key': oKey 'id': oKey.guid,
}; 'name': '(' + oKey.id.substr(-8).toUpperCase() + ') ' + sUser,
})); 'key': oKey,
'class': iIndex % 2 ? 'odd' : 'even'
};
});
}), true));
}); });
this.submitRequest = ko.observable(false); this.submitRequest = ko.observable(false);
@ -236,7 +248,8 @@
aKeys.push({ aKeys.push({
'empty': !oOption.key, 'empty': !oOption.key,
'selected': ko.observable(!!oOption.key), 'selected': ko.observable(!!oOption.key),
'user': oOption.key.user, 'removable': this.signKey().id !== oOption.key.id,
'users': oOption.key.users,
'hash': oOption.key.id.substr(-8).toUpperCase(), 'hash': oOption.key.id.substr(-8).toUpperCase(),
'key': oOption.key 'key': oOption.key
}); });
@ -360,11 +373,12 @@
if (oIdentity && oIdentity.email()) if (oIdentity && oIdentity.email())
{ {
sEmail = oIdentity.email(); sEmail = oIdentity.email();
aRec.unshift(sEmail);
oKey = PgpStore.findPrivateKeyByEmailNotNative(sEmail); oKey = PgpStore.findPrivateKeyByEmailNotNative(sEmail);
if (oKey) if (oKey)
{ {
this.signKey({ this.signKey({
'user': oKey.user || sEmail, 'users': oKey.users || [sEmail],
'hash': oKey.id.substr(-8).toUpperCase(), 'hash': oKey.id.substr(-8).toUpperCase(),
'key': oKey 'key': oKey
}); });
@ -378,16 +392,19 @@
if (aRec && 0 < aRec.length) if (aRec && 0 < aRec.length)
{ {
this.encryptKeys(_.compact(_.map(aRec, function (sEmail) { this.encryptKeys(_.uniq(_.compact(_.map(aRec, function (sEmail) {
var oKey = PgpStore.findPublicKeyByEmailNotNative(sEmail) || null; var oKey = PgpStore.findPublicKeyByEmailNotNative(sEmail) || null;
return { return {
'empty': !oKey, 'empty': !oKey,
'selected': ko.observable(!!oKey), 'selected': ko.observable(!!oKey),
'user': oKey ? (oKey.user || sEmail) : sEmail, 'removable': oIdentity && oIdentity.email() && oIdentity.email() !== sEmail,
'users': oKey ? (oKey.users || [sEmail]) : [sEmail],
'hash': oKey ? oKey.id.substr(-8).toUpperCase() : '', 'hash': oKey ? oKey.id.substr(-8).toUpperCase() : '',
'key': oKey 'key': oKey
}; };
}))); })), function (oEncryptKey) {
return oEncryptKey.hash;
}));
if (0 < this.encryptKeys().length) if (0 < this.encryptKeys().length)
{ {

View file

@ -27,12 +27,16 @@
No private key found No private key found
</div> </div>
<div class="key-list-wrp" data-bind="visible: signKey"> <div class="key-list-wrp" data-bind="visible: signKey">
<div class="key-list__item"> <div class="key-list__item row-fluid">
<div class="key-list__item-hash"> <div class="key-list__item-hash span4">
(<span data-bind="text: signKey() ? signKey().hash : ''"></span>) (<span data-bind="text: signKey() ? signKey().hash : ''"></span>)
</div> </div>
<div class="key-list__item-name"> <div class="key-list__item-names span8">
<span data-bind="text: signKey() ? signKey().user : ''"></span> <div data-bind="if: signKey()">
<div data-bind="foreach: signKey().users">
<div class="key-list__item-name" data-bind="text: $data"></div>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -54,18 +58,22 @@
</div> </div>
<div class="key-list-wrp" data-bind="visible: encryptKeys() && encryptKeys().length > 0"> <div class="key-list-wrp" data-bind="visible: encryptKeys() && encryptKeys().length > 0">
<div data-bind="foreach: encryptKeys"> <div data-bind="foreach: encryptKeys">
<div class="key-list__item"> <div class="key-list__item row-fluid">
<div class="key-list__item-delete" data-bind="click: $parent.deletePublickKey"> <div class="key-list__item-delete span1" data-bind="click: removable ? $parent.deletePublickKey : null, css: {'disabled': !removable}">
<i class="icon-trash"></i> <i class="icon-trash"></i>
</div> </div>
<div class="key-list__item-hash" data-bind="visible: !empty"> <div class="key-list__item-hash span3" data-bind="visible: !empty">
(<span data-bind="text: hash"></span>) (<span data-bind="text: hash"></span>)
</div> </div>
<div class="key-list__item-name" data-bind="css: {'empty': empty}"> <div class="span8">
<span data-bind="text: user"></span> <span class="key-list__item-names" data-bind="css: {'empty': empty}">
</div> <span data-bind="foreach: users">
<div class="key-list__item-error" data-bind="visible: empty"> <div class="key-list__item-name" data-bind="text: $data"></div>
(Public key not found) </span>
</span>
<span class="key-list__item-error" data-bind="visible: empty">
(Public key not found)
</span>
</div> </div>
</div> </div>
</div> </div>
@ -78,19 +86,20 @@
<input type="password" class="inputPassword input-block-level i18n" <input type="password" class="inputPassword input-block-level i18n"
autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-i18n="[placeholder]POPUPS_COMPOSE_OPEN_PGP/LABEL_PASSWORD" data-i18n="[placeholder]POPUPS_COMPOSE_OPEN_PGP/LABEL_PASSWORD"
data-bind="visible: sign, value: password, hasfocus: password.focus, onEnter: doCommand" /> data-bind="visible: sign, textInput: password, hasfocus: password.focus, onEnter: doCommand" />
</div> </div>
<div class="span7" data-bind="visible: encrypt() && 0 < publicKeysOptions().length"> <div class="span7" data-bind="visible: encrypt() && 0 < publicKeysOptions().length">
<div class="form-inline"> <div class="form-inline">
<select class="input-block-level" data-bind="options: publicKeysOptions, value: selectedPublicKey, <select class="input-block-level" data-bind="options: publicKeysOptions, value: selectedPublicKey,
optionsCaption: optionsCaption, optionsText: 'name', optionsValue: 'id', optionsCaption: optionsCaption, optionsText: 'name', optionsValue: 'id',
optionsAfterRender: defautOptionsAfterRender"></select> optionsAfterRender: addOptionClass"></select>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus"> <button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus,
enable: (sign() || encrypt()) && (!sign() || (sign() && password().length)) && (!encrypt() || encrypt() && encryptKeys().length > 0)">
<i data-bind="css: {'icon-key': !submitRequest(), 'icon-spinner animated': submitRequest()}"></i> <i data-bind="css: {'icon-key': !submitRequest(), 'icon-spinner animated': submitRequest()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-bind="visible: sign() && !encrypt()" data-i18n="POPUPS_COMPOSE_OPEN_PGP/BUTTON_SIGN"></span> <span class="i18n" data-bind="visible: sign() && !encrypt()" data-i18n="POPUPS_COMPOSE_OPEN_PGP/BUTTON_SIGN"></span>

View file

@ -18,11 +18,17 @@
<div class="controls key-list" data-bind="foreach: privateKeys" style="margin-top: 5px"> <div class="controls key-list" data-bind="foreach: privateKeys" style="margin-top: 5px">
<div class="key-list__item"> <div class="key-list__item">
<i class="key-list__item__radio icon-radio-unchecked"></i> <i class="key-list__item__radio icon-radio-unchecked"></i>
<span class="key-list__item__name"> <div class="key-list__item__names">
<span data-bind="text: user"></span> <span data-bind="foreach: users">
&nbsp; <div>
<span>[<span data-bind="text: id"></span>]</span> <span class="key-list__item__name">
</span> <span data-bind="text: $data"></span>
&nbsp;
<span>[<span data-bind="text: $parent.id"></span>]</span>
</span>
</div>
</span>
</div>
</div> </div>
<!--<div data-bind="json: $data"></div>--> <!--<div data-bind="json: $data"></div>-->
</div> </div>
@ -31,13 +37,13 @@
<label class="i18n control-label" data-i18n="POPUPS_MESSAGE_OPEN_PGP/LABEL_PASSWORD"></label> <label class="i18n control-label" data-i18n="POPUPS_MESSAGE_OPEN_PGP/LABEL_PASSWORD"></label>
<div class="controls"> <div class="controls">
<input type="password" class="inputPassword input-xlarge" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false" <input type="password" class="inputPassword input-xlarge" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"
data-bind="value: password, hasfocus: password.focus, onEnter: doCommand" /> data-bind="textInput: password, hasfocus: password.focus, onEnter: doCommand" />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus"> <button class="btn buttonDo" data-bind="command: doCommand, hasfocus: buttonFocus, enable: password().length > 0">
<i data-bind="css: {'icon-key': !submitRequest(), 'icon-spinner animated': submitRequest()}"></i> <i data-bind="css: {'icon-key': !submitRequest(), 'icon-spinner animated': submitRequest()}"></i>
&nbsp;&nbsp; &nbsp;&nbsp;
<span class="i18n" data-i18n="POPUPS_MESSAGE_OPEN_PGP/BUTTON_DECRYPT"></span> <span class="i18n" data-i18n="POPUPS_MESSAGE_OPEN_PGP/BUTTON_DECRYPT"></span>

View file

@ -33,7 +33,11 @@
<span class="open-pgp-key-img i18n" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE"> <span class="open-pgp-key-img i18n" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PRIVATE">
<i class="icon-lock"></i> <i class="icon-lock"></i>
</span> </span>
<span class="open-pgp-key-user" data-bind="text: user"></span> <span class="open-pgp-key-user">
<!-- ko foreach: users -->
<div class="open-pgp-key-user-address" data-bind="text: $data"></div>
<!-- /ko -->
</span>
<a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }"> <a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }">
<span class="i18n" data-i18n="SETTINGS_OPEN_PGP/DELETING_ASK"></span> <span class="i18n" data-i18n="SETTINGS_OPEN_PGP/DELETING_ASK"></span>
</a> </a>
@ -56,8 +60,13 @@
<span class="open-pgp-key-img i18n" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC"> <span class="open-pgp-key-img i18n" data-i18n="[title]SETTINGS_OPEN_PGP/TITLE_PUBLIC">
<i class="icon-key"></i> <i class="icon-key"></i>
</span> </span>
<span class="open-pgp-key-user" data-bind="text: user"></span> <span class="open-pgp-key-user">
(<span class="open-pgp-key-id" data-bind="text: id"></span>) <!-- ko foreach: users -->
<div class="open-pgp-key-user-address">
<span data-bind="text: $data"></span> (<span class="open-pgp-key-id" data-bind="text: $parent.id"></span>)
</div>
<!-- /ko -->
</span>
<a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }"> <a class="btn btn-small btn-small-small btn-danger pull-right button-delete button-delete-transitions" data-bind="css: {'delete-access': deleteAccess()}, click: function(oOpenPGP) { $root.deleteOpenPgpKey(oOpenPGP); }">
<span class="i18n" data-i18n="SETTINGS_OPEN_PGP/DELETING_ASK"></span> <span class="i18n" data-i18n="SETTINGS_OPEN_PGP/DELETING_ASK"></span>
</a> </a>