Merge branch 'master' into UserMailTemplates

# Conflicts:
#	dev/App/User.js
#	dev/Common/Enums.js
#	dev/Screen/User/Settings.js
#	dev/Settings/Admin/General.js
#	snappymail/v/0.0.0/app/libraries/RainLoop/Actions.php
#	snappymail/v/0.0.0/app/libraries/RainLoop/Actions/Admin.php
#	snappymail/v/0.0.0/app/libraries/RainLoop/Actions/User.php
This commit is contained in:
djmaze 2022-03-15 15:00:14 +01:00
commit f7f56b3789
794 changed files with 96444 additions and 74188 deletions

View file

@ -1,18 +1,14 @@
import ko from 'ko';
import { Settings } from 'Common/Globals';
import { fireEvent } from 'Common/Globals';
import { getNotification } from 'Common/Translator';
import Remote from 'Remote/Admin/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewCenter } from 'Knoin/AbstractViews';
import { AbstractViewLogin } from 'Knoin/AbstractViews';
class LoginAdminView extends AbstractViewCenter {
export class AdminLoginView extends AbstractViewLogin {
constructor() {
super('Admin/Login', 'AdminLogin');
this.hideSubmitButton = Settings.app('hideSubmitButton');
super('AdminLogin');
this.addObservables({
login: '',
@ -26,8 +22,6 @@ class LoginAdminView extends AbstractViewCenter {
submitError: ''
});
this.formError = ko.observable(false).extend({ falseTimeout: 500 });
this.addSubscribables({
login: () => this.loginError(false),
password: () => this.passwordError(false)
@ -39,42 +33,34 @@ class LoginAdminView extends AbstractViewCenter {
}
submitCommand(self, event) {
const valid = event.target.form.reportValidity(),
name = this.login().trim(),
pass = this.password();
let form = event.target.form,
data = new FormData(form),
valid = form.reportValidity() && fireEvent('sm-admin-login', data);
this.loginError(!name);
this.passwordError(!pass);
this.loginError(!this.login());
this.passwordError(!this.password());
this.formError(!valid);
if (valid) {
this.submitRequest(true);
Remote.adminLogin(
iError => {
Remote.request('AdminLogin',
(iError, oData) => {
fireEvent('sm-admin-login-response', {
error: iError,
data: oData
});
if (iError) {
this.submitRequest(false);
this.submitError(getNotification(iError));
} else {
rl.route.reload();
rl.setData(oData.Result);
}
},
name,
pass,
this.totp()
data
);
}
return valid;
}
onShow() {
rl.route.off();
}
submitForm() {
// return false;
}
}
export { LoginAdminView };

View file

@ -5,7 +5,7 @@ class MenuSettingsAdminView extends AbstractViewLeft {
* @param {?} screen
*/
constructor(screen) {
super('Admin/Settings/Menu', 'AdminMenu');
super('AdminMenu');
this.menu = screen.menu;
}

View file

@ -1,34 +1,16 @@
import ko from 'ko';
import Remote from 'Remote/Admin/Fetch';
import { PackageAdminStore } from 'Stores/Admin/Package';
import { AbstractViewRight } from 'Knoin/AbstractViews';
import { leftPanelDisabled, Settings } from 'Common/Globals';
import { leftPanelDisabled } from 'Common/Globals';
class PaneSettingsAdminView extends AbstractViewRight {
export class PaneSettingsAdminView extends AbstractViewRight {
constructor() {
super('Admin/Settings/Pane', 'AdminPane');
this.version = ko.observable(Settings.app('version'));
super('AdminPane');
this.leftPanelDisabled = leftPanelDisabled;
this.adminManLoadingVisibility = ko
.computed(() => PackageAdminStore.loading() ? 'visible' : 'hidden');
}
toggleLeft(item, event) {
event.preventDefault();
event.stopPropagation();
leftPanelDisabled(!leftPanelDisabled());
}
logoutClick() {
Remote.adminLogout(() => rl.logoutReload());
Remote.request('AdminLogout', () => rl.logoutReload());
}
}
export { PaneSettingsAdminView };

View file

@ -2,10 +2,9 @@ import { getNotification } from 'Common/Translator';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class AccountPopupView extends AbstractViewPopup {
export class AccountPopupView extends AbstractViewPopup {
constructor() {
super('Account');
@ -26,45 +25,42 @@ class AccountPopupView extends AbstractViewPopup {
this.email.subscribe(() => this.emailError(false));
this.password.subscribe(() => this.passwordError(false));
decorateKoCommands(this, {
addAccountCommand: self => !self.submitRequest()
});
}
addAccountCommand() {
this.emailError(!this.email().trim());
this.passwordError(!this.password().trim());
if (this.emailError() || this.passwordError()) {
return false;
submitForm() {
if (!this.submitRequest()) {
const email = this.email().trim(), pass = this.password();
this.emailError(!email);
this.passwordError(!pass);
if (!this.emailError() && pass) {
this.submitRequest(true);
Remote.request('AccountSetup', (iError, data) => {
this.submitRequest(false);
if (iError) {
this.submitError(getNotification(iError));
this.submitErrorAdditional((data && data.ErrorMessageAdditional) || '');
} else {
rl.app.accountsAndIdentities();
this.close();
}
}, {
Email: email,
Password: pass,
New: this.isNew() ? 1 : 0
}
);
}
}
this.submitRequest(true);
Remote.accountSetup(
(iError, data) => {
this.submitRequest(false);
if (iError) {
this.submitError(getNotification(iError));
this.submitErrorAdditional((data && data.ErrorMessageAdditional) || '');
} else {
rl.app.accountsAndIdentities();
this.cancelCommand();
}
},
this.email(),
this.password(),
this.isNew()
);
return true;
}
clearPopup() {
this.isNew(true);
this.email('');
onShow(account) {
if (account && account.isAdditional()) {
this.isNew(false);
this.email(account.email);
} else {
this.isNew(true);
this.email('');
}
this.password('');
this.emailError(false);
@ -74,14 +70,4 @@ class AccountPopupView extends AbstractViewPopup {
this.submitError('');
this.submitErrorAdditional('');
}
onShow(account) {
this.clearPopup();
if (account && account.canBeEdit()) {
this.isNew(false);
this.email(account.email);
}
}
}
export { AccountPopupView, AccountPopupView as default };

View file

@ -1,96 +0,0 @@
import { PgpUserStore } from 'Stores/User/Pgp';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class AddOpenPgpKeyPopupView extends AbstractViewPopup {
constructor() {
super('AddOpenPgpKey');
this.addObservables({
key: '',
keyError: false,
keyErrorMessage: ''
});
this.key.subscribe(() => {
this.keyError(false);
this.keyErrorMessage('');
});
decorateKoCommands(this, {
addOpenPgpKeyCommand: 1
});
}
addOpenPgpKeyCommand() {
// eslint-disable-next-line max-len
const reg = /[-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}[\s\S]+?[-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}/gi,
openpgpKeyring = PgpUserStore.openpgpKeyring;
let keyTrimmed = this.key().trim();
if (/[\n]/.test(keyTrimmed)) {
keyTrimmed = keyTrimmed.replace(/[\r]+/g, '').replace(/[\n]{2,}/g, '\n\n');
}
this.keyError(!keyTrimmed);
this.keyErrorMessage('');
if (!openpgpKeyring || this.keyError()) {
return false;
}
let match = null,
count = 30,
done = false;
do {
match = reg.exec(keyTrimmed);
if (match && 0 < count) {
if (match[0] && match[1] && match[2] && match[1] === match[2]) {
let err = null;
if ('PRIVATE' === match[1]) {
err = openpgpKeyring.privateKeys.importKey(match[0]);
} else if ('PUBLIC' === match[1]) {
err = openpgpKeyring.publicKeys.importKey(match[0]);
}
if (err) {
this.keyError(true);
this.keyErrorMessage(err && err[0] ? '' + err[0] : '');
console.log(err);
}
}
--count;
done = false;
} else {
done = true;
}
} while (!done);
openpgpKeyring.store();
rl.app.reloadOpenPgpKeys();
if (this.keyError()) {
return false;
}
this.cancelCommand();
return true;
}
clearPopup() {
this.key('');
this.keyError(false);
this.keyErrorMessage('');
}
onShow() {
this.clearPopup();
}
}
export { AddOpenPgpKeyPopupView, AddOpenPgpKeyPopupView as default };

View file

@ -1,13 +1,13 @@
import ko from 'ko';
import { koComputable } from 'External/ko';
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
import { MessageUserStore } from 'Stores/User/Message';
import { MessagelistUserStore } from 'Stores/User/Messagelist';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { FolderUserStore } from 'Stores/User/Folder';
class AdvancedSearchPopupView extends AbstractViewPopup {
export class AdvancedSearchPopupView extends AbstractViewPopup {
constructor() {
super('AdvancedSearch');
@ -17,15 +17,18 @@ class AdvancedSearchPopupView extends AbstractViewPopup {
subject: '',
text: '',
selectedDateValue: -1,
selectedTreeValue: '',
hasAttachment: false,
starred: false,
unseen: false
});
let prefix = 'SEARCH/LABEL_ADV_DATE_';
this.selectedDates = ko.computed(() => {
this.showMultisearch = koComputable(() => FolderUserStore.hasCapability('MULTISEARCH'));
this.selectedDates = koComputable(() => {
translatorTrigger();
let prefix = 'SEARCH/LABEL_ADV_DATE_';
return [
{ id: -1, name: i18n(prefix + 'ALL') },
{ id: 3, name: i18n(prefix + '3_DAYS') },
@ -37,18 +40,24 @@ class AdvancedSearchPopupView extends AbstractViewPopup {
];
});
decorateKoCommands(this, {
searchCommand: 1
this.selectedTree = koComputable(() => {
translatorTrigger();
let prefix = 'SEARCH/LABEL_ADV_SUBFOLDERS_';
return [
{ id: '', name: i18n(prefix + 'NONE') },
{ id: 'subtree-one', name: i18n(prefix + 'SUBTREE_ONE') },
{ id: 'subtree', name: i18n(prefix + 'SUBTREE') }
];
});
}
searchCommand() {
submitForm() {
const search = this.buildSearchString();
if (search) {
MessageUserStore.mainMessageListSearch(search);
MessagelistUserStore.mainSearch(search);
}
this.cancelCommand();
this.close();
}
parseSearchStringValue(search) {
@ -69,68 +78,38 @@ class AdvancedSearchPopupView extends AbstractViewPopup {
});
}
buildSearchStringValue(value) {
if (value.includes(' ')) {
value = '"' + value + '"';
}
return value;
}
buildSearchString() {
const result = [],
from_ = this.from().trim(),
to = this.to().trim(),
subject = this.subject().trim(),
text = this.text().trim(),
isPart = [],
hasPart = [];
if (from_) {
result.push('from:' + this.buildSearchStringValue(from_));
}
if (to) {
result.push('to:' + this.buildSearchStringValue(to));
}
if (subject) {
result.push('subject:' + this.buildSearchStringValue(subject));
}
if (this.hasAttachment()) {
hasPart.push('attachment');
}
if (this.unseen()) {
isPart.push('unseen');
}
if (this.starred()) {
isPart.push('flagged');
}
if (hasPart.length) {
result.push('has:' + hasPart.join(','));
}
if (isPart.length) {
result.push('is:' + isPart.join(','));
}
const
data = new FormData(),
append = (key, value) => value.length && data.append(key, value);
append('from', this.from().trim());
append('to', this.to().trim());
append('subject', this.subject().trim());
append('text', this.text().trim());
append('in', this.selectedTreeValue());
if (-1 < this.selectedDateValue()) {
let d = new Date();
d.setDate(d.getDate() - this.selectedDateValue());
result.push('date:' + d.format('Y.m.d') + '/');
append('date', d.format('Y.m.d') + '/');
}
if (text) {
result.push('text:' + this.buildSearchStringValue(text));
let result = new URLSearchParams(data).toString();
if (this.hasAttachment()) {
result += '&attachment';
}
if (this.unseen()) {
result += '&unseen';
}
if (this.starred()) {
result += '&flagged';
}
return result.join(' ').trim();
return result.replace(/^&+/, '');
}
clearPopup() {
onShow(search) {
this.from('');
this.to('');
this.subject('');
@ -140,12 +119,7 @@ class AdvancedSearchPopupView extends AbstractViewPopup {
this.hasAttachment(false);
this.starred(false);
this.unseen(false);
}
onShow(search) {
this.clearPopup();
this.parseSearchStringValue(search);
}
}
export { AdvancedSearchPopupView, AdvancedSearchPopupView as default };

View file

@ -1,84 +1,68 @@
import { Scope } from 'Common/Enums';
import { i18n } from 'Common/Translator';
import { isFunction } from 'Common/Utils';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class AskPopupView extends AbstractViewPopup {
export class AskPopupView extends AbstractViewPopup {
constructor() {
super('Ask');
this.addObservables({
askDesc: '',
yesButton: '',
noButton: ''
noButton: '',
passphrase: '',
askPass: false
});
this.fYesAction = null;
this.fNoAction = null;
this.bFocusYesOnShow = true;
this.bDisabeCloseOnEsc = true;
}
clearPopup() {
this.askDesc('');
this.yesButton(i18n('POPUPS_ASK/BUTTON_YES'));
this.noButton(i18n('POPUPS_ASK/BUTTON_NO'));
this.fYesAction = null;
this.fNoAction = null;
this.focusOnShow = true;
}
yesClick() {
this.cancelCommand();
this.close();
isFunction(this.fYesAction) && this.fYesAction.call(null);
isFunction(this.fYesAction) && this.fYesAction();
}
noClick() {
this.cancelCommand();
this.close();
isFunction(this.fNoAction) && this.fNoAction.call(null);
isFunction(this.fNoAction) && this.fNoAction();
}
/**
* @param {string} sAskDesc
* @param {Function=} fYesFunc
* @param {Function=} fNoFunc
* @param {string=} sYesButton
* @param {string=} sNoButton
* @param {boolean=} bFocusYesOnShow = true
* @param {boolean=} focusOnShow = true
* @returns {void}
*/
onShow(askDesc, fYesFunc = null, fNoFunc = null, yesButton = '', noButton = '', isFocusYesOnShow = true) {
this.clearPopup();
this.fYesAction = fYesFunc || null;
this.fNoAction = fNoFunc || null;
this.askDesc(askDesc || '');
if (yesButton) {
this.yesButton(yesButton);
}
if (noButton) {
this.noButton(noButton);
}
this.bFocusYesOnShow = !!isFocusYesOnShow;
onShow(sAskDesc, fYesFunc = null, fNoFunc = null, focusOnShow = true, askPass = false, btnText = '') {
this.askDesc(sAskDesc || '');
this.askPass(askPass);
this.passphrase('');
this.yesButton(i18n(btnText || 'POPUPS_ASK/BUTTON_YES'));
this.noButton(i18n(askPass ? 'GLOBAL/CANCEL' : 'POPUPS_ASK/BUTTON_NO'));
this.fYesAction = fYesFunc;
this.fNoAction = fNoFunc;
this.focusOnShow = focusOnShow ? (askPass ? 'input[type="password"]' : '.buttonYes') : '';
}
onShowWithDelay() {
if (this.bFocusYesOnShow) {
this.querySelector('.buttonYes').focus();
}
afterShow() {
this.focusOnShow && this.querySelector(this.focusOnShow).focus();
}
onClose() {
this.noClick();
return false;
}
onBuild() {
// shortcuts.add('tab', 'shift', Scope.Ask, () => {
shortcuts.add('tab,arrowright,arrowleft', '', Scope.Ask, () => {
// shortcuts.add('tab', 'shift', 'Ask', () => {
shortcuts.add('tab,arrowright,arrowleft', '', 'Ask', () => {
let btn = this.querySelector('.buttonYes');
if (btn.matches(':focus')) {
btn = this.querySelector('.buttonNo');
@ -86,12 +70,18 @@ class AskPopupView extends AbstractViewPopup {
btn.focus();
return false;
});
shortcuts.add('escape', '', Scope.Ask, () => {
this.noClick();
return false;
});
}
}
export { AskPopupView, AskPopupView as default };
AskPopupView.password = function(sAskDesc, btnText) {
return new Promise(resolve => {
this.showModal([
sAskDesc,
() => resolve(this.__vm.passphrase()),
() => resolve(null),
true,
true,
btnText
]);
});
}

File diff suppressed because it is too large Load diff

View file

@ -1,385 +0,0 @@
import ko from 'ko';
import { pString, defaultOptionsAfterRender } from 'Common/Utils';
import { Scope } from 'Common/Enums';
import { i18n } from 'Common/Translator';
import { PgpUserStore } from 'Stores/User/Pgp';
import { EmailModel } from 'Model/Email';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
const KEY_NAME_SUBSTR = -8,
i18nPGP = (key, params) => i18n('PGP_NOTIFICATIONS/' + key, params);
class ComposeOpenPgpPopupView extends AbstractViewPopup {
constructor() {
super('ComposeOpenPgp');
this.publicKeysOptionsCaption = i18nPGP('ADD_A_PUBLICK_KEY');
this.privateKeysOptionsCaption = i18nPGP('SELECT_A_PRIVATE_KEY');
this.addObservables({
notification: '',
sign: false,
encrypt: false,
password: '',
text: '',
selectedPrivateKey: null,
selectedPublicKey: null,
signKey: null,
submitRequest: false
});
this.encryptKeys = ko.observableArray();
this.addComputables({
encryptKeysView: () => this.encryptKeys.map(oKey => (oKey ? oKey.key : null)).filter(v => v),
privateKeysOptions: () => {
const opts = PgpUserStore.openpgpkeysPrivate().map(oKey => {
if (this.signKey() && this.signKey().key.id === oKey.id) {
return null;
}
return oKey.users.map(user => ({
id: oKey.guid,
name: '(' + oKey.id.substr(KEY_NAME_SUBSTR).toUpperCase() + ') ' + user,
key: oKey
}));
});
return opts.flat().filter(v => v);
},
publicKeysOptions: () => {
const opts = PgpUserStore.openpgpkeysPublic().map(oKey => {
if (this.encryptKeysView().includes(oKey)) {
return null;
}
return oKey.users.map(user => ({
id: oKey.guid,
name: '(' + oKey.id.substr(KEY_NAME_SUBSTR).toUpperCase() + ') ' + user,
key: oKey
}));
});
return opts.flat().filter(v => v);
}
});
this.resultCallback = null;
this.selectedPrivateKey.subscribe((value) => {
if (value) {
this.selectCommand();
this.updateCommand();
}
});
this.selectedPublicKey.subscribe((value) => {
if (value) {
this.addCommand();
}
});
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
this.deletePublickKey = this.deletePublickKey.bind(this);
decorateKoCommands(this, {
doCommand: self => !self.submitRequest() && (self.sign() || self.encrypt()),
selectCommand: 1,
addCommand: 1,
updateCommand: 1,
});
}
doCommand() {
let result = true,
privateKey = null,
aPublicKeys = [];
this.submitRequest(true);
if (result && this.sign()) {
if (!this.signKey()) {
this.notification(i18nPGP('NO_PRIVATE_KEY_FOUND'));
result = false;
} else if (!this.signKey().key) {
this.notification(
i18nPGP('NO_PRIVATE_KEY_FOUND_FOR', {
EMAIL: this.signKey().email
})
);
result = false;
}
if (result) {
const privateKeys = this.signKey().key.getNativeKeys();
privateKey = privateKeys[0] || null;
try {
if (privateKey) {
privateKey.decrypt(pString(this.password()));
}
} catch (e) {
privateKey = null;
}
if (!privateKey) {
this.notification(i18nPGP('NO_PRIVATE_KEY_FOUND'));
result = false;
}
}
}
if (result && this.encrypt()) {
if (this.encryptKeys.length) {
aPublicKeys = [];
this.encryptKeys.forEach(oKey => {
if (oKey && oKey.key) {
aPublicKeys = aPublicKeys.concat(oKey.key.getNativeKeys().flat(Infinity).filter(v => v));
} else if (oKey && oKey.email) {
this.notification(
i18nPGP('NO_PUBLIC_KEYS_FOUND_FOR', {
EMAIL: oKey.email
})
);
result = false;
}
});
if (result && (!aPublicKeys.length || this.encryptKeys.length !== aPublicKeys.length)) {
result = false;
}
} else {
this.notification(i18nPGP('NO_PUBLIC_KEYS_FOUND'));
result = false;
}
}
if (result && this.resultCallback) {
setTimeout(() => {
let pgpPromise = null;
try {
if (aPublicKeys.length) {
if (privateKey) {
pgpPromise = PgpUserStore.openpgp.encrypt({
data: this.text(),
publicKeys: aPublicKeys,
privateKeys: [privateKey]
});
} else {
pgpPromise = PgpUserStore.openpgp.encrypt({
data: this.text(),
publicKeys: aPublicKeys
});
}
} else if (privateKey) {
pgpPromise = PgpUserStore.openpgp.sign({
data: this.text(),
privateKeys: [privateKey]
});
}
} catch (e) {
console.log(e);
this.notification(
i18nPGP('PGP_ERROR', {
ERROR: '' + e
})
);
}
if (pgpPromise) {
try {
pgpPromise
.then((mData) => {
this.resultCallback(mData.data);
this.cancelCommand();
})
.catch((e) => {
this.notification(
i18nPGP('PGP_ERROR', {
ERROR: '' + e
})
);
});
} catch (e) {
this.notification(
i18nPGP('PGP_ERROR', {ERROR: '' + e})
);
}
}
this.submitRequest(false);
}, 20);
} else {
this.submitRequest(false);
}
return result;
}
selectCommand() {
const keyId = this.selectedPrivateKey(),
option = keyId ? this.privateKeysOptions().find(item => item && keyId === item.id) : null;
if (option) {
this.signKey({
empty: !option.key,
selected: ko.observable(!!option.key),
users: option.key.users,
hash: option.key.id.substr(KEY_NAME_SUBSTR).toUpperCase(),
key: option.key
});
}
}
addCommand() {
const keyId = this.selectedPublicKey(),
option = keyId ? this.publicKeysOptions().find(item => item && keyId === item.id) : null;
if (option) {
this.encryptKeys.push({
empty: !option.key,
selected: ko.observable(!!option.key),
removable: ko.observable(!this.sign() || !this.signKey() || this.signKey().key.id !== option.key.id),
users: option.key.users,
hash: option.key.id.substr(KEY_NAME_SUBSTR).toUpperCase(),
key: option.key
});
}
}
updateCommand() {
this.encryptKeys.forEach(oKey =>
oKey.removable(!this.sign() || !this.signKey() || this.signKey().key.id !== oKey.key.id)
);
}
deletePublickKey(publicKey) {
this.encryptKeys.remove(publicKey);
}
clearPopup() {
this.notification('');
this.sign(false);
this.encrypt(false);
this.password('');
this.signKey(null);
this.encryptKeys([]);
this.text('');
this.resultCallback = null;
}
onBuild() {
// shortcuts.add('tab', 'shift', Scope.ComposeOpenPgp, () => {
shortcuts.add('tab', '', Scope.ComposeOpenPgp, () => {
let btn = this.querySelector('.inputPassword');
if (btn.matches(':focus')) {
btn = this.querySelector('.buttonDo');
}
btn.focus();
return false;
});
}
onHideWithDelay() {
this.clearPopup();
}
onShowWithDelay() {
this.querySelector(this.sign() ? '.inputPassword' : '.buttonDo').focus();
}
onShow(fCallback, sText, identity, sTo, sCc, sBcc) {
this.clearPopup();
let rec = [],
emailLine = '';
const email = new EmailModel();
this.resultCallback = fCallback;
if (sTo) {
rec.push(sTo);
}
if (sCc) {
rec.push(sCc);
}
if (sBcc) {
rec.push(sBcc);
}
rec = rec.join(', ').split(',');
rec = rec.map(value => {
email.clear();
email.parse(value.trim());
return email.email || false;
}).filter(v => v);
if (identity && identity.email()) {
emailLine = identity.email();
rec.unshift(emailLine);
const keys = PgpUserStore.findAllPrivateKeysByEmailNotNative(emailLine);
if (keys && keys[0]) {
this.signKey({
users: keys[0].users || [emailLine],
hash: keys[0].id.substr(KEY_NAME_SUBSTR).toUpperCase(),
key: keys[0]
});
}
}
if (this.signKey()) {
this.sign(true);
}
if (rec.length) {
this.encryptKeys(
rec.map(recEmail => {
const keys = PgpUserStore.findAllPublicKeysByEmailNotNative(recEmail);
return keys
? keys.map(publicKey => ({
empty: !publicKey,
selected: ko.observable(!!publicKey),
removable: ko.observable(
!this.sign() || !this.signKey() || this.signKey().key.id !== publicKey.id
),
users: publicKey ? publicKey.users || [recEmail] : [recEmail],
hash: publicKey ? publicKey.id.substr(KEY_NAME_SUBSTR).toUpperCase() : '',
key: publicKey
}))
: [];
}).flat().validUnique(encryptKey => encryptKey.hash)
);
if (this.encryptKeys.length) {
this.encrypt(true);
}
}
this.text(sText);
}
}
export { ComposeOpenPgpPopupView, ComposeOpenPgpPopupView as default };

View file

@ -1,14 +1,10 @@
import ko from 'ko';
import {
SaveSettingsStep,
Scope
} from 'Common/Enums';
import { koArrayWithDestroy } from 'External/ko';
import { SaveSettingsStep } from 'Common/Enums';
import { ComposeType } from 'Common/EnumsUser';
import { registerShortcut } from 'Common/Globals';
import { arrayLength, pInt } from 'Common/Utils';
import { delegateRunOnDestroy, computedPaginatorHelper, showMessageComposer } from 'Common/UtilsUser';
import { download, computedPaginatorHelper, showMessageComposer } from 'Common/UtilsUser';
import { Selector } from 'Common/Selector';
import { serverRequestRaw, serverRequest } from 'Common/Links';
@ -23,14 +19,18 @@ import { EmailModel } from 'Model/Email';
import { ContactModel } from 'Model/Contact';
import { ContactPropertyModel, ContactPropertyType } from 'Model/ContactProperty';
import { decorateKoCommands, hideScreenPopup } from 'Knoin/Knoin';
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
const CONTACTS_PER_PAGE = 50,
import { AskPopupView } from 'View/Popup/Ask';
const
CONTACTS_PER_PAGE = 50,
ScopeContacts = 'Contacts',
propertyIsMail = prop => prop.isType(ContactPropertyType.Email),
propertyIsName = prop => prop.isType(ContactPropertyType.FirstName) || prop.isType(ContactPropertyType.LastName);
class ContactsPopupView extends AbstractViewPopup {
export class ContactsPopupView extends AbstractViewPopup {
constructor() {
super('Contacts');
@ -66,7 +66,7 @@ class ContactsPopupView extends AbstractViewPopup {
this.contacts = ContactUserStore;
this.viewProperties = ko.observableArray();
this.viewProperties = koArrayWithDestroy();
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
@ -136,17 +136,16 @@ class ContactsPopupView extends AbstractViewPopup {
});
decorateKoCommands(this, {
newCommand: 1,
// close: self => !self.watchDirty(),
deleteCommand: self => 0 < self.contactsCheckedOrSelected().length,
newMessageCommand: self => 0 < self.contactsCheckedOrSelected().length,
clearCommand: 1,
saveCommand: self => !self.viewSaving() && !self.viewReadOnly()
&& (self.contactHasValidName() || self.viewProperties.find(prop => propertyIsMail(prop) && prop.isValid())),
syncCommand: self => !self.contacts.syncing() && !self.contacts.importing()
});
}
newCommand() {
newContact() {
this.populateViewContact(null);
this.currentContact(null);
}
@ -183,7 +182,7 @@ class ContactsPopupView extends AbstractViewPopup {
if (arrayLength(aE)) {
this.bBackToCompose = false;
hideScreenPopup(ContactsPopupView);
this.close();
switch (this.sLastComposeFocusedField) {
case 'cc':
@ -207,7 +206,7 @@ class ContactsPopupView extends AbstractViewPopup {
return true;
}
clearCommand() {
clearSearch() {
this.search('');
}
@ -217,7 +216,7 @@ class ContactsPopupView extends AbstractViewPopup {
const requestUid = Jua.randomId();
Remote.contactSave(
Remote.request('ContactSave',
(iError, oData) => {
let res = false;
this.viewSaving(false);
@ -243,15 +242,16 @@ class ContactsPopupView extends AbstractViewPopup {
this.watchDirty(false);
setTimeout(() => this.viewSaveTrigger(SaveSettingsStep.Idle), 1000);
}
},
requestUid,
this.viewID(),
this.viewProperties.map(oItem => oItem.toJSON())
}, {
RequestUid: requestUid,
Uid: this.viewID(),
Properties: this.viewProperties.map(oItem => oItem.toJSON())
}
);
}
syncCommand() {
rl.app.contactsSync(iError => {
ContactUserStore.sync(iError => {
iError && alert(getNotification(iError));
this.reloadContactList(true);
@ -316,11 +316,11 @@ class ContactsPopupView extends AbstractViewPopup {
}
exportVcf() {
rl.app.download(serverRequestRaw('ContactsVcf'));
download(serverRequestRaw('ContactsVcf'), 'contacts.vcf');
}
exportCsv() {
rl.app.download(serverRequestRaw('ContactsCsv'));
download(serverRequestRaw('ContactsCsv'), 'contacts.csv');
}
removeCheckedOrSelectedContactsFromList() {
@ -345,31 +345,30 @@ class ContactsPopupView extends AbstractViewPopup {
}
setTimeout(() => {
contacts.forEach(contact => {
ContactUserStore.remove(contact);
delegateRunOnDestroy(contact);
});
contacts.forEach(contact => ContactUserStore.remove(contact));
}, 500);
}
}
deleteSelectedContacts() {
if (this.contactsCheckedOrSelected().length) {
Remote.contactsDelete((iError, oData) => {
if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) {
this.reloadContactList(this.bDropPageAfterDelete);
} else {
setTimeout(() => this.reloadContactList(this.bDropPageAfterDelete), 500);
Remote.request('ContactsDelete',
(iError, oData) => {
if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) {
this.reloadContactList(this.bDropPageAfterDelete);
} else {
setTimeout(() => this.reloadContactList(this.bDropPageAfterDelete), 500);
}
}, {
Uids: this.contactsCheckedOrSelectedUids().join(',')
}
}, this.contactsCheckedOrSelectedUids());
);
this.removeCheckedOrSelectedContactsFromList();
}
}
removeProperty(oProp) {
this.viewProperties.remove(oProp);
delegateRunOnDestroy(oProp);
}
/**
@ -393,7 +392,6 @@ class ContactsPopupView extends AbstractViewPopup {
this.viewID(id);
// delegateRunOnDestroy(this.viewProperties());
// this.viewProperties([]);
this.viewProperties(contact.properties);
@ -415,7 +413,7 @@ class ContactsPopupView extends AbstractViewPopup {
}
ContactUserStore.loading(true);
Remote.contacts(
Remote.request('Contacts',
(iError, data) => {
let count = 0,
list = [];
@ -432,27 +430,31 @@ class ContactsPopupView extends AbstractViewPopup {
this.contactsCount(count);
delegateRunOnDestroy(ContactUserStore());
ContactUserStore(list);
ContactUserStore.loading(false);
this.viewClearSearch(!!this.search());
},
offset,
CONTACTS_PER_PAGE,
this.search()
{
Offset: offset,
Limit: CONTACTS_PER_PAGE,
Search: this.search()
},
null,
'',
['Contacts']
);
}
onBuild(dom) {
this.selector.init(dom.querySelector('.b-list-content'), Scope.Contacts);
this.selector.init(dom.querySelector('.b-list-content'), ScopeContacts);
shortcuts.add('delete', '', Scope.Contacts, () => {
registerShortcut('delete', '', ScopeContacts, () => {
this.deleteCommand();
return false;
});
shortcuts.add('c,w', '', Scope.Contacts, () => {
registerShortcut('c,w', '', ScopeContacts, () => {
this.newMessageCommand();
return false;
});
@ -460,7 +462,7 @@ class ContactsPopupView extends AbstractViewPopup {
const self = this;
dom.addEventListener('click', event => {
let el = event.target.closestWithin('.e-paginator .e-page', dom);
let el = event.target.closestWithin('.e-paginator a', dom);
if (el && ko.dataFor(el)) {
self.contactsPage(pInt(ko.dataFor(el).value));
self.reloadContactList();
@ -491,23 +493,28 @@ class ContactsPopupView extends AbstractViewPopup {
}
}
onClose() {
if (this.watchDirty() && AskPopupView.hidden()) {
showScreenPopup(AskPopupView, [
i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'),
() => this.close()
]);
return false;
}
}
onShow(bBackToCompose, sLastComposeFocusedField) {
this.bBackToCompose = !!bBackToCompose;
this.sLastComposeFocusedField = sLastComposeFocusedField;
rl.route.off();
this.reloadContactList(true);
}
onHide() {
rl.route.on();
this.currentContact(null);
this.emptySelection(true);
this.search('');
this.contactsCount(0);
delegateRunOnDestroy(ContactUserStore());
ContactUserStore([]);
this.sLastComposeFocusedField = '';
@ -519,5 +526,3 @@ class ContactsPopupView extends AbstractViewPopup {
}
}
}
export { ContactsPopupView, ContactsPopupView as default };

View file

@ -1,4 +1,4 @@
import { pInt, pString } from 'Common/Utils';
import { pInt, forEachObjectEntry } from 'Common/Utils';
import { i18n, getNotification } from 'Common/Translator';
import Remote from 'Remote/Admin/Fetch';
@ -8,7 +8,26 @@ import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { DomainAdminStore } from 'Stores/Admin/Domain';
class DomainPopupView extends AbstractViewPopup {
const domainToParams = oDomain => ({
Name: oDomain.name(),
IncHost: oDomain.imapHost(),
IncPort: oDomain.imapPort(),
IncSecure: oDomain.imapSecure(),
UseSieve: oDomain.useSieve() ? 1 : 0,
SieveHost: oDomain.sieveHost(),
SievePort: oDomain.sievePort(),
SieveSecure: oDomain.sieveSecure(),
OutHost: oDomain.smtpHost(),
OutPort: oDomain.smtpPort(),
OutSecure: oDomain.smtpSecure(),
OutAuth: oDomain.smtpAuth() ? 1 : 0,
OutUsePhpMail: oDomain.smtpPhpMail() ? 1 : 0
});
export class DomainPopupView extends AbstractViewPopup {
constructor() {
super('Domain');
@ -27,9 +46,9 @@ class DomainPopupView extends AbstractViewPopup {
testingSieveErrorDesc: '',
testingSmtpErrorDesc: '',
imapServerFocus: false,
sieveServerFocus: false,
smtpServerFocus: false,
imapHostFocus: false,
sieveHostFocus: false,
smtpHostFocus: false,
});
this.addComputables({
@ -42,7 +61,7 @@ class DomainPopupView extends AbstractViewPopup {
if (this.edit()) {
result = i18n('POPUPS_DOMAIN/TITLE_EDIT_DOMAIN', { NAME: name });
if (aliasName) {
result += ' ' + aliasName;
result += ' ' + aliasName;
}
} else {
result = name
@ -64,10 +83,10 @@ class DomainPopupView extends AbstractViewPopup {
return (
this.name() &&
this.imapServer() &&
this.imapHost() &&
this.imapPort() &&
(useSieve ? this.sieveServer() && this.sievePort() : true) &&
((this.smtpServer() && this.smtpPort()) || usePhpMail)
(useSieve ? this.sieveHost() && this.sievePort() : true) &&
((this.smtpHost() && this.smtpPort()) || usePhpMail)
);
},
@ -81,28 +100,28 @@ class DomainPopupView extends AbstractViewPopup {
testingSmtpError: value => value || this.testingSmtpErrorDesc(''),
// smart form improvements
imapServerFocus: value =>
value && this.name() && !this.imapServer() && this.imapServer(this.name().replace(/[.]?[*][.]?/g, '')),
imapHostFocus: value =>
value && this.name() && !this.imapHost() && this.imapHost(this.name().replace(/[.]?[*][.]?/g, '')),
sieveServerFocus: value =>
value && this.imapServer() && !this.sieveServer() && this.sieveServer(this.imapServer()),
sieveHostFocus: value =>
value && this.imapHost() && !this.sieveHost() && this.sieveHost(this.imapHost()),
smtpServerFocus: value => value && this.imapServer() && !this.smtpServer()
&& this.smtpServer(this.imapServer().replace(/imap/gi, 'smtp')),
smtpHostFocus: value => value && this.imapHost() && !this.smtpHost()
&& this.smtpHost(this.imapHost().replace(/imap/gi, 'smtp')),
imapSecure: value => {
if (this.enableSmartPorts()) {
const port = pInt(this.imapPort());
switch (pString(value)) {
case '0':
case '2':
switch (pInt(value)) {
case 0:
case 2:
if (993 === port) {
this.imapPort('143');
this.imapPort(143);
}
break;
case '1':
case 1:
if (143 === port) {
this.imapPort('993');
this.imapPort(993);
}
break;
// no default
@ -113,20 +132,20 @@ class DomainPopupView extends AbstractViewPopup {
smtpSecure: value => {
if (this.enableSmartPorts()) {
const port = pInt(this.smtpPort());
switch (pString(value)) {
case '0':
switch (pInt(value)) {
case 0:
if (465 === port || 587 === port) {
this.smtpPort('25');
this.smtpPort(25);
}
break;
case '1':
case 1:
if (25 === port || 587 === port) {
this.smtpPort('465');
this.smtpPort(465);
}
break;
case '2':
case 2:
if (25 === port || 465 === port) {
this.smtpPort('587');
this.smtpPort(587);
}
break;
// no default
@ -143,20 +162,26 @@ class DomainPopupView extends AbstractViewPopup {
createOrAddCommand() {
this.saving(true);
Remote.createOrUpdateDomain(
Remote.request('AdminDomainSave',
this.onDomainCreateOrSaveResponse.bind(this),
this
Object.assign(domainToParams(this), {
Create: this.edit() ? 0 : 1,
IncShortLogin: this.imapShortLogin() ? 1 : 0,
OutShortLogin: this.smtpShortLogin() ? 1 : 0,
OutSetSender: this.smtpSetSender() ? 1 : 0,
WhiteList: this.whiteList()
})
);
}
testConnectionCommand() {
this.testingDone(false);
this.testingImapError(false);
this.testingSieveError(false);
this.testingSmtpError(false);
this.clearTesting(false);
this.testing(true);
Remote.testConnectionForDomain(
Remote.request('AdminDomainTest',
(iError, oData) => {
this.testing(false);
if (iError) {
@ -185,7 +210,7 @@ class DomainPopupView extends AbstractViewPopup {
}
}
},
this
domainToParams(this)
);
}
@ -195,7 +220,7 @@ class DomainPopupView extends AbstractViewPopup {
this.savingError(getNotification(iError));
} else {
DomainAdminStore.fetch();
this.closeCommand();
this.close();
}
}
@ -209,56 +234,36 @@ class DomainPopupView extends AbstractViewPopup {
onShow(oDomain) {
this.saving(false);
this.clearTesting();
this.clearForm();
if (oDomain) {
this.enableSmartPorts(false);
this.edit(true);
this.name(oDomain.Name);
this.imapServer(oDomain.IncHost);
this.imapPort('' + pInt(oDomain.IncPort));
this.imapSecure(oDomain.IncSecure);
this.imapShortLogin(!!oDomain.IncShortLogin);
this.useSieve(!!oDomain.UseSieve);
this.sieveServer(oDomain.SieveHost);
this.sievePort('' + pInt(oDomain.SievePort));
this.sieveSecure(oDomain.SieveSecure);
this.smtpServer(oDomain.OutHost);
this.smtpPort('' + pInt(oDomain.OutPort));
this.smtpSecure(oDomain.OutSecure);
this.smtpShortLogin(!!oDomain.OutShortLogin);
this.smtpAuth(!!oDomain.OutAuth);
this.smtpSetSender(!!oDomain.OutSetSender);
this.smtpPhpMail(!!oDomain.OutUsePhpMail);
this.whiteList(oDomain.WhiteList);
this.aliasName(oDomain.AliasName);
forEachObjectEntry(oDomain, (key, value) => this[key] && this[key](value));
this.enableSmartPorts(true);
}
}
getDefaults() {
return {
enableSmartPorts: false,
savingError: '',
name: '',
imapServer: '',
imapPort: '143',
imapHost: '',
imapPort: 143,
imapSecure: 0,
imapShortLogin: false,
useSieve: false,
sieveServer: '',
sievePort: '4190',
sieveHost: '',
sievePort: 4190,
sieveSecure: 0,
smtpServer: '',
smtpPort: '25',
smtpHost: '',
smtpPort: 25,
smtpSecure: 0,
smtpShortLogin: false,
smtpAuth: true,
@ -266,17 +271,13 @@ class DomainPopupView extends AbstractViewPopup {
smtpPhpMail: false,
whiteList: '',
aliasName: '',
enableSmartPorts: false
aliasName: ''
};
}
clearForm() {
this.edit(false);
Object.entries(this.getDefaults()).forEach(([key, value]) => this[key](value));
forEachObjectEntry(this.getDefaults(), (key, value) => this[key](value));
this.enableSmartPorts(true);
}
}
export { DomainPopupView, DomainPopupView as default };

View file

@ -7,7 +7,7 @@ import Remote from 'Remote/Admin/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class DomainAliasPopupView extends AbstractViewPopup {
export class DomainAliasPopupView extends AbstractViewPopup {
constructor() {
super('DomainAlias');
@ -35,15 +35,19 @@ class DomainAliasPopupView extends AbstractViewPopup {
createCommand() {
this.saving(true);
Remote.createDomainAlias(iError => {
this.saving(false);
if (iError) {
this.savingError(getNotification(iError));
} else {
DomainAdminStore.fetch();
this.closeCommand();
}
}, this.name(), this.alias());
Remote.request('AdminDomainAliasSave',
iError => {
this.saving(false);
if (iError) {
this.savingError(getNotification(iError));
} else {
DomainAdminStore.fetch();
this.close();
}
}, {
Name: this.name(),
Alias: this.alias()
});
}
onShow() {
@ -59,5 +63,3 @@ class DomainAliasPopupView extends AbstractViewPopup {
this.alias('');
}
}
export { DomainAliasPopupView, DomainAliasPopupView as default };

View file

@ -1,179 +0,0 @@
import ko from 'ko';
import { FilterAction } from 'Model/Filter';
import { FilterConditionField, FilterConditionType } from 'Model/FilterCondition';
import { defaultOptionsAfterRender } from 'Common/Utils';
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
import { FolderUserStore } from 'Stores/User/Folder';
import { SieveUserStore } from 'Stores/User/Sieve';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { folderListOptionsBuilder } from 'Common/UtilsUser';
class FilterPopupView extends AbstractViewPopup {
constructor() {
super('Filter');
this.addObservables({
isNew: true,
filter: null,
allowMarkAsRead: false,
selectedFolderValue: ''
});
this.fTrueCallback = null;
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
this.folderSelectList = ko.computed(() =>
folderListOptionsBuilder(
[FolderUserStore.sieveAllowFileintoInbox ? '' : 'INBOX'],
[['', '']],
item => item ? item.localName() : ''
)
);
this.selectedFolderValue.subscribe(() => this.filter() && this.filter().actionValueError(false));
['actionTypeOptions','fieldOptions','typeOptions','typeOptionsSize','typeOptionsBody'].forEach(
key => this[key] = ko.observableArray()
);
initOnStartOrLangChange(this.populateOptions.bind(this));
SieveUserStore.capa.subscribe(this.populateOptions, this);
decorateKoCommands(this, {
saveFilterCommand: 1
});
}
saveFilterCommand() {
if (this.filter()) {
if (FilterAction.MoveTo === this.filter().actionType()) {
this.filter().actionValue(this.selectedFolderValue());
}
if (!this.filter().verify()) {
return false;
}
this.fTrueCallback && this.fTrueCallback(this.filter());
this.modalVisibility() && this.closeCommand();
}
return true;
}
populateOptions() {
this.actionTypeOptions([]);
let i18nFilter = key => i18n('POPUPS_FILTER/SELECT_' + key);
this.fieldOptions([
{ id: FilterConditionField.From, name: i18n('GLOBAL/FROM') },
{ id: FilterConditionField.Recipient, name: i18nFilter('FIELD_RECIPIENTS') },
{ id: FilterConditionField.Subject, name: i18n('GLOBAL/SUBJECT') },
{ id: FilterConditionField.Size, name: i18nFilter('FIELD_SIZE') },
{ id: FilterConditionField.Header, name: i18nFilter('FIELD_HEADER') }
]);
this.typeOptions([
{ id: FilterConditionType.Contains, name: i18nFilter('TYPE_CONTAINS') },
{ id: FilterConditionType.NotContains, name: i18nFilter('TYPE_NOT_CONTAINS') },
{ id: FilterConditionType.EqualTo, name: i18nFilter('TYPE_EQUAL_TO') },
{ id: FilterConditionType.NotEqualTo, name: i18nFilter('TYPE_NOT_EQUAL_TO') }
]);
// this.actionTypeOptions.push({id: FilterAction.None,
// name: i18n('GLOBAL/NONE')});
const modules = SieveUserStore.capa;
if (modules) {
if (modules.includes('imap4flags')) {
this.allowMarkAsRead(true);
}
if (modules.includes('fileinto')) {
this.actionTypeOptions.push({
id: FilterAction.MoveTo,
name: i18nFilter('ACTION_MOVE_TO')
});
this.actionTypeOptions.push({
id: FilterAction.Forward,
name: i18nFilter('ACTION_FORWARD_TO')
});
}
if (modules.includes('reject')) {
this.actionTypeOptions.push({ id: FilterAction.Reject, name: i18nFilter('ACTION_REJECT') });
}
if (modules.includes('vacation')) {
this.actionTypeOptions.push({
id: FilterAction.Vacation,
name: i18nFilter('ACTION_VACATION_MESSAGE')
});
}
if (modules.includes('body')) {
this.fieldOptions.push({ id: FilterConditionField.Body, name: i18nFilter('FIELD_BODY') });
}
if (modules.includes('regex')) {
this.typeOptions.push({ id: FilterConditionType.Regex, name: 'Regex' });
}
}
this.actionTypeOptions.push({ id: FilterAction.Discard, name: i18nFilter('ACTION_DISCARD') });
this.typeOptionsSize([
{ id: FilterConditionType.Over, name: i18nFilter('TYPE_OVER') },
{ id: FilterConditionType.Under, name: i18nFilter('TYPE_UNDER') }
]);
this.typeOptionsBody([
{ id: FilterConditionType.Text, name: i18nFilter('TYPE_TEXT') },
{ id: FilterConditionType.Raw, name: i18nFilter('TYPE_RAW') }
]);
}
removeCondition(oConditionToDelete) {
if (this.filter()) {
this.filter().removeCondition(oConditionToDelete);
}
}
clearPopup() {
this.isNew(true);
this.fTrueCallback = null;
this.filter(null);
}
onShow(oFilter, fTrueCallback, bEdit) {
this.clearPopup();
this.fTrueCallback = fTrueCallback;
this.filter(oFilter);
if (oFilter) {
this.selectedFolderValue(oFilter.actionValue());
}
this.isNew(!bEdit);
if (!bEdit && oFilter) {
oFilter.nameFocused(true);
}
}
onShowWithDelay() {
this.isNew() && this.filter() && this.filter().nameFocused(true);
}
}
export { FilterPopupView, FilterPopupView as default };

View file

@ -2,13 +2,14 @@ import { i18n, getNotification } from 'Common/Translator';
import { setFolderHash } from 'Common/Cache';
import { MessageUserStore } from 'Stores/User/Message';
import { MessagelistUserStore } from 'Stores/User/Messagelist';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class FolderClearPopupView extends AbstractViewPopup {
export class FolderClearPopupView extends AbstractViewPopup {
constructor() {
super('FolderClear');
@ -19,17 +20,11 @@ class FolderClearPopupView extends AbstractViewPopup {
});
this.addComputables({
folderFullNameForClear: () => {
dangerDescHtml: () => {
const folder = this.selectedFolder();
return folder ? folder.printableFullName() : '';
},
folderNameForClear: () => {
const folder = this.selectedFolder();
return folder ? folder.localName() : '';
},
dangerDescHtml: () => i18n('POPUPS_CLEAR_FOLDER/DANGER_DESC_HTML_1', { FOLDER: this.folderNameForClear() })
// return i18n('POPUPS_CLEAR_FOLDER/DANGER_DESC_HTML_1', { FOLDER: folder ? folder.fullName.replace(folder.delimiter, ' / ') : '' });
return i18n('POPUPS_CLEAR_FOLDER/DANGER_DESC_HTML_1', { FOLDER: folder ? folder.localName() : '' });
}
});
decorateKoCommands(this, {
@ -44,38 +39,31 @@ class FolderClearPopupView extends AbstractViewPopup {
const folderToClear = this.selectedFolder();
if (folderToClear) {
MessageUserStore.message(null);
MessageUserStore.list([]);
MessagelistUserStore([]);
this.clearingProcess(true);
folderToClear.messageCountAll(0);
folderToClear.messageCountUnread(0);
setFolderHash(folderToClear.fullNameRaw, '');
setFolderHash(folderToClear.fullName, '');
Remote.folderClear(iError => {
Remote.request('FolderClear', iError => {
this.clearingProcess(false);
if (iError) {
this.clearingError(getNotification(iError));
} else {
rl.app.reloadMessageList(true);
this.cancelCommand();
MessagelistUserStore.reload(true);
this.close();
}
}, folderToClear.fullNameRaw);
}, {
Folder: folderToClear.fullName
});
}
}
clearPopup() {
this.clearingProcess(false);
this.selectedFolder(null);
}
onShow(folder) {
this.clearPopup();
if (folder) {
this.selectedFolder(folder);
}
this.clearingProcess(false);
this.selectedFolder(folder || null);
}
}
export { FolderClearPopupView, FolderClearPopupView as default };

View file

@ -1,73 +1,86 @@
import ko from 'ko';
import { koComputable } from 'External/ko';
import { Notification } from 'Common/Enums';
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { defaultOptionsAfterRender } from 'Common/Utils';
import { folderListOptionsBuilder } from 'Common/UtilsUser';
import { folderListOptionsBuilder, sortFolders } from 'Common/Folders';
import { getNotification } from 'Common/Translator';
import { FolderUserStore } from 'Stores/User/Folder';
import { SettingsUserStore } from 'Stores/User/Settings';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class FolderCreatePopupView extends AbstractViewPopup {
import { setFolder, getFolderFromCacheList } from 'Common/Cache';
import { FolderModel } from 'Model/FolderCollection';
export class FolderCreatePopupView extends AbstractViewPopup {
constructor() {
super('FolderCreate');
this.addObservables({
folderName: '',
folderSubscribe: SettingsUserStore.hideUnsubscribed(),
selectedParentValue: UNUSED_OPTION_VALUE
});
this.parentFolderSelectList = ko.computed(() =>
this.parentFolderSelectList = koComputable(() =>
folderListOptionsBuilder(
[],
[['', '']],
oItem =>
oItem ? (oItem.isSystemFolder() ? oItem.name() + ' ' + oItem.manageFolderSystemName() : oItem.name()) : '',
FolderUserStore.namespace
? item => FolderUserStore.namespace !== item.fullNameRaw.substr(0, FolderUserStore.namespace.length)
? item => FolderUserStore.namespace !== item.fullName.slice(0, FolderUserStore.namespace.length)
: null,
true
)
);
this.defaultOptionsAfterRender = defaultOptionsAfterRender;
decorateKoCommands(this, {
createFolderCommand: self => self.simpleFolderNameValidation(self.folderName())
});
}
createFolderCommand() {
let parentFolderName = this.selectedParentValue();
if (!parentFolderName && 1 < FolderUserStore.namespace.length) {
parentFolderName = FolderUserStore.namespace.substr(0, FolderUserStore.namespace.length - 1);
submitForm() {
if (/^[^\\/]+$/g.test(this.folderName())) {
let parentFolderName = this.selectedParentValue();
if (!parentFolderName && 1 < FolderUserStore.namespace.length) {
parentFolderName = FolderUserStore.namespace.slice(0, FolderUserStore.namespace.length - 1);
}
Remote.abort('Folders').post('FolderCreate', FolderUserStore.foldersCreating, {
Folder: this.folderName(),
Parent: parentFolderName,
Subscribe: this.folderSubscribe() ? 1 : 0
})
.then(
data => {
const folder = getFolderFromCacheList(parentFolderName),
subFolder = FolderModel.reviveFromJson(data.Result),
folders = (folder ? folder.subFolders : FolderUserStore.folderList);
setFolder(subFolder);
folders.push(subFolder);
sortFolders(folders);
/*
var collator = new Intl.Collator(undefined, {numeric: true, sensitivity: 'base'});
console.log((folder ? folder.subFolders : FolderUserStore.folderList).sort(collator.compare));
*/
},
error => {
FolderUserStore.folderListError(
getNotification(error.code, '', Notification.CantCreateFolder)
+ '.\n' + error.message);
}
);
this.close();
}
rl.app.foldersPromisesActionHelper(
Remote.folderCreate(this.folderName(), parentFolderName),
Notification.CantCreateFolder
);
this.cancelCommand();
}
simpleFolderNameValidation(sName) {
return /^[^\\/]+$/g.test(sName);
}
clearPopup() {
this.folderName('');
this.selectedParentValue('');
}
onShow() {
this.clearPopup();
this.folderName('');
this.selectedParentValue('');
}
}
export { FolderCreatePopupView, FolderCreatePopupView as default };

View file

@ -1,41 +1,34 @@
import ko from 'ko';
import { koComputable, addSubscribablesTo } from 'External/ko';
import { SetSystemFoldersNotification } from 'Common/EnumsUser';
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { defaultOptionsAfterRender, addSubscribablesTo } from 'Common/Utils';
import { folderListOptionsBuilder } from 'Common/UtilsUser';
import { initOnStartOrLangChange, i18n } from 'Common/Translator';
import { defaultOptionsAfterRender } from 'Common/Utils';
import { folderListOptionsBuilder } from 'Common/Folders';
import { i18n } from 'Common/Translator';
import { FolderUserStore } from 'Stores/User/Folder';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class FolderSystemPopupView extends AbstractViewPopup {
export class FolderSystemPopupView extends AbstractViewPopup {
constructor() {
super('FolderSystem');
this.sChooseOnText = '';
this.sUnuseText = '';
initOnStartOrLangChange(() => {
this.sChooseOnText = i18n('POPUPS_SYSTEM_FOLDERS/SELECT_CHOOSE_ONE');
this.sUnuseText = i18n('POPUPS_SYSTEM_FOLDERS/SELECT_UNUSE_NAME');
});
this.notification = ko.observable('');
this.folderSelectList = ko.computed(() =>
this.folderSelectList = koComputable(() =>
folderListOptionsBuilder(
FolderUserStore.folderListSystemNames(),
[
['', this.sChooseOnText],
[UNUSED_OPTION_VALUE, this.sUnuseText]
['', i18n('POPUPS_SYSTEM_FOLDERS/SELECT_CHOOSE_ONE')],
[UNUSED_OPTION_VALUE, i18n('POPUPS_SYSTEM_FOLDERS/SELECT_UNUSE_NAME')]
]
)
);
this.sentFolder = FolderUserStore.sentFolder;
this.draftFolder = FolderUserStore.draftFolder;
this.draftsFolder = FolderUserStore.draftsFolder;
this.spamFolder = FolderUserStore.spamFolder;
this.trashFolder = FolderUserStore.trashFolder;
this.archiveFolder = FolderUserStore.archiveFolder;
@ -44,7 +37,7 @@ class FolderSystemPopupView extends AbstractViewPopup {
addSubscribablesTo(FolderUserStore, {
sentFolder: fSaveSystemFolders,
draftFolder: fSaveSystemFolders,
draftsFolder: fSaveSystemFolders,
spamFolder: fSaveSystemFolders,
trashFolder: fSaveSystemFolders,
archiveFolder: fSaveSystemFolders
@ -80,5 +73,3 @@ class FolderSystemPopupView extends AbstractViewPopup {
this.notification(notification);
}
}
export { FolderSystemPopupView, FolderSystemPopupView as default };

View file

@ -2,12 +2,11 @@ import { getNotification } from 'Common/Translator';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
const reEmail = /^[^@\s]+@[^@\s]+$/;
class IdentityPopupView extends AbstractViewPopup {
export class IdentityPopupView extends AbstractViewPopup {
constructor() {
super('Identity');
@ -60,60 +59,57 @@ class IdentityPopupView extends AbstractViewPopup {
this.replyTo.valueHasMutated();
this.bcc.valueHasMutated();
*/
decorateKoCommands(this, {
addOrEditIdentityCommand: self => !self.submitRequest()
});
}
addOrEditIdentityCommand() {
if (this.signature && this.signature.__fetchEditorValue) {
this.signature.__fetchEditorValue();
}
if (!this.emailHasError()) {
this.emailHasError(!this.email().trim());
}
if (this.emailHasError()) {
if (!this.owner()) {
this.emailFocused(true);
submitForm() {
if (!this.submitRequest()) {
if (this.signature && this.signature.__fetchEditorValue) {
this.signature.__fetchEditorValue();
}
return false;
}
if (!this.emailHasError()) {
this.emailHasError(!this.email().trim());
}
if (this.replyToHasError()) {
this.replyToFocused(true);
return false;
}
if (this.bccHasError()) {
this.bccFocused(true);
return false;
}
this.submitRequest(true);
Remote.identityUpdate(
iError => {
this.submitRequest(false);
if (iError) {
this.submitError(getNotification(iError));
} else {
rl.app.accountsAndIdentities();
this.cancelCommand();
if (this.emailHasError()) {
if (!this.owner()) {
this.emailFocused(true);
}
},
this.id,
this.email(),
this.name(),
this.replyTo(),
this.bcc(),
this.signature(),
this.signatureInsertBefore()
);
return true;
return;
}
if (this.replyToHasError()) {
this.replyToFocused(true);
return;
}
if (this.bccHasError()) {
this.bccFocused(true);
return;
}
this.submitRequest(true);
Remote.request('IdentityUpdate', iError => {
this.submitRequest(false);
if (iError) {
this.submitError(getNotification(iError));
} else {
rl.app.accountsAndIdentities();
this.close();
}
}, {
Id: this.id,
Email: this.email(),
Name: this.name(),
ReplyTo: this.replyTo(),
Bcc: this.bcc(),
Signature: this.signature(),
SignatureInsertBefore: this.signatureInsertBefore() ? 1 : 0
}
);
}
}
clearPopup() {
@ -162,13 +158,11 @@ class IdentityPopupView extends AbstractViewPopup {
}
}
onShowWithDelay() {
afterShow() {
this.owner() || this.emailFocused(true);
}
onHideWithDelay() {
afterHide() {
this.clearPopup();
}
}
export { IdentityPopupView, IdentityPopupView as default };

View file

@ -1,5 +1,4 @@
import { Scope } from 'Common/Enums';
import { addShortcut } from 'Common/Globals';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
export class KeyboardShortcutsHelpPopupView extends AbstractViewPopup {
@ -12,9 +11,9 @@ export class KeyboardShortcutsHelpPopupView extends AbstractViewPopup {
const tabs = dom.querySelectorAll('.tabs input'),
last = tabs.length - 1;
// shortcuts.add('tab', 'shift',
shortcuts.add('tab,arrowleft,arrowright', '',
Scope.KeyboardShortcutsHelp,
// addShortcut('tab', 'shift',
addShortcut('tab,arrowleft,arrowright', '',
'KeyboardShortcutsHelp',
event => {
let next = 0;
tabs.forEach((node, index) => {

View file

@ -1,10 +1,11 @@
import ko from 'ko';
import { koComputable } from 'External/ko';
import { convertLangName } from 'Common/Translator';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class LanguagesPopupView extends AbstractViewPopup {
export class LanguagesPopupView extends AbstractViewPopup {
constructor() {
super('Languages');
@ -13,7 +14,7 @@ class LanguagesPopupView extends AbstractViewPopup {
this.langs = ko.observableArray();
this.languages = ko.computed(() => {
this.languages = koComputable(() => {
const userLanguage = this.userLanguage();
return this.langs.map(language => ({
key: language,
@ -35,7 +36,7 @@ class LanguagesPopupView extends AbstractViewPopup {
this.languages().forEach(item => item.selected(item.key === currentLang));
}
onBeforeShow() {
beforeShow() {
this.fLang = null;
this.userLanguage('');
@ -51,8 +52,6 @@ class LanguagesPopupView extends AbstractViewPopup {
changeLanguage(lang) {
this.fLang && this.fLang(lang);
this.cancelCommand();
this.close();
}
}
export { LanguagesPopupView, LanguagesPopupView as default };

View file

@ -1,121 +0,0 @@
import ko from 'ko';
import { pString } from 'Common/Utils';
import { Scope } from 'Common/Enums';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class MessageOpenPgpPopupView extends AbstractViewPopup {
constructor() {
super('MessageOpenPgp');
this.addObservables({
notification: '',
selectedKey: null,
password: '',
submitRequest: false
});
this.privateKeys = ko.observableArray();
this.resultCallback = null;
decorateKoCommands(this, {
doCommand: self => !self.submitRequest()
});
}
doCommand() {
this.submitRequest(true);
setTimeout(() => {
let privateKey = null;
try {
if (this.resultCallback && this.selectedKey()) {
const privateKeys = this.selectedKey().getNativeKeys();
privateKey = privateKeys && privateKeys[0] ? privateKeys[0] : null;
if (privateKey) {
try {
if (!privateKey.decrypt(pString(this.password()))) {
console.log('Error: Private key cannot be decrypted');
privateKey = null;
}
} catch (e) {
console.log(e);
privateKey = null;
}
} else {
console.log('Error: Private key cannot be found');
}
}
} catch (e) {
console.log(e);
privateKey = null;
}
this.submitRequest(false);
this.cancelCommand();
this.resultCallback(privateKey);
}, 100);
}
clearPopup() {
this.notification('');
this.password('');
this.selectedKey(false);
this.submitRequest(false);
this.resultCallback = null;
this.privateKeys([]);
}
onBuild(oDom) {
// shortcuts.add('tab', 'shift', Scope.MessageOpenPgp, () => {
shortcuts.add('tab', '', Scope.MessageOpenPgp, () => {
let btn = this.querySelector('.inputPassword');
if (btn.matches(':focus')) {
btn = this.querySelector('.buttonDo');
}
btn.focus();
return false;
});
const self = this;
oDom.addEventListener('click', event => {
const el = event.target.closestWithin('.key-list__item', oDom);
if (el) {
oDom.querySelectorAll('.key-list__item .key-list__item__radio').forEach(node =>
node.textContent = el === node ? '⦿' : '○'
);
self.selectedKey(ko.dataFor(el));
// this.querySelector('.inputPassword').focus();
}
});
}
onHideWithDelay() {
this.clearPopup();
}
onShow(fCallback, privateKeys) {
this.clearPopup();
this.resultCallback = fCallback;
this.privateKeys(privateKeys);
if (this.viewModelDom) {
const el = this.viewModelDom.querySelector('.key-list__item');
el && el.click();
}
}
}
export { MessageOpenPgpPopupView, MessageOpenPgpPopupView as default };

View file

@ -1,105 +0,0 @@
import { pInt } from 'Common/Utils';
import { PgpUserStore } from 'Stores/User/Pgp';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class NewOpenPgpKeyPopupView extends AbstractViewPopup {
constructor() {
super('NewOpenPgpKey');
this.addObservables({
email: '',
emailError: false,
name: '',
password: '',
keyBitLength: 4096,
submitRequest: false,
submitError: ''
});
this.email.subscribe(() => this.emailError(false));
decorateKoCommands(this, {
generateOpenPgpKeyCommand: 1
});
}
generateOpenPgpKeyCommand() {
const userId = {},
openpgpKeyring = PgpUserStore.openpgpKeyring;
this.emailError(!this.email().trim());
if (!openpgpKeyring || this.emailError()) {
return false;
}
userId.email = this.email();
if (this.name()) {
userId.name = this.name();
}
this.submitRequest(true);
this.submitError('');
setTimeout(() => {
try {
PgpUserStore.openpgp
.generateKey({
userIds: [userId],
numBits: pInt(this.keyBitLength()),
passphrase: this.password().trim()
})
.then((keyPair) => {
this.submitRequest(false);
if (keyPair && keyPair.privateKeyArmored) {
openpgpKeyring.privateKeys.importKey(keyPair.privateKeyArmored);
openpgpKeyring.publicKeys.importKey(keyPair.publicKeyArmored);
openpgpKeyring.store();
rl.app.reloadOpenPgpKeys();
this.cancelCommand();
}
})
.catch((e) => {
this.submitRequest(false);
this.showError(e);
});
} catch (e) {
this.submitRequest(false);
this.showError(e);
}
}, 100);
return true;
}
showError(e) {
console.log(e);
if (e && e.message) {
this.submitError(e.message);
}
}
clearPopup() {
this.name('');
this.password('');
this.email('');
this.emailError(false);
this.keyBitLength(4096);
this.submitError('');
}
onShow() {
this.clearPopup();
}
}
export { NewOpenPgpKeyPopupView, NewOpenPgpKeyPopupView as default };

View file

@ -0,0 +1,109 @@
//import { pInt } from 'Common/Utils';
import { GnuPGUserStore } from 'Stores/User/GnuPG';
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
import { IdentityUserStore } from 'Stores/User/Identity';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { SettingsCapa } from 'Common/Globals';
export class OpenPgpGeneratePopupView extends AbstractViewPopup {
constructor() {
super('OpenPgpGenerate');
this.identities = IdentityUserStore;
this.addObservables({
email: '',
emailError: false,
name: '',
password: '',
keyType: 'ECC',
submitRequest: false,
submitError: '',
backupPublicKey: true,
backupPrivateKey: false,
saveGnuPGPublic: true,
saveGnuPGPrivate: false
});
this.canGnuPG = SettingsCapa('GnuPG');
this.email.subscribe(() => this.emailError(false));
}
submitForm() {
const type = this.keyType().toLowerCase(),
userId = {
name: this.name(),
email: this.email()
},
cfg = {
type: type,
userIDs: [userId],
passphrase: this.password().trim()
// format: 'armored' // output key format, defaults to 'armored' (other options: 'binary' or 'object')
}
/*
if ('ecc' === type) {
cfg.curve = 'curve25519';
} else {
cfg.rsaBits = pInt(this.keyBitLength());
}
*/
this.emailError(!this.email().trim());
if (this.emailError()) {
return;
}
this.submitRequest(true);
this.submitError('');
openpgp.generateKey(cfg).then(keyPair => {
if (keyPair) {
const fn = () => {
this.submitRequest(false);
this.close();
};
OpenPGPUserStore.storeKeyPair(keyPair);
keyPair.onServer = (this.backupPublicKey() ? 1 : 0) + (this.backupPrivateKey() ? 2 : 0);
keyPair.inGnuPG = (this.saveGnuPGPublic() ? 1 : 0) + (this.saveGnuPGPrivate() ? 2 : 0);
if (keyPair.onServer || keyPair.inGnuPG) {
if (!this.backupPrivateKey() && !this.saveGnuPGPrivate()) {
delete keyPair.privateKey;
}
GnuPGUserStore.storeKeyPair(keyPair, fn);
} else {
fn();
}
}
})
.catch((e) => {
this.submitRequest(false);
this.showError(e);
});
}
showError(e) {
console.log(e);
if (e && e.message) {
this.submitError(e.message);
}
}
onShow() {
this.name(''/*IdentityUserStore()[0].name()*/);
this.password('');
this.email(''/*IdentityUserStore()[0].email()*/);
this.emailError(false);
this.submitError('');
}
}

View file

@ -0,0 +1,74 @@
import { GnuPGUserStore } from 'Stores/User/GnuPG';
import { OpenPGPUserStore } from 'Stores/User/OpenPGP';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
export class OpenPgpImportPopupView extends AbstractViewPopup {
constructor() {
super('OpenPgpImport');
this.addObservables({
key: '',
keyError: false,
keyErrorMessage: '',
saveGnuPG: true,
saveServer: false
});
this.canGnuPG = GnuPGUserStore.isSupported();
this.key.subscribe(() => {
this.keyError(false);
this.keyErrorMessage('');
});
}
submitForm() {
let keyTrimmed = this.key().trim();
if (/\n/.test(keyTrimmed)) {
keyTrimmed = keyTrimmed.replace(/\r+/g, '').replace(/\n{2,}/g, '\n\n');
}
this.keyError(!keyTrimmed);
this.keyErrorMessage('');
if (!keyTrimmed) {
return;
}
let match = null,
count = 30,
done = false;
// eslint-disable-next-line max-len
const reg = /[-]{3,6}BEGIN[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}[\s\S]+?[-]{3,6}END[\s]PGP[\s](PRIVATE|PUBLIC)[\s]KEY[\s]BLOCK[-]{3,6}/gi;
do {
match = reg.exec(keyTrimmed);
if (match && 0 < count) {
if (match[0] && match[1] && match[2] && match[1] === match[2]) {
this.saveGnuPG() && GnuPGUserStore.isSupported() && GnuPGUserStore.importKey(this.key());
OpenPGPUserStore.isSupported() && OpenPGPUserStore.importKey(this.key());
}
--count;
done = false;
} else {
done = true;
}
} while (!done);
if (this.keyError()) {
return;
}
this.close();
}
onShow() {
this.key('');
this.keyError(false);
this.keyErrorMessage('');
}
}

View file

@ -0,0 +1,126 @@
import { doc, addShortcut } from 'Common/Globals';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
export class OpenPgpKeyPopupView extends AbstractViewPopup {
constructor() {
super('OpenPgpKey');
this.addObservables({
key: '',
keyDom: null
});
}
selectKey() {
const el = this.keyDom();
if (el) {
let sel = getSelection(),
range = doc.createRange();
sel.removeAllRanges();
range.selectNodeContents(el);
sel.addRange(range);
}
if (navigator.clipboard) {
navigator.clipboard.writeText(this.key()).then(
() => console.log('Copied to clipboard'),
err => console.error(err)
);
}
}
onShow(openPgpKey) {
// TODO: show more info
this.key(openPgpKey ? openPgpKey.armor : '');
/*
this.key = key;
const aEmails = [];
if (key.users) {
key.users.forEach(user => user.userID.email && aEmails.push(user.userID.email));
}
this.id = key.getKeyID().toHex();
this.fingerprint = key.getFingerprint();
this.can_encrypt = !!key.getEncryptionKey();
this.can_sign = !!key.getSigningKey();
this.emails = aEmails;
this.armor = armor;
this.askDelete = ko.observable(false);
this.openForDeletion = ko.observable(null).askDeleteHelper();
key.id = key.subkeys[0].keyid;
key.fingerprint = key.subkeys[0].fingerprint;
key.uids.forEach(uid => uid.email && aEmails.push(uid.email));
key.emails = aEmails;
"disabled": false,
"expired": false,
"revoked": false,
"is_secret": true,
"can_sign": true,
"can_decrypt": true
"can_verify": true
"can_encrypt": true,
"uids": [
{
"name": "demo",
"comment": "",
"email": "demo@snappymail.eu",
"uid": "demo <demo@snappymail.eu>",
"revoked": false,
"invalid": false
}
],
"subkeys": [
{
"fingerprint": "2C223F20EA2ADB4CB68F81D95F3A5CDC09AD8AE3",
"keyid": "5F3A5CDC09AD8AE3",
"timestamp": 1643381672,
"expires": 0,
"is_secret": false,
"invalid": false,
"can_encrypt": false,
"can_sign": true,
"disabled": false,
"expired": false,
"revoked": false,
"can_certify": true,
"can_authenticate": false,
"is_qualified": false,
"is_de_vs": false,
"pubkey_algo": 303,
"length": 256,
"keygrip": "5A1A6C7310D0508C68E8E74F15068301E83FD1AE",
"is_cardkey": false,
"curve": "ed25519"
},
{
"fingerprint": "3CD720549D8833872C267D08F1230DCE2A561ADE",
"keyid": "F1230DCE2A561ADE",
"timestamp": 1643381672,
"expires": 0,
"is_secret": false,
"invalid": false,
"can_encrypt": true,
"can_sign": false,
"disabled": false,
"expired": false,
"revoked": false,
"can_certify": false,
"can_authenticate": false,
"is_qualified": false,
"is_de_vs": false,
"pubkey_algo": 302,
"length": 256,
"keygrip": "886921A7E06BE56F8E8C51797BB476BB26DF21BF",
"is_cardkey": false,
"curve": "cv25519"
}
]
*/
}
onBuild() {
addShortcut('a', 'meta', 'OpenPgpKey', () => {
this.selectKey();
return false;
});
}
}

View file

@ -1,16 +1,15 @@
import ko from 'ko';
import { Scope } from 'Common/Enums';
import { getNotification, i18n } from 'Common/Translator';
import { arrayLength } from 'Common/Utils';
import Remote from 'Remote/Admin/Fetch';
import { decorateKoCommands, isPopupVisible, showScreenPopup } from 'Knoin/Knoin';
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { AskPopupView } from 'View/Popup/Ask';
class PluginPopupView extends AbstractViewPopup {
export class PluginPopupView extends AbstractViewPopup {
constructor() {
super('Plugin');
@ -21,17 +20,14 @@ class PluginPopupView extends AbstractViewPopup {
readme: ''
});
this.configures = ko.observableArray();
this.config = ko.observableArray();
this.addComputables({
hasReadme: () => !!this.readme(),
hasConfiguration: () => 0 < this.configures().length
hasConfiguration: () => 0 < this.config().length
});
this.bDisabeCloseOnEsc = true;
this.sDefaultScope = Scope.All;
this.tryToClosePopup = this.tryToClosePopup.debounce(200);
this.keyScope.scope = 'all';
decorateKoCommands(this, {
saveCommand: self => self.hasConfiguration()
@ -39,32 +35,40 @@ class PluginPopupView extends AbstractViewPopup {
}
saveCommand() {
const list = {
const oConfig = {
Id: this.id(),
Settings: {}
};
this.configures.forEach(oItem => {
let value = oItem.value();
},
setItem = item => {
let value = item.value();
if (false === value || true === value) {
value = value ? 1 : 0;
}
list.Settings[oItem.Name] = value;
oConfig.Settings[item.Name] = value;
};
this.config.forEach(oItem => {
if (7 == oItem.Type) {
// Group
oItem.config.forEach(oSubItem => setItem(oSubItem));
} else {
setItem(oItem);
}
});
this.saveError('');
Remote.pluginSettingsUpdate(iError =>
iError
Remote.request('AdminPluginSettingsUpdate',
iError => iError
? this.saveError(getNotification(iError))
: this.cancelCommand()
, list);
: this.close(),
oConfig);
}
onShow(oPlugin) {
this.id('');
this.name('');
this.readme('');
this.configures([]);
this.config([]);
if (oPlugin) {
this.id(oPlugin.Id);
@ -73,38 +77,30 @@ class PluginPopupView extends AbstractViewPopup {
const config = oPlugin.Config;
if (arrayLength(config)) {
this.configures(
config.map(item => ({
value: ko.observable(item[0]),
placeholder: ko.observable(item[6]),
Name: item[1],
Type: item[2],
Label: item[3],
Default: item[4],
Desc: item[5]
}))
this.config(
config.map(item => {
if (7 == item.Type) {
// Group
item.config.forEach(subItem => {
subItem.value = ko.observable(subItem.value);
});
} else {
item.value = ko.observable(item.value);
}
return item;
})
);
}
}
}
tryToClosePopup() {
if (!isPopupVisible(AskPopupView)) {
onClose() {
if (AskPopupView.hidden()) {
showScreenPopup(AskPopupView, [
i18n('POPUPS_ASK/DESC_WANT_CLOSE_THIS_WINDOW'),
() => this.modalVisibility() && this.cancelCommand()
() => this.close()
]);
}
}
onBuild() {
shortcuts.add('escape', '', Scope.All, () => {
if (this.modalVisibility()) {
this.tryToClosePopup();
return false;
}
});
return false;
}
}
export { PluginPopupView, PluginPopupView as default };

View file

@ -1,146 +0,0 @@
import ko from 'ko';
import { getNotification, i18nToNodes } from 'Common/Translator';
import { addObservablesTo } from 'Common/Utils';
import { delegateRunOnDestroy } from 'Common/UtilsUser';
import Remote from 'Remote/User/Fetch';
import { FilterModel } from 'Model/Filter';
import { SieveUserStore } from 'Stores/User/Sieve';
import { showScreenPopup/*, decorateKoCommands*/ } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { FilterPopupView } from 'View/Popup/Filter';
class SieveScriptPopupView extends AbstractViewPopup {
constructor() {
super('SieveScript');
addObservablesTo(this, {
saveError: false,
saveErrorText: '',
rawActive: false,
allowToggle: false,
script: null
});
this.sieveCapabilities = SieveUserStore.capa.join(' ');
this.saving = false;
this.filterForDeletion = ko.observable(null).deleteAccessHelper();
/*
decorateKoCommands(this, {
saveScriptCommand: 1
});
*/
}
saveScriptCommand() {
let self = this,
script = self.script();
if (!self.saving/* && script.hasChanges()*/) {
if (!script.verify()) {
return false;
}
if (!script.exists() && SieveUserStore.scripts.find(item => item.name() === script.name())) {
script.nameError(true);
return false;
}
self.saving = true;
self.saveError(false);
if (self.allowToggle()) {
script.body(script.filtersToRaw());
}
Remote.filtersScriptSave(
(iError, data) => {
self.saving = false;
if (iError) {
self.saveError(true);
self.saveErrorText((data && data.ErrorMessageAdditional) || getNotification(iError));
} else {
script.exists() || SieveUserStore.scripts.push(script);
script.exists(true);
script.hasChanges(false);
}
},
script
);
}
return true;
}
deleteFilter(filter) {
this.script().filters.remove(filter);
delegateRunOnDestroy(filter);
}
addFilter() {
/* this = SieveScriptModel */
const filter = new FilterModel();
filter.generateID();
showScreenPopup(FilterPopupView, [
filter,
() => {
this.filters.push(filter);
},
false
]);
}
editFilter(filter) {
const clonedFilter = filter.cloneSelf();
showScreenPopup(FilterPopupView, [
clonedFilter,
() => {
const script = this.script(),
filters = script.filters(),
index = filters.indexOf(filter);
if (-1 < index) {
delegateRunOnDestroy(filters[index]);
filters[index] = clonedFilter;
script.filters(filters);
}
},
true
]);
}
toggleFiltersRaw() {
if (!this.rawActive()) {
let script = this.script(),
changed = script.hasChanges();
script.body(script.filtersToRaw());
script.hasChanges(changed);
}
this.rawActive(!this.rawActive());
}
onBuild(oDom) {
oDom.addEventListener('click', event => {
const el = event.target.closestWithin('td.e-action', oDom),
filter = el && ko.dataFor(el);
filter && this.editFilter(filter);
});
}
onShow(oScript) {
this.script(oScript);
this.rawActive(!oScript.allowFilters());
this.allowToggle(oScript.allowFilters());
this.saveError(false);
}
onShowWithDelay() {
// Sometimes not everything is translated, try again
i18nToNodes(this.viewModelDom);
}
}
export { SieveScriptPopupView, SieveScriptPopupView as default };

View file

@ -1,46 +0,0 @@
import { Scope } from 'Common/Enums';
import { doc } from 'Common/Globals';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class ViewOpenPgpKeyPopupView extends AbstractViewPopup {
constructor() {
super('ViewOpenPgpKey');
this.addObservables({
key: '',
keyDom: null
});
}
clearPopup() {
this.key('');
}
selectKey() {
const el = this.keyDom();
if (el) {
let sel = getSelection(),
range = doc.createRange();
sel.removeAllRanges();
range.selectNodeContents(el);
sel.addRange(range);
}
}
onShow(openPgpKey) {
this.clearPopup();
if (openPgpKey) {
this.key(openPgpKey.armor);
}
}
onBuild() {
shortcuts.add('a', 'meta', Scope.ViewOpenPgpKey, () => {
this.selectKey();
return false;
});
}
}
export { ViewOpenPgpKeyPopupView, ViewOpenPgpKeyPopupView as default };

View file

@ -1,107 +0,0 @@
import { AppUserStore } from 'Stores/User/App';
import { AccountUserStore } from 'Stores/User/Account';
import { MessageUserStore } from 'Stores/User/Message';
import { Capa, Scope } from 'Common/Enums';
import { settings } from 'Common/Links';
import { showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewRight } from 'Knoin/AbstractViews';
import { KeyboardShortcutsHelpPopupView } from 'View/Popup/KeyboardShortcutsHelp';
import { AccountPopupView } from 'View/Popup/Account';
import { ContactsPopupView } from 'View/Popup/Contacts';
import { doc, Settings, leftPanelDisabled } from 'Common/Globals';
import { ThemeStore } from 'Stores/Theme';
export class AbstractSystemDropDownUserView extends AbstractViewRight {
constructor(name) {
super(name, 'SystemDropDown');
this.allowAccounts = Settings.capa(Capa.AdditionalAccounts);
this.allowSettings = Settings.capa(Capa.Settings);
this.allowHelp = Settings.capa(Capa.Help);
this.accountEmail = AccountUserStore.email;
this.accounts = AccountUserStore.accounts;
this.accountsLoading = AccountUserStore.loading;
this.accountsUnreadCount = AccountUserStore.accountsUnreadCount;
this.addObservables({
currentAudio: '',
accountMenuDropdownTrigger: false
});
this.allowContacts = AppUserStore.allowContacts();
addEventListener('audio.stop', () => this.currentAudio(''));
addEventListener('audio.start', e => this.currentAudio(e.detail));
}
stopPlay() {
dispatchEvent(new CustomEvent('audio.api.stop'));
}
accountClick(account, event) {
if (account && 0 === event.button) {
AccountUserStore.loading(true);
setTimeout(() => AccountUserStore.loading(false), 1000);
}
return true;
}
emailTitle() {
return AccountUserStore.email();
}
settingsClick() {
this.allowSettings && rl.route.setHash(settings());
}
settingsHelp() {
this.allowHelp && showScreenPopup(KeyboardShortcutsHelpPopupView);
}
addAccountClick() {
this.allowAccounts && showScreenPopup(AccountPopupView);
}
contactsClick() {
this.allowContacts && showScreenPopup(ContactsPopupView);
}
toggleLayout()
{
const mobile = !ThemeStore.isMobile();
doc.cookie = 'rllayout=' + (mobile ? 'mobile' : 'desktop');
ThemeStore.isMobile(mobile);
leftPanelDisabled(mobile);
// location.reload();
}
logoutClick() {
rl.app.logout();
}
onBuild() {
shortcuts.add('m,contextmenu', '', [Scope.MessageList, Scope.MessageView, Scope.Settings], () => {
if (this.viewModelVisible) {
MessageUserStore.messageFullScreenMode(false);
this.accountMenuDropdownTrigger(true);
return false;
}
});
// shortcuts help
shortcuts.add('?,f1,help', '', [Scope.MessageList, Scope.MessageView, Scope.Settings], () => {
if (this.viewModelVisible) {
showScreenPopup(KeyboardShortcutsHelpPopupView);
return false;
}
});
}
}

View file

@ -1,9 +1,7 @@
import ko from 'ko';
import { Notification } from 'Common/Enums';
import { ClientSideKeyName } from 'Common/EnumsUser';
import { Settings, SettingsGet } from 'Common/Globals';
import { getNotification, reload as translatorReload, convertLangName } from 'Common/Translator';
import { ClientSideKeyNameLastSignMe } from 'Common/EnumsUser';
import { SettingsGet, fireEvent } from 'Common/Globals';
import { getNotification, translatorReload, convertLangName } from 'Common/Translator';
import { LanguageStore } from 'Stores/Language';
@ -12,20 +10,19 @@ import * as Local from 'Storage/Client';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands, showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewCenter } from 'Knoin/AbstractViews';
import { AbstractViewLogin } from 'Knoin/AbstractViews';
import { LanguagesPopupView } from 'View/Popup/Languages';
const SignMeOff = 0,
const
SignMeOff = 0,
SignMeOn = 1,
SignMeUnused = 2;
class LoginUserView extends AbstractViewCenter {
export class LoginUserView extends AbstractViewLogin {
constructor() {
super('User/Login', 'Login');
this.hideSubmitButton = Settings.app('hideSubmitButton');
super();
this.addObservables({
loadingDesc: SettingsGet('LoadingDescription'),
@ -46,8 +43,6 @@ class LoginUserView extends AbstractViewCenter {
signMeType: SignMeUnused
});
this.formError = ko.observable(false).extend({ falseTimeout: 500 });
this.allowLanguagesOnLogin = !!SettingsGet('AllowLanguagesOnLogin');
this.language = LanguageStore.language;
@ -95,7 +90,7 @@ class LoginUserView extends AbstractViewCenter {
submitCommand(self, event) {
let form = event.target.form,
data = new FormData(form),
valid = form.reportValidity();
valid = form.reportValidity() && fireEvent('sm-user-login', data);
this.emailError(!this.email());
this.passwordError(!this.password());
@ -105,8 +100,12 @@ class LoginUserView extends AbstractViewCenter {
this.submitRequest(true);
data.set('Language', this.bSendLanguage ? this.language() : '');
data.set('SignMe', this.signMe() ? 1 : 0);
Remote.login(
Remote.request('Login',
(iError, oData) => {
fireEvent('sm-user-login-response', {
error: iError,
data: oData
});
if (iError) {
this.submitRequest(false);
if (Notification.InvalidInputArgument == iError) {
@ -116,25 +115,22 @@ class LoginUserView extends AbstractViewCenter {
Notification.UnknownNotification));
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
} else {
rl.route.reload();
rl.setData(oData.Result);
}
},
data
);
Local.set(ClientSideKeyName.LastSignMe, this.signMe() ? '-1-' : '-0-');
Local.set(ClientSideKeyNameLastSignMe, this.signMe() ? '-1-' : '-0-');
}
return valid;
}
onShow() {
rl.route.off();
}
onBuild(dom) {
super.onBuild(dom);
onBuild() {
const signMeLocal = Local.get(ClientSideKeyName.LastSignMe),
signMe = (SettingsGet('SignMe') || '').toLowerCase();
const signMe = (SettingsGet('SignMe') || '').toLowerCase();
switch (signMe) {
case 'defaultoff':
@ -143,7 +139,7 @@ class LoginUserView extends AbstractViewCenter {
'defaulton' === signMe ? SignMeOn : SignMeOff
);
switch (signMeLocal) {
switch (Local.get(ClientSideKeyNameLastSignMe)) {
case '-1-':
this.signMeType(SignMeOn);
break;
@ -160,13 +156,7 @@ class LoginUserView extends AbstractViewCenter {
}
}
submitForm() {
// return false;
}
selectLanguage() {
showScreenPopup(LanguagesPopupView, [this.language, this.languages(), LanguageStore.userLanguage()]);
}
}
export { LoginUserView };

View file

@ -1,15 +1,16 @@
import ko from 'ko';
import { Capa, Scope } from 'Common/Enums';
import { leftPanelDisabled, moveAction, Settings } from 'Common/Globals';
import { Scope } from 'Common/Enums';
import { moveAction, addShortcut } from 'Common/Globals';
import { mailBox, settings } from 'Common/Links';
import { setFolderHash } from 'Common/Cache';
import { addComputablesTo } from 'External/ko';
import { AppUserStore } from 'Stores/User/App';
import { SettingsUserStore } from 'Stores/User/Settings';
import { FolderUserStore } from 'Stores/User/Folder';
import { MessageUserStore } from 'Stores/User/Message';
import { ThemeStore } from 'Stores/Theme';
import { MessagelistUserStore } from 'Stores/User/Messagelist';
import { showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewLeft } from 'Knoin/AbstractViews';
@ -18,9 +19,13 @@ import { showMessageComposer } from 'Common/UtilsUser';
import { FolderCreatePopupView } from 'View/Popup/FolderCreate';
import { ContactsPopupView } from 'View/Popup/Contacts';
export class FolderListMailBoxUserView extends AbstractViewLeft {
import { moveMessagesToFolder } from 'Common/Folders';
import { setExpandedFolder } from 'Model/FolderCollection';
export class MailFolderList extends AbstractViewLeft {
constructor() {
super('User/MailBox/FolderList', 'MailFolderList');
super();
this.oContentScrollable = null;
@ -32,14 +37,29 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
this.moveAction = moveAction;
this.foldersListWithSingleInboxRootFolder = FolderUserStore.singleRootFolder;
this.foldersListWithSingleInboxRootFolder = ko.observable(false);
this.leftPanelDisabled = leftPanelDisabled;
this.allowComposer = Settings.capa(Capa.Composer);
this.allowContacts = AppUserStore.allowContacts();
this.folderListFocused = ko.computed(() => Scope.FolderList === AppUserStore.focusedState());
addComputablesTo(this, {
folderListVisible: () => {
let multiple = false,
inbox, visible,
result = FolderUserStore.folderList().filter(folder => {
if (folder.isInbox()) {
inbox = folder;
}
visible = folder.visible();
multiple |= visible && !folder.isInbox();
return visible;
});
if (inbox && !multiple) {
inbox.collapsed(false);
}
this.foldersListWithSingleInboxRootFolder(!multiple);
return result;
}
});
}
onBuild(dom) {
@ -54,7 +74,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
const folder = ko.dataFor(el);
if (folder) {
const collapsed = folder.collapsed();
rl.app.setExpandedFolder(folder.fullNameHash, collapsed);
setExpandedFolder(folder.fullName, collapsed);
folder.collapsed(!collapsed);
event.preventDefault();
@ -65,16 +85,15 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
el = eqs(event, 'a');
if (el && el.matches('.selectable')) {
ThemeStore.isMobile() && leftPanelDisabled(true);
event.preventDefault();
const folder = ko.dataFor(el);
if (folder) {
if (moveAction()) {
moveAction(false);
rl.app.moveMessagesToFolder(
FolderUserStore.currentFolderFullNameRaw(),
MessageUserStore.listCheckedOrSelectedUidsWithSubMails(),
folder.fullNameRaw,
moveMessagesToFolder(
FolderUserStore.currentFolderFullName(),
MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails(),
folder.fullName,
event.ctrlKey
);
} else {
@ -82,13 +101,13 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
MessageUserStore.message(null);
}
if (folder.fullNameRaw === FolderUserStore.currentFolderFullNameRaw()) {
setFolderHash(folder.fullNameRaw, '');
if (folder.fullName === FolderUserStore.currentFolderFullName()) {
setFolderHash(folder.fullName, '');
}
rl.route.setHash(
hasher.setHash(
mailBox(folder.fullNameHash, 1,
(event.target.matches('.flag-icon') && !folder.isFlagged()) ? 'is:flagged' : ''
(event.target.matches('.flag-icon') && !folder.isFlagged()) ? 'flagged' : ''
)
);
}
@ -98,9 +117,9 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
}
});
shortcuts.add('arrowup,arrowdown', '', Scope.FolderList, event => {
addShortcut('arrowup,arrowdown', '', Scope.FolderList, event => {
let items = [], index = 0;
dom.querySelectorAll('.b-folders li a:not(.hidden)').forEach(node => {
dom.querySelectorAll('li a:not(.hidden)').forEach(node => {
if (node.offsetHeight || node.getClientRects().length) {
items.push(node);
if (node.matches('.focused')) {
@ -122,8 +141,8 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
return false;
});
shortcuts.add('enter,open', '', Scope.FolderList, () => {
const item = qs('.b-folders li a:not(.hidden).focused');
addShortcut('enter,open', '', Scope.FolderList, () => {
const item = qs('li a:not(.hidden).focused');
if (item) {
AppUserStore.focusedState(Scope.MessageList);
item.click();
@ -132,30 +151,30 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
return false;
});
shortcuts.add('space', '', Scope.FolderList, () => {
const item = qs('.b-folders li a:not(.hidden).focused'),
addShortcut('space', '', Scope.FolderList, () => {
const item = qs('li a:not(.hidden).focused'),
folder = item && ko.dataFor(item);
if (folder) {
const collapsed = folder.collapsed();
rl.app.setExpandedFolder(folder.fullNameHash, collapsed);
setExpandedFolder(folder.fullName, collapsed);
folder.collapsed(!collapsed);
}
return false;
});
// shortcuts.add('tab', 'shift', Scope.FolderList, () => {
shortcuts.add('escape,tab,arrowright', '', Scope.FolderList, () => {
// addShortcut('tab', 'shift', Scope.FolderList, () => {
addShortcut('escape,tab,arrowright', '', Scope.FolderList, () => {
AppUserStore.focusedState(Scope.MessageList);
moveAction(false);
return false;
});
AppUserStore.focusedState.subscribe(value => {
let el = qs('.b-folders li a.focused');
let el = qs('li a.focused');
el && el.classList.remove('focused');
if (Scope.FolderList === value) {
el = qs('.b-folders li a.selected');
el = qs('li a.selected');
el && el.classList.add('focused');
}
});
@ -187,7 +206,7 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
}
configureFolders() {
rl.route.setHash(settings('folders'));
hasher.setHash(settings('folders'));
}
contactsClick() {

File diff suppressed because it is too large Load diff

View file

@ -1,23 +1,36 @@
import ko from 'ko';
import { koComputable } from 'External/ko';
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import {
Capa,
Scope
} from 'Common/Enums';
import { Scope } from 'Common/Enums';
import {
ComposeType,
ClientSideKeyName,
ClientSideKeyNameLastReplyAction,
ClientSideKeyNameMessageHeaderFullInfo,
ClientSideKeyNameMessageAttachmentControls,
FolderType,
MessageSetAction
} from 'Common/EnumsUser';
import { $htmlCL, leftPanelDisabled, keyScopeReal, moveAction, Settings, getFullscreenElement, exitFullscreen } from 'Common/Globals';
import {
elementById,
$htmlCL,
leftPanelDisabled,
keyScopeReal,
moveAction,
Settings,
SettingsCapa,
getFullscreenElement,
exitFullscreen,
fireEvent,
addShortcut,
registerShortcut
} from 'Common/Globals';
import { arrayLength, inFocus } from 'Common/Utils';
import { mailToHelper, showMessageComposer } from 'Common/UtilsUser';
import { arrayLength } from 'Common/Utils';
import { download, mailToHelper, showMessageComposer, initFullscreen } from 'Common/UtilsUser';
import { SMAudio } from 'Common/Audio';
@ -31,120 +44,101 @@ import { SettingsUserStore } from 'Stores/User/Settings';
import { AccountUserStore } from 'Stores/User/Account';
import { FolderUserStore } from 'Stores/User/Folder';
import { MessageUserStore } from 'Stores/User/Message';
import { MessagelistUserStore } from 'Stores/User/Messagelist';
import { ThemeStore } from 'Stores/Theme';
import * as Local from 'Storage/Client';
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands, createCommand } from 'Knoin/Knoin';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewRight } from 'Knoin/AbstractViews';
class MessageViewMailBoxUserView extends AbstractViewRight {
import { PgpUserStore } from 'Stores/User/Pgp';
import { MimeToMessage } from 'Mime/Utils';
const
oMessageScrollerDom = () => elementById('messageItem') || {},
currentMessage = () => MessageUserStore.message();
export class MailMessageView extends AbstractViewRight {
constructor() {
super('User/MailBox/MessageView', 'MailMessageView');
super();
const
/**
* @param {Function} fExecute
* @param {Function} fCanExecute = true
* @returns {Function}
*/
createCommand = (fExecute, fCanExecute) => {
let fResult = () => {
fResult.canExecute() && fExecute.call(null);
return false;
};
fResult.canExecute = koComputable(() => fCanExecute());
return fResult;
},
createCommandReplyHelper = type =>
createCommand(() => {
this.lastReplyAction(type);
this.replyOrforward(type);
}, this.canBeRepliedOrForwarded),
createCommand(() => this.replyOrforward(type), this.canBeRepliedOrForwarded),
createCommandActionHelper = (folderType, useFolder) =>
createCommand(() => {
const message = MessageUserStore.message();
if (message && this.allowMessageListActions) {
const message = currentMessage();
if (message) {
MessageUserStore.message(null);
rl.app.deleteMessagesFromFolder(folderType, message.folder, [message.uid], useFolder);
}
}, this.messageVisibility);
this.oHeaderDom = null;
this.oMessageScrollerDom = null;
this.addObservables({
showAttachmnetControls: false,
showAttachmentControls: false,
downloadAsZipLoading: false,
lastReplyAction_: '',
showFullInfo: '1' === Local.get(ClientSideKeyName.MessageHeaderFullInfo),
moreDropdownTrigger: false
showFullInfo: '1' === Local.get(ClientSideKeyNameMessageHeaderFullInfo),
moreDropdownTrigger: false,
// viewer
viewFromShort: '',
viewFromDkimData: ['none', ''],
viewToShort: ''
});
this.moveAction = moveAction;
this.allowComposer = Settings.capa(Capa.Composer);
this.allowMessageActions = Settings.capa(Capa.MessageActions);
this.allowMessageListActions = Settings.capa(Capa.MessageListActions);
this.allowMessageActions = SettingsCapa('MessageActions');
const attachmentsActions = Settings.app('attachmentsActions');
this.attachmentsActions = ko.observableArray(arrayLength(attachmentsActions) ? attachmentsActions : []);
this.message = MessageUserStore.message;
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
this.messageLoadingThrottle = MessageUserStore.messageLoading;
this.messagesBodiesDom = MessageUserStore.messagesBodiesDom;
this.isMessageSelected = MessageUserStore.isMessageSelected;
this.messageActiveDom = MessageUserStore.messageActiveDom;
this.messageError = MessageUserStore.messageError;
this.hasCheckedMessages = MessagelistUserStore.hasCheckedMessages;
this.messageLoadingThrottle = MessageUserStore.loading;
this.messagesBodiesDom = MessageUserStore.bodiesDom;
this.messageError = MessageUserStore.error;
this.fullScreenMode = MessageUserStore.messageFullScreenMode;
this.fullScreenMode = MessageUserStore.fullScreen;
this.toggleFullScreen = MessageUserStore.toggleFullScreen;
this.messageListOfThreadsLoading = ko.observable(false).extend({ rateLimit: 1 });
this.highlightUnselectedAttachments = ko.observable(false).extend({ falseTimeout: 2000 });
this.showAttachmnetControlsState = v => Local.set(ClientSideKeyName.MessageAttachmentControls, !!v);
this.showAttachmentControlsState = v => Local.set(ClientSideKeyNameMessageAttachmentControls, !!v);
this.downloadAsZipError = ko.observable(false).extend({ falseTimeout: 7000 });
this.messageDomFocused = ko.observable(false).extend({ rateLimit: 0 });
// commands
this.replyCommand = createCommandReplyHelper(ComposeType.Reply);
this.replyAllCommand = createCommandReplyHelper(ComposeType.ReplyAll);
this.forwardCommand = createCommandReplyHelper(ComposeType.Forward);
this.forwardAsAttachmentCommand = createCommandReplyHelper(ComposeType.ForwardAsAttachment);
this.editAsNewCommand = createCommandReplyHelper(ComposeType.EditAsNew);
this.deleteCommand = createCommandActionHelper(FolderType.Trash, true);
this.deleteWithoutMoveCommand = createCommandActionHelper(FolderType.Trash, false);
this.archiveCommand = createCommandActionHelper(FolderType.Archive, true);
this.spamCommand = createCommandActionHelper(FolderType.Spam, true);
this.notSpamCommand = createCommandActionHelper(FolderType.NotSpam, true);
// viewer
this.viewFolder = '';
this.viewUid = '';
this.viewHash = '';
this.addObservables({
viewBodyTopValue: 0,
viewSubject: '',
viewFromShort: '',
viewFromDkimData: ['none', ''],
viewToShort: '',
viewFrom: '',
viewTo: '',
viewCc: '',
viewBcc: '',
viewReplyTo: '',
viewTimeStamp: 0,
viewSize: '',
viewSpamScore: 0,
viewSpamStatus: '',
viewLineAsCss: '',
viewViewLink: '',
viewUnsubscribeLink: '',
viewDownloadLink: '',
viewIsImportant: false,
viewIsFlagged: false,
hasVirus: null
});
this.addComputables({
allowAttachmnetControls: () => this.attachmentsActions.length && Settings.capa(Capa.AttachmentsActions),
allowAttachmentControls: () => this.attachmentsActions.length && SettingsCapa('AttachmentsActions'),
downloadAsZipAllowed: () => this.attachmentsActions.includes('zip') && this.allowAttachmnetControls(),
downloadAsZipAllowed: () => this.attachmentsActions.includes('zip') && this.allowAttachmentControls(),
lastReplyAction: {
read: this.lastReplyAction_,
@ -155,7 +149,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
)
},
messageVisibility: () => !MessageUserStore.messageLoading() && !!MessageUserStore.message(),
messageVisibility: () => !MessageUserStore.loading() && !!currentMessage(),
canBeRepliedOrForwarded: () => !this.isDraftFolder() && this.messageVisibility(),
@ -181,26 +175,26 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
return '';
},
messageFocused: () => Scope.MessageView === AppUserStore.focusedState(),
pgpSupported: () => currentMessage() && PgpUserStore.isSupported(),
messageListAndMessageViewLoading:
() => MessageUserStore.listCompleteLoading() || MessageUserStore.messageLoading()
messageListOrViewLoading:
() => MessagelistUserStore.isLoading() | MessageUserStore.loading()
});
this.addSubscribables({
showAttachmnetControls: v => MessageUserStore.message()
&& MessageUserStore.message().attachments.forEach(item => item && item.checked(!!v)),
showAttachmentControls: v => currentMessage()
&& currentMessage().attachments.forEach(item => item && item.checked(!!v)),
lastReplyAction_: value => Local.set(ClientSideKeyName.LastReplyAction, value),
lastReplyAction_: value => Local.set(ClientSideKeyNameLastReplyAction, value),
message: message => {
this.messageActiveDom(null);
MessageUserStore.activeDom(null);
if (message) {
this.showAttachmnetControls(false);
if (Local.get(ClientSideKeyName.MessageAttachmentControls)) {
this.showAttachmentControls(false);
if (Local.get(ClientSideKeyNameMessageAttachmentControls)) {
setTimeout(() => {
this.showAttachmnetControls(true);
this.showAttachmentControls(true);
}, 50);
}
@ -208,36 +202,13 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.scrollMessageToTop();
}
let spam = message.spamResult();
this.viewFolder = message.folder;
this.viewUid = message.uid;
this.viewHash = message.hash;
this.viewSubject(message.subject());
this.viewFromShort(message.fromToLine(true, true));
this.viewFromDkimData(message.fromDkimData());
this.viewToShort(message.toToLine(true, true));
this.viewFrom(message.fromToLine());
this.viewTo(message.toToLine());
this.viewCc(message.ccToLine());
this.viewBcc(message.bccToLine());
this.viewReplyTo(message.replyToToLine());
this.viewTimeStamp(message.dateTimeStampInUTC());
this.viewSize(message.friendlySize());
this.viewSpamScore(message.spamScore());
this.viewSpamStatus(spam ? i18n(message.isSpam() ? 'GLOBAL/SPAM' : 'GLOBAL/NOT_SPAM') + ': ' + spam : '');
this.viewLineAsCss(message.lineAsCss());
this.viewViewLink(message.viewLink());
this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
this.viewDownloadLink(message.downloadLink());
this.viewIsImportant(message.isImportant());
this.viewIsFlagged(message.isFlagged());
this.hasVirus(message.hasVirus());
} else {
MessageUserStore.selectorMessageSelected(null);
MessagelistUserStore.selectedMessage(null);
this.viewFolder = '';
this.viewUid = '';
this.viewHash = '';
this.scrollMessageToTop();
@ -245,57 +216,59 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
},
fullScreenMode: value => {
value && currentMessage() && AppUserStore.focusedState(Scope.MessageView);
if (this.oContent) {
value ? this.oContent.requestFullscreen() : exitFullscreen();
} else {
$htmlCL.toggle('rl-message-fullscreen', value);
}
}
},
showFullInfo: value => Local.set(ClientSideKeyNameMessageHeaderFullInfo, value ? '1' : '0')
});
MessageUserStore.messageViewTrigger.subscribe(() => {
const message = MessageUserStore.message();
this.viewIsFlagged(message ? message.isFlagged() : false);
});
this.lastReplyAction(Local.get(ClientSideKeyNameLastReplyAction) || ComposeType.Reply);
this.lastReplyAction(Local.get(ClientSideKeyName.LastReplyAction) || ComposeType.Reply);
// commands
this.replyCommand = createCommandReplyHelper(ComposeType.Reply);
this.replyAllCommand = createCommandReplyHelper(ComposeType.ReplyAll);
this.forwardCommand = createCommandReplyHelper(ComposeType.Forward);
this.forwardAsAttachmentCommand = createCommandReplyHelper(ComposeType.ForwardAsAttachment);
this.editAsNewCommand = createCommandReplyHelper(ComposeType.EditAsNew);
addEventListener('mailbox.message-view.toggle-full-screen', () => this.toggleFullScreen());
this.deleteCommand = createCommandActionHelper(FolderType.Trash, true);
this.deleteWithoutMoveCommand = createCommandActionHelper(FolderType.Trash, false);
this.archiveCommand = createCommandActionHelper(FolderType.Archive, true);
this.spamCommand = createCommandActionHelper(FolderType.Spam, true);
this.notSpamCommand = createCommandActionHelper(FolderType.NotSpam, true);
decorateKoCommands(this, {
closeMessageCommand: 1,
messageEditCommand: self => self.messageVisibility(),
goUpCommand: self => !self.messageListAndMessageViewLoading(),
goDownCommand: self => !self.messageListAndMessageViewLoading()
goUpCommand: self => !self.messageListOrViewLoading(),
goDownCommand: self => !self.messageListOrViewLoading()
});
}
closeMessageCommand() {
closeMessage() {
MessageUserStore.message(null);
}
messageEditCommand() {
this.editMessage();
if (currentMessage()) {
showMessageComposer([ComposeType.Draft, currentMessage()]);
}
}
goUpCommand() {
dispatchEvent(new CustomEvent('mailbox.message-list.selector.go-up',
{detail:SettingsUserStore.usePreviewPane() || !!MessageUserStore.message()} // bForceSelect
));
fireEvent('mailbox.message-list.selector.go-up',
SettingsUserStore.usePreviewPane() || !!currentMessage() // bForceSelect
);
}
goDownCommand() {
dispatchEvent(new CustomEvent('mailbox.message-list.selector.go-down',
{detail:SettingsUserStore.usePreviewPane() || !!MessageUserStore.message()} // bForceSelect
));
}
toggleFullScreen() {
try {
getSelection().removeAllRanges();
} catch (e) {} // eslint-disable-line no-empty
this.fullScreenMode(!this.fullScreenMode());
fireEvent('mailbox.message-list.selector.go-down',
SettingsUserStore.usePreviewPane() || !!currentMessage() // bForceSelect
);
}
/**
@ -303,44 +276,13 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
* @returns {void}
*/
replyOrforward(sType) {
showMessageComposer([sType, MessageUserStore.message()]);
}
checkHeaderHeight() {
this.oHeaderDom && this.viewBodyTopValue(MessageUserStore.message() ? this.oHeaderDom.offsetHeight : 0);
this.lastReplyAction(sType);
showMessageComposer([sType, currentMessage()]);
}
onBuild(dom) {
this.oMessageScrollerDom = dom.querySelector('.messageItem');
this.fullScreenMode.subscribe(value =>
value && MessageUserStore.message() && AppUserStore.focusedState(Scope.MessageView));
this.showFullInfo.subscribe(value => Local.set(ClientSideKeyName.MessageHeaderFullInfo, value ? '1' : '0'));
let el = dom.querySelector('.messageItemHeader');
this.oHeaderDom = el;
if (el) {
if (!this.resizeObserver) {
this.resizeObserver = new ResizeObserver(this.checkHeaderHeight.debounce(50).bind(this));
}
this.resizeObserver.observe(el);
} else if (this.resizeObserver) {
this.resizeObserver.disconnect();
}
let event = 'fullscreenchange';
el = dom.querySelector('.b-message-view-wrapper');
if (!el.requestFullscreen && el.webkitRequestFullscreen) {
el.requestFullscreen = el.webkitRequestFullscreen;
event = 'webkit'+event;
}
if (el.requestFullscreen) {
this.oContent = el;
el.addEventListener(event, () =>
this.fullScreenMode(getFullscreenElement() === el)
);
}
const el = dom.querySelector('.b-content');
this.oContent = initFullscreen(el, () => MessageUserStore.fullScreen(getFullscreenElement() === el));
const eqs = (ev, s) => ev.target.closestWithin(s, dom);
dom.addEventListener('click', event => {
@ -381,50 +323,36 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
el = eqs(event, '.attachmentsPlace .attachmentItem .attachmentNameParent');
if (el) {
const attachment = ko.dataFor(el);
attachment && attachment.download && rl.app.download(attachment.linkDownload());
attachment && attachment.linkDownload() && download(attachment.linkDownload(), attachment.fileName);
}
if (eqs(event, '.messageItemHeader .subjectParent .flagParent')) {
const message = MessageUserStore.message();
message && rl.app.messageListAction(
const message = currentMessage();
message && MessagelistUserStore.setAction(
message.folder,
message.isFlagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag,
[message]
);
}
el = eqs(event, '.thread-list .flagParent');
if (el) {
const message = ko.dataFor(el);
message && message.folder && message.uid && rl.app.messageListAction(
message.folder,
message.isFlagged() ? MessageSetAction.UnsetFlag : MessageSetAction.SetFlag,
[message]
);
this.threadsDropdownTrigger(true);
return false;
}
});
AppUserStore.focusedState.subscribe((value) => {
AppUserStore.focusedState.subscribe(value => {
if (Scope.MessageView !== value) {
this.scrollMessageToTop();
this.scrollMessageToLeft();
}
});
keyScopeReal.subscribe(value => this.messageDomFocused(Scope.MessageView === value && !inFocus()));
keyScopeReal.subscribe(value => this.messageDomFocused(Scope.MessageView === value));
// initShortcuts
// exit fullscreen, back
shortcuts.add('escape,backspace', '', Scope.MessageView, () => {
if (this.viewModelVisible && MessageUserStore.message()) {
addShortcut('escape', '', Scope.MessageView, () => {
if (!this.viewModelDom.hidden && currentMessage()) {
const preview = SettingsUserStore.usePreviewPane();
if (this.fullScreenMode()) {
this.fullScreenMode(false);
if (MessageUserStore.fullScreen()) {
MessageUserStore.fullScreen(false);
if (preview) {
AppUserStore.focusedState(Scope.MessageList);
@ -440,14 +368,14 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
});
// fullscreen
shortcuts.add('enter,open', '', Scope.MessageView, () => {
this.toggleFullScreen();
addShortcut('enter,open', '', Scope.MessageView, () => {
MessageUserStore.toggleFullScreen();
return false;
});
// reply
shortcuts.add('r,mailreply', '', [Scope.MessageList, Scope.MessageView], () => {
if (MessageUserStore.message()) {
registerShortcut('r,mailreply', '', [Scope.MessageList, Scope.MessageView], () => {
if (currentMessage()) {
this.replyCommand();
return false;
}
@ -455,80 +383,80 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
});
// replyAll
shortcuts.add('a', '', [Scope.MessageList, Scope.MessageView], () => {
if (MessageUserStore.message()) {
registerShortcut('a', '', [Scope.MessageList, Scope.MessageView], () => {
if (currentMessage()) {
this.replyAllCommand();
return false;
}
});
shortcuts.add('mailreply', 'shift', [Scope.MessageList, Scope.MessageView], () => {
if (MessageUserStore.message()) {
registerShortcut('mailreply', 'shift', [Scope.MessageList, Scope.MessageView], () => {
if (currentMessage()) {
this.replyAllCommand();
return false;
}
});
// forward
shortcuts.add('f,mailforward', '', [Scope.MessageList, Scope.MessageView], () => {
if (MessageUserStore.message()) {
registerShortcut('f,mailforward', '', [Scope.MessageList, Scope.MessageView], () => {
if (currentMessage()) {
this.forwardCommand();
return false;
}
});
// message information
shortcuts.add('i', 'meta', [Scope.MessageList, Scope.MessageView], () => {
if (MessageUserStore.message()) {
registerShortcut('i', 'meta', [Scope.MessageList, Scope.MessageView], () => {
if (currentMessage()) {
this.showFullInfo(!this.showFullInfo());
}
return false;
});
// toggle message blockquotes
shortcuts.add('b', '', [Scope.MessageList, Scope.MessageView], () => {
const message = MessageUserStore.message();
registerShortcut('b', '', [Scope.MessageList, Scope.MessageView], () => {
const message = currentMessage();
if (message && message.body) {
message.body.querySelectorAll('.rlBlockquoteSwitcher').forEach(node => node.click());
return false;
}
});
shortcuts.add('arrowup,arrowleft', 'meta', [Scope.MessageList, Scope.MessageView], () => {
addShortcut('arrowup,arrowleft', 'meta', [Scope.MessageList, Scope.MessageView], () => {
this.goUpCommand();
return false;
});
shortcuts.add('arrowdown,arrowright', 'meta', [Scope.MessageList, Scope.MessageView], () => {
addShortcut('arrowdown,arrowright', 'meta', [Scope.MessageList, Scope.MessageView], () => {
this.goDownCommand();
return false;
});
// print
shortcuts.add('p,printscreen', 'meta', [Scope.MessageView, Scope.MessageList], () => {
MessageUserStore.message() && MessageUserStore.message().printMessage();
addShortcut('p,printscreen', 'meta', [Scope.MessageView, Scope.MessageList], () => {
currentMessage() && currentMessage().printMessage();
return false;
});
// delete
shortcuts.add('delete', '', Scope.MessageView, () => {
addShortcut('delete', '', Scope.MessageView, () => {
this.deleteCommand();
return false;
});
shortcuts.add('delete', 'shift', Scope.MessageView, () => {
addShortcut('delete', 'shift', Scope.MessageView, () => {
this.deleteWithoutMoveCommand();
return false;
});
// change focused state
shortcuts.add('arrowleft', '', Scope.MessageView, () => {
if (!this.fullScreenMode() && MessageUserStore.message() && SettingsUserStore.usePreviewPane()
&& !this.oMessageScrollerDom.scrollLeft) {
addShortcut('arrowleft', '', Scope.MessageView, () => {
if (!MessageUserStore.fullScreen() && currentMessage() && SettingsUserStore.usePreviewPane()
&& !oMessageScrollerDom().scrollLeft) {
AppUserStore.focusedState(Scope.MessageList);
return false;
}
});
shortcuts.add('tab', 'shift', Scope.MessageView, () => {
if (!this.fullScreenMode() && MessageUserStore.message() && SettingsUserStore.usePreviewPane()) {
addShortcut('tab', 'shift', Scope.MessageView, () => {
if (!MessageUserStore.fullScreen() && currentMessage() && SettingsUserStore.usePreviewPane()) {
AppUserStore.focusedState(Scope.MessageList);
}
return false;
@ -539,42 +467,42 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
* @returns {boolean}
*/
isDraftFolder() {
return MessageUserStore.message() && FolderUserStore.draftFolder() === MessageUserStore.message().folder;
return currentMessage() && FolderUserStore.draftsFolder() === currentMessage().folder;
}
/**
* @returns {boolean}
*/
isSentFolder() {
return MessageUserStore.message() && FolderUserStore.sentFolder() === MessageUserStore.message().folder;
return currentMessage() && FolderUserStore.sentFolder() === currentMessage().folder;
}
/**
* @returns {boolean}
*/
isSpamFolder() {
return MessageUserStore.message() && FolderUserStore.spamFolder() === MessageUserStore.message().folder;
return currentMessage() && FolderUserStore.spamFolder() === currentMessage().folder;
}
/**
* @returns {boolean}
*/
isSpamDisabled() {
return MessageUserStore.message() && FolderUserStore.spamFolder() === UNUSED_OPTION_VALUE;
return currentMessage() && FolderUserStore.spamFolder() === UNUSED_OPTION_VALUE;
}
/**
* @returns {boolean}
*/
isArchiveFolder() {
return MessageUserStore.message() && FolderUserStore.archiveFolder() === MessageUserStore.message().folder;
return currentMessage() && FolderUserStore.archiveFolder() === currentMessage().folder;
}
/**
* @returns {boolean}
*/
isArchiveDisabled() {
return MessageUserStore.message() && FolderUserStore.archiveFolder() === UNUSED_OPTION_VALUE;
return currentMessage() && FolderUserStore.archiveFolder() === UNUSED_OPTION_VALUE;
}
/**
@ -588,34 +516,32 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
showMessageComposer();
}
editMessage() {
if (MessageUserStore.message()) {
showMessageComposer([ComposeType.Draft, MessageUserStore.message()]);
}
}
scrollMessageToTop() {
this.oMessageScrollerDom.scrollTop = (50 < this.oMessageScrollerDom.scrollTop) ? 50 : 0;
oMessageScrollerDom().scrollTop = (50 < oMessageScrollerDom().scrollTop) ? 50 : 0;
}
scrollMessageToLeft() {
this.oMessageScrollerDom.scrollLeft = 0;
oMessageScrollerDom().scrollLeft = 0;
}
downloadAsZip() {
const hashes = (MessageUserStore.message() ? MessageUserStore.message().attachments : [])
.map(item => (item && !item.isLinked && item.checked() ? item.download : ''))
const hashes = (currentMessage() ? currentMessage().attachments : [])
.map(item => (item && !item.isLinked() && item.checked() ? item.download : ''))
.filter(v => v);
if (hashes.length) {
Remote.attachmentsActions('Zip', hashes, this.downloadAsZipLoading)
.then(result => {
if (result && result.Result && result.Result.FileHash) {
rl.app.download(attachmentDownload(result.Result.FileHash));
} else {
this.downloadAsZipError(true);
}
})
.catch(() => this.downloadAsZipError(true));
Remote.post('AttachmentsActions', this.downloadAsZipLoading, {
Do: 'Zip',
Hashes: hashes
})
.then(result => {
let hash = result && result.Result && result.Result.FileHash;
if (hash) {
download(attachmentDownload(hash), hash+'.zip');
} else {
this.downloadAsZipError(true);
}
})
.catch(() => this.downloadAsZipError(true));
} else {
this.highlightUnselectedAttachments(true);
}
@ -626,14 +552,14 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
* @returns {void}
*/
showImages() {
MessageUserStore.message() && MessageUserStore.message().showExternalImages();
currentMessage().showExternalImages();
}
/**
* @returns {string}
*/
printableCheckedMessageCount() {
const cnt = MessageUserStore.listCheckedOrSelectedUidsWithSubMails().length;
const cnt = MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails().length;
return 0 < cnt ? (100 > cnt ? cnt : '99+') : '';
}
@ -642,24 +568,69 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
* @returns {void}
*/
readReceipt() {
let oMessage = MessageUserStore.message()
if (oMessage && oMessage.readReceipt()) {
Remote.sendReadReceiptMessage(
()=>0,
oMessage.folder,
oMessage.uid,
oMessage.readReceipt(),
i18n('READ_RECEIPT/SUBJECT', { SUBJECT: oMessage.subject() }),
i18n('READ_RECEIPT/BODY', { 'READ-RECEIPT': AccountUserStore.email() })
);
let oMessage = currentMessage()
if (oMessage.readReceipt()) {
Remote.request('SendReadReceiptMessage', null, {
MessageFolder: oMessage.folder,
MessageUid: oMessage.uid,
ReadReceipt: oMessage.readReceipt(),
Subject: i18n('READ_RECEIPT/SUBJECT', { SUBJECT: oMessage.subject() }),
Text: i18n('READ_RECEIPT/BODY', { 'READ-RECEIPT': AccountUserStore.email() })
});
oMessage.isReadReceipt(true);
oMessage.flags.push('$mdnsent');
// oMessage.flags.valueHasMutated();
MessageFlagsCache.store(oMessage);
rl.app.reloadFlagsCurrentMessageListAndMessageFromCache();
MessagelistUserStore.reloadFlagsAndCachedMessage();
}
}
}
export { MessageViewMailBoxUserView };
pgpDecrypt() {
const oMessage = currentMessage();
PgpUserStore.decrypt(oMessage).then(result => {
if (result) {
oMessage.pgpDecrypted(true);
if (result.data) {
MimeToMessage(result.data, oMessage);
oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain();
if (result.signatures && result.signatures.length) {
oMessage.pgpSigned(true);
oMessage.pgpVerified({
signatures: result.signatures,
success: !!result.signatures.length
});
}
}
}
});
}
pgpVerify(/*self, event*/) {
const oMessage = currentMessage()/*, ctrl = event.target.closest('.openpgp-control')*/;
PgpUserStore.verify(oMessage).then(result => {
if (result) {
oMessage.pgpVerified(result);
}
/*
if (result && result.success) {
i18n('OPENPGP/GOOD_SIGNATURE', {
USER: validKey.user + ' (' + validKey.id + ')'
});
message.getText()
} else {
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
additional = keyIds
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
: '';
i18n('OPENPGP/ERROR', {
ERROR: 'message'
}) + (additional ? ' (' + additional + ')' : '');
}
*/
});
}
}

View file

@ -1,10 +0,0 @@
import { AbstractSystemDropDownUserView } from 'View/User/AbstractSystemDropDown';
class SystemDropDownMailBoxUserView extends AbstractSystemDropDownUserView
{
constructor() {
super('User/MailBox/SystemDropDown');
}
}
export { SystemDropDownMailBoxUserView };

View file

@ -1,36 +1,23 @@
import { leftPanelDisabled } from 'Common/Globals';
import { settings, mailbox } from 'Common/Links';
import { getFolderInboxName } from 'Common/Cache';
import { AbstractViewLeft } from 'Knoin/AbstractViews';
import { ThemeStore } from 'Stores/Theme';
export class MenuSettingsUserView extends AbstractViewLeft {
export class SettingsMenuUserView extends AbstractViewLeft {
/**
* @param {Object} screen
*/
constructor(screen) {
super('User/Settings/Menu', 'SettingsMenu');
this.leftPanelDisabled = leftPanelDisabled;
super();
this.menu = screen.menu;
}
onBuild(dom) {
dom.addEventListener('click', event =>
ThemeStore.isMobile()
&& event.target.closestWithin('.b-settins-left nav a', dom)
&& leftPanelDisabled(true)
);
}
link(route) {
return settings(route);
}
backToMailBoxClick() {
rl.route.setHash(mailbox(getFolderInboxName()));
hasher.setHash(mailbox(getFolderInboxName()));
}
}

View file

@ -7,10 +7,11 @@ import { ThemeStore } from 'Stores/Theme';
import { AbstractViewRight } from 'Knoin/AbstractViews';
export class PaneSettingsUserView extends AbstractViewRight {
export class SettingsPaneUserView extends AbstractViewRight {
constructor() {
super('User/Settings/Pane', 'SettingsPane');
super();
this.isMobile = ThemeStore.isMobile;
this.leftPanelDisabled = leftPanelDisabled;
}
@ -18,25 +19,13 @@ export class PaneSettingsUserView extends AbstractViewRight {
MessageUserStore.message(null);
}
hideLeft(item, event) {
event.preventDefault();
event.stopPropagation();
leftPanelDisabled(true);
}
showLeft(item, event) {
event.preventDefault();
event.stopPropagation();
leftPanelDisabled(false);
}
onBuild(dom) {
dom.addEventListener('click', () => ThemeStore.isMobile() && leftPanelDisabled(true));
dom.addEventListener('click', () =>
ThemeStore.isMobile() && !event.target.closestWithin('.toggleLeft', dom) && leftPanelDisabled(true)
);
}
backToMailBoxClick() {
rl.route.setHash(mailbox(getFolderInboxName()));
hasher.setHash(mailbox(getFolderInboxName()));
}
}

View file

@ -1,10 +0,0 @@
import { AbstractSystemDropDownUserView } from 'View/User/AbstractSystemDropDown';
class SystemDropDownSettingsUserView extends AbstractSystemDropDownUserView
{
constructor() {
super('User/Settings/SystemDropDown');
}
}
export { SystemDropDownSettingsUserView };

View file

@ -0,0 +1,143 @@
import { AppUserStore } from 'Stores/User/App';
import { AccountUserStore } from 'Stores/User/Account';
import { MessageUserStore } from 'Stores/User/Message';
//import { FolderUserStore } from 'Stores/User/Folder';
import { Scope } from 'Common/Enums';
import { settings } from 'Common/Links';
import { showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewRight } from 'Knoin/AbstractViews';
import { KeyboardShortcutsHelpPopupView } from 'View/Popup/KeyboardShortcutsHelp';
import { AccountPopupView } from 'View/Popup/Account';
import { ContactsPopupView } from 'View/Popup/Contacts';
import { doc, leftPanelDisabled, fireEvent, SettingsCapa, registerShortcut } from 'Common/Globals';
import { ThemeStore } from 'Stores/Theme';
import Remote from 'Remote/User/Fetch';
import { getNotification } from 'Common/Translator';
//import { clearCache } from 'Common/Cache';
//import { koComputable } from 'External/ko';
export class SystemDropDownUserView extends AbstractViewRight {
constructor() {
super();
this.allowAccounts = SettingsCapa('AdditionalAccounts');
this.accountEmail = AccountUserStore.email;
this.accounts = AccountUserStore.accounts;
this.accountsLoading = AccountUserStore.loading;
/*
this.accountsUnreadCount = : koComputable(() => 0);
this.accountsUnreadCount = : koComputable(() => AccountUserStore.accounts().reduce((result, item) => result + item.count(), 0));
*/
this.addObservables({
currentAudio: '',
accountMenuDropdownTrigger: false
});
this.allowContacts = AppUserStore.allowContacts();
addEventListener('audio.stop', () => this.currentAudio(''));
addEventListener('audio.start', e => this.currentAudio(e.detail));
}
stopPlay() {
fireEvent('audio.api.stop');
}
accountClick(account, event) {
let email = account && account.email;
if (email && 0 === event.button && AccountUserStore.email() != email) {
AccountUserStore.loading(true);
event.preventDefault();
event.stopPropagation();
Remote.request('AccountSwitch',
(iError/*, oData*/) => {
if (iError) {
AccountUserStore.loading(false);
alert(getNotification(iError).replace('%EMAIL%', email));
if (account.isAdditional()) {
showScreenPopup(AccountPopupView, [account]);
}
} else {
/* // Not working yet
forEachObjectEntry(oData.Result, (key, value) => rl.settings.set(key, value));
clearCache();
// MessageUserStore.setMessage();
// MessageUserStore.purgeMessageBodyCache();
// MessageUserStore.hideMessageBodies();
MessagelistUserStore([]);
// FolderUserStore.folderList([]);
loadFolders(value => {
if (value) {
// 4. Change to INBOX = reload MessageList
// MessagelistUserStore.setMessageList();
}
});
AccountUserStore.loading(false);
*/
rl.route.reload();
}
}, {Email:email}
);
}
return true;
}
emailTitle() {
return AccountUserStore.email();
}
settingsClick() {
hasher.setHash(settings());
}
settingsHelp() {
showScreenPopup(KeyboardShortcutsHelpPopupView);
}
addAccountClick() {
this.allowAccounts && showScreenPopup(AccountPopupView);
}
contactsClick() {
this.allowContacts && showScreenPopup(ContactsPopupView);
}
toggleLayout()
{
const mobile = !ThemeStore.isMobile();
doc.cookie = 'rllayout=' + (mobile ? 'mobile' : 'desktop') + '; samesite=strict';
ThemeStore.isMobile(mobile);
leftPanelDisabled(mobile);
}
logoutClick() {
rl.app.logout();
}
onBuild() {
registerShortcut('m', '', [Scope.MessageList, Scope.MessageView, Scope.Settings], () => {
if (!this.viewModelDom.hidden) {
MessageUserStore.fullScreen(false);
this.accountMenuDropdownTrigger(true);
return false;
}
});
// shortcuts help
registerShortcut('?,f1,help', '', [Scope.MessageList, Scope.MessageView, Scope.Settings], () => {
if (!this.viewModelDom.hidden) {
showScreenPopup(KeyboardShortcutsHelpPopupView);
return false;
}
});
}
}