mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-07-13 03:27:39 +03:00
#89 Composer move sign/encrypt buttons from menu to view
This commit is contained in:
parent
26d3509e28
commit
5b051745fe
6 changed files with 152 additions and 174 deletions
|
|
@ -133,13 +133,13 @@ export const GnuPGUserStore = new class {
|
||||||
/**
|
/**
|
||||||
* Checks if verifying/encrypting a message is possible with given email addresses.
|
* Checks if verifying/encrypting a message is possible with given email addresses.
|
||||||
*/
|
*/
|
||||||
hasPublicKeyForEmails(recipients, all) {
|
hasPublicKeyForEmails(recipients) {
|
||||||
const count = recipients.length,
|
const count = recipients.length,
|
||||||
length = count ? recipients.filter(email =>
|
length = count ? recipients.filter(email =>
|
||||||
// (key.can_verify || key.can_encrypt) &&
|
// (key.can_verify || key.can_encrypt) &&
|
||||||
this.publicKeys.find(key => key.emails.includes(email))
|
this.publicKeys.find(key => key.emails.includes(email))
|
||||||
).length : 0;
|
).length : 0;
|
||||||
return length && (!all || length === count);
|
return length && length === count;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrivateKeyFor(query, sign) {
|
getPrivateKeyFor(query, sign) {
|
||||||
|
|
|
||||||
|
|
@ -145,12 +145,12 @@ export const OpenPGPUserStore = new class {
|
||||||
/**
|
/**
|
||||||
* Checks if verifying/encrypting a message is possible with given email addresses.
|
* Checks if verifying/encrypting a message is possible with given email addresses.
|
||||||
*/
|
*/
|
||||||
hasPublicKeyForEmails(recipients, all) {
|
hasPublicKeyForEmails(recipients) {
|
||||||
const count = recipients.length,
|
const count = recipients.length,
|
||||||
length = count ? recipients.filter(email =>
|
length = count ? recipients.filter(email =>
|
||||||
this.publicKeys().find(key => key.emails.includes(email))
|
this.publicKeys().find(key => key.emails.includes(email))
|
||||||
).length : 0;
|
).length : 0;
|
||||||
return length && (!all || length === count);
|
return length && length === count;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPrivateKeyFor(query/*, sign*/) {
|
getPrivateKeyFor(query/*, sign*/) {
|
||||||
|
|
|
||||||
|
|
@ -74,33 +74,28 @@ export const PgpUserStore = new class {
|
||||||
return 0 === text.trim().indexOf('-----BEGIN PGP MESSAGE-----');
|
return 0 === text.trim().indexOf('-----BEGIN PGP MESSAGE-----');
|
||||||
}
|
}
|
||||||
|
|
||||||
async mailvelopeHasPublicKeyForEmails(recipients, all) {
|
async mailvelopeHasPublicKeyForEmails(recipients) {
|
||||||
const
|
const
|
||||||
keyring = this.mailvelopeKeyring,
|
keyring = this.mailvelopeKeyring,
|
||||||
mailvelope = keyring && await keyring.validKeyForAddress(recipients)
|
mailvelope = keyring && await keyring.validKeyForAddress(recipients)
|
||||||
/*.then(LookupResult => Object.entries(LookupResult))*/,
|
/*.then(LookupResult => Object.entries(LookupResult))*/,
|
||||||
entries = mailvelope && Object.entries(mailvelope);
|
entries = mailvelope && Object.entries(mailvelope);
|
||||||
return !!(entries && (all ? (entries.filter(value => value[1]).length === recipients.length) : entries.length));
|
return entries && entries.filter(value => value[1]).length === recipients.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if verifying/encrypting a message is possible with given email addresses.
|
* Checks if verifying/encrypting a message is possible with given email addresses.
|
||||||
* Returns the first library that can.
|
* Returns the first library that can.
|
||||||
*/
|
*/
|
||||||
async hasPublicKeyForEmails(recipients, all) {
|
async hasPublicKeyForEmails(recipients) {
|
||||||
const count = recipients.length;
|
const count = recipients.length;
|
||||||
if (count) {
|
if (count) {
|
||||||
if (OpenPGPUserStore.hasPublicKeyForEmails(recipients, all)) {
|
if (OpenPGPUserStore.hasPublicKeyForEmails(recipients)) {
|
||||||
return 'openpgp';
|
return 'openpgp';
|
||||||
}
|
}
|
||||||
|
if (GnuPGUserStore.hasPublicKeyForEmails(recipients)) {
|
||||||
if (GnuPGUserStore.hasPublicKeyForEmails(recipients, all)) {
|
|
||||||
return 'gnupg';
|
return 'gnupg';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await this.mailvelopeHasPublicKeyForEmails(recipients, all)) {
|
|
||||||
return 'mailvelope';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -113,10 +113,6 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> div {
|
|
||||||
line-height: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
textarea, input[type="text"] {
|
textarea, input[type="text"] {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -134,8 +134,6 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
this.bSkipNextHide = false;
|
this.bSkipNextHide = false;
|
||||||
|
|
||||||
this.capaOpenPGP = PgpUserStore.isSupported();
|
|
||||||
|
|
||||||
this.addObservables({
|
this.addObservables({
|
||||||
identitiesDropdownTrigger: false,
|
identitiesDropdownTrigger: false,
|
||||||
|
|
||||||
|
|
@ -183,8 +181,6 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
|
|
||||||
viewArea: 'body',
|
viewArea: 'body',
|
||||||
|
|
||||||
composeUploaderButton: null, // initDom
|
|
||||||
composeUploaderDropPlace: null, // initDom
|
|
||||||
attacheMultipleAllowed: false,
|
attacheMultipleAllowed: false,
|
||||||
addAttachmentEnabled: false,
|
addAttachmentEnabled: false,
|
||||||
|
|
||||||
|
|
@ -283,7 +279,10 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
currentIdentity: value => {
|
currentIdentity: value => {
|
||||||
this.canPgpSign(false);
|
this.canPgpSign(false);
|
||||||
value && PgpUserStore.getKeyForSigning(value.email()).then(result => {
|
value && PgpUserStore.getKeyForSigning(value.email()).then(result => {
|
||||||
console.log({canPgpSign:result});
|
console.log({
|
||||||
|
email: value.email(),
|
||||||
|
canPgpSign:result
|
||||||
|
});
|
||||||
this.canPgpSign(result)
|
this.canPgpSign(result)
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
@ -1120,126 +1119,123 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onBuild() {
|
onBuild(dom) {
|
||||||
// initUploader
|
// initUploader
|
||||||
|
const oJua = new Jua({
|
||||||
|
action: serverRequest('Upload'),
|
||||||
|
clickElement: dom.querySelector('#composeUploadButton'),
|
||||||
|
dragAndDropElement: dom.querySelector('.b-attachment-place')
|
||||||
|
}),
|
||||||
|
uploadCache = {},
|
||||||
|
attachmentSizeLimit = pInt(SettingsGet('AttachmentLimit'));
|
||||||
|
|
||||||
if (this.composeUploaderButton()) {
|
oJua
|
||||||
const oJua = new Jua({
|
// .on('onLimitReached', (limit) => {
|
||||||
action: serverRequest('Upload'),
|
// alert(limit);
|
||||||
clickElement: this.composeUploaderButton(),
|
// })
|
||||||
dragAndDropElement: this.composeUploaderDropPlace()
|
.on('onDragEnter', () => {
|
||||||
}),
|
this.dragAndDropOver(true);
|
||||||
uploadCache = {},
|
})
|
||||||
attachmentSizeLimit = pInt(SettingsGet('AttachmentLimit'));
|
.on('onDragLeave', () => {
|
||||||
|
this.dragAndDropOver(false);
|
||||||
oJua
|
})
|
||||||
// .on('onLimitReached', (limit) => {
|
.on('onBodyDragEnter', () => {
|
||||||
// alert(limit);
|
this.attachmentsArea();
|
||||||
// })
|
this.dragAndDropVisible(true);
|
||||||
.on('onDragEnter', () => {
|
})
|
||||||
this.dragAndDropOver(true);
|
.on('onBodyDragLeave', () => {
|
||||||
})
|
this.dragAndDropVisible(false);
|
||||||
.on('onDragLeave', () => {
|
})
|
||||||
this.dragAndDropOver(false);
|
.on('onProgress', (id, loaded, total) => {
|
||||||
})
|
let item = uploadCache[id];
|
||||||
.on('onBodyDragEnter', () => {
|
if (!item) {
|
||||||
this.attachmentsArea();
|
item = this.getAttachmentById(id);
|
||||||
this.dragAndDropVisible(true);
|
|
||||||
})
|
|
||||||
.on('onBodyDragLeave', () => {
|
|
||||||
this.dragAndDropVisible(false);
|
|
||||||
})
|
|
||||||
.on('onProgress', (id, loaded, total) => {
|
|
||||||
let item = uploadCache[id];
|
|
||||||
if (!item) {
|
|
||||||
item = this.getAttachmentById(id);
|
|
||||||
if (item) {
|
|
||||||
uploadCache[id] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item) {
|
if (item) {
|
||||||
item.progress(Math.floor((loaded / total) * 100));
|
uploadCache[id] = item;
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.on('onSelect', (sId, oData) => {
|
|
||||||
this.dragAndDropOver(false);
|
|
||||||
|
|
||||||
const fileName = undefined === oData.FileName ? '' : oData.FileName.toString(),
|
if (item) {
|
||||||
size = pInt(oData.Size, null),
|
item.progress(Math.floor((loaded / total) * 100));
|
||||||
attachment = new ComposeAttachmentModel(sId, fileName, size);
|
}
|
||||||
|
})
|
||||||
|
.on('onSelect', (sId, oData) => {
|
||||||
|
this.dragAndDropOver(false);
|
||||||
|
|
||||||
attachment.cancel = this.cancelAttachmentHelper(sId, oJua);
|
const fileName = undefined === oData.FileName ? '' : oData.FileName.toString(),
|
||||||
|
size = pInt(oData.Size, null),
|
||||||
|
attachment = new ComposeAttachmentModel(sId, fileName, size);
|
||||||
|
|
||||||
this.attachments.push(attachment);
|
attachment.cancel = this.cancelAttachmentHelper(sId, oJua);
|
||||||
|
|
||||||
this.attachmentsArea();
|
this.attachments.push(attachment);
|
||||||
|
|
||||||
if (0 < size && 0 < attachmentSizeLimit && attachmentSizeLimit < size) {
|
this.attachmentsArea();
|
||||||
|
|
||||||
|
if (0 < size && 0 < attachmentSizeLimit && attachmentSizeLimit < size) {
|
||||||
|
attachment
|
||||||
|
.waiting(false)
|
||||||
|
.uploading(true)
|
||||||
|
.complete(true)
|
||||||
|
.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.on('onStart', (id) => {
|
||||||
|
let item = uploadCache[id];
|
||||||
|
if (!item) {
|
||||||
|
item = this.getAttachmentById(id);
|
||||||
|
if (item) {
|
||||||
|
uploadCache[id] = item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item) {
|
||||||
|
item
|
||||||
|
.waiting(false)
|
||||||
|
.uploading(true)
|
||||||
|
.complete(false);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('onComplete', (id, result, data) => {
|
||||||
|
const attachment = this.getAttachmentById(id),
|
||||||
|
response = (data && data.Result) || {},
|
||||||
|
errorCode = response.ErrorCode,
|
||||||
|
attachmentJson = result && response.Attachment;
|
||||||
|
|
||||||
|
let error = '';
|
||||||
|
if (null != errorCode) {
|
||||||
|
error = getUploadErrorDescByCode(errorCode);
|
||||||
|
} else if (!attachmentJson) {
|
||||||
|
error = i18n('UPLOAD/ERROR_UNKNOWN');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attachment) {
|
||||||
|
if (error) {
|
||||||
attachment
|
attachment
|
||||||
.waiting(false)
|
.waiting(false)
|
||||||
.uploading(true)
|
.uploading(false)
|
||||||
.complete(true)
|
.complete(true)
|
||||||
.error(i18n('UPLOAD/ERROR_FILE_IS_TOO_BIG'));
|
.error(error + '\n' + response.ErrorMessage);
|
||||||
|
} else if (attachmentJson) {
|
||||||
return false;
|
attachment
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
})
|
|
||||||
.on('onStart', (id) => {
|
|
||||||
let item = uploadCache[id];
|
|
||||||
if (!item) {
|
|
||||||
item = this.getAttachmentById(id);
|
|
||||||
if (item) {
|
|
||||||
uploadCache[id] = item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (item) {
|
|
||||||
item
|
|
||||||
.waiting(false)
|
.waiting(false)
|
||||||
.uploading(true)
|
.uploading(false)
|
||||||
.complete(false);
|
.complete(true);
|
||||||
}
|
|
||||||
})
|
|
||||||
.on('onComplete', (id, result, data) => {
|
|
||||||
const attachment = this.getAttachmentById(id),
|
|
||||||
response = (data && data.Result) || {},
|
|
||||||
errorCode = response.ErrorCode,
|
|
||||||
attachmentJson = result && response.Attachment;
|
|
||||||
|
|
||||||
let error = '';
|
attachment.initByUploadJson(attachmentJson);
|
||||||
if (null != errorCode) {
|
|
||||||
error = getUploadErrorDescByCode(errorCode);
|
|
||||||
} else if (!attachmentJson) {
|
|
||||||
error = i18n('UPLOAD/ERROR_UNKNOWN');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attachment) {
|
if (undefined === uploadCache[id]) {
|
||||||
if (error) {
|
delete uploadCache[id];
|
||||||
attachment
|
|
||||||
.waiting(false)
|
|
||||||
.uploading(false)
|
|
||||||
.complete(true)
|
|
||||||
.error(error + '\n' + response.ErrorMessage);
|
|
||||||
} else if (attachmentJson) {
|
|
||||||
attachment
|
|
||||||
.waiting(false)
|
|
||||||
.uploading(false)
|
|
||||||
.complete(true);
|
|
||||||
|
|
||||||
attachment.initByUploadJson(attachmentJson);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (undefined === uploadCache[id]) {
|
|
||||||
delete uploadCache[id];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
this.addAttachmentEnabled(true);
|
this.addAttachmentEnabled(true);
|
||||||
}
|
|
||||||
|
|
||||||
shortcuts.add('q', 'meta', Scope.Compose, ()=>false);
|
shortcuts.add('q', 'meta', Scope.Compose, ()=>false);
|
||||||
shortcuts.add('w', 'meta', Scope.Compose, ()=>false);
|
shortcuts.add('w', 'meta', Scope.Compose, ()=>false);
|
||||||
|
|
@ -1514,7 +1510,7 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
quotedMailHeader: '', // header to be added before the quoted mail
|
quotedMailHeader: '', // header to be added before the quoted mail
|
||||||
keepAttachments: false, // add attachments of quotedMail to editor (default: false)
|
keepAttachments: false, // add attachments of quotedMail to editor (default: false)
|
||||||
*/
|
*/
|
||||||
signMsg: confirm('Also sign this message?')
|
signMsg: this.pgpSign() || confirm('Sign this message?')
|
||||||
}).then(editor => this.mailvelope = editor);
|
}).then(editor => this.mailvelope = editor);
|
||||||
}
|
}
|
||||||
this.viewArea('mailvelope');
|
this.viewArea('mailvelope');
|
||||||
|
|
@ -1542,11 +1538,12 @@ class ComposePopupView extends AbstractViewPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
initPgpEncrypt() {
|
initPgpEncrypt() {
|
||||||
PgpUserStore.hasPublicKeyForEmails(this.allRecipients(), 1).then(result => {
|
const recipients = this.allRecipients();
|
||||||
|
PgpUserStore.hasPublicKeyForEmails(recipients).then(result => {
|
||||||
console.log({canPgpEncrypt:result});
|
console.log({canPgpEncrypt:result});
|
||||||
this.canPgpEncrypt(result);
|
this.canPgpEncrypt(result);
|
||||||
});
|
});
|
||||||
PgpUserStore.mailvelopeHasPublicKeyForEmails(this.allRecipients(), 1).then(result => {
|
PgpUserStore.mailvelopeHasPublicKeyForEmails(recipients).then(result => {
|
||||||
console.log({canMailvelope:result});
|
console.log({canMailvelope:result});
|
||||||
this.canMailvelope(result);
|
this.canMailvelope(result);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,7 @@
|
||||||
<a class="btn" data-i18n="GLOBAL/BCC"
|
<a class="btn" data-i18n="GLOBAL/BCC"
|
||||||
data-bind="visible: !showBcc(), click: function () { showBcc(true); }"></a>
|
data-bind="visible: !showBcc(), click: function () { showBcc(true); }"></a>
|
||||||
<a class="btn fontastic" data-bind="visible: allowContacts, command: contactsCommand" data-i18n="[title]GLOBAL/CONTACTS">📇</a>
|
<a class="btn fontastic" data-bind="visible: allowContacts, command: contactsCommand" data-i18n="[title]GLOBAL/CONTACTS">📇</a>
|
||||||
<div class="btn-group dropdown" data-bind="registerBootstrapDropdown: true" style="display:inline;vertical-align:top">
|
<div class="btn-group dropdown" data-bind="registerBootstrapDropdown: true" style="display:inline-block;vertical-align:top">
|
||||||
<a class="btn dropdown-toggle fontastic">☰</a>
|
<a class="btn dropdown-toggle fontastic">☰</a>
|
||||||
<ul class="dropdown-menu right-edge" role="menu">
|
<ul class="dropdown-menu right-edge" role="menu">
|
||||||
<li data-bind="click: function () { showBcc(!showBcc()); }">
|
<li data-bind="click: function () { showBcc(!showBcc()); }">
|
||||||
|
|
@ -79,20 +79,6 @@
|
||||||
<span data-i18n="COMPOSE/BUTTON_MARK_AS_IMPORTANT"></span>
|
<span data-i18n="COMPOSE/BUTTON_MARK_AS_IMPORTANT"></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<!-- ko if: capaOpenPGP -->
|
|
||||||
<li class="dividerbar" data-bind="click: togglePgpSign, css: {'disabled': !canPgpSign()}">
|
|
||||||
<a>
|
|
||||||
<i class="fontastic" data-bind="text: pgpSign() ? '✍' : '☐'"></i>
|
|
||||||
<span data-i18n="OPENPGP/LABEL_SIGN"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<li data-bind="click: togglePgpEncrypt, css: {'disabled': !canPgpEncrypt()}">
|
|
||||||
<a>
|
|
||||||
<i class="fontastic" data-bind="text: pgpEncrypt() ? '🔒' : '☐'"></i>
|
|
||||||
<span data-i18n="OPENPGP/LABEL_ENCRYPT"></span>
|
|
||||||
</a>
|
|
||||||
</li>
|
|
||||||
<!-- /ko -->
|
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -131,41 +117,45 @@
|
||||||
<input type="text" size="70" autocomplete="off" data-bind="textInput: subject" />
|
<input type="text" size="70" autocomplete="off" data-bind="textInput: subject" />
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td style="display:flex">
|
|
||||||
<div class="btn-group" style="flex-grow:1" id="area-toggle">
|
|
||||||
<button type="button" class="btn" data-bind="click: bodyArea,
|
|
||||||
css: { 'active': 'body' == viewArea() }">
|
|
||||||
<i class="icon-file-text"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn" data-bind="click: attachmentsArea,
|
|
||||||
css: { 'btn-danger': attachmentsInErrorCount(), 'active': 'attachments' == viewArea() },
|
|
||||||
tooltipErrorTip: attachmentsErrorTooltip">
|
|
||||||
<span data-bind="visible: attachmentsCount()">
|
|
||||||
<b data-bind="text: attachmentsCount"></b>
|
|
||||||
|
|
||||||
</span>
|
|
||||||
<i data-bind="css: { 'icon-attachment': !attachmentsInProcessCount(), 'icon-spinner': attachmentsInProcessCount()}"></i>
|
|
||||||
</button>
|
|
||||||
<button type="button" class="btn" data-bind="visible: canMailvelope, click: mailvelopeArea, css: { 'active': 'mailvelope' == viewArea() }">
|
|
||||||
<i class="mailvelope-icon"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div class="btn-group">
|
|
||||||
<a class="btn"
|
|
||||||
style="padding-left: 10px; padding-right: 10px;"
|
|
||||||
data-bind="visible: addAttachmentEnabled(), initDom: composeUploaderButton" data-i18n="[title]COMPOSE/ATTACH_FILES">
|
|
||||||
<sup style="font-weight: bold; font-size: 100%; top: -0.3em;">+</sup><i class="icon-attachment"></i>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
</table>
|
||||||
|
<div style="display:flex">
|
||||||
|
<div class="btn-group" style="flex-grow:1" id="area-toggle">
|
||||||
|
<button type="button" class="btn" data-bind="click: bodyArea,
|
||||||
|
css: { 'active': 'body' == viewArea() }">
|
||||||
|
<i class="icon-file-text"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn" data-bind="click: attachmentsArea,
|
||||||
|
css: { 'btn-danger': attachmentsInErrorCount(), 'active': 'attachments' == viewArea() },
|
||||||
|
tooltipErrorTip: attachmentsErrorTooltip">
|
||||||
|
<span data-bind="visible: attachmentsCount()">
|
||||||
|
<b data-bind="text: attachmentsCount"></b>
|
||||||
|
|
||||||
|
</span>
|
||||||
|
<i data-bind="css: { 'icon-attachment': !attachmentsInProcessCount(), 'icon-spinner': attachmentsInProcessCount()}"></i>
|
||||||
|
</button>
|
||||||
|
<button type="button" class="btn" data-bind="visible: canMailvelope, click: mailvelopeArea, css: { 'active': 'mailvelope' == viewArea() }">
|
||||||
|
<i class="mailvelope-icon"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group">
|
||||||
|
<a class="btn" data-bind="click: togglePgpSign, visible: canPgpSign, css: {'btn-success': pgpSign()}">
|
||||||
|
<i class="fontastic" data-bind="text: pgpSign() ? '☑' : '☐'"></i>
|
||||||
|
<span data-i18n="OPENPGP/LABEL_SIGN"></span>
|
||||||
|
</a>
|
||||||
|
<a class="btn" data-bind="click: togglePgpEncrypt, visible: canPgpEncrypt, css: {'btn-success': pgpEncrypt()}">
|
||||||
|
<i class="fontastic" data-bind="text: pgpEncrypt() ? '☑' : '☐'"></i>
|
||||||
|
<span data-i18n="OPENPGP/LABEL_ENCRYPT"></span>
|
||||||
|
</a>
|
||||||
|
<a class="btn fontastic" style="padding-left: 10px; padding-right: 10px;" id="composeUploadButton"
|
||||||
|
data-bind="visible: addAttachmentEnabled()" data-i18n="[title]COMPOSE/ATTACH_FILES">
|
||||||
|
⁺📎
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="attachmentAreaParent" data-bind="visible: 'attachments' == viewArea()">
|
<div class="attachmentAreaParent" data-bind="visible: 'attachments' == viewArea()">
|
||||||
<div class="b-attachment-place" data-bind="visible: addAttachmentEnabled() && dragAndDropVisible(), initDom: composeUploaderDropPlace, css: {'dragAndDropOver': dragAndDropOver}"
|
<div class="b-attachment-place" data-bind="visible: addAttachmentEnabled() && dragAndDropVisible(), css: {'dragAndDropOver': dragAndDropOver}"
|
||||||
data-i18n="COMPOSE/ATTACH_DROP_FILES_DESC"></div>
|
data-i18n="COMPOSE/ATTACH_DROP_FILES_DESC"></div>
|
||||||
<ul class="attachmentList" data-bind="foreach: attachments">
|
<ul class="attachmentList" data-bind="foreach: attachments">
|
||||||
<li class="attachmentItem" data-bind="attr: { 'title': title }, css: { 'waiting': waiting, 'error': '' !== error() }">
|
<li class="attachmentItem" data-bind="attr: { 'title': title }, css: { 'waiting': waiting, 'error': '' !== error() }">
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue