Merge branch 'master' into plugin-hooks

# Conflicts:
#	dev/View/User/Login.js
This commit is contained in:
djmaze 2021-04-15 16:58:59 +02:00
commit 2ab42b1f03
547 changed files with 29045 additions and 52993 deletions

View file

@ -79,7 +79,6 @@ import { AbstractApp } from 'App/Abstract';
import { ComposePopupView } from 'View/Popup/Compose';
import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
import { AskPopupView } from 'View/Popup/Ask';
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
// Every 5 minutes
const refreshFolders = 300000;
@ -187,16 +186,18 @@ class AppUser extends AbstractApp {
}
MessageUserStore.listLoading(false);
},
FolderUserStore.currentFolderFullNameRaw(),
iOffset,
SettingsUserStore.messagesPerPage(),
MessageUserStore.listSearch(),
MessageUserStore.listThreadUid()
{
Folder: FolderUserStore.currentFolderFullNameRaw(),
Offset: iOffset,
Limit: SettingsUserStore.messagesPerPage(),
Search: MessageUserStore.listSearch(),
ThreadUid: MessageUserStore.listThreadUid()
}
);
}
recacheInboxMessageList() {
Remote.messageList(()=>{}, getFolderInboxName(), 0, SettingsUserStore.messagesPerPage(), '', '', true);
Remote.messageList(null, {Folder: getFolderInboxName()}, true);
}
/**
@ -910,136 +911,124 @@ class AppUser extends AbstractApp {
rl.setWindowTitle();
if (SettingsGet('Auth')) {
if (
Settings.capa(Capa.TwoFactor) &&
Settings.capa(Capa.TwoFactorForce) &&
SettingsGet('RequireTwoFactor')
) {
this.bootend();
showScreenPopup(TwoFactorConfigurationPopupView, [true]);
} else {
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
rl.setWindowTitle(i18n('GLOBAL/LOADING'));
// require.ensure([], function() { // require code splitting
this.foldersReload(value => {
try {
this.bootend();
this.foldersReload(value => {
try {
this.bootend();
if (value) {
if (startupUrl) {
rl.route.setHash(root(startupUrl), true);
}
if (window.crypto && crypto.getRandomValues && Settings.capa(Capa.OpenPGP)) {
const openpgpCallback = () => {
if (!window.openpgp) {
return false;
}
PgpUserStore.openpgp = openpgp;
if (window.Worker) {
try {
PgpUserStore.openpgp.initWorker({ path: openPgpWorkerJs() });
} catch (e) {
console.error(e);
}
}
PgpUserStore.openpgpKeyring = new openpgp.Keyring();
PgpUserStore.capaOpenPGP(true);
this.reloadOpenPgpKeys();
return true;
};
if (!openpgpCallback()) {
const script = createElement('script', {src:openPgpJs()});
script.onload = openpgpCallback;
script.onerror = () => console.error(script.src);
doc.head.append(script);
}
} else {
PgpUserStore.capaOpenPGP(false);
}
startScreens([
MailBoxUserScreen,
Settings.capa(Capa.Settings) ? SettingsUserScreen : null
// false ? AboutUserScreen : null
]);
setInterval(() => {
const cF = FolderUserStore.currentFolderFullNameRaw(),
iF = getFolderInboxName();
this.folderInformation(iF);
if (iF !== cF) {
this.folderInformation(cF);
}
this.folderInformationMultiply();
}, refreshFolders);
// Every 15 minutes
setInterval(this.quota, 900000);
// Every 20 minutes
setInterval(this.foldersReload, 1200000);
setTimeout(this.contactsSync, 10000);
contactsSyncInterval = 5 <= contactsSyncInterval ? contactsSyncInterval : 20;
contactsSyncInterval = 320 >= contactsSyncInterval ? contactsSyncInterval : 320;
setInterval(this.contactsSync, contactsSyncInterval * 60000 + 5000);
this.accountsAndIdentities(true);
setTimeout(() => {
const cF = FolderUserStore.currentFolderFullNameRaw();
if (getFolderInboxName() !== cF) {
this.folderInformation(cF);
}
this.folderInformationMultiply(true);
}, 1000);
setTimeout(this.quota, 5000);
setTimeout(() => Remote.appDelayStart(()=>{}), 35000);
// When auto-login is active
if (
!!SettingsGet('AccountSignMe') &&
navigator.registerProtocolHandler &&
Settings.capa(Capa.Composer)
) {
setTimeout(() => {
try {
navigator.registerProtocolHandler(
'mailto',
location.protocol + '//' + location.host + location.pathname + '?mailto&to=%s',
(SettingsGet('Title') || 'SnappyMail')
);
} catch (e) {} // eslint-disable-line no-empty
if (SettingsGet('MailToEmail')) {
mailToHelper(SettingsGet('MailToEmail'));
}
}, 500);
}
['touchstart','mousedown','mousemove','keydown'].forEach(
t => doc.addEventListener(t, SettingsUserStore.delayLogout, {passive:true})
);
SettingsUserStore.delayLogout();
setTimeout(() => this.initVerticalLayoutResizer(), 1);
} else {
this.logout();
if (value) {
if (startupUrl) {
rl.route.setHash(root(startupUrl), true);
}
} catch (e) {
console.error(e);
}
});
// }); // require code splitting
}
if (window.crypto && crypto.getRandomValues && Settings.capa(Capa.OpenPGP)) {
const openpgpCallback = () => {
if (!window.openpgp) {
return false;
}
PgpUserStore.openpgp = openpgp;
if (window.Worker) {
try {
PgpUserStore.openpgp.initWorker({ path: openPgpWorkerJs() });
} catch (e) {
console.error(e);
}
}
PgpUserStore.openpgpKeyring = new openpgp.Keyring();
PgpUserStore.capaOpenPGP(true);
this.reloadOpenPgpKeys();
return true;
};
if (!openpgpCallback()) {
const script = createElement('script', {src:openPgpJs()});
script.onload = openpgpCallback;
script.onerror = () => console.error(script.src);
doc.head.append(script);
}
} else {
PgpUserStore.capaOpenPGP(false);
}
startScreens([
MailBoxUserScreen,
Settings.capa(Capa.Settings) ? SettingsUserScreen : null
// false ? AboutUserScreen : null
]);
setInterval(() => {
const cF = FolderUserStore.currentFolderFullNameRaw(),
iF = getFolderInboxName();
this.folderInformation(iF);
if (iF !== cF) {
this.folderInformation(cF);
}
this.folderInformationMultiply();
}, refreshFolders);
// Every 15 minutes
setInterval(this.quota, 900000);
// Every 20 minutes
setInterval(this.foldersReload, 1200000);
setTimeout(this.contactsSync, 10000);
contactsSyncInterval = 5 <= contactsSyncInterval ? contactsSyncInterval : 20;
contactsSyncInterval = 320 >= contactsSyncInterval ? contactsSyncInterval : 320;
setInterval(this.contactsSync, contactsSyncInterval * 60000 + 5000);
this.accountsAndIdentities(true);
setTimeout(() => {
const cF = FolderUserStore.currentFolderFullNameRaw();
if (getFolderInboxName() !== cF) {
this.folderInformation(cF);
}
this.folderInformationMultiply(true);
}, 1000);
setTimeout(this.quota, 5000);
setTimeout(() => Remote.appDelayStart(()=>{}), 35000);
// When auto-login is active
if (
!!SettingsGet('AccountSignMe') &&
navigator.registerProtocolHandler &&
Settings.capa(Capa.Composer)
) {
setTimeout(() => {
try {
navigator.registerProtocolHandler(
'mailto',
location.protocol + '//' + location.host + location.pathname + '?mailto&to=%s',
(SettingsGet('Title') || 'SnappyMail')
);
} catch (e) {} // eslint-disable-line no-empty
if (SettingsGet('MailToEmail')) {
mailToHelper(SettingsGet('MailToEmail'));
}
}, 500);
}
['touchstart','mousedown','mousemove','keydown'].forEach(
t => doc.addEventListener(t, SettingsUserStore.delayLogout, {passive:true})
);
SettingsUserStore.delayLogout();
setTimeout(() => this.initVerticalLayoutResizer(), 1);
} else {
this.logout();
}
} catch (e) {
console.error(e);
}
});
} else {
this.bootend();
startScreens([LoginUserScreen]);

View file

@ -4,8 +4,6 @@
* @enum {string}
*/
export const Capa = {
TwoFactor: 'TWO_FACTOR',
TwoFactorForce: 'TWO_FACTOR_FORCE',
OpenPGP: 'OPEN_PGP',
Prefetch: 'PREFETCH',
Composer: 'COMPOSER',
@ -86,9 +84,6 @@ export const Notification = {
DomainNotAllowed: 109,
AccountNotAllowed: 110,
AccountTwoFactorAuthRequired: 120,
AccountTwoFactorAuthError: 121,
ContactsSyncError: 140,
CantGetMessageList: 201,

View file

@ -14,6 +14,22 @@ export const FolderType = {
User: 99
};
/**
* @enum {string}
*/
export const FolderSortMode = {
DateDesc: '', // default 'REVERSE DATE'
DateAsc: 'DATE',
FromDesc: 'REVERSE FROM',
FromAsc: 'FROM',
SizeDesc: 'REVERSE SIZE',
SizeAsc: 'SIZE',
SubjectDesc: 'REVERSE SUBJECT',
SubjectAsc: 'SUBJECT'
// ToDesc: 'REVERSE TO',
// ToAsc: 'TO',
};
/**
* @enum {string}
*/

View file

@ -11,113 +11,109 @@ const
sizes = ['B', 'KiB', 'MiB', 'GiB', 'TiB'],
exts = {
eml: 'message/rfc822',
mime: 'message/rfc822',
rtx: 'text/richtext',
vcard: 'text/vcard',
vcf: 'text/vcard',
htm: 'text/html',
html: 'text/html',
csv: 'text/csv',
ics: 'text/calendar',
ifb: 'text/calendar',
xml: 'text/xml',
json: app+'json',
p10: app+'pkcs10',
p7c: app+'pkcs7-mime',
p7m: app+'pkcs7-mime',
p7s: app+'pkcs7-signature',
torrent: app+'x-bittorrent',
eml: 'message/rfc822',
mime: 'message/rfc822',
vcard: 'text/vcard',
vcf: 'text/vcard',
htm: 'text/html',
html: 'text/html',
csv: 'text/csv',
ics: 'text/calendar',
xml: 'text/xml',
json: app+'json',
asc: app+'pgp-signature',
p10: app+'pkcs10',
p7c: app+'pkcs7-mime',
p7m: app+'pkcs7-mime',
p7s: app+'pkcs7-signature',
torrent: app+'x-bittorrent',
// scripts
js: app+'javascript',
pl: 'text/perl',
css: 'text/css',
asp: 'text/asp',
php: app+'x-httpd-php',
phtml: app+'x-httpd-php',
// scripts
js: app+'javascript',
pl: 'text/perl',
css: 'text/css',
asp: 'text/asp',
php: app+'x-php',
// images
jpg: 'image/jpeg',
ico: 'image/x-icon',
tif: 'image/tiff',
svg: 'image/svg+xml',
svgz: 'image/svg+xml',
// images
jpg: 'image/jpeg',
ico: 'image/x-icon',
tif: 'image/tiff',
svg: 'image/svg+xml',
svgz: 'image/svg+xml',
// archives
zip: app+'zip',
'7z': app+'x-7z-compressed',
rar: app+'x-rar-compressed',
cab: app+'vnd.ms-cab-compressed',
gz: app+'x-gzip',
tgz: app+'x-gzip',
bz: app+'x-bzip',
bz2: app+'x-bzip2',
deb: app+'x-debian-package',
// archives
zip: app+'zip',
'7z': app+'x-7z-compressed',
rar: app+'x-rar-compressed',
cab: app+'vnd.ms-cab-compressed',
gz: app+'x-gzip',
tgz: app+'x-gzip',
bz: app+'x-bzip',
bz2: app+'x-bzip2',
deb: app+'x-debian-package',
// audio
mp3: 'audio/mpeg',
wav: 'audio/x-wav',
mp4a: 'audio/mp4',
weba: 'audio/webm',
m3u: 'audio/x-mpegurl',
// audio
mp3: 'audio/mpeg',
wav: 'audio/x-wav',
mp4a: 'audio/mp4',
weba: 'audio/webm',
m3u: 'audio/x-mpegurl',
// video
qt: 'video/quicktime',
mov: 'video/quicktime',
wmv: 'video/windows-media',
avi: 'video/x-msvideo',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
mp4v: 'video/mp4',
mpg4: 'video/mp4',
ogv: 'video/ogg',
m4v: 'video/x-m4v',
asf: 'video/x-ms-asf',
asx: 'video/x-ms-asf',
wm: 'video/x-ms-wm',
wmx: 'video/x-ms-wmx',
wvx: 'video/x-ms-wvx',
movie: 'video/x-sgi-movie',
// video
qt: 'video/quicktime',
mov: 'video/quicktime',
wmv: 'video/windows-media',
avi: 'video/x-msvideo',
'3gp': 'video/3gpp',
'3g2': 'video/3gpp2',
mp4v: 'video/mp4',
mpg4: 'video/mp4',
ogv: 'video/ogg',
m4v: 'video/x-m4v',
asf: 'video/x-ms-asf',
asx: 'video/x-ms-asf',
wm: 'video/x-ms-wm',
wmx: 'video/x-ms-wmx',
wvx: 'video/x-ms-wvx',
movie: 'video/x-sgi-movie',
// adobe
pdf: app+'pdf',
psd: 'image/vnd.adobe.photoshop',
ai: app+'postscript',
eps: app+'postscript',
ps: app+'postscript',
// adobe
pdf: app+'pdf',
psd: 'image/vnd.adobe.photoshop',
ai: app+'postscript',
eps: app+'postscript',
ps: app+'postscript',
// ms office
doc: app+'msword',
rtf: app+'rtf',
xls: app+'vnd.ms-excel',
ppt: app+'vnd.ms-powerpoint',
docx: msOffice+'wordprocessingml.document',
xlsx: msOffice+'spreadsheetml.sheet',
dotx: msOffice+'wordprocessingml.template',
pptx: msOffice+'presentationml.presentation',
// ms office
doc: app+'msword',
rtf: app+'rtf',
xls: app+'vnd.ms-excel',
ppt: app+'vnd.ms-powerpoint',
docx: msOffice+'wordprocessingml.document',
xlsx: msOffice+'spreadsheetml.sheet',
dotx: msOffice+'wordprocessingml.template',
pptx: msOffice+'presentationml.presentation',
// open office
odt: openDoc+'text',
ods: openDoc+'spreadsheet',
odp: openDoc+'presentation'
};
// open office
odt: openDoc+'text',
ods: openDoc+'spreadsheet',
odp: openDoc+'presentation'
};
export const FileType = {
Unknown: 'unknown',
Text: 'text',
Html: 'html',
Code: 'code',
Eml: 'eml',
WordText: 'word-text',
Word: 'word',
Pdf: 'pdf',
Image: 'image',
Audio: 'audio',
Video: 'video',
Sheet: 'sheet',
Spreadsheet: 'spreadsheet',
Presentation: 'presentation',
Certificate: 'certificate',
CertificateBin: 'certificate-bin',
Archive: 'archive'
};
@ -138,7 +134,7 @@ export const FileInfo = {
return app + 'ms-tnef';
}
let ext = fileName.split('.').pop();
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf|asc)$/.test(ext))
if (/^(txt|text|def|list|in|ini|log|sql|cfg|conf)$/.test(ext))
return 'text/plain';
if (/^(mpe?g|mpe|m1v|m2v)$/.test(ext))
return 'video/mpeg';
@ -186,15 +182,12 @@ export const FileInfo = {
case 'video' == mimeTypeParts[0] || 'mkv' == ext || 'avi' == ext:
result = FileType.Video;
break;
case ['php', 'js', 'css'].includes(ext):
case ['php', 'js', 'css', 'xml', 'html'].includes(ext) || 'text/html' == mimeType:
result = FileType.Code;
break;
case 'eml' == ext || ['message/delivery-status', 'message/rfc822'].includes(mimeType):
result = FileType.Eml;
break;
case 'text/html' == mimeType || 'html' == ext:
result = FileType.Html;
break;
case 'text' == mimeTypeParts[0] || 'txt' == ext || 'log' == ext:
result = FileType.Text;
break;
@ -204,19 +197,17 @@ export const FileInfo = {
case 'pdf' == type || 'pdf' == ext:
result = FileType.Pdf;
break;
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType) ||
['asc', 'pem', 'ppk'].includes(ext):
case [app+'pgp-signature', app+'pgp-keys'].includes(mimeType)
|| ['asc', 'pem', 'ppk'].includes(ext)
|| [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
result = FileType.Certificate;
break;
case [app+'pkcs7-signature'].includes(mimeType) || 'p7s' == ext:
result = FileType.CertificateBin;
break;
case match(msOffice+'.wordprocessingml') || match(openDoc+'.text') || match('vnd.ms-word')
|| ['rtf', 'msword', 'vnd.msword'].includes(type):
result = FileType.WordText;
result = FileType.Word;
break;
case match(msOffice+'.spreadsheetml') || match(openDoc+'.spreadsheet') || match('ms-excel'):
result = FileType.Sheet;
result = FileType.Spreadsheet;
break;
case match(msOffice+'.presentationml') || match(openDoc+'.presentation') || match('ms-powerpoint'):
result = FileType.Presentation;
@ -232,45 +223,23 @@ export const FileInfo = {
* @returns {string}
*/
getTypeIconClass: fileType => {
let result = ['icon-file', ''];
let result = 'icon-file';
switch (fileType) {
case FileType.Text:
case FileType.Eml:
case FileType.WordText:
result[0] += '-text';
break;
case FileType.Html:
case FileType.Code:
result[0] += '-code';
break;
case FileType.Image:
result[0] += '-image';
break;
case FileType.Audio:
result[0] += '-music';
break;
case FileType.Video:
result[0] += '-movie';
break;
case FileType.Archive:
result[0] += '-zip';
break;
case FileType.Certificate:
case FileType.CertificateBin:
result[0] += '-certificate';
break;
case FileType.Sheet:
result[0] += '-excel';
break;
case FileType.Presentation:
result[0] += '-chart-graph';
break;
case FileType.Pdf:
result = ['icon-none', 'pdf'];
break;
// no default
case FileType.Word:
return result + '-text';
case FileType.Code:
case FileType.Image:
case FileType.Audio:
case FileType.Video:
case FileType.Archive:
case FileType.Certificate:
case FileType.Spreadsheet:
case FileType.Presentation:
return result + '-' + fileType;
}
return result;
},
@ -283,7 +252,7 @@ export const FileInfo = {
getCombinedIconClass: data => {
if (isNonEmptyArray(data)) {
let icons = data
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1])[0] : '')
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item[0]), item[1]) : '')
.validUnique();
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
@ -297,7 +266,7 @@ export const FileInfo = {
friendlySize: bytes => {
bytes = parseInt(bytes, 10) || 0;
let i = Math.floor(Math.log(bytes) / Math.log(1024));
return (bytes / Math.pow(1024, i)).toFixed(2>i ? 0 : 1) + sizes[i];
return (bytes / Math.pow(1024, i)).toFixed(2>i ? 0 : 1) + ' ' + sizes[i];
}
};

View file

@ -179,12 +179,13 @@ export function mailBox(folder, page = 1, search = '', threadUid = '') {
}
if (1 < page) {
result = result.replace(/[/]+$/, '') + '/p' + page;
result = result.replace(/\/+$/, '') + '/p' + page;
}
if (search) {
result = result.replace(/[/]+$/, '') + '/' + encodeURI(search);
result = result.replace(/\/+$/, '') + '/' + encodeURI(search);
}
return result;
}

View file

@ -3,7 +3,7 @@ import { Notification, UploadErrorCode } from 'Common/Enums';
import { langLink } from 'Common/Links';
import { doc, createElement } from 'Common/Globals';
let I18N_DATA = window.rainloopI18N || {};
let I18N_DATA = window.snappymailI18N || {};
export const trigger = ko.observable(false);
@ -14,14 +14,16 @@ export const trigger = ko.observable(false);
* @returns {string}
*/
export function i18n(key, valueList, defaulValue) {
let result = I18N_DATA[key] || defaulValue || key;
let path = key.split('/');
if (!I18N_DATA[path[0]] || !path[1]) {
return defaulValue || key;
}
let result = I18N_DATA[path[0]][path[1]] || defaulValue || key;
if (valueList) {
Object.entries(valueList).forEach(([key, value]) => {
result = result.replace('%' + key + '%', value);
});
}
return result;
}
@ -75,7 +77,7 @@ function getNotificationMessage(code) {
let key = getKeyByValue(Notification, code);
if (key) {
key = i18nKey(key).replace('_NOTIFICATION', '_ERROR');
return I18N_DATA['NOTIFICATIONS/' + key];
return I18N_DATA.NOTIFICATIONS[key];
}
}
@ -125,13 +127,13 @@ export function reload(admin, language) {
const script = createElement('script');
script.onload = () => {
// reload the data
if (window.rainloopI18N) {
I18N_DATA = window.rainloopI18N || {};
if (window.snappymailI18N) {
I18N_DATA = window.snappymailI18N;
i18nToNodes(doc);
dispatchEvent(new CustomEvent('reload-time'));
trigger(!trigger());
}
window.rainloopI18N = null;
window.snappymailI18N = null;
script.remove();
resolve();
};
@ -150,7 +152,7 @@ export function reload(admin, language) {
*/
export function convertLangName(language, isEng = false) {
return i18n(
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/LANG_' + language.toUpperCase().replace(/[^a-zA-Z0-9]+/g, '_'),
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language,
null,
language
);

View file

@ -27,7 +27,6 @@ class AbstractCheckbox extends AbstractComponent {
this.inline = !!params.inline;
this.labeled = undefined !== params.label;
this.labelAnimated = !!params.labelAnimated;
}
click() {

View file

@ -10,8 +10,6 @@ import { AbstractModel } from 'Knoin/AbstractModel';
import { SMAudio } from 'Common/Audio';
const bAllowPdfPreview = undefined !== navigator.mimeTypes['application/pdf'];
export class AttachmentModel extends AbstractModel {
constructor() {
super();
@ -92,27 +90,21 @@ export class AttachmentModel extends AbstractModel {
* @returns {boolean}
*/
isText() {
return (
FileType.Text === this.fileType ||
FileType.Eml === this.fileType ||
FileType.Certificate === this.fileType ||
FileType.Html === this.fileType ||
FileType.Code === this.fileType
);
return FileType.Text === this.fileType || FileType.Eml === this.fileType;
}
/**
* @returns {boolean}
*/
isPdf() {
return FileType.Pdf === this.fileType;
pdfPreview() {
return null != navigator.mimeTypes['application/pdf'] && FileType.Pdf === this.fileType;
}
/**
* @returns {boolean}
*/
hasPreview() {
return this.isImage() || (this.isPdf() && bAllowPdfPreview) || this.isText();
return this.isImage() || this.pdfPreview() || this.isText();
}
/**
@ -155,13 +147,6 @@ export class AttachmentModel extends AbstractModel {
return link ? 'background:url(' + link + ')' : '';
}
/**
* @returns {string}
*/
linkPreviewAsPlain() {
return serverRequestRaw('ViewAsPlain', this.download);
}
/**
* @returns {string}
*/
@ -169,11 +154,11 @@ export class AttachmentModel extends AbstractModel {
let result = '';
switch (true) {
case this.isImage():
case this.isPdf() && bAllowPdfPreview:
case this.pdfPreview():
result = this.linkPreview();
break;
case this.isText():
result = this.linkPreviewAsPlain();
result = serverRequestRaw('ViewAsPlain', this.download);
break;
// no default
}
@ -181,18 +166,6 @@ export class AttachmentModel extends AbstractModel {
return result;
}
/**
* @returns {string}
*/
generateTransferDownloadUrl() {
let link = this.linkDownload();
if ('http' !== link.substr(0, 4)) {
link = location.protocol + '//' + location.host + location.pathname + link;
}
return this.mimeType + ':' + this.fileName + ':' + link;
}
/**
* @param {AttachmentModel} attachment
* @param {*} event
@ -201,7 +174,11 @@ export class AttachmentModel extends AbstractModel {
eventDragStart(attachment, event) {
const localEvent = event.originalEvent || event;
if (attachment && localEvent && localEvent.dataTransfer && localEvent.dataTransfer.setData) {
localEvent.dataTransfer.setData('DownloadURL', this.generateTransferDownloadUrl());
let link = this.linkDownload();
if ('http' !== link.substr(0, 4)) {
link = location.protocol + '//' + location.host + location.pathname + link;
}
localEvent.dataTransfer.setData('DownloadURL', this.mimeType + ':' + this.fileName + ':' + link);
}
return true;
@ -211,13 +188,6 @@ export class AttachmentModel extends AbstractModel {
* @returns {string}
*/
iconClass() {
return FileInfo.getTypeIconClass(this.fileType)[0];
}
/**
* @returns {string}
*/
iconText() {
return FileInfo.getTypeIconClass(this.fileType)[1];
return FileInfo.getTypeIconClass(this.fileType);
}
}

View file

@ -96,13 +96,6 @@ export class ComposeAttachmentModel extends AbstractModel {
* @returns {string}
*/
iconClass() {
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[0];
}
/**
* @returns {string}
*/
iconText() {
return FileInfo.getIconClass(this.fileExt(), this.mimeType())[1];
return FileInfo.getIconClass(this.fileExt(), this.mimeType());
}
}

View file

@ -10,6 +10,7 @@ import * as Local from 'Storage/Client';
import { AppUserStore } from 'Stores/User/App';
import { FolderUserStore } from 'Stores/User/Folder';
import { SettingsUserStore } from 'Stores/User/Settings';
import ko from 'ko';
@ -113,6 +114,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
AppUserStore.threadsAllowed(!!(Settings.app('useImapThread') && this.IsThreadsSupported));
FolderUserStore.folderListOptimized(!!this.Optimized);
FolderUserStore.sortSupported(!!this.IsSortSupported);
let update = false;
@ -123,8 +125,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
SettingsGet('DraftFolder') +
SettingsGet('SpamFolder') +
SettingsGet('TrashFolder') +
SettingsGet('ArchiveFolder') +
SettingsGet('NullFolder'))
SettingsGet('ArchiveFolder'))
) {
Settings.set('SentFolder', this.SystemFolders[ServerFolderType.SENT] || null);
Settings.set('DraftFolder', this.SystemFolders[ServerFolderType.DRAFTS] || null);
@ -147,8 +148,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
DraftFolder: FolderUserStore.draftFolder(),
SpamFolder: FolderUserStore.spamFolder(),
TrashFolder: FolderUserStore.trashFolder(),
ArchiveFolder: FolderUserStore.archiveFolder(),
NullFolder: 'NullFolder'
ArchiveFolder: FolderUserStore.archiveFolder()
});
}
@ -301,7 +301,10 @@ export class FolderModel extends AbstractModel {
canBeDeleted: () => !folder.isSystemFolder() && !folder.subFolders.length,
canBeSubscribed: () => !folder.isSystemFolder() && folder.selectable && Settings.app('useImapSubscribe'),
canBeSubscribed: () => !folder.isSystemFolder()
&& SettingsUserStore.hideUnsubscribed()
&& folder.selectable
&& Settings.app('useImapSubscribe'),
canBeSelected: () => !folder.isSystemFolder() && folder.selectable,

View file

@ -45,6 +45,9 @@ export class MessageModel extends AbstractModel {
this.addObservables({
subject: '',
size: 0,
spamScore: 0,
spamResult: '',
isSpam: false,
dateTimeStampInUTC: 0,
priority: MessagePriority.Normal,
@ -116,6 +119,9 @@ export class MessageModel extends AbstractModel {
this._reset();
this.subject('');
this.size(0);
this.spamScore(0);
this.spamResult('');
this.isSpam(false);
this.dateTimeStampInUTC(0);
this.priority(MessagePriority.Normal);
@ -211,10 +217,10 @@ export class MessageModel extends AbstractModel {
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
fromToLine(friendlyView, wrapWithLink = false) {
fromToLine(friendlyView, wrapWithLink) {
return this.from.toString(friendlyView, wrapWithLink);
}
@ -232,37 +238,37 @@ export class MessageModel extends AbstractModel {
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
toToLine(friendlyView, wrapWithLink = false) {
toToLine(friendlyView, wrapWithLink) {
return this.to.toString(friendlyView, wrapWithLink);
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
ccToLine(friendlyView, wrapWithLink = false) {
ccToLine(friendlyView, wrapWithLink) {
return this.cc.toString(friendlyView, wrapWithLink);
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
bccToLine(friendlyView, wrapWithLink = false) {
bccToLine(friendlyView, wrapWithLink) {
return this.bcc.toString(friendlyView, wrapWithLink);
}
/**
* @param {boolean} friendlyView
* @param {boolean=} wrapWithLink = false
* @param {boolean=} wrapWithLink
* @returns {string}
*/
replyToToLine(friendlyView, wrapWithLink = false) {
replyToToLine(friendlyView, wrapWithLink) {
return this.replyTo.toString(friendlyView, wrapWithLink);
}
@ -318,7 +324,7 @@ export class MessageModel extends AbstractModel {
* @param {boolean=} last = false
* @returns {Array}
*/
replyEmails(excludeEmails, last = false) {
replyEmails(excludeEmails, last) {
const result = [],
unic = undefined === excludeEmails ? {} : excludeEmails;
@ -339,7 +345,7 @@ export class MessageModel extends AbstractModel {
* @param {boolean=} last = false
* @returns {Array.<Array>}
*/
replyAllEmails(excludeEmails, last = false) {
replyAllEmails(excludeEmails, last) {
let data = [];
const toResult = [],
ccResult = [],
@ -364,7 +370,7 @@ export class MessageModel extends AbstractModel {
/**
* @param {boolean=} print = false
*/
viewPopupMessage(print = false) {
viewPopupMessage(print) {
const timeStampInUTC = this.dateTimeStampInUTC() || 0,
ccLine = this.ccToLine(false),
m = 0 < timeStampInUTC ? new Date(timeStampInUTC * 1000) : null,
@ -414,6 +420,9 @@ export class MessageModel extends AbstractModel {
this.subject(message.subject());
this.size(message.size());
this.spamScore(message.spamScore());
this.spamResult(message.spamResult());
this.isSpam(message.isSpam());
this.dateTimeStampInUTC(message.dateTimeStampInUTC());
this.priority(message.priority());

View file

@ -55,76 +55,17 @@ class RemoteUserFetch extends AbstractFetchRemote {
* @param {string} sPassword
* @param {boolean} bSignMe
* @param {string=} sLanguage
* @param {string=} sAdditionalCode
* @param {boolean=} bAdditionalCodeSignMe
*/
login(fCallback, sEmail, sPassword, bSignMe, sLanguage, sAdditionalCode, bAdditionalCodeSignMe) {
login(fCallback, sEmail, sPassword, bSignMe, sLanguage) {
this.defaultRequest(fCallback, 'Login', {
Email: sEmail,
Login: '',
Password: sPassword,
Language: sLanguage || '',
AdditionalCode: sAdditionalCode || '',
AdditionalCodeSignMe: bAdditionalCodeSignMe ? 1 : 0,
SignMe: bSignMe ? 1 : 0
});
}
/**
* @param {?Function} fCallback
*/
getTwoFactor(fCallback) {
this.defaultRequest(fCallback, 'GetTwoFactorInfo');
}
/**
* @param {?Function} fCallback
*/
createTwoFactor(fCallback) {
this.defaultRequest(fCallback, 'CreateTwoFactorSecret');
}
/**
* @param {?Function} fCallback
*/
clearTwoFactor(fCallback) {
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
}
/**
* @param {?Function} fCallback
*/
showTwoFactorSecret(fCallback) {
this.defaultRequest(fCallback, 'ShowTwoFactorSecret');
}
/**
* @param {?Function} fCallback
* @param {string} sCode
*/
testTwoFactor(fCallback, sCode) {
this.defaultRequest(fCallback, 'TestTwoFactorInfo', {
Code: sCode
});
}
/**
* @param {?Function} fCallback
* @param {boolean} bEnable
*/
enableTwoFactor(fCallback, bEnable) {
this.defaultRequest(fCallback, 'EnableTwoFactor', {
Enable: bEnable ? 1 : 0
});
}
/**
* @param {?Function} fCallback
*/
clearTwoFactorInfo(fCallback) {
this.defaultRequest(fCallback, 'ClearTwoFactorInfo');
}
/**
* @param {?Function} fCallback
*/
@ -297,54 +238,44 @@ class RemoteUserFetch extends AbstractFetchRemote {
/**
* @param {Function} fCallback
* @param {string} sFolderFullNameRaw
* @param {number=} iOffset = 0
* @param {number=} iLimit = 20
* @param {string=} sSearch = ''
* @param {string=} sThreadUid = ''
* @param {object} params
* @param {boolean=} bSilent = false
*/
messageList(fCallback, sFolderFullNameRaw, iOffset = 0, iLimit = 20, sSearch = '', sThreadUid = '', bSilent = false) {
sFolderFullNameRaw = pString(sFolderFullNameRaw);
const folderHash = getFolderHash(sFolderFullNameRaw),
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads(),
messageList(fCallback, params, bSilent = false) {
const
sFolderFullNameRaw = pString(params.Folder),
folderHash = getFolderHash(sFolderFullNameRaw),
useThreads = AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
inboxUidNext = getFolderInboxName() === sFolderFullNameRaw ? getFolderUidNext(sFolderFullNameRaw) : '';
let params = {}, sGetAdd = '';
params.Folder = sFolderFullNameRaw;
params.ThreadUid = useThreads ? params.ThreadUid : '';
params = Object.assign({
Folder: '',
Offset: 0,
Limit: SettingsUserStore.messagesPerPage(),
Search: '',
UidNext: inboxUidNext,
UseThreads: useThreads,
ThreadUid: '',
Sort: FolderUserStore.sortMode()
}, params);
if (folderHash && (!sSearch || !sSearch.includes('is:'))) {
let sGetAdd = '';
if (folderHash && (!params.Search || !params.Search.includes('is:'))) {
sGetAdd = 'MessageList/' +
SUB_QUERY_PREFIX +
'/' +
urlsafeArray([
sFolderFullNameRaw,
iOffset,
iLimit,
sSearch,
SettingsGet('ProjectHash'),
folderHash,
inboxUidNext,
useThreads ? 1 : 0,
useThreads ? sThreadUid : ''
]);
} else {
params = {
Folder: sFolderFullNameRaw,
Offset: iOffset,
Limit: iLimit,
Search: sSearch,
UidNext: inboxUidNext,
UseThreads: useThreads ? 1 : 0,
ThreadUid: useThreads ? sThreadUid : ''
};
urlsafeArray([SettingsGet('ProjectHash'),folderHash].concat(Object.values(params)));
params = {};
}
this.defaultRequest(
fCallback,
'MessageList',
params,
sSearch ? 300000 : 30000,
30000,
sGetAdd,
bSilent ? [] : ['MessageList']
);
@ -562,7 +493,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
* @param {Object} oData
*/
sendMessage(fCallback, oData) {
this.defaultRequest(fCallback, 'SendMessage', oData, 300000);
this.defaultRequest(fCallback, 'SendMessage', oData, 30000);
}
/**

View file

@ -74,7 +74,7 @@ export class MailBoxUserScreen extends AbstractScreen {
FolderUserStore.currentFolder(folder);
MessageUserStore.listPage(page);
MessageUserStore.listPage(1 > page ? 1 : page);
MessageUserStore.listSearch(search);
MessageUserStore.listThreadUid(threadUid);
@ -119,23 +119,15 @@ export class MailBoxUserScreen extends AbstractScreen {
*/
routes() {
const
fNormS = (request, vals) => {
if (request) {
vals[0] = decodeURI(pString(vals[0]));
vals[1] = pInt(vals[1]);
} else {
vals[0] = getFolderInboxName();
vals[1] = 1;
}
return [vals[0], 1 > vals[1] ? 1 : vals[1], decodeURI(pString(vals[2]))];
},
fNormD = (request, vals) =>
[decodeURI(request ? pString(vals[0]) : getFolderInboxName()), 1, decodeURI(pString(vals[1]))];
folder = (request, vals) => request ? decodeURI(pString(vals[0])) : getFolderInboxName(),
fNormS = (request, vals) => [folder(request, vals), request ? pInt(vals[1]) : 1, decodeURI(pString(vals[2]))];
return [
[/^([^/]*)$/, { 'normalize_': fNormS }],
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { 'normalize_': fNormD }],
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { 'normalize_': fNormS }]
[/^([^/]*)$/, { normalize_: fNormS }],
[/^([a-zA-Z0-9~]+)\/(.+)\/?$/, { normalize_: (request, vals) =>
[folder(request, vals), 1, decodeURI(pString(vals[1]))]
}],
[/^([a-zA-Z0-9~]+)\/p([1-9][0-9]*)(?:\/(.+)\/?)?$/, { normalize_: fNormS }]
];
}
}

View file

@ -64,7 +64,7 @@ export class SettingsUserScreen extends AbstractSettingsScreen {
settingsAddViewModel(FiltersUserSettings, 'SettingsFilters', 'SETTINGS_LABELS/LABEL_FILTERS_NAME', 'filters');
}
if (Settings.capa(Capa.AutoLogout) || Settings.capa(Capa.TwoFactor)) {
if (Settings.capa(Capa.AutoLogout)) {
settingsAddViewModel(SecurityUserSettings, 'SettingsSecurity', 'SETTINGS_LABELS/LABEL_SECURITY_NAME', 'security');
}

View file

@ -19,7 +19,7 @@ export class ContactsAdminSettings {
addObservablesTo(this, {
enableContacts: !!SettingsGet('ContactsEnable'),
contactsSync: !!SettingsGet('ContactsSync'),
contactsType: '',
contactsType: SettingsGet('ContactsPdoType'),
pdoDsn: SettingsGet('ContactsPdoDsn'),
pdoUser: SettingsGet('ContactsPdoUser'),
@ -69,8 +69,6 @@ export class ContactsAdminSettings {
})
.extend({ notify: 'always' });
this.contactsType(SettingsGet('ContactsPdoType'));
addSubscribablesTo(this, {
enableContacts: value =>
Remote.saveAdminConfig(null, {

View file

@ -53,7 +53,11 @@ export class GeneralAdminSettings {
this.weakPassword = rl.app.weakPassword;
// fetch('./data/VERSION?' + Math.random()).then(response => this.dataFolderAccess(response.ok));
/** https://github.com/RainLoop/rainloop-webmail/issues/1924
if (this.weakPassword) {
fetch('./data/VERSION?' + Math.random()).then(response => this.dataFolderAccess(response.ok));
}
*/
this.mainAttachmentLimit = ko
.observable(pInt(SettingsGet('AttachmentLimit')) / (1024 * 1024))

View file

@ -16,10 +16,6 @@ export class SecurityAdminSettings {
verifySslCertificate: !!SettingsGet('VerifySslCertificate'),
allowSelfSigned: !!SettingsGet('AllowSelfSigned'),
isTwoFactorDropperShown: false,
twoFactorDropperUser: '',
twoFactorDropperUserFocused: false,
adminLogin: SettingsGet('AdminLogin'),
adminLoginError: false,
adminPassword: '',
@ -30,9 +26,7 @@ export class SecurityAdminSettings {
adminPasswordUpdateError: false,
adminPasswordUpdateSuccess: false,
capaOpenPGP: Settings.capa(Capa.OpenPGP),
capaTwoFactorAuth: Settings.capa(Capa.TwoFactor),
capaTwoFactorAuthForce: Settings.capa(Capa.TwoFactorForce)
capaOpenPGP: Settings.capa(Capa.OpenPGP)
});
addSubscribablesTo(this, {
@ -60,18 +54,6 @@ export class SecurityAdminSettings {
CapaOpenPGP: value ? 1 : 0
}),
capaTwoFactorAuth: value => {
value || this.capaTwoFactorAuthForce(false);
Remote.saveAdminConfig(null, {
CapaTwoFactorAuth: value ? 1 : 0
});
},
capaTwoFactorAuthForce: value =>
Remote.saveAdminConfig(null, {
CapaTwoFactorAuthForce: value ? 1 : 0
}),
useLocalProxyForExternalImages: value =>
Remote.saveAdminConfig(null, {
UseLocalProxyForExternalImages: value ? 1 : 0
@ -120,15 +102,6 @@ export class SecurityAdminSettings {
return true;
}
showTwoFactorDropper() {
this.twoFactorDropperUser('');
this.isTwoFactorDropperShown(true);
setTimeout(() => {
this.twoFactorDropperUserFocused(true);
}, 50);
}
onNewAdminPasswordResponse(iError, data) {
if (iError) {
this.adminPasswordUpdateError(true);
@ -147,9 +120,5 @@ export class SecurityAdminSettings {
this.adminPassword('');
this.adminPasswordNew('');
this.adminPasswordNew2('');
this.isTwoFactorDropperShown(false);
this.twoFactorDropperUser('');
this.twoFactorDropperUserFocused(false);
}
}

View file

@ -2,6 +2,7 @@ import ko from 'ko';
import { Notification } from 'Common/Enums';
import { ClientSideKeyName } from 'Common/EnumsUser';
import { Settings } from 'Common/Globals';
import { getNotification } from 'Common/Translator';
import { removeFolderFromCacheList } from 'Common/Cache';
@ -9,6 +10,7 @@ import { removeFolderFromCacheList } from 'Common/Cache';
import * as Local from 'Storage/Client';
import { FolderUserStore } from 'Stores/User/Folder';
import { SettingsUserStore } from 'Stores/User/Settings';
import Remote from 'Remote/User/Fetch';
@ -23,6 +25,7 @@ export class FoldersUserSettings {
this.folderList = FolderUserStore.folderList;
this.folderListOptimized = FolderUserStore.folderListOptimized;
this.folderListError = FolderUserStore.folderListError;
this.hideUnsubscribed = SettingsUserStore.hideUnsubscribed;
this.loading = ko.computed(() => {
const loading = FolderUserStore.foldersLoading(),
@ -36,6 +39,9 @@ export class FoldersUserSettings {
this.folderForDeletion = ko.observable(null).deleteAccessHelper();
this.folderForEdit = ko.observable(null).extend({ toggleSubscribeProperty: [this, 'edited'] });
this.useImapSubscribe = Settings.app('useImapSubscribe');
this.hideUnsubscribed.subscribe(value => Remote.saveSetting('HideUnsubscribed', value ? 1 : 0));
}
folderEditOnEnter(folder) {

View file

@ -5,18 +5,13 @@ import { Capa, SaveSettingsStep } from 'Common/Enums';
import { Settings } from 'Common/Globals';
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
import { showScreenPopup } from 'Knoin/Knoin';
import { SettingsUserStore } from 'Stores/User/Settings';
import Remote from 'Remote/User/Fetch';
import { TwoFactorConfigurationPopupView } from 'View/Popup/TwoFactorConfiguration';
export class SecurityUserSettings {
constructor() {
this.capaAutoLogout = Settings.capa(Capa.AutoLogout);
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
this.autoLogout = SettingsUserStore.autoLogout;
this.autoLogoutTrigger = ko.observable(SaveSettingsStep.Idle);
@ -43,8 +38,4 @@ export class SecurityUserSettings {
));
}
}
configureTwoFactor() {
showScreenPopup(TwoFactorConfigurationPopupView);
}
}

View file

@ -1,6 +1,6 @@
import ko from 'ko';
import { FolderType } from 'Common/EnumsUser';
import { FolderType, FolderSortMode } from 'Common/EnumsUser';
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { addObservablesTo, addSubscribablesTo } from 'Common/Utils';
import { folderListOptionsBuilder } from 'Common/UtilsUser';
@ -19,6 +19,12 @@ export const FolderUserStore = new class {
*/
displaySpecSetting: false,
/**
* If the IMAP server supports SORT
*/
sortSupported: false,
// sortMode: '',
sentFolder: '',
draftFolder: '',
spamFolder: '',
@ -36,6 +42,8 @@ export const FolderUserStore = new class {
foldersInboxUnreadCount: 0
});
this.sortMode = ko.observable('').extend({ limitedList: Object.values(FolderSortMode) });
this.namespace = '';
this.folderList = ko.observableArray();

View file

@ -30,6 +30,7 @@ export const SettingsUserStore = new class {
allowDraftAutosave: !!SettingsGet('AllowDraftAutosave'),
useThreads: !!SettingsGet('UseThreads'),
replySameFolder: !!SettingsGet('ReplySameFolder'),
hideUnsubscribed: !!SettingsGet('HideUnsubscribed'),
autoLogout: pInt(SettingsGet('AutoLogout'))
});

View file

@ -42,7 +42,6 @@
@import "Filter.less";
@import "Template.less";
@import "OpenPgpKey.less";
@import "TwoFactor.less";
@import "Identity.less";
@import "AdvancedSearch.less";
@import "Attachmnets.less";

View file

@ -29,11 +29,10 @@
}
.checkboxAttachment {
position: absolute;
top: 0;
right: 0;
padding: 1px 4px 0 5px;
bottom: 6px;
cursor: pointer;
position: absolute;
right: 6px;
}
.attachmentSize {
@ -104,16 +103,6 @@
}
}
.attachmentIconText {
display: inline-block;
font-size: 28px;
width: 60px;
height: 56px;
color: #aaa;
line-height: 56px;
font-style: normal;
}
/*
.attachmentIconParent.hasPreview.isImage {
.iconMain {

View file

@ -18,14 +18,9 @@
}
.attachmentAreaParent {
padding: 10px 10px 6px 10px;
background: #ddd;
border-top: 1px solid #ccc;
overflow-y: auto;
}
.attachmentAreaParent {
padding: 10px 10px 6px 10px;
.no-attachments-desc {
padding: 50px 10px;

View file

@ -54,7 +54,7 @@
}
.controls {
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword, .inputAdditionalCode {
.inputLoginForm, .inputEmail, .inputLogin, .inputPassword {
font-size: 18px;
height: 40px;
line-height: 20px;

View file

@ -6,6 +6,11 @@ html.rl-no-preview-pane {
}
}
#sort-list-dropdown-id {
padding-left: 6px;
padding-right: 6px;
}
.messageList {
.toolbar {
@ -254,9 +259,9 @@ html.rl-no-preview-pane {
padding: 0 10px 0 5px;
}
time {
time, .sizeParent {
margin: 0 5px;
color: #999;
opacity: 0.6;
font-size: 11px;
white-space: nowrap;
}
@ -472,7 +477,7 @@ html .messageList .line-loading {
line-height: 16px;
margin-left: 30px;
order: 1;
width: calc(100% - 60px);
width: calc(100% - 120px);
}
.senderParent {
@ -483,9 +488,13 @@ html .messageList .line-loading {
order: 1;
}
.attachmentParent {
.sizeParent {
order: 2;
}
.attachmentParent {
order: 3;
}
}
}

View file

@ -136,15 +136,8 @@
}
.informationFull {
margin-top: 10px;
border: 1px solid #999;
border-radius: 5px;
padding: 8px 0;
overflow: hidden;
margin-top: 30px;
.size {
margin-left: 4px;
}
table {
width: 100%;
@ -155,7 +148,7 @@
}
td {
padding: 0 10px;
padding: 4px;
vertical-align: top;
min-width: 43px;
}
@ -163,17 +156,15 @@
td:first-child {
border-right: 1px solid #999;
text-align: right;
white-space: nowrap;
width: 1%;
}
.uiLine {
}
.uiLabel {
.labelTo {
white-space: nowrap;
}
.uiLabelValue {
td + td {
word-break: break-all;
}
}
@ -273,13 +264,9 @@
margin: 0;
}
.checkboxAttachment {
display: none;
}
&.selection-mode {
&:not(.selection-mode) {
.checkboxAttachment {
display: inline-block;
display: none;
}
}

View file

@ -6,7 +6,7 @@
}
.list-table {
margin-top: 40px;
margin-top: 1em;
.folder-padding {
display: inline-block;

View file

@ -1,8 +0,0 @@
.b-two-factor-content {
width: 750px;
.modal-body {
min-height: 100px;
}
}

View file

@ -64,8 +64,7 @@ class FolderSystemPopupView extends AbstractViewPopup {
DraftFolder: FolderUserStore.draftFolder(),
SpamFolder: FolderUserStore.spamFolder(),
TrashFolder: FolderUserStore.trashFolder(),
ArchiveFolder: FolderUserStore.archiveFolder(),
NullFolder: 'NullFolder'
ArchiveFolder: FolderUserStore.archiveFolder()
});
}).debounce(1000),
fCallback = () => {

View file

@ -1,193 +0,0 @@
import { Capa } from 'Common/Enums';
import { Settings } from 'Common/Globals';
import { pString } from 'Common/Utils';
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
import Remote from 'Remote/User/Fetch';
import { showScreenPopup } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { TwoFactorTestPopupView } from 'View/Popup/TwoFactorTest';
class TwoFactorConfigurationPopupView extends AbstractViewPopup {
constructor() {
super('TwoFactorConfiguration');
this.addObservables({
lock: false,
processing: false,
clearing: false,
secreting: false,
viewUser: '',
twoFactorStatus: false,
twoFactorTested: false,
viewSecret: '',
viewBackupCodes: '',
viewUrlTitle: '',
viewUrl: '',
viewEnable_: false
});
this.capaTwoFactor = Settings.capa(Capa.TwoFactor);
const fn = iError => iError && this.viewEnable_(false);
this.addComputables({
viewEnable: {
read: this.viewEnable_,
write: (value) => {
value = !!value;
if (value && this.twoFactorTested()) {
this.viewEnable_(value);
Remote.enableTwoFactor(fn, value);
} else {
if (!value) {
this.viewEnable_(value);
}
Remote.enableTwoFactor(fn, false);
}
}
},
viewTwoFactorEnableTooltip: () => {
translatorTrigger();
return this.twoFactorTested() || this.viewEnable_()
? ''
: i18n('POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_TEST_BEFORE_DESC');
},
viewTwoFactorStatus: () => {
translatorTrigger();
return i18n(
this.twoFactorStatus()
? 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_CONFIGURED_DESC'
: 'POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC'
);
},
twoFactorAllowedEnable: () => this.viewEnable() || this.twoFactorTested()
});
this.onResult = this.onResult.bind(this);
this.onShowSecretResult = this.onShowSecretResult.bind(this);
}
showSecret() {
this.secreting(true);
Remote.showTwoFactorSecret(this.onShowSecretResult);
}
hideSecret() {
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrlTitle('');
this.viewUrl('');
}
createTwoFactor() {
this.processing(true);
Remote.createTwoFactor(this.onResult);
}
logout() {
rl.app.logout();
}
testTwoFactor() {
showScreenPopup(TwoFactorTestPopupView, [this.twoFactorTested]);
}
clearTwoFactor() {
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrlTitle('');
this.viewUrl('');
this.twoFactorTested(false);
this.clearing(true);
Remote.clearTwoFactor(this.onResult);
}
onShow(bLock) {
this.lock(!!bLock);
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrlTitle('');
this.viewUrl('');
}
onHide() {
if (this.lock()) {
location.reload();
}
}
getQr() {
return (
'otpauth://totp/' +
encodeURIComponent(this.viewUser()) +
'?secret=' +
encodeURIComponent(this.viewSecret()) +
'&issuer=' +
encodeURIComponent('')
);
}
onResult(iError, oData) {
this.processing(false);
this.clearing(false);
if (iError) {
this.viewUser('');
this.viewEnable_(false);
this.twoFactorStatus(false);
this.twoFactorTested(false);
this.viewSecret('');
this.viewBackupCodes('');
this.viewUrlTitle('');
this.viewUrl('');
} else {
this.viewUser(pString(oData.Result.User));
this.viewEnable_(!!oData.Result.Enable);
this.twoFactorStatus(!!oData.Result.IsSet);
this.twoFactorTested(!!oData.Result.Tested);
this.viewSecret(pString(oData.Result.Secret));
this.viewBackupCodes(pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' '));
this.viewUrlTitle(pString(oData.Result.UrlTitle));
this.viewUrl(qr.toDataURL({ level: 'M', size: 8, value: this.getQr() }));
}
}
onShowSecretResult(iError, data) {
this.secreting(false);
if (iError) {
this.viewSecret('');
this.viewUrlTitle('');
this.viewUrl('');
} else {
this.viewSecret(pString(data.Result.Secret));
this.viewUrlTitle(pString(data.Result.UrlTitle));
this.viewUrl(qr.toDataURL({ level: 'M', size: 6, value: this.getQr() }));
}
}
onBuild() {
if (this.capaTwoFactor) {
this.processing(true);
Remote.getTwoFactor(this.onResult);
}
}
}
export { TwoFactorConfigurationPopupView, TwoFactorConfigurationPopupView as default };

View file

@ -1,51 +0,0 @@
import Remote from 'Remote/User/Fetch';
import { decorateKoCommands } from 'Knoin/Knoin';
import { AbstractViewPopup } from 'Knoin/AbstractViews';
class TwoFactorTestPopupView extends AbstractViewPopup {
constructor() {
super('TwoFactorTest');
this.addObservables({
code: '',
codeStatus: null,
testing: false
});
this.koTestedTrigger = null;
decorateKoCommands(this, {
testCodeCommand: self => self.code() && !self.testing()
});
}
testCodeCommand() {
this.testing(true);
Remote.testTwoFactor(iError => {
this.testing(false);
this.codeStatus(!iError);
if (this.koTestedTrigger && this.codeStatus()) {
this.koTestedTrigger(true);
}
}, this.code());
}
clearPopup() {
this.code('');
this.codeStatus(null);
this.testing(false);
this.koTestedTrigger = null;
}
onShow(koTestedTrigger) {
this.clearPopup();
this.koTestedTrigger = koTestedTrigger;
}
}
export { TwoFactorTestPopupView, TwoFactorTestPopupView as default };

View file

@ -43,7 +43,6 @@ class LoginUserView extends AbstractViewCenter {
email: SettingsGet('DevEmail'),
password: SettingsGet('DevPassword'),
signMe: false,
additionalCode: '',
emailError: false,
passwordError: false,
@ -56,9 +55,6 @@ class LoginUserView extends AbstractViewCenter {
langRequest: false,
additionalCodeError: false,
additionalCodeSignMe: false,
additionalCodeVisibility: false,
signMeType: LoginSignMeType.Unused
});
@ -84,15 +80,10 @@ class LoginUserView extends AbstractViewCenter {
this.addSubscribables({
email: () => {
this.emailError(false);
this.additionalCode('');
this.additionalCodeVisibility(false);
},
password: () => this.passwordError(false),
additionalCode: () => this.additionalCodeError(false),
additionalCodeVisibility: () => this.additionalCodeError(false),
submitError: value => value || this.submitErrorAddidional(''),
signMeType: iValue => this.signMe(LoginSignMeType.DefaultOn === iValue),
@ -121,16 +112,13 @@ class LoginUserView extends AbstractViewCenter {
submitCommand(self, event) {
let email = this.email().trim(),
valid = event.target.form.reportValidity() && email,
pass = this.password(),
totp = this.additionalCodeVisibility(),
code = totp ? this.additionalCode() : '';
pass = this.password();
this.emailError(!email);
this.passwordError(!pass);
this.additionalCodeError(totp && !code);
let pluginResultCode = 0, pluginResultMessage = '';
Plugins.runHook('user-login-submit', [(iResultCode, sResultMessage) => {
runHook('user-login-submit', [(iResultCode, sResultMessage) => {
pluginResultCode = iResultCode;
pluginResultMessage = sResultMessage;
}]);
@ -156,13 +144,6 @@ class LoginUserView extends AbstractViewCenter {
}
this.submitError(getNotification(iError, oData.ErrorMessage, Notification.UnknownNotification));
this.submitErrorAddidional((oData && oData.ErrorMessageAdditional) || '');
} else if (oData.TwoFactorAuth) {
this.submitRequest(false);
this.additionalCode('');
this.additionalCodeVisibility(true);
let input = this.querySelector('.inputAdditionalCode');
input.required = true;
setTimeout(() => input.focus(), 100);
} else {
rl.route.reload();
}
@ -170,9 +151,7 @@ class LoginUserView extends AbstractViewCenter {
email,
pass,
!!this.signMe(),
this.bSendLanguage ? this.language() : '',
code,
!!(totp && this.additionalCodeSignMe())
this.bSendLanguage ? this.language() : ''
);
Local.set(ClientSideKeyName.LastSignMe, this.signMe() ? '-1-' : '-0-');

View file

@ -26,7 +26,6 @@ export class FolderListMailBoxUserView extends AbstractViewLeft {
this.composeInEdit = AppUserStore.composeInEdit;
this.messageList = MessageUserStore.list;
this.folderList = FolderUserStore.folderList;
this.folderListSystem = FolderUserStore.folderListSystem;
this.foldersChanging = FolderUserStore.foldersChanging;

View file

@ -52,7 +52,6 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
constructor() {
super('User/MailBox/MessageList', 'MailMessageList');
this.sLastUid = null;
this.bPrefetch = false;
this.emptySubjectValue = '';
@ -69,43 +68,34 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
this.popupVisibility = ko.computed(() => 0 < popupVisibilityNames().length);
this.message = MessageUserStore.message;
this.messageList = MessageUserStore.list;
this.messageListDisableAutoSelect = MessageUserStore.listDisableAutoSelect;
this.folderList = FolderUserStore.folderList;
this.sortSupported = FolderUserStore.sortSupported;
this.composeInEdit = AppUserStore.composeInEdit;
this.leftPanelDisabled = leftPanelDisabled;
this.selectorMessageSelected = MessageUserStore.selectorMessageSelected;
this.selectorMessageFocused = MessageUserStore.selectorMessageFocused;
this.isMessageSelected = MessageUserStore.isMessageSelected;
this.messageListSearch = MessageUserStore.listSearch;
this.messageListThreadUid = MessageUserStore.listThreadUid;
this.messageListError = MessageUserStore.listError;
this.folderMenuForMove = FolderUserStore.folderMenuForMove;
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
this.mainMessageListSearch = MessageUserStore.mainMessageListSearch;
this.messageListEndFolder = MessageUserStore.listEndFolder;
this.messageListEndThreadUid = MessageUserStore.listEndThreadUid;
this.messageListCheckedOrSelected = MessageUserStore.listCheckedOrSelected;
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
this.messageListCompleteLoadingThrottle = MessageUserStore.listCompleteLoading;
this.messageListCompleteLoadingThrottleForAnimation = MessageUserStore.listLoadingAnimation;
initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'));
this.userQuota = QuotaUserStore.quota;
this.userUsageSize = QuotaUserStore.usage;
this.userUsageProc = QuotaUserStore.percentage;
this.addObservables({
moveDropdownTrigger: false,
moreDropdownTrigger: false,
sortDropdownTrigger: false,
dragOverArea: null,
dragOverBodyArea: null,
@ -137,7 +127,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
},
inputProxyMessageListSearch: {
read: this.mainMessageListSearch,
read: MessageUserStore.mainMessageListSearch,
write: value => this.sLastSearchValue = value
},
@ -146,19 +136,19 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
return c && MessageUserStore.list.length > c;
},
hasMessages: () => 0 < this.messageList.length,
hasMessages: () => 0 < MessageUserStore.list.length,
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === this.messageListEndFolder(),
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === MessageUserStore.listEndFolder(),
isSpamDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(),
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === this.messageListEndFolder(),
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === MessageUserStore.listEndFolder(),
isDraftFolder: () => (FolderUserStore.draftFolder() || 0) === this.messageListEndFolder(),
isDraftFolder: () => (FolderUserStore.draftFolder() || 0) === MessageUserStore.listEndFolder(),
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === this.messageListEndFolder(),
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === MessageUserStore.listEndFolder(),
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === this.messageListEndFolder(),
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === MessageUserStore.listEndFolder(),
isArchiveDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(),
@ -174,17 +164,28 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessageUserStore.listChecked().length : true,
messageListFocused: () => Scope.MessageList === AppUserStore.focusedState()
messageListFocused: () => Scope.MessageList === AppUserStore.focusedState(),
sortText: () => {
let mode = FolderUserStore.sortMode(),
desc = '' === mode || mode.includes('REVERSE');
mode = mode.split(/\s+/);
if (mode.includes('FROM')) {
return '@' + (desc ? '⬆' : '⬇');
}
if (mode.includes('SUBJECT')) {
return '𝐒' + (desc ? '⬆' : '⬇');
}
return (mode.includes('SIZE') ? '✉' : '📅') + (desc ? '⬇' : '⬆');
}
});
this.hasCheckedOrSelectedLines = MessageUserStore.hasCheckedOrSelected,
this.quotaTooltip = this.quotaTooltip.bind(this);
this.selector = new Selector(
this.messageList,
this.selectorMessageSelected,
this.selectorMessageFocused,
MessageUserStore.list,
MessageUserStore.selectorMessageSelected,
MessageUserStore.selectorMessageFocused,
'.messageListItem .actionHandle',
'.messageListItem .checkboxMessage',
'.messageListItem.focused'
@ -209,7 +210,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
addEventListener('mailbox.message.show', e => {
const sFolder = e.detail.Folder, sUid = e.detail.Uid;
const message = this.messageList.find(
const message = MessageUserStore.list.find(
item => item && sFolder === item.folder && sUid === item.uid
);
@ -246,6 +247,11 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
});
}
changeSort(self, event) {
FolderUserStore.sortMode(event.target.closest('li').dataset.sort);
this.reloadCommand();
}
clearCommand() {
if (Settings.capa(Capa.DangerousActions)) {
showScreenPopup(FolderClearPopupView, [FolderUserStore.currentFolder()]);
@ -375,7 +381,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
return false;
});
if (!SettingsUserStore.usePreviewPane() && !this.message()) {
if (!SettingsUserStore.usePreviewPane() && !MessageUserStore.message()) {
this.selector.iFocusedNextHelper = up ? -1 : 1;
} else {
this.selector.iSelectNextHelper = up ? -1 : 1;
@ -391,18 +397,18 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
useAutoSelect() {
return !this.messageListDisableAutoSelect()
&& !/is:unseen/.test(this.mainMessageListSearch())
return !MessageUserStore.listDisableAutoSelect()
&& !/is:unseen/.test(MessageUserStore.mainMessageListSearch())
&& SettingsUserStore.usePreviewPane();
}
searchEnterAction() {
this.mainMessageListSearch(this.sLastSearchValue);
MessageUserStore.mainMessageListSearch(this.sLastSearchValue);
this.inputMessageListSearchFocus(false);
}
cancelSearch() {
this.mainMessageListSearch('');
MessageUserStore.mainMessageListSearch('');
this.inputMessageListSearchFocus(false);
}
@ -571,7 +577,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
flagMessages(currentMessage) {
const checked = this.messageListCheckedOrSelected();
const checked = MessageUserStore.listCheckedOrSelected();
if (currentMessage) {
const checkedUids = checked.map(message => message.uid);
if (checkedUids.includes(currentMessage.uid)) {
@ -591,7 +597,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
flagMessagesFast(bFlag) {
const checked = this.messageListCheckedOrSelected();
const checked = MessageUserStore.listCheckedOrSelected();
if (checked.length) {
if (undefined === bFlag) {
const flagged = checked.filter(message => message.isFlagged());
@ -611,7 +617,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
seenMessagesFast(seen) {
const checked = this.messageListCheckedOrSelected();
const checked = MessageUserStore.listCheckedOrSelected();
if (checked.length) {
if (undefined === seen) {
const unseen = checked.filter(message => message.isUnseen());
@ -656,7 +662,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
!this.messageListSearchDesc() &&
!this.messageListError() &&
!this.messageListEndThreadUid() &&
this.messageList.length &&
MessageUserStore.list.length &&
(this.isSpamFolder() || this.isTrashFolder())
);
}
@ -700,7 +706,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
initShortcuts() {
shortcuts.add('enter,open', '', Scope.MessageList, () => {
if (this.message() && this.useAutoSelect()) {
if (MessageUserStore.message() && this.useAutoSelect()) {
dispatchEvent(new CustomEvent('mailbox.message-view.toggle-full-screen'));
return false;
}
@ -756,9 +762,9 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
}
shortcuts.add('t', '', [Scope.MessageList], () => {
let message = this.selectorMessageSelected();
let message = MessageUserStore.selectorMessageSelected();
if (!message) {
message = this.selectorMessageFocused();
message = MessageUserStore.selectorMessageFocused();
}
if (message && 0 < message.threadsLen()) {
@ -831,7 +837,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
return false;
});
shortcuts.add('tab,arrowright', '', Scope.MessageList, () => {
this.message() && AppUserStore.focusedState(Scope.MessageView);
MessageUserStore.message() && AppUserStore.focusedState(Scope.MessageView);
return false;
});
@ -841,7 +847,7 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
prefetchNextTick() {
if (!this.bPrefetch && !ifvisible.now() && this.viewModelVisible) {
const message = this.messageList.find(
const message = MessageUserStore.list.find(
item => item && !hasRequestedMessage(item.folder, item.uid)
);
if (message) {
@ -866,15 +872,15 @@ export class MessageListMailBoxUserView extends AbstractViewRight {
advancedSearchClick() {
Settings.capa(Capa.SearchAdv)
&& showScreenPopup(AdvancedSearchPopupView, [this.mainMessageListSearch()]);
&& showScreenPopup(AdvancedSearchPopupView, [MessageUserStore.mainMessageListSearch()]);
}
quotaTooltip() {
return i18n('MESSAGE_LIST/QUOTA_SIZE', {
SIZE: FileInfo.friendlySize(this.userUsageSize()),
PROC: this.userUsageProc(),
LIMIT: FileInfo.friendlySize(this.userQuota())
});
SIZE: FileInfo.friendlySize(QuotaUserStore.usage()),
PROC: QuotaUserStore.percentage(),
LIMIT: FileInfo.friendlySize(QuotaUserStore.quota())
}).replace(/<[^>]+>/g, '');
}
initUploaderForAppend() {

View file

@ -71,8 +71,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
moreDropdownTrigger: false
});
this.pswp = null;
this.moveAction = moveAction;
this.allowComposer = Settings.capa(Capa.Composer);
@ -84,7 +82,6 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.message = MessageUserStore.message;
this.hasCheckedMessages = MessageUserStore.hasCheckedMessages;
this.messageListCheckedOrSelectedUidsWithSubMails = MessageUserStore.listCheckedOrSelectedUidsWithSubMails;
this.messageLoadingThrottle = MessageUserStore.messageLoading;
this.messagesBodiesDom = MessageUserStore.messagesBodiesDom;
this.useThreads = SettingsUserStore.useThreads;
@ -136,6 +133,8 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
viewReplyTo: '',
viewTimeStamp: 0,
viewSize: '',
viewSpamScore: 0,
viewSpamStatus: '',
viewLineAsCss: '',
viewViewLink: '',
viewUnsubscribeLink: '',
@ -220,13 +219,15 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
this.viewFromShort(message.fromToLine(true, true));
this.viewFromDkimData(message.fromDkimData());
this.viewToShort(message.toToLine(true, true));
this.viewFrom(message.fromToLine(false));
this.viewTo(message.toToLine(false));
this.viewCc(message.ccToLine(false));
this.viewBcc(message.bccToLine(false));
this.viewReplyTo(message.replyToToLine(false));
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(i18n(message.isSpam() ? 'GLOBAL/SPAM' : 'GLOBAL/NOT_SPAM') + ': ' + message.spamResult());
this.viewLineAsCss(message.lineAsCss());
this.viewViewLink(message.viewLink());
this.viewUnsubscribeLink(message.getFirstUnsubsribeLink());
@ -717,7 +718,7 @@ class MessageViewMailBoxUserView extends AbstractViewRight {
* @returns {string}
*/
printableCheckedMessageCount() {
const cnt = this.messageListCheckedOrSelectedUidsWithSubMails().length;
const cnt = MessageUserStore.listCheckedOrSelectedUidsWithSubMails().length;
return 0 < cnt ? (100 > cnt ? cnt : '99+') : '';
}