Merge remote-tracking branch 'origin/master' into addressbook

This commit is contained in:
djmaze 2022-09-02 15:15:01 +02:00
commit 1a5b8819fa
204 changed files with 2050 additions and 2773 deletions

View file

@ -24,7 +24,6 @@ import {
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import {
MessageFlagsCache,
getFolderInboxName,
getFolderFromCacheList
} from 'Common/Cache';
@ -60,11 +59,11 @@ import { FolderSystemPopupView } from 'View/Popup/FolderSystem';
import { AskPopupView } from 'View/Popup/Ask';
import {
folderInformation,
folderInformationMultiply,
refreshFoldersInterval,
messagesMoveHelper,
messagesDeleteHelper,
fetchFolderInformation
messagesDeleteHelper
} from 'Common/Folders';
import { loadFolders } from 'Model/FolderCollection';
@ -216,51 +215,7 @@ export class AppUser extends AbstractApp {
* @param {Array=} list = []
*/
folderInformation(folder, list) {
if (folder && folder.trim()) {
fetchFolderInformation(
(iError, data) => {
if (!iError && data.Result) {
const result = data.Result,
folderFromCache = getFolderFromCacheList(result.Folder);
if (folderFromCache) {
const oldHash = folderFromCache.hash,
unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails);
// folderFromCache.revivePropertiesFromJson(result);
folderFromCache.expires = Date.now();
folderFromCache.uidNext = result.UidNext;
folderFromCache.hash = result.Hash;
folderFromCache.totalEmails(result.totalEmails);
folderFromCache.unreadEmails(result.unreadEmails);
if (unreadCountChange) {
MessageFlagsCache.clearFolder(folderFromCache.fullName);
}
if (result.MessagesFlags.length) {
result.MessagesFlags.forEach(message =>
MessageFlagsCache.setFor(folderFromCache.fullName, message.Uid.toString(), message.Flags)
);
MessagelistUserStore.reloadFlagsAndCachedMessage();
}
MessagelistUserStore.notifyNewMessages(folderFromCache.fullName, result.NewMessages);
if (!oldHash || unreadCountChange || result.Hash !== oldHash) {
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
MessagelistUserStore.reload();
} else if (getFolderInboxName() === folderFromCache.fullName) {
Remote.messageList(null, {Folder: getFolderInboxName()}, true);
}
}
}
}
},
folder,
list
);
}
folderInformation(folder, list);
}
logout() {
@ -309,9 +264,9 @@ export class AppUser extends AbstractApp {
setInterval(() => {
const cF = FolderUserStore.currentFolderFullName(),
iF = getFolderInboxName();
this.folderInformation(iF);
folderInformation(iF);
if (iF !== cF) {
this.folderInformation(cF);
folderInformation(cF);
}
folderInformationMultiply();
}, refreshFoldersInterval);
@ -323,7 +278,7 @@ export class AppUser extends AbstractApp {
setTimeout(() => {
const cF = FolderUserStore.currentFolderFullName();
if (getFolderInboxName() !== cF) {
this.folderInformation(cF);
folderInformation(cF);
}
FolderUserStore.hasCapability('LIST-STATUS') || folderInformationMultiply(true);
}, 1000);

View file

@ -4,7 +4,7 @@ import { addObservablesTo } from 'External/ko';
let notificator = null,
player = null,
canPlay = type => player && !!player.canPlayType(type).replace('no', ''),
canPlay = type => !!player?.canPlayType(type).replace('no', ''),
audioCtx = window.AudioContext || window.webkitAudioContext,
@ -92,7 +92,7 @@ export const SMAudio = new class {
}
pause() {
player && player.pause();
player?.pause();
fireEvent('audio.stop');
}

View file

@ -37,8 +37,7 @@ export const
* @param {string} folderHash
* @returns {string}
*/
getFolderFullName = folderHash =>
folderHash && FOLDERS_NAME_CACHE[folderHash] ? FOLDERS_NAME_CACHE[folderHash] : '',
getFolderFullName = folderHash => (folderHash && FOLDERS_NAME_CACHE[folderHash]) || '',
/**
* @param {string} folderHash
@ -79,9 +78,7 @@ export class MessageFlagsCache
* @returns {bool}
*/
static hasFlag(folderFullName, uid, flag) {
return MESSAGE_FLAGS_CACHE[folderFullName]
&& MESSAGE_FLAGS_CACHE[folderFullName][uid]
&& MESSAGE_FLAGS_CACHE[folderFullName][uid].includes(flag);
return MESSAGE_FLAGS_CACHE[folderFullName]?.[uid]?.includes(flag);
}
/**
@ -90,7 +87,7 @@ export class MessageFlagsCache
* @returns {?Array}
*/
static getFor(folderFullName, uid) {
return MESSAGE_FLAGS_CACHE[folderFullName] && MESSAGE_FLAGS_CACHE[folderFullName][uid];
return MESSAGE_FLAGS_CACHE[folderFullName]?.[uid];
}
/**

View file

@ -76,7 +76,6 @@ export const
ClientSideKeyNameExpandedFolders = 3,
ClientSideKeyNameFolderListSize = 4,
ClientSideKeyNameMessageListSize = 5,
ClientSideKeyNameLastReplyAction = 6,
ClientSideKeyNameLastSignMe = 7,
ClientSideKeyNameMessageHeaderFullInfo = 9,
ClientSideKeyNameMessageAttachmentControls = 10;

View file

@ -256,7 +256,7 @@ export const FileInfo = {
.map(item => item ? FileInfo.getIconClass(FileInfo.getExtension(item.fileName), item.mimeType) : '')
.validUnique();
return (icons && 1 === icons.length && 'icon-file' !== icons[0])
return (1 === icons?.length && 'icon-file' !== icons[0])
? icons[0]
: 'icon-attachment';
}

View file

@ -56,7 +56,7 @@ fetchFolderInformation = (fCallback, folder, list = []) => {
Remote.request('FolderInformation', fCallback, {
Folder: folder,
FlagsUids: uids,
UidNext: (folderFromCache && folderFromCache.uidNext) || 0 // Used to check for new messages
UidNext: folderFromCache?.uidNext || 0 // Used to check for new messages
});
} else if (SettingsUserStore.useThreads()) {
MessagelistUserStore.reloadFlagsAndCachedMessage();
@ -129,6 +129,58 @@ folderListOptionsBuilder = (
// Every 5 minutes
refreshFoldersInterval = 300000,
/**
* @param {string} folder
* @param {Array=} list = []
*/
folderInformation = (folder, list) => {
if (folder?.trim()) {
fetchFolderInformation(
(iError, data) => {
if (!iError && data.Result) {
const result = data.Result,
folderFromCache = getFolderFromCacheList(result.Folder);
if (folderFromCache) {
const oldHash = folderFromCache.hash,
unreadCountChange = (folderFromCache.unreadEmails() !== result.unreadEmails);
// folderFromCache.revivePropertiesFromJson(result);
folderFromCache.expires = Date.now();
folderFromCache.uidNext = result.UidNext;
folderFromCache.hash = result.Hash;
folderFromCache.totalEmails(result.totalEmails);
folderFromCache.unreadEmails(result.unreadEmails);
if (unreadCountChange) {
MessageFlagsCache.clearFolder(folderFromCache.fullName);
}
if (result.MessagesFlags.length) {
result.MessagesFlags.forEach(message =>
MessageFlagsCache.setFor(folderFromCache.fullName, message.Uid.toString(), message.Flags)
);
MessagelistUserStore.reloadFlagsAndCachedMessage();
}
MessagelistUserStore.notifyNewMessages(folderFromCache.fullName, result.NewMessages);
if (!oldHash || unreadCountChange || result.Hash !== oldHash) {
if (folderFromCache.fullName === FolderUserStore.currentFolderFullName()) {
MessagelistUserStore.reload();
} else if (getFolderInboxName() === folderFromCache.fullName) {
Remote.messageList(null, {Folder: getFolderInboxName()}, true);
}
}
}
}
},
folder,
list
);
}
},
/**
* @param {boolean=} boot = false
*/
@ -162,7 +214,7 @@ folderInformationMultiply = (boot = false) => {
} else if (unreadCountChange
&& folder.fullName === FolderUserStore.currentFolderFullName()
&& MessagelistUserStore.length) {
rl.app.folderInformation(folder.fullName, MessagelistUserStore());
folderInformation(folder.fullName, MessagelistUserStore());
}
}
});
@ -197,7 +249,7 @@ messagesMoveHelper = (fromFolderFullName, toFolderFullName, uidsForMove) => {
isSpam = sSpamFolder === toFolderFullName,
isHam = !isSpam && sSpamFolder === fromFolderFullName && getFolderInboxName() === toFolderFullName;
Remote.request('MessageMove',
Remote.abort('MessageList').request('MessageMove',
moveOrDeleteResponseHelper,
{
FromFolder: fromFolderFullName,
@ -205,23 +257,17 @@ messagesMoveHelper = (fromFolderFullName, toFolderFullName, uidsForMove) => {
Uids: uidsForMove.join(','),
MarkAsRead: (isSpam || FolderUserStore.trashFolder() === toFolderFullName) ? 1 : 0,
Learning: isSpam ? 'SPAM' : isHam ? 'HAM' : ''
},
null,
'',
['MessageList']
}
);
},
messagesDeleteHelper = (sFromFolderFullName, aUidForRemove) => {
Remote.request('MessageDelete',
Remote.abort('MessageList').request('MessageDelete',
moveOrDeleteResponseHelper,
{
Folder: sFromFolderFullName,
Uids: aUidForRemove.join(',')
},
null,
'',
['MessageList']
}
);
},

View file

@ -31,7 +31,7 @@ export const
new CustomEvent(name, {detail:detail, cancelable: !!cancelable})
),
formFieldFocused = () => doc.activeElement && doc.activeElement.matches('input,textarea'),
formFieldFocused = () => doc.activeElement?.matches('input,textarea'),
addShortcut = (...args) => shortcuts.add(...args),

View file

@ -1,6 +1,5 @@
import { createElement, SettingsGet } from 'Common/Globals';
import { createElement } from 'Common/Globals';
import { forEachObjectEntry, pInt } from 'Common/Utils';
import { proxy } from 'Common/Links';
const
tpl = createElement('template'),
@ -29,7 +28,7 @@ export const
* @param {string} text
* @returns {string}
*/
encodeHtml = text => (text && text.toString ? text.toString() : '' + text).replace(htmlre, m => htmlmap[m]),
encodeHtml = text => (text?.toString?.() || '' + text).replace(htmlre, m => htmlmap[m]),
/**
* Clears the Message Html for viewing
@ -39,7 +38,6 @@ export const
cleanHtml = (html, oAttachments, removeColors) => {
const
debug = false, // Config()->Get('debug', 'enable', false);
useProxy = !!SettingsGet('UseLocalProxyForExternalImages'),
detectHiddenImages = true, // !!SettingsGet('try_to_detect_hidden_images'),
result = {
@ -49,7 +47,7 @@ export const
findAttachmentByCid = cid => oAttachments.findByCid(cid),
findLocationByCid = cid => {
const attachment = findAttachmentByCid(cid);
return attachment && attachment.contentLocation ? attachment : 0;
return attachment?.contentLocation ? attachment : 0;
},
// convert body attributes to CSS
@ -100,7 +98,7 @@ export const
'BGSOUND','KEYGEN','SOURCE','OBJECT','EMBED','APPLET','IFRAME','FRAME','FRAMESET','VIDEO','AUDIO','AREA','MAP'
],
nonEmptyTags = [
'A','B','EM','I','SPAN','STRONG','TABLE'
'A','B','EM','I','SPAN','STRONG'
];
tpl.innerHTML = html
@ -138,7 +136,7 @@ export const
}
// if (['CENTER','FORM'].includes(name)) {
if ('FORM' === name || 'O:P' === name || (nonEmptyTags.includes(name) && ('' == oElement.textContent.trim()))) {
replaceWithChildren(oElement);
('A' !== name || !oElement.querySelector('IMG')) && replaceWithChildren(oElement);
return;
}
/*
@ -229,52 +227,58 @@ export const
value = getAttribute('src');
delAttribute('src');
let attachment;
if (detectHiddenImages
&& 'IMG' === name
&& (('' != getAttribute('height') && 3 > pInt(getAttribute('height')))
|| ('' != getAttribute('width') && 3 > pInt(getAttribute('width')))
|| [
'email.microsoftemail.com/open',
'github.com/notifications/beacon/',
'mandrillapp.com/track/open',
'list-manage.com/track/open'
].filter(uri => value.toLowerCase().includes(uri)).length
)) {
skipStyle = true;
setAttribute('style', 'display:none');
setAttribute('data-x-hidden-src', value);
}
else if ((attachment = findLocationByCid(value)))
{
if (attachment.download) {
oElement.loading = 'lazy';
oElement.src = attachment.linkPreview();
attachment.isLinked(true);
if ('IMG' === name) {
let attachment;
if (detectHiddenImages
&& (('' != getAttribute('height') && 3 > pInt(getAttribute('height')))
|| ('' != getAttribute('width') && 3 > pInt(getAttribute('width')))
|| [
'email.microsoftemail.com/open',
'github.com/notifications/beacon/',
'mandrillapp.com/track/open',
'list-manage.com/track/open'
].filter(uri => value.toLowerCase().includes(uri)).length
)) {
skipStyle = true;
setAttribute('style', 'display:none');
setAttribute('data-x-src-hidden', value);
}
}
else if ('cid:' === value.slice(0, 4))
{
attachment = findAttachmentByCid(value.slice(4));
if (attachment && attachment.download) {
oElement.src = attachment.linkPreview();
attachment.isInline(true);
attachment.isLinked(true);
else if ((attachment = findLocationByCid(value)))
{
if (attachment.download) {
oElement.loading = 'lazy';
oElement.src = attachment.linkPreview();
attachment.isLinked(true);
}
}
else if ('cid:' === value.slice(0, 4))
{
value = value.slice(4);
setAttribute('data-x-src-cid', value);
attachment = findAttachmentByCid(value);
if (attachment?.download) {
oElement.src = attachment.linkPreview();
attachment.isInline(true);
attachment.isLinked(true);
}
}
else if (/^(https?:)?\/\//i.test(value))
{
setAttribute('data-x-src', value);
result.hasExternals = true;
}
else if ('data:image/' === value.slice(0, 11))
{
oElement.src = value;
}
else
{
setAttribute('data-x-src-broken', value);
}
}
else if (/^(https?:)?\/\//i.test(value))
{
setAttribute('data-x-src', useProxy ? proxy(value) : value);
result.hasExternals = true;
}
else if ('data:image/' === value.slice(0, 11))
{
setAttribute('src', value);
}
else
{
setAttribute('data-x-broken-src', value);
setAttribute('data-x-src-broken', value);
}
}
@ -316,14 +320,14 @@ export const
let lowerUrl = found.toLowerCase();
if ('cid:' === lowerUrl.slice(0, 4)) {
const attachment = findAttachmentByCid(found);
if (attachment && attachment.linkPreview && name) {
if (attachment?.linkPreview && name) {
oStyle[property] = "url('" + attachment.linkPreview() + "')";
attachment.isInline(true);
attachment.isLinked(true);
}
} else if (/^(https?:)?\/\//.test(lowerUrl)) {
result.hasExternals = true;
urls_remote.push([property, useProxy ? proxy(found) : value]);
urls_remote.push([property, found]);
} else if ('data:image/' === lowerUrl.slice(0, 11)) {
oStyle[property] = value;
} else {
@ -430,9 +434,8 @@ export const
parent = node,
ordered = 'OL' == node.tagName,
i = 0;
while (parent && parent.parentNode && parent.parentNode.closest) {
parent = parent.parentNode.closest('ol,ul');
parent && (prefix = ' ' + prefix);
while ((parent = parent?.parentNode?.closest('ol,ul'))) {
prefix = ' ' + prefix;
}
node.querySelectorAll(':scope > li').forEach(li => {
li.prepend('\n' + prefix + (ordered ? `${++i}. ` : ' * '));
@ -563,7 +566,7 @@ export class HtmlEditor {
editor.on('focus', () => this.blurTimer && clearTimeout(this.blurTimer));
editor.on('mode', () => {
this.blurTrigger();
this.onModeChange && this.onModeChange(!this.isPlain());
this.onModeChange?.(!this.isPlain());
});
}
}
@ -571,7 +574,7 @@ export class HtmlEditor {
blurTrigger() {
if (this.onBlur) {
clearTimeout(this.blurTimer);
this.blurTimer = setTimeout(() => this.onBlur && this.onBlur(), 200);
this.blurTimer = setTimeout(() => this.onBlur?.(), 200);
}
}
@ -681,7 +684,7 @@ export class HtmlEditor {
hasFocus() {
try {
return this.editor && !!this.editor.focusManager.hasFocus;
return !!this.editor?.focusManager.hasFocus;
} catch (e) {
return false;
}

View file

@ -49,10 +49,7 @@ export function runSettingsViewModelHooks(admin) {
* @param {string} name
* @returns {?}
*/
rl.pluginSettingsGet = (pluginSection, name) => {
let plugins = SettingsGet('Plugins');
plugins = plugins && null != plugins[pluginSection] ? plugins[pluginSection] : null;
return plugins ? (null == plugins[name] ? null : plugins[name]) : null;
};
rl.pluginSettingsGet = (pluginSection, name) =>
SettingsGet('Plugins')?.[pluginSection]?.[name];
rl.pluginPopupView = AbstractViewPopup;

View file

@ -422,7 +422,7 @@ export class Selector {
lineUid = '';
const uid = this.getItemUid(item);
if (event && event.shiftKey) {
if (event?.shiftKey) {
if (uid && this.sLastUid && uid !== this.sLastUid) {
list = this.list();
checked = item.checked();

View file

@ -142,7 +142,7 @@ export const
* @param {Function=} langCallback = null
*/
initOnStartOrLangChange = (startCallback, langCallback = null) => {
startCallback && startCallback();
startCallback?.();
startCallback && trigger.subscribe(startCallback);
langCallback && trigger.subscribe(langCallback);
},

View file

@ -26,8 +26,7 @@ export const
.trim(),
defaultOptionsAfterRender = (domItem, item) =>
domItem && item && undefined !== item.disabled
&& domItem.classList.toggle('disabled', domItem.disabled = item.disabled),
item && undefined !== item.disabled && domItem?.classList.toggle('disabled', domItem.disabled = item.disabled),
// unescape(encodeURIComponent()) makes the UTF-16 DOMString to an UTF-8 string
b64EncodeJSON = data => btoa(unescape(encodeURIComponent(JSON.stringify(data)))),

View file

@ -131,7 +131,7 @@ computedPaginatorHelper = (koCurrentPage, koPageCount) => {
* @returns {boolean}
*/
mailToHelper = mailToUrl => {
if (mailToUrl && 'mailto:' === mailToUrl.slice(0, 7).toLowerCase()) {
if ('mailto:' === mailToUrl?.slice(0, 7).toLowerCase()) {
mailToUrl = mailToUrl.slice(7).split('?');
const
@ -181,7 +181,7 @@ setLayoutResizer = (source, target, sClientSideKeyName, mode) =>
target.removeAttribute('style');
source.removeAttribute('style');
}
source.observer && source.observer.disconnect();
source.observer?.disconnect();
// source.classList.toggle('resizable', mode);
if (mode) {
const length = Local.get(sClientSideKeyName + mode) || SettingsGet('Resizer' + sClientSideKeyName + mode),
@ -248,7 +248,7 @@ setLayoutResizer = (source, target, sClientSideKeyName, mode) =>
}
source.layoutResizer.mode = mode;
source.layoutResizer.key = sClientSideKeyName;
source.observer && source.observer.observe(source, { box: 'border-box' });
source.observer?.observe(source, { box: 'border-box' });
}
},
@ -266,7 +266,7 @@ populateMessageBody = (oMessage, preload) => {
oMessage = preload ? oMessage : null;
let
isNew = false,
json = oData && oData.Result,
json = oData?.Result,
message = oMessage || MessageUserStore.message();
if (

View file

@ -12,7 +12,7 @@ export class AbstractInput {
this.value = params.value || '';
this.label = params.label || '';
this.enable = null == params.enable ? true : params.enable;
this.trigger = params.trigger && params.trigger.subscribe ? params.trigger : null;
this.trigger = params.trigger?.subscribe ? params.trigger : null;
this.placeholder = params.placeholder || '';
this.labeled = null != params.label;

View file

@ -2,7 +2,7 @@ import { doc, createElement, addEventsListeners } from 'Common/Globals';
import { EmailModel } from 'Model/Email';
const contentType = 'snappymail/emailaddress',
getAddressKey = li => li && li.emailaddress && li.emailaddress.key;
getAddressKey = li => li?.emailaddress?.key;
let dragAddress, datalist;
@ -19,7 +19,7 @@ export class EmailAddressesComponent {
const self = this,
// In Chrome we have no access to dataTransfer.getData unless it's the 'drop' event
// In Chrome Mobile dataTransfer.types.includes(contentType) fails, only text/plain is set
validDropzone = () => dragAddress && dragAddress.li.parentNode !== self.ul,
validDropzone = () => dragAddress?.li.parentNode !== self.ul,
fnDrag = e => validDropzone() && e.preventDefault();
self.element = element;
@ -116,7 +116,7 @@ export class EmailAddressesComponent {
value,
items => {
self._resetDatalist();
items && items.forEach(item => datalist.append(new Option(item)));
items?.forEach(item => datalist.append(new Option(item)));
}
)
}
@ -262,7 +262,7 @@ export class EmailAddressesComponent {
if (v.obj) {
let li = createElement('li',{title:v.obj.toLine(),draggable:'true'}),
el = createElement('span');
el.append(v.obj.toLine(true, false, true));
el.append(v.obj.toLine(true));
li.append(el);
el = createElement('a',{href:'#', class:'ficon'});

View file

@ -176,8 +176,8 @@ class SquireUI
quote: {
html: '"',
cmd: () => {
let parent = this.getParentNodeName('UL,OL');
(parent && 'BLOCKQUOTE' == parent) ? squire.decreaseQuoteLevel() : squire.increaseQuoteLevel();
let parent = squire.getSelectionClosest('UL,OL,BLOCKQUOTE')?.nodeName;
('BLOCKQUOTE' == parent) ? squire.decreaseQuoteLevel() : squire.increaseQuoteLevel();
},
hint: 'Blockquote'
},
@ -198,11 +198,10 @@ class SquireUI
link: {
html: '🔗',
cmd: () => {
if ('A' === this.getParentNodeName()) {
squire.removeLink();
} else {
let url = prompt("Link","https://");
url != null && url.length && squire.makeLink(url);
let node = squire.getSelectionClosest('A'),
url = prompt("Link", node?.href || "https://");
if (url != null) {
url.length ? squire.makeLink(url) : (node && squire.removeLink());
}
},
hint: 'Link'
@ -210,12 +209,9 @@ class SquireUI
imageUrl: {
html: '🖼️',
cmd: () => {
if ('IMG' === this.getParentNodeName()) {
// squire.removeLink();
} else {
let src = prompt("Image","https://");
src != null && src.length && squire.insertImage(src);
}
let node = squire.getSelectionClosest('IMG'),
src = prompt("Image", node?.src || "https://");
src.length ? squire.insertImage(src) : (node && squire.detach(node));
},
hint: 'Image URL'
},
@ -385,15 +381,10 @@ class SquireUI
this.squire.focus();
}
getParentNodeName(selector) {
let parent = this.squire.getSelectionClosest(selector);
return parent ? parent.nodeName : null;
}
doList(type) {
let parent = this.getParentNodeName('UL,OL'),
let parent = this.squire.getSelectionClosest('UL,OL')?.nodeName,
fn = {UL:'makeUnorderedList',OL:'makeOrderedList'};
(parent && parent == type) ? this.squire.removeList() : this.squire[fn[type]]();
(parent == type) ? this.squire.removeList() : this.squire[fn[type]]();
}
testPresenceinSelection(format, validation) {
@ -412,7 +403,7 @@ class SquireUI
}
this.mode = mode; // 'wysiwyg' or 'plain'
cl.add('squire-mode-'+mode);
this.onModeChange && this.onModeChange();
this.onModeChange?.();
setTimeout(()=>this.focus(),1);
}
}

View file

@ -14,8 +14,8 @@ const rlContentType = 'snappymail/action',
// In Chrome we have no access to dataTransfer.getData unless it's the 'drop' event
// In Chrome Mobile dataTransfer.types.includes(rlContentType) fails, only text/plain is set
dragMessages = () => dragData && 'messages' === dragData.action,
dragSortable = () => dragData && 'sortable' === dragData.action,
dragMessages = () => 'messages' === dragData?.action,
dragSortable = () => 'sortable' === dragData?.action,
setDragAction = (e, action, effect, data, img) => {
dragData = {
action: action,
@ -45,8 +45,8 @@ Object.assign(ko.bindingHandlers, {
let editor = null;
const fValue = fValueAccessor(),
fUpdateEditorValue = () => fValue && fValue.__editor && fValue.__editor.setHtmlOrPlain(fValue()),
fUpdateKoValue = () => fValue && fValue.__editor && fValue(fValue.__editor.getDataWithHtmlMark()),
fUpdateEditorValue = () => fValue?.__editor?.setHtmlOrPlain(fValue()),
fUpdateKoValue = () => fValue?.__editor && fValue(fValue.__editor.getDataWithHtmlMark()),
fOnReady = () => {
fValue.__editor = editor;
fUpdateEditorValue();
@ -76,7 +76,7 @@ Object.assign(ko.bindingHandlers, {
focused = fValue.focused;
element.addresses = new EmailAddressesComponent(element, {
focusCallback: value => focused && focused(!!value),
focusCallback: value => focused?.(!!value),
autoCompleteSource: fAllBindings.get('autoCompleteSource'),
onChange: value => fValue(value)
});
@ -137,7 +137,7 @@ Object.assign(ko.bindingHandlers, {
if (dragMessages()) {
fnStop(e);
element.classList.add('droppableHover');
if (folder && folder.collapsed()) {
if (folder?.collapsed()) {
dragTimer.start(() => {
folder.collapsed(false);
setExpandedFolder(folder.fullName, true);
@ -153,7 +153,7 @@ Object.assign(ko.bindingHandlers, {
fnStop(e);
if (dragMessages() && ['move','copy'].includes(e.dataTransfer.effectAllowed)) {
let data = dragData.data;
if (folder && data && data.folder && isArray(data.uids)) {
if (folder && data?.folder && isArray(data.uids)) {
moveMessagesToFolder(data.folder, data.uids, folder.fullName, data.copy && e.ctrlKey);
}
}
@ -220,7 +220,7 @@ Object.assign(ko.bindingHandlers, {
options.list(arr);
}
dragData = null;
options.afterMove && options.afterMove();
options.afterMove?.();
}
}
});

8
dev/External/ko.js vendored
View file

@ -26,14 +26,14 @@ export const
addSubscribablesTo = (target, subscribables) =>
forEachObjectEntry(subscribables, (key, fn) => target[key].subscribe(fn)),
dispose = disposable => disposable && isFunction(disposable.dispose) && disposable.dispose(),
dispose = disposable => isFunction(disposable?.dispose) && disposable.dispose(),
// With this we don't need delegateRunOnDestroy
koArrayWithDestroy = data => {
data = ko.observableArray(data);
data.subscribe(changes =>
changes.forEach(item =>
'deleted' === item.status && null == item.moved && item.value.onDestroy && item.value.onDestroy()
'deleted' === item.status && null == item.moved && item.value.onDestroy?.()
)
, data, 'arrayChange');
return data;
@ -172,12 +172,12 @@ ko.extenders.toggleSubscribeProperty = (target, options) => {
const prop = options[1];
if (prop) {
target.subscribe(
prev => prev && prev[prop] && prev[prop](false),
prev => prev?.[prop]?.(false),
options[0],
'beforeChange'
);
target.subscribe(next => next && next[prop] && next[prop](true), options[0]);
target.subscribe(next => next?.[prop]?.(true), options[0]);
}
return target;

View file

@ -50,8 +50,7 @@ export class AbstractModel {
// forEachObjectEntry(this, (key, value) => {
forEachObjectValue(this, value => {
/** clear CollectionModel */
let arr = ko.isObservableArray(value) ? value() : value;
arr && arr.onDestroy && arr.onDestroy();
(ko.isObservableArray(value) ? value() : value)?.onDestroy?.();
/** destroy ko.observable/ko.computed? */
// dispose(value);
/** clear object value */
@ -76,7 +75,7 @@ export class AbstractModel {
*/
static reviveFromJson(json) {
let obj = this.validJson(json) ? new this() : null;
obj && obj.revivePropertiesFromJson(json);
obj?.revivePropertiesFromJson(json);
return obj;
}

View file

@ -128,7 +128,7 @@ export class AbstractViewSettings
addSubscribablesTo(this, {
[prop]: (value => {
this[trigger](SaveSettingsStep.Animate);
valueCb && valueCb(value);
valueCb?.(value);
rl.app.Remote.saveSetting(name, value,
iError => {
this[trigger](iError ? SaveSettingsStep.FalseResult : SaveSettingsStep.TrueResult);

View file

@ -10,10 +10,7 @@ let
defaultScreenName = '';
const
autofocus = dom => {
const af = dom.querySelector('[autofocus]');
af && af.focus();
},
autofocus = dom => dom.querySelector('[autofocus]')?.focus(),
visiblePopups = new Set,
@ -21,7 +18,7 @@ const
* @param {string} screenName
* @returns {?Object}
*/
screen = screenName => screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null,
screen = screenName => (screenName && SCREENS[screenName]) || null,
/**
* @param {Function} ViewModelClass
@ -77,10 +74,10 @@ const
if (e.target === vmDom) {
if (vmDom.classList.contains('animate')) {
autofocus(vmDom);
vm.afterShow && vm.afterShow();
vm.afterShow?.();
} else {
vmDom.close();
vm.afterHide && vm.afterHide();
vm.afterHide?.();
}
}
};
@ -101,7 +98,7 @@ const
});
} else {
visiblePopups.delete(vm);
vm.onHide && vm.onHide();
vm.onHide?.();
vm.keyScope.unset();
vmDom.classList.remove('animate'); // trigger the transitions
}
@ -118,7 +115,7 @@ const
vm
);
vm.onBuild && vm.onBuild(vmDom);
vm.onBuild?.(vmDom);
fireEvent('rl-view-model', vm);
} else {
@ -142,10 +139,10 @@ const
},
hideScreen = (screenToHide, destroy) => {
screenToHide.onHide && screenToHide.onHide();
screenToHide.onHide?.();
forEachViewModel(screenToHide, (vm, dom) => {
dom.hidden = true;
vm.onHide && vm.onHide();
vm.onHide?.();
destroy && vm.viewModelDom.remove();
});
},
@ -155,7 +152,7 @@ const
* @returns {void}
*/
hideScreenPopup = ViewModelClassToHide => {
if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) {
if (ViewModelClassToHide?.__vm && ViewModelClassToHide?.__dom) {
ViewModelClassToHide.__vm.modalVisible(false);
}
},
@ -190,7 +187,7 @@ const
}
}
if (vmScreen && vmScreen.__started) {
if (vmScreen?.__started) {
isSameScreen = currentScreen && vmScreen === currentScreen;
if (!vmScreen.__builded) {
@ -200,7 +197,7 @@ const
buildViewModel(ViewModelClass, vmScreen)
);
vmScreen.onBuild && vmScreen.onBuild();
vmScreen.onBuild?.();
}
setTimeout(() => {
@ -214,19 +211,19 @@ const
// show screen
if (!isSameScreen) {
vmScreen.onShow && vmScreen.onShow();
vmScreen.onShow?.();
forEachViewModel(vmScreen, (vm, dom) => {
vm.beforeShow && vm.beforeShow();
vm.beforeShow?.();
i18nToNodes(dom);
dom.hidden = false;
vm.onShow && vm.onShow();
vm.onShow?.();
autofocus(dom);
});
}
// --
vmScreen.__cross && vmScreen.__cross.parse(subPart);
vmScreen.__cross?.parse(subPart);
}, 1);
}
}
@ -248,11 +245,11 @@ export const
if (vm) {
params = params || [];
vm.beforeShow && vm.beforeShow(...params);
vm.beforeShow?.(...params);
vm.modalVisible(true);
vm.onShow && vm.onShow(...params);
vm.onShow?.(...params);
}
},
@ -295,7 +292,7 @@ export const
const c = elementById('rl-content'), l = elementById('rl-loading');
c && (c.hidden = false);
l && l.remove();
l?.remove();
},
/**

View file

@ -8,12 +8,26 @@ export function ParseMime(text)
{
class MimePart
{
/*
constructor() {
this.id = 0;
this.start = 0;
this.end = 0;
this.parts = [];
this.bodyStart = 0;
this.bodyEnd = 0;
this.boundary = '';
this.bodyText = '';
this.headers = {};
}
*/
header(name) {
return this.headers && this.headers[name];
return this.headers[name];
}
headerValue(name) {
return (this.header(name) || {value:null}).value;
return this.header(name)?.value;
}
get raw() {
@ -26,12 +40,21 @@ export function ParseMime(text)
get body() {
let body = this.bodyRaw,
// charset = this.header('content-type')?.params.charset,
encoding = this.headerValue('content-transfer-encoding');
if ('quoted-printable' == encoding) {
body = body.replace(/=\r?\n/g, '').replace(QPDecodeIn, QPDecodeOut);
} else if ('base64' == encoding) {
body = atob(body.replace(/\r?\n/g, ''));
}
/*
try {
// https://developer.mozilla.org/en-US/docs/Web/API/Encoding_API/Encodings
return new TextDecoder(charset).decode(Uint8Array.from(body, c => c.charCodeAt(0)));
// return new TextDecoder(charset).decode(new TextEncoder().encode(body));
} catch (e) {
}
*/
return body;
}
@ -51,21 +74,17 @@ export function ParseMime(text)
forEach(fn) {
fn(this);
if (this.parts) {
this.parts.forEach(part => part.forEach(fn));
}
this.parts.forEach(part => part.forEach(fn));
}
getByContentType(type) {
if (type == this.headerValue('content-type')) {
return this;
}
if (this.parts) {
let i = 0, p = this.parts, part;
for (i; i < p.length; ++i) {
if ((part = p[i].getByContentType(type))) {
return part;
}
let i = 0, p = this.parts, part;
for (i; i < p.length; ++i) {
if ((part = p[i].getByContentType(type))) {
return part;
}
}
}
@ -73,18 +92,19 @@ export function ParseMime(text)
const ParsePart = (mimePart, start_pos = 0, id = '') =>
{
let part = new MimePart;
let part = new MimePart,
headers = {};
if (id) {
part.id = id;
part.start = start_pos;
part.end = start_pos + mimePart.length;
}
part.parts = [];
// get headers
let head = mimePart.match(/^[\s\S]+?\r?\n\r?\n/);
if (head) {
head = head[0];
let headers = {};
head.replace(/\r?\n\s+/g, ' ').split(/\r?\n/).forEach(header => {
let match = header.match(/^([^:]+):\s*([^;]+)/),
params = {};
@ -96,7 +116,6 @@ export function ParseMime(text)
params: params
};
});
part.headers = headers;
// get body
part.bodyStart = start_pos + head.length;
@ -106,7 +125,6 @@ export function ParseMime(text)
let boundary = headers['content-type'].params.boundary;
if (boundary) {
part.boundary = boundary;
part.parts = [];
let regex = new RegExp('(?:^|\r?\n)--' + boundary + '(?:--)?(?:\r?\n|$)', 'g'),
body = mimePart.slice(head.length),
bodies = body.split(regex),
@ -125,6 +143,8 @@ export function ParseMime(text)
}
}
part.headers = headers;
return part;
};

View file

@ -12,7 +12,7 @@ export class AbstractCollectionModel extends Array
}
onDestroy() {
this.forEach(item => item.onDestroy && item.onDestroy());
this.forEach(item => item.onDestroy?.());
}
/**

View file

@ -1,4 +1,3 @@
import { encodeHtml } from 'Common/Html';
import { AbstractModel } from 'Knoin/AbstractModel';
@ -284,7 +283,7 @@ export class EmailModel extends AbstractModel {
*/
static reviveFromJson(json) {
const email = super.reviveFromJson(json);
email && email.clearDuplicateName();
email?.clearDuplicateName();
return email;
}
@ -333,45 +332,13 @@ export class EmailModel extends AbstractModel {
/**
* @param {boolean} friendlyView = false
* @param {boolean=} wrapWithLink = false
* @param {boolean=} useEncodeHtml = false
* @returns {string}
*/
toLine(friendlyView, wrapWithLink, useEncodeHtml) {
let result = '',
toLink = (to, txt) => '<a href="mailto:' + to + '" target="_blank" tabindex="-1">' + encodeHtml(txt) + '</a>';
if (this.email) {
if (friendlyView && this.name) {
result = wrapWithLink
? toLink(
encodeHtml(this.email) + '?to=' + encodeURIComponent('"' + this.name + '" <' + this.email + '>'),
this.name
)
: (useEncodeHtml ? encodeHtml(this.name) : this.name);
} else {
result = this.email;
if (this.name) {
if (wrapWithLink) {
result =
encodeHtml('"' + this.name + '" <')
+ toLink(
encodeHtml(this.email) + '?to=' + encodeURIComponent('"' + this.name + '" <' + this.email + '>'),
result
)
+ encodeHtml('>');
} else {
result = '"' + this.name + '" <' + result + '>';
if (useEncodeHtml) {
result = encodeHtml(result);
}
}
} else if (wrapWithLink) {
result = toLink(encodeHtml(this.email), this.email);
}
}
}
return result;
toLine(friendlyView) {
let result = this.email;
return (result && this.name)
? (friendlyView ? this.name : '"' + this.name + '" <' + result + '>')
: result;
}
static splitEmailLine(line) {
@ -384,7 +351,7 @@ export class EmailModel extends AbstractModel {
? new EmailModel(item.address.replace(/^[<]+(.*)[>]+$/g, '$1'), item.name || '')
: null;
if (address && address.email) {
if (address?.email) {
exists = true;
}

View file

@ -30,7 +30,7 @@ export class EmailCollectionModel extends AbstractCollectionModel
toStringClear() {
const result = [];
this.forEach(email => {
if (email && email.email && email.name) {
if (email?.email && email?.name) {
result.push(email.email);
}
});

View file

@ -100,8 +100,8 @@ export const
.post('Folders', FolderUserStore.foldersLoading)
.then(data => {
data = FolderCollectionModel.reviveFromJson(data.Result);
data && data.storeIt();
fCallback && fCallback(true);
data?.storeIt();
fCallback?.(true);
// Repeat every 15 minutes?
// this.foldersTimeout = setTimeout(loadFolders, 900000);
})
@ -128,7 +128,7 @@ export class FolderCollectionModel extends AbstractCollectionModel
*/
static reviveFromJson(object) {
const expandedFolders = Local.get(ClientSideKeyNameExpandedFolders);
if (object && object.SystemFolders) {
if (object?.SystemFolders) {
forEachObjectEntry(SystemFolders, key =>
SystemFolders[key] = SettingsGet(key+'Folder') || object.SystemFolders[FolderType[key]]
);

View file

@ -3,10 +3,10 @@ import ko from 'ko';
import { MessagePriority } from 'Common/EnumsUser';
import { i18n } from 'Common/Translator';
import { doc } from 'Common/Globals';
import { doc, SettingsGet } from 'Common/Globals';
import { encodeHtml, plainToHtml, cleanHtml } from 'Common/Html';
import { isArray, arrayLength, forEachObjectEntry } from 'Common/Utils';
import { serverRequestRaw } from 'Common/Links';
import { serverRequestRaw, proxy } from 'Common/Links';
import { FolderUserStore } from 'Stores/User/Folder';
import { SettingsUserStore } from 'Stores/User/Settings';
@ -89,11 +89,15 @@ const
})
},
/**
* @param {EmailCollectionModel} emails
* @param {Object} unic
* @param {Map} localEmails
*/
replyHelper = (emails, unic, localEmails) => {
emails.forEach(email => {
if (undefined === unic[email.email]) {
unic[email.email] = true;
localEmails.push(email);
if (!unic[email.email] && !localEmails.has(email.email)) {
localEmails.set(email.email, email);
}
});
};
@ -319,7 +323,7 @@ export class MessageModel extends AbstractModel {
*/
fromDkimData() {
let result = ['none', ''];
if (1 === arrayLength(this.from) && this.from[0] && this.from[0].dkimStatus) {
if (1 === arrayLength(this.from) && this.from[0]?.dkimStatus) {
result = [this.from[0].dkimStatus, this.from[0].dkimValue || ''];
}
@ -375,7 +379,6 @@ export class MessageModel extends AbstractModel {
focused: this.focused(),
important: this.isImportant(),
withAttachments: !!this.attachments().length,
emptySubject: !this.subject(),
// hasChildrenMessage: 1 < this.threadsLen(),
hasUnseenSubMessage: this.hasUnseenSubMessage(),
hasFlaggedSubMessage: this.hasFlaggedSubMessage()
@ -396,7 +399,7 @@ export class MessageModel extends AbstractModel {
* @returns {string}
*/
fromAsSingleEmail() {
return isArray(this.from) && this.from[0] ? this.from[0].email : '';
return (isArray(this.from) && this.from[0]?.email) || '';
}
/**
@ -415,50 +418,36 @@ export class MessageModel extends AbstractModel {
/**
* @param {Object} excludeEmails
* @param {boolean=} last = false
* @returns {Array}
*/
replyEmails(excludeEmails, last) {
const result = [],
unic = undefined === excludeEmails ? {} : excludeEmails;
replyEmails(excludeEmails) {
const
result = new Map(),
unic = excludeEmails || {};
replyHelper(this.replyTo, unic, result);
if (!result.length) {
replyHelper(this.from, unic, result);
}
if (!result.length && !last) {
return this.replyEmails({}, true);
}
return result;
result.size || replyHelper(this.from, unic, result);
return result.size ? [...result.values()] : [this.to[0]];
}
/**
* @param {Object} excludeEmails
* @param {boolean=} last = false
* @returns {Array.<Array>}
*/
replyAllEmails(excludeEmails, last) {
let data = [];
const toResult = [],
ccResult = [],
unic = undefined === excludeEmails ? {} : excludeEmails;
replyAllEmails(excludeEmails) {
const
toResult = new Map(),
ccResult = new Map(),
unic = excludeEmails || {};
replyHelper(this.replyTo, unic, toResult);
if (!toResult.length) {
if (!toResult.size) {
replyHelper(this.from, unic, toResult);
}
replyHelper(this.to, unic, toResult);
replyHelper(this.cc, unic, ccResult);
if (!toResult.length && !last) {
data = this.replyAllEmails({}, true);
return [data[0], ccResult];
}
return [toResult, ccResult];
return [[...toResult.values()], [...ccResult.values()]];
}
viewHtml() {
@ -617,16 +606,18 @@ export class MessageModel extends AbstractModel {
this.hasImages(false);
body.rlHasImages = false;
let attr = 'data-x-src';
body.querySelectorAll('[' + attr + ']').forEach(node => {
if (node.matches('img')) {
node.loading = 'lazy';
}
node.src = node.getAttribute(attr);
let attr = 'data-x-src',
src, useProxy = !!SettingsGet('UseLocalProxyForExternalImages');
body.querySelectorAll('img[' + attr + ']').forEach(node => {
node.loading = 'lazy';
src = node.getAttribute(attr);
node.src = useProxy ? proxy(src) : src;
});
body.querySelectorAll('[data-x-style-url]').forEach(node => {
JSON.parse(node.dataset.xStyleUrl).forEach(data => node.style[data[0]] = "url('" + data[1] + "')");
JSON.parse(node.dataset.xStyleUrl).forEach(data =>
node.style[data[0]] = "url('" + (useProxy ? proxy(data[1]) : data[1]) + "')"
);
});
}
}

View file

@ -49,7 +49,7 @@ fetchJSON = (action, sGetAdd, params, timeout, jsonCallback) => {
} else {
params.Action = action;
}
abort(action);
// abort(action);
const controller = new AbortController(),
signal = controller.signal;
oRequests[action] = controller;
@ -123,23 +123,20 @@ export class AbstractFetchRemote
* @param {Object=} oParameters
* @param {?number=} iTimeout
* @param {string=} sGetAdd = ''
* @param {Array=} aAbortActions = []
*/
request(sAction, fCallback, params, iTimeout, sGetAdd, abortActions) {
params = params || {};
const start = Date.now();
if (sAction && abortActions) {
abortActions.forEach(actionToAbort => abort(actionToAbort));
}
abortActions && console.error('abortActions is obsolete');
fetchJSON(sAction, pString(sGetAdd),
params,
undefined === iTimeout ? 30000 : pInt(iTimeout),
data => {
let cached = false;
if (data && data.Time) {
if (data?.Time) {
cached = pInt(data.Time) > Date.now() - start;
}
@ -194,7 +191,7 @@ export class AbstractFetchRemote
if (trigger) {
value = !!value;
(isArray(trigger) ? trigger : [trigger]).forEach(fTrigger => {
fTrigger && fTrigger(value);
fTrigger?.(value);
});
}
}
@ -210,12 +207,12 @@ export class AbstractFetchRemote
}
/*
let isCached = false, type = '';
if (data && data.Time) {
if (data?.Time) {
isCached = pInt(data.Time) > microtime() - start;
}
// backward capability
switch (true) {
case 'success' === textStatus && data && data.Result && action === data.Action:
case 'success' === textStatus && data?.Result && action === data.Action:
type = AbstractFetchRemote.SUCCESS;
break;
case 'abort' === textStatus && (!data || !data.__aborted__):

View file

@ -24,13 +24,13 @@ class RemoteUserFetch extends AbstractFetchRemote {
const
sFolderFullName = pString(params.Folder),
folder = getFolderFromCacheList(sFolderFullName),
folderHash = (folder && folder.hash) || '';
folderHash = folder?.hash || '';
params = Object.assign({
Offset: 0,
Limit: SettingsUserStore.messagesPerPage(),
Search: '',
UidNext: (folder && folder.uidNext) || 0, // Used to check for new messages
UidNext: folder?.uidNext || 0, // Used to check for new messages
Sort: FolderUserStore.sortMode(),
Hash: folderHash + SettingsGet('AccountHash')
}, params);
@ -51,12 +51,12 @@ class RemoteUserFetch extends AbstractFetchRemote {
params = {};
}
bSilent || this.abort('MessageList');
this.request('MessageList',
fCallback,
params,
60000, // 60 seconds before aborting
sGetAdd,
bSilent ? [] : ['MessageList']
sGetAdd
);
}
@ -71,7 +71,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
iUid = pInt(iUid);
if (getFolderFromCacheList(sFolderFullName) && 0 < iUid) {
this.request('Message',
this.abort('Message').request('Message',
fCallback,
{},
null,
@ -83,8 +83,7 @@ class RemoteUserFetch extends AbstractFetchRemote {
iUid,
AppUserStore.threadsAllowed() && SettingsUserStore.useThreads() ? 1 : 0,
SettingsGet('AccountHash')
]),
['Message']
])
);
return true;

View file

@ -53,7 +53,7 @@ export class AbstractSettingsScreen extends AbstractScreen {
settingsScreen
);
settingsScreen.onBuild && settingsScreen.onBuild(viewModelDom);
settingsScreen.onBuild?.(viewModelDom);
} else {
console.log('Cannot find sub settings view model position: SettingsSubScreen');
}
@ -67,10 +67,10 @@ export class AbstractSettingsScreen extends AbstractScreen {
this.oCurrentSubScreen = settingsScreen;
// show
settingsScreen.beforeShow && settingsScreen.beforeShow();
settingsScreen.beforeShow?.();
i18nToNodes(settingsScreen.viewModelDom);
settingsScreen.viewModelDom.hidden = false;
settingsScreen.onShow && settingsScreen.onShow();
settingsScreen.onShow?.();
this.menu.forEach(item => {
item.selected(
@ -90,7 +90,7 @@ export class AbstractSettingsScreen extends AbstractScreen {
onHide() {
let subScreen = this.oCurrentSubScreen;
if (subScreen) {
subScreen.onHide && subScreen.onHide();
subScreen.onHide?.();
subScreen.viewModelDom.hidden = true;
}
}

View file

@ -1,10 +1,10 @@
import { Scope } from 'Common/Enums';
import { Layout, ClientSideKeyNameMessageListSize } from 'Common/EnumsUser';
import { doc, leftPanelDisabled, moveAction, Settings, elementById } from 'Common/Globals';
import { doc, createElement, leftPanelDisabled, moveAction, Settings, elementById } from 'Common/Globals';
import { pString, pInt } from 'Common/Utils';
import { setLayoutResizer } from 'Common/UtilsUser';
import { getFolderFromCacheList, getFolderFullName, getFolderInboxName } from 'Common/Cache';
import { i18n } from 'Common/Translator';
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
import { SettingsUserStore } from 'Stores/User/Settings';
import { AppUserStore } from 'Stores/User/App';
@ -22,6 +22,12 @@ import { AbstractScreen } from 'Knoin/AbstractScreen';
export class MailBoxUserScreen extends AbstractScreen {
constructor() {
var styleSheet = createElement('style');
doc.head.appendChild(styleSheet);
initOnStartOrLangChange(() =>
styleSheet.innerText = '.subjectParent:empty::after,.subjectParent .subject:empty::after'
+'{content:"'+i18n('MESSAGE/EMPTY_SUBJECT_TEXT')+'"}'
);
super('mailbox', [
SystemDropDownUserView,
MailFolderList,
@ -89,7 +95,7 @@ export class MailBoxUserScreen extends AbstractScreen {
/* // Disabled in SystemDropDown.html
const email = AccountUserStore.email();
AccountUserStore.accounts.forEach(item =>
item && email === item.email && item.count(e.detail)
email === item?.email && item?.count(e.detail)
);
*/
this.updateWindowTitle();

View file

@ -57,7 +57,7 @@ export class AdminSettingsAbout /*extends AbstractViewSettings*/ {
this.coreChecking(true);
Remote.request('AdminUpdateInfo', (iError, data) => {
this.coreChecking(false);
if (!iError && data && data.Result) {
if (!iError && data?.Result) {
this.coreReal(true);
this.coreUpdatable(!!data.Result.Updatable);
this.coreWarning(!!data.Result.Warning);
@ -79,7 +79,7 @@ export class AdminSettingsAbout /*extends AbstractViewSettings*/ {
this.coreUpdating(false);
this.coreVersion('');
this.coreVersionCompare(-2);
if (!iError && data && data.Result) {
if (!iError && data?.Result) {
this.coreReal(true);
window.location.reload();
} else {

View file

@ -85,7 +85,7 @@ export class AdminSettingsContacts extends AbstractViewSettings {
this.testContactsSuccess(true);
} else {
this.testContactsError(true);
if (data && data.Result) {
if (data?.Result) {
this.testContactsErrorMessage(data.Result.Message || '');
} else {
this.testContactsErrorMessage('');

View file

@ -68,7 +68,7 @@ export class AdminSettingsGeneral extends AbstractViewSettings {
this.uploadData = SettingsGet('PhpUploadSizes');
this.uploadDataDesc =
this.uploadData && (this.uploadData.upload_max_filesize || this.uploadData.post_max_size)
(this.uploadData?.upload_max_filesize || this.uploadData?.post_max_size)
? [
this.uploadData.upload_max_filesize
? 'upload_max_filesize = ' + this.uploadData.upload_max_filesize + '; '

View file

@ -24,9 +24,9 @@ export class AdminSettingsPackages extends AbstractViewSettings {
this.packages = PackageAdminStore;
addComputablesTo(this, {
packagesCurrent: () => PackageAdminStore().filter(item => item && item.installed && !item.canBeUpdated),
packagesUpdate: () => PackageAdminStore().filter(item => item && item.installed && item.canBeUpdated),
packagesAvailable: () => PackageAdminStore().filter(item => item && !item.installed),
packagesCurrent: () => PackageAdminStore().filter(item => item?.installed && !item.canBeUpdated),
packagesUpdate: () => PackageAdminStore().filter(item => item?.installed && item.canBeUpdated),
packagesAvailable: () => PackageAdminStore().filter(item => !item?.installed),
visibility: () => (PackageAdminStore.loading() ? 'visible' : 'hidden')
});
@ -59,7 +59,7 @@ export class AdminSettingsPackages extends AbstractViewSettings {
requestHelper(packageToRequest, install) {
return (iError, data) => {
PackageAdminStore.forEach(item => {
if (item && packageToRequest && item.loading && item.loading() && packageToRequest.file === item.file) {
if (packageToRequest && item?.loading?.() && packageToRequest.file === item.file) {
packageToRequest.loading(false);
item.loading(false);
}
@ -109,11 +109,11 @@ export class AdminSettingsPackages extends AbstractViewSettings {
let disable = plugin.enabled();
plugin.enabled(!disable);
Remote.request('AdminPluginDisable',
(iError, data) => {
(iError, data) => {
if (iError) {
plugin.enabled(disable);
this.packagesError(
(Notification.UnsupportedPluginPackage === iError && data && data.ErrorMessage)
(Notification.UnsupportedPluginPackage === iError && data?.ErrorMessage)
? data.ErrorMessage
: getNotification(iError)
);

View file

@ -21,28 +21,46 @@ export class AdminSettingsSecurity extends AbstractViewSettings {
adminPasswordNew: '',
adminPasswordNew2: '',
adminPasswordNewError: false,
adminTOTP: SettingsGet('AdminTOTP'),
adminTOTP: '',
adminPasswordUpdateError: false,
adminPasswordUpdateSuccess: false,
saveError: false,
saveSuccess: false,
viewQRCode: '',
capaOpenPGP: SettingsCapa('OpenPGP')
});
const reset = () => {
this.adminPasswordUpdateError(false);
this.adminPasswordUpdateSuccess(false);
this.saveError(false);
this.saveSuccess(false);
this.adminPasswordNewError(false);
};
addSubscribablesTo(this, {
adminPassword: () => {
this.adminPasswordUpdateError(false);
this.adminPasswordUpdateSuccess(false);
this.saveError(false);
this.saveSuccess(false);
},
adminLogin: () => this.adminLoginError(false),
adminTOTP: value => {
if (/[A-Z2-7]{16,}/.test(value) && 0 == value.length * 5 % 8) {
Remote.request('AdminQRCode', (iError, data) => {
if (!iError) {
console.dir({data:data});
this.viewQRCode(data.Result);
}
}, {
'username': this.adminLogin(),
'TOTP': this.adminTOTP()
});
} else {
this.viewQRCode('');
}
},
adminPasswordNew: reset,
adminPasswordNew2: reset,
@ -50,12 +68,14 @@ export class AdminSettingsSecurity extends AbstractViewSettings {
capaOpenPGP: value => Remote.saveSetting('CapaOpenPGP', value)
});
this.adminTOTP(SettingsGet('AdminTOTP'));
decorateKoCommands(this, {
saveNewAdminPasswordCommand: self => self.adminLogin().trim() && self.adminPassword()
saveAdminUserCommand: self => self.adminLogin().trim() && self.adminPassword()
});
}
saveNewAdminPasswordCommand() {
saveAdminUserCommand() {
if (!this.adminLogin().trim()) {
this.adminLoginError(true);
return false;
@ -66,18 +86,18 @@ export class AdminSettingsSecurity extends AbstractViewSettings {
return false;
}
this.adminPasswordUpdateError(false);
this.adminPasswordUpdateSuccess(false);
this.saveError(false);
this.saveSuccess(false);
Remote.request('AdminPasswordUpdate', (iError, data) => {
if (iError) {
this.adminPasswordUpdateError(true);
this.saveError(true);
} else {
this.adminPassword('');
this.adminPasswordNew('');
this.adminPasswordNew2('');
this.adminPasswordUpdateSuccess(true);
this.saveSuccess(true);
this.weakPassword(!!data.Result.Weak);
}

View file

@ -30,7 +30,7 @@ export class UserSettingsAccounts /*extends AbstractViewSettings*/ {
}
editAccount(account) {
if (account && account.isAdditional()) {
if (account?.isAdditional()) {
showScreenPopup(AccountPopupView, [account]);
}
}
@ -48,7 +48,7 @@ export class UserSettingsAccounts /*extends AbstractViewSettings*/ {
* @returns {void}
*/
deleteAccount(accountToRemove) {
if (accountToRemove && accountToRemove.askDelete()) {
if (accountToRemove?.askDelete()) {
this.accountForDeletion(null);
if (accountToRemove) {
this.accounts.remove((account) => accountToRemove === account);
@ -72,7 +72,7 @@ export class UserSettingsAccounts /*extends AbstractViewSettings*/ {
* @returns {void}
*/
deleteIdentity(identityToRemove) {
if (identityToRemove && identityToRemove.askDelete()) {
if (identityToRemove?.askDelete()) {
this.identityForDeletion(null);
if (identityToRemove) {

View file

@ -16,13 +16,12 @@ export class UserSettingsContacts /*extends AbstractViewSettings*/ {
this.syncUser = ContactUserStore.syncUser;
this.syncPass = ContactUserStore.syncPass;
const i18nSyncMode = key => i18n('SETTINGS_CONTACTS/SYNC_' + key);
this.syncModeOptions = koComputable(() => {
translatorTrigger();
return [
{ id: 0, name: i18nSyncMode('NO') },
{ id: 1, name: i18nSyncMode('YES') },
{ id: 2, name: i18nSyncMode('READ') },
{ id: 0, name: i18n('GLOBAL/NO') },
{ id: 1, name: i18n('GLOBAL/YES') },
{ id: 2, name: i18n('SETTINGS_CONTACTS/SYNC_READ') },
];
});

View file

@ -50,6 +50,6 @@ export class UserSettingsFilters /*extends AbstractViewSettings*/ {
}
onShow() {
window.Sieve && window.Sieve.updateList();
window.Sieve?.updateList();
}
}

View file

@ -1,5 +1,4 @@
import ko from 'ko';
import { koComputable } from 'External/ko';
import { Notification } from 'Common/Enums';
import { FolderMetadataKeys } from 'Common/EnumsUser';
@ -48,14 +47,7 @@ export class UserSettingsFolders /*extends AbstractViewSettings*/ {
this.folderListError = FolderUserStore.folderListError;
this.hideUnsubscribed = SettingsUserStore.hideUnsubscribed;
this.loading = koComputable(() => {
const loading = FolderUserStore.foldersLoading(),
creating = FolderUserStore.foldersCreating(),
deleting = FolderUserStore.foldersDeleting(),
renaming = FolderUserStore.foldersRenaming();
return loading || creating || deleting || renaming;
});
this.loading = FolderUserStore.foldersChanging;
this.folderForDeletion = folderForDeletion;

View file

@ -30,11 +30,6 @@ export class UserSettingsGeneral extends AbstractViewSettings {
this.language = LanguageStore.language;
this.languages = LanguageStore.languages;
this.messageReadDelay = SettingsUserStore.messageReadDelay;
this.messagesPerPage = SettingsUserStore.messagesPerPage;
this.editorDefaultType = SettingsUserStore.editorDefaultType;
this.layout = SettingsUserStore.layout;
this.soundNotification = SMAudio.notifications;
this.notificationSound = ko.observable(SettingsGet('NotificationSound'));
@ -43,14 +38,14 @@ export class UserSettingsGeneral extends AbstractViewSettings {
this.desktopNotification = NotificationUserStore.enabled;
this.isDesktopNotificationAllowed = NotificationUserStore.allowed;
this.viewHTML = SettingsUserStore.viewHTML;
this.showImages = SettingsUserStore.showImages;
this.removeColors = SettingsUserStore.removeColors;
this.hideDeleted = SettingsUserStore.hideDeleted;
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
this.threadsAllowed = AppUserStore.threadsAllowed;
this.useThreads = SettingsUserStore.useThreads;
this.replySameFolder = SettingsUserStore.replySameFolder;
['layout', 'messageReadDelay', 'messagesPerPage',
'editorDefaultType', 'requestReadReceipt', 'requestDsn', 'pgpSign', 'pgpEncrypt',
'viewHTML', 'showImages', 'removeColors', 'hideDeleted',
'useCheckboxesInList', 'useThreads', 'replySameFolder', 'msgDefaultAction'
].forEach(name => this[name] = SettingsUserStore[name]);
this.allowLanguagesOnSettings = !!SettingsGet('AllowLanguagesOnSettings');
this.languageTrigger = ko.observable(SaveSettingsStep.Idle);
@ -78,6 +73,14 @@ export class UserSettingsGeneral extends AbstractViewSettings {
];
},
msgDefaultActions: () => {
translatorTrigger();
return [
{ id: 1, name: i18n('MESSAGE/BUTTON_REPLY') }, // ComposeType.Reply,
{ id: 2, name: i18n('MESSAGE/BUTTON_REPLY_ALL') } // ComposeType.ReplyAll
];
},
layoutTypes: () => {
translatorTrigger();
return [
@ -89,11 +92,13 @@ export class UserSettingsGeneral extends AbstractViewSettings {
});
this.addSetting('EditorDefaultType');
this.addSetting('MsgDefaultAction');
this.addSetting('MessageReadDelay');
this.addSetting('MessagesPerPage');
this.addSetting('Layout', () => MessagelistUserStore([]));
this.addSettings(['ViewHTML', 'ShowImages', 'HideDeleted', 'UseCheckboxesInList', 'ReplySameFolder',
'requestReadReceipt', 'requestDsn', 'pgpSign', 'pgpEncrypt',
'DesktopNotifications', 'SoundNotification']);
const fReloadLanguageHelper = (saveSettingsStep) => () => {

View file

@ -74,7 +74,7 @@ export class UserSettingsThemes /*extends AbstractViewSettings*/ {
.on('onComplete', (id, result, data) => {
themeBackground.loading(false);
if (result && id && data && data.Result && data.Result.Name && data.Result.Hash) {
if (result && id && data?.Result?.Name && data?.Result?.Hash) {
themeBackground.name(data.Result.Name);
themeBackground.hash(data.Result.Hash);
} else {
@ -94,11 +94,7 @@ export class UserSettingsThemes /*extends AbstractViewSettings*/ {
}
}
if (!errorMsg && data.ErrorMessage) {
errorMsg = data.ErrorMessage;
}
themeBackground.error(errorMsg || i18n('SETTINGS_THEMES/ERROR_UNKNOWN'));
themeBackground.error(errorMsg || data.ErrorMessage || i18n('SETTINGS_THEMES/ERROR_UNKNOWN'));
}
return true;

View file

@ -17,7 +17,7 @@ function filtersToSieveScript(filters)
''
];
const quote = string => '"' + string.trim().replace(/(\\|")/, '\\\\$1') + '"';
const quote = string => '"' + string.trim().replace(/(\\|")/g, '\\$1') + '"';
const StripSpaces = string => string.replace(/\s+/, ' ').trim();
// conditionToSieveScript
@ -294,8 +294,9 @@ export class SieveScriptModel extends AbstractModel
return {
name: this.name(),
active: this.active() ? 1 : 0,
body: this.body(),
filters: this.filters.map(item => item.toJson())
body: this.body()
// body: this.allowFilters() ? this.body() : this.filtersToRaw()
// filters: this.filters.map(item => item.toJson())
};
}

View file

@ -13,8 +13,7 @@ import {
const
// import { defaultOptionsAfterRender } from 'Common/Utils';
defaultOptionsAfterRender = (domItem, item) =>
domItem && item && undefined !== item.disabled
&& domItem.classList.toggle('disabled', domItem.disabled = item.disabled),
item && undefined !== item.disabled && domItem?.classList.toggle('disabled', domItem.disabled = item.disabled),
// import { folderListOptionsBuilder } from 'Common/Folders';
/**

View file

@ -22,8 +22,8 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
saveError: false,
errorText: '',
rawActive: false,
allowToggle: false,
script: null,
saving: false,
sieveCapabilities: '',
availableActions: '',
@ -31,8 +31,6 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
availableTests: ''
});
this.saving = false;
this.filterForDeletion = ko.observable(null).askDeleteHelper();
}
@ -48,7 +46,7 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
saveScript() {
let self = this,
script = self.script();
if (!self.saving/* && script.hasChanges()*/) {
if (!self.saving()/* && script.hasChanges()*/) {
this.errorText('');
self.saveError(false);
@ -62,21 +60,21 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
}
try {
parseScript(this.script().body());
parseScript(script.body());
} catch (e) {
this.errorText(e.message);
return;
}
self.saving = true;
self.saving(true);
if (self.allowToggle()) {
if (script.allowFilters()) {
script.body(script.filtersToRaw());
}
Remote.request('FiltersScriptSave',
(iError, data) => {
self.saving = false;
self.saving(false);
if (iError) {
self.saveError(true);
@ -85,6 +83,7 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
script.exists() || scripts.push(script);
script.exists(true);
script.hasChanges(false);
// this.close();
}
},
script.toJson()
@ -110,17 +109,18 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
const clonedFilter = filter.assignTo();
FilterPopupView.showModal([
clonedFilter,
() => clonedFilter.assignTo(filter),
() => {
clonedFilter.assignTo(filter);
const script = this.script();
script.hasChanges(script.body() != script.filtersToRaw());
},
true
]);
}
toggleFiltersRaw() {
let script = this.script(), notRaw = !this.rawActive();
if (notRaw) {
script.body(script.filtersToRaw());
script.hasChanges(script.hasChanges());
}
const script = this.script(), notRaw = !this.rawActive();
notRaw && script.body(script.filtersToRaw());
this.rawActive(notRaw);
}
@ -140,10 +140,8 @@ export class SieveScriptPopupView extends rl.pluginPopupView {
this.availableTests([...Object.keys(availableTests())].join(', '));
oScript = oScript || new SieveScriptModel();
let raw = !oScript.allowFilters();
this.script(oScript);
this.rawActive(raw);
this.allowToggle(!raw);
this.rawActive(!oScript.allowFilters());
this.saveError(false);
this.errorText('');

View file

@ -22,7 +22,7 @@ PackageAdminStore.fetch = () => {
const loading = {};
PackageAdminStore.forEach(item => {
if (item && item.loading()) {
if (item?.loading()) {
loading[item.file] = item;
}
});

View file

@ -21,6 +21,6 @@ AppUserStore.focusedState.subscribe(value => {
ThemeStore.isMobile() && leftPanelDisabled('FolderList' !== value);
}
let dom = elementById('V-Mail'+name);
dom && dom.classList.toggle('focused', name === value);
dom?.classList.toggle('focused', name === value);
});
});

View file

@ -33,12 +33,12 @@ ContactUserStore.sync = fResultFunc => {
line = JSON.parse(line);
if ('ContactsSync' === line.Action) {
ContactUserStore.syncing(false);
fResultFunc && fResultFunc(line.ErrorCode, line);
fResultFunc?.(line.ErrorCode, line);
}
} catch (e) {
ContactUserStore.syncing(false);
console.error(e);
fResultFunc && fResultFunc(Notification.UnknownError);
fResultFunc?.(Notification.UnknownError);
}
}, 'ContactsSync');
}

View file

@ -64,7 +64,7 @@ export const FolderUserStore = new class {
foldersChanging: () =>
self.foldersLoading() | self.foldersCreating() | self.foldersDeleting() | self.foldersRenaming(),
folderListSystemNames: () => {
systemFoldersNames: () => {
const list = [getFolderInboxName()],
others = [self.sentFolder(), self.draftsFolder(), self.spamFolder(), self.trashFolder(), self.archiveFolder()];
@ -74,21 +74,15 @@ export const FolderUserStore = new class {
return list;
},
folderListSystem: () =>
self.folderListSystemNames().map(name => getFolderFromCacheList(name)).filter(v => v)
systemFolders: () =>
self.systemFoldersNames().map(name => getFolderFromCacheList(name)).filter(v => v)
});
const
subscribeRemoveSystemFolder = observable => {
observable.subscribe(() => {
const folder = getFolderFromCacheList(observable());
folder && folder.type(FolderType.User);
}, self, 'beforeChange');
observable.subscribe(() => getFolderFromCacheList(observable())?.type(FolderType.User), self, 'beforeChange');
},
fSetSystemFolderType = type => value => {
const folder = getFolderFromCacheList(value);
folder && folder.type(type);
};
fSetSystemFolderType = type => value => getFolderFromCacheList(value)?.type(type);
subscribeRemoveSystemFolder(self.sentFolder);
subscribeRemoveSystemFolder(self.draftsFolder);
@ -134,8 +128,7 @@ export const FolderUserStore = new class {
fSearchFunction = (list) => {
list.forEach(folder => {
if (
folder &&
folder.selectable() &&
folder?.selectable() &&
folder.exists &&
timeout > folder.expires &&
(folder.isSystemFolder() || (folder.isSubscribed() && (folder.checkable() || !bDisplaySpecSetting)))
@ -143,7 +136,7 @@ export const FolderUserStore = new class {
timeouts.push([folder.expires, folder.fullName]);
}
if (folder && folder.subFolders.length) {
if (folder?.subFolders.length) {
fSearchFunction(folder.subFolders());
}
});
@ -157,13 +150,14 @@ export const FolderUserStore = new class {
const folder = getFolderFromCacheList(aItem[1]);
if (folder) {
folder.expires = utc;
// result.indexOf(aItem[1]) ||
result.push(aItem[1]);
}
return limit <= result.length;
});
return result.filter((value, index, self) => self.indexOf(value) == index);
return result;
}
saveSystemFolders(folders) {

View file

@ -38,7 +38,7 @@ export const GnuPGUserStore = new class {
this.privateKeys([]);
Remote.request('GnupgGetKeys',
(iError, oData) => {
if (oData && oData.Result) {
if (oData?.Result) {
this.keyring = oData.Result;
const initKey = (key, isPrivate) => {
const aEmails = [];
@ -73,7 +73,7 @@ export const GnuPGUserStore = new class {
key.view = () => {
const fetch = pass => Remote.request('GnupgExportKey',
(iError, oData) => {
if (oData && oData.Result) {
if (oData?.Result) {
key.armor = oData.Result;
showScreenPopup(OpenPgpKeyPopupView, [key]);
}
@ -111,10 +111,10 @@ export const GnuPGUserStore = new class {
importKey(key, callback) {
Remote.request('GnupgImportKey',
(iError, oData) => {
if (oData && oData.Result/* && (oData.Result.imported || oData.Result.secretimported)*/) {
if (oData?.Result/* && (oData.Result.imported || oData.Result.secretimported)*/) {
this.loadKeyrings();
}
callback && callback(iError, oData);
callback?.(iError, oData);
}, {
Key: key
}
@ -131,10 +131,10 @@ export const GnuPGUserStore = new class {
storeKeyPair(keyPair, callback) {
Remote.request('PgpStoreKeyPair',
(iError, oData) => {
if (oData && oData.Result) {
if (oData?.Result) {
// this.gnupgKeyring = oData.Result;
}
callback && callback(iError, oData);
callback?.(iError, oData);
}, keyPair
);
}
@ -187,7 +187,7 @@ export const GnuPGUserStore = new class {
}
if (null !== params.Passphrase) {
const result = await Remote.post('GnupgDecrypt', null, params);
if (result && result.Result) {
if (result?.Result) {
return result.Result;
}
}
@ -208,7 +208,7 @@ export const GnuPGUserStore = new class {
data.SigPart = data.SigPart.body;
}
let response = await Remote.post('MessagePgpVerify', null, data);
if (response && response.Result) {
if (response?.Result) {
return {
fingerprint: response.Result.fingerprint,
success: 0 == response.Result.status // GOODSIG

View file

@ -42,7 +42,7 @@ export const MessageUserStore = new class {
purgeMessageBodyCache() {
const messagesDom = this.bodiesDom(),
children = messagesDom && messagesDom.children;
children = messagesDom?.children;
if (children) {
while (15 < children.length) {
children[0].remove();

View file

@ -270,8 +270,7 @@ MessagelistUserStore.setAction = (sFolderFullName, iSetAction, messages) => {
let folder,
alreadyUnread = 0,
rootUids = messages.map(oMessage => oMessage && oMessage.uid ? oMessage.uid : null)
.validUnique(),
rootUids = messages.map(oMessage => oMessage?.uid).validUnique(),
length = rootUids.length;
if (sFolderFullName && length) {
@ -333,7 +332,7 @@ MessagelistUserStore.removeMessagesFromList = (
? messageList.filter(item => item && uidForRemove.includes(pInt(item.uid)))
: [];
messages.forEach(item => item && item.isUnseen() && ++unseenCount);
messages.forEach(item => item?.isUnseen() && ++unseenCount);
if (fromFolder) {
fromFolder.hash = '';
@ -387,7 +386,7 @@ MessagelistUserStore.removeMessagesFromList = (
if (MessagelistUserStore.threadUid()) {
if (
messageList.length &&
messageList.find(item => item && item.deleted() && item.uid == MessagelistUserStore.threadUid())
messageList.find(item => item?.deleted() && item.uid == MessagelistUserStore.threadUid())
) {
const message = messageList.find(item => item && !item.deleted());
let setHash;

View file

@ -6,7 +6,7 @@ import { fireEvent } from 'Common/Globals';
* Might not work due to the new ServiceWorkerRegistration.showNotification
*/
const HTML5Notification = window.Notification,
HTML5NotificationStatus = () => (HTML5Notification && HTML5Notification.permission) || 'denied',
HTML5NotificationStatus = () => HTML5Notification?.permission || 'denied',
NotificationsDenied = () => 'denied' === HTML5NotificationStatus(),
NotificationsGranted = () => 'granted' === HTML5NotificationStatus(),
dispatchMessage = data => {
@ -26,7 +26,7 @@ if (WorkerNotifications && ServiceWorkerRegistration && ServiceWorkerRegistratio
/* Listen for close requests from the ServiceWorker */
WorkerNotifications.addEventListener('message', event => {
const obj = JSON.parse(event.data);
obj && 'notificationclick' === obj.action && dispatchMessage(obj.data);
'notificationclick' === obj?.action && dispatchMessage(obj.data);
});
} else {
WorkerNotifications = null;
@ -60,7 +60,7 @@ export const NotificationUserStore = new class {
icon: imageSrc || Links.staticLink('css/images/icon-message-notification.png'),
data: messageData
};
if (messageData && messageData.Uid) {
if (messageData?.Uid) {
options.tag = messageData.Uid;
}
if (WorkerNotifications) {
@ -83,7 +83,7 @@ export const NotificationUserStore = new class {
.catch(e => console.error(e));
} else {
const notification = new HTML5Notification(title, options);
notification.show && notification.show();
notification.show?.();
notification.onclick = messageData ? () => dispatchMessage(messageData) : null;
setTimeout(() => notification.close(), 7000);
}

View file

@ -18,8 +18,21 @@ const
key.emails.includes(query) || query == key.id || query == key.fingerprint
),
askPassphrase = async (privateKey, btnTxt = 'LABEL_SIGN') =>
await AskPopupView.password('OpenPGP.js key<br>' + privateKey.id + ' ' + privateKey.emails[0], 'OPENPGP/'+btnTxt),
decryptKey = async (privateKey, btnTxt = 'LABEL_SIGN') => {
if (privateKey.key.isDecrypted()) {
return privateKey.key;
}
const passphrase = await AskPopupView.password(
'OpenPGP.js key<br>' + privateKey.id + ' ' + privateKey.emails[0],
'OPENPGP/'+btnTxt
);
if (null !== passphrase) {
return await openpgp.decryptKey({
privateKey: privateKey.key,
passphrase
});
}
},
/**
* OpenPGP.js v5 removed the localStorage (keyring)
@ -178,19 +191,12 @@ export const OpenPGPUserStore = new class {
}
}
if (privateKey) try {
const passphrase = await askPassphrase(privateKey, 'BUTTON_DECRYPT');
if (null !== passphrase) {
const
publicKey = findOpenPGPKey(this.publicKeys, sender/*, sign*/),
decryptedKey = await openpgp.decryptKey({
privateKey: privateKey.key,
passphrase
});
const decryptedKey = await decryptKey(privateKey, 'BUTTON_DECRYPT');
if (decryptedKey) {
const publicKey = findOpenPGPKey(this.publicKeys, sender/*, sign*/);
return await openpgp.decrypt({
message,
verificationKeys: publicKey && publicKey.key,
verificationKeys: publicKey?.key,
// expectSigned: true,
// signature: '', // Detached signature
decryptionKeys: decryptedKey
@ -246,18 +252,14 @@ export const OpenPGPUserStore = new class {
* https://docs.openpgpjs.org/global.html#sign
*/
async sign(text, privateKey, detached) {
const passphrase = await askPassphrase(privateKey);
if (null !== passphrase) {
privateKey = await openpgp.decryptKey({
privateKey: privateKey.key,
passphrase
});
const signingKey = await decryptKey(privateKey);
if (signingKey) {
const message = detached
? await openpgp.createMessage({ text: text })
: await openpgp.createCleartextMessage({ text: text });
return await openpgp.sign({
message: message,
signingKeys: privateKey,
signingKeys: signingKey,
detached: !!detached
});
}
@ -272,14 +274,10 @@ export const OpenPGPUserStore = new class {
recipients = recipients.map(email => this.publicKeys().find(key => key.emails.includes(email))).filter(key => key);
if (count === recipients.length) {
if (signPrivateKey) {
const passphrase = await askPassphrase(signPrivateKey);
if (null === passphrase) {
signPrivateKey = await decryptKey(signPrivateKey);
if (!signPrivateKey) {
return;
}
signPrivateKey = await openpgp.decryptKey({
privateKey: signPrivateKey.key,
passphrase
});
}
return await openpgp.encrypt({
message: await openpgp.createMessage({ text: text }),

View file

@ -169,7 +169,7 @@ export const
}
);
if (result) {
if (result.error && result.error.message) {
if (result.error?.message) {
if ('PWD_DIALOG_CANCEL' !== result.error.code) {
alert(result.error.code + ': ' + result.error.message);
}

View file

@ -1,7 +1,7 @@
import ko from 'ko';
import { koComputable } from 'External/ko';
import { Layout, EditorDefaultType } from 'Common/EnumsUser';
import { Layout, EditorDefaultType, ComposeType } from 'Common/EnumsUser';
import { pInt } from 'Common/Utils';
import { addObservablesTo } from 'External/ko';
import { $htmlCL, SettingsGet, fireEvent } from 'Common/Globals';
@ -22,6 +22,13 @@ export const SettingsUserStore = new class {
]
});
self.msgDefaultAction = ko.observable(1).extend({
limitedList: [
ComposeType.Reply,
ComposeType.ReplyAll
]
});
self.messagesPerPage = ko.observable(25).extend({ debounce: 999 });
self.messageReadDelay = ko.observable(5).extend({ debounce: 999 });
@ -36,7 +43,12 @@ export const SettingsUserStore = new class {
replySameFolder: 0,
hideUnsubscribed: 0,
hideDeleted: 1,
autoLogout: 0
autoLogout: 0,
requestReadReceipt: 0,
requestDsn: 0,
pgpSign: 0,
pgpEncrypt: 0
});
self.init();
@ -74,6 +86,7 @@ export const SettingsUserStore = new class {
self.messagesPerPage(pInt(SettingsGet('MessagesPerPage')));
self.messageReadDelay(pInt(SettingsGet('MessageReadDelay')));
self.autoLogout(pInt(SettingsGet('AutoLogout')));
self.msgDefaultAction(SettingsGet('MsgDefaultAction'));
self.viewHTML(SettingsGet('ViewHTML'));
self.showImages(SettingsGet('ShowImages'));
@ -85,5 +98,10 @@ export const SettingsUserStore = new class {
self.hideUnsubscribed(SettingsGet('HideUnsubscribed'));
self.hideDeleted(SettingsGet('HideDeleted'));
self.requestReadReceipt(SettingsGet('requestReadReceipt'));
self.requestDsn(SettingsGet('requestDsn'));
self.pgpSign(SettingsGet('pgpSign'));
self.pgpEncrypt(SettingsGet('pgpEncrypt'));
}
};

View file

@ -1,10 +1,4 @@
.UserBackground body {
background-size: contain;
background-repeat: no-repeat;
background-position: center;
}
#rl-content {
display:flex;
height: 100%;

View file

@ -5,4 +5,9 @@
padding: 3em 15px;
text-align: center;
}
button:focus {
box-shadow: 0 0 1px inset;
}
}

View file

@ -86,9 +86,8 @@
}
input, .btn {
background: none !important;
background: none;
border: 1px solid @glass-m-color;
color: @glass-color;
}
.fontastic + input {

View file

@ -1,6 +1,6 @@
.UserBackground body {
background-size: contain;
background-size: cover;
background-repeat: no-repeat;
background-position: center;
}

View file

@ -246,6 +246,11 @@ html:not(rl-mobile) {
white-space: nowrap;
}
.subjectParent:empty {
font-style: italic;
opacity: 0.5;
}
.threadsCountParent {
display: inline;
overflow: hidden;
@ -266,11 +271,6 @@ html:not(rl-mobile) {
border-color: #666;
}
&.emptySubject .subjectParent {
font-style: italic;
opacity: 0.5;
}
.threads-len {
border-radius: 6px;
border: 1px solid #ccc;

View file

@ -113,12 +113,16 @@ html.rl-no-preview-pane {
margin-bottom: 8px;
}
.subject, .emptySubjectText {
.subject {
flex-grow: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subject:empty {
font-style: italic;
opacity: 0.5;
}
.messageButtons {
margin-top: 5px;
@ -166,12 +170,6 @@ html.rl-no-preview-pane {
}
}
.emptySubjectText {
font-style: italic;
font-weight: normal;
color: #999;
}
.hasVirus {
background: #f00;
color: #fff;

View file

@ -38,7 +38,7 @@ export class AccountPopupView extends AbstractViewPopup {
this.submitRequest(false);
if (iError) {
this.submitError(getNotification(iError));
this.submitErrorAdditional((data && data.ErrorMessageAdditional) || '');
this.submitErrorAdditional(data?.ErrorMessageAdditional);
} else {
rl.app.accountsAndIdentities();
this.close();
@ -54,7 +54,7 @@ export class AccountPopupView extends AbstractViewPopup {
}
onShow(account) {
if (account && account.isAdditional()) {
if (account?.isAdditional()) {
this.isNew(false);
this.email(account.email);
} else {

View file

@ -1,6 +1,7 @@
import { koComputable } from 'External/ko';
import { i18n, trigger as translatorTrigger } from 'Common/Translator';
import { pString } from 'Common/Utils';
import { MessagelistUserStore } from 'Stores/User/Messagelist';
@ -16,6 +17,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
to: '',
subject: '',
text: '',
repliedValue: -1,
selectedDateValue: -1,
selectedTreeValue: '',
@ -26,9 +28,18 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
this.showMultisearch = koComputable(() => FolderUserStore.hasCapability('MULTISEARCH'));
this.repliedOptions = koComputable(() => {
translatorTrigger();
return [
{ id: -1, name: '' },
{ id: 1, name: i18n('GLOBAL/YES') },
{ id: 0, name: i18n('GLOBAL/NO') }
];
});
this.selectedDates = koComputable(() => {
translatorTrigger();
let prefix = 'SEARCH/LABEL_ADV_DATE_';
let prefix = 'SEARCH/LABEL_DATE_';
return [
{ id: -1, name: i18n(prefix + 'ALL') },
{ id: 3, name: i18n(prefix + '3_DAYS') },
@ -42,7 +53,7 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
this.selectedTree = koComputable(() => {
translatorTrigger();
let prefix = 'SEARCH/LABEL_ADV_SUBFOLDERS_';
let prefix = 'SEARCH/LABEL_SUBFOLDERS_';
return [
{ id: '', name: i18n(prefix + 'NONE') },
{ id: 'subtree-one', name: i18n(prefix + 'SUBTREE_ONE') },
@ -60,66 +71,61 @@ export class AdvancedSearchPopupView extends AbstractViewPopup {
this.close();
}
parseSearchStringValue(search) {
const parts = (search || '').split(/[\s]+/g);
parts.forEach(part => {
switch (part) {
case 'has:attachment':
this.hasAttachment(true);
break;
case 'is:unseen,flagged':
this.starred(true);
/* falls through */
case 'is:unseen':
this.unseen(true);
break;
// no default
}
});
}
buildSearchString() {
const
self = this,
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()) {
append('from', self.from().trim());
append('to', self.to().trim());
append('subject', self.subject().trim());
append('text', self.text().trim());
append('in', self.selectedTreeValue());
if (-1 < self.selectedDateValue()) {
let d = new Date();
d.setDate(d.getDate() - this.selectedDateValue());
d.setDate(d.getDate() - self.selectedDateValue());
append('since', d.toISOString().split('T')[0]);
}
let result = new URLSearchParams(data).toString();
if (this.hasAttachment()) {
if (self.hasAttachment()) {
result += '&attachment';
}
if (this.unseen()) {
if (self.unseen()) {
result += '&unseen';
}
if (this.starred()) {
if (self.starred()) {
result += '&flagged';
}
if (1 == self.repliedValue()) {
result += '&answered';
}
if (0 == self.repliedValue()) {
result += '&unanswered';
}
return result.replace(/^&+/, '');
}
onShow(search) {
this.from('');
this.to('');
this.subject('');
this.text('');
this.selectedDateValue(-1);
this.hasAttachment(false);
this.starred(false);
this.unseen(false);
this.parseSearchStringValue(search);
const self = this,
params = new URLSearchParams('?'+search);
self.from(pString(params.get('from')));
self.to(pString(params.get('to')));
self.subject(pString(params.get('subject')));
self.text(pString(params.get('text')));
self.selectedTreeValue(pString(params.get('in')));
// self.selectedDateValue(params.get('since'));
self.selectedDateValue(-1);
self.hasAttachment(params.has('attachment'));
self.starred(params.has('flagged'));
self.unseen(params.has('unseen'));
if (params.has('answered')) {
self.repliedValue(1);
} else if (params.has('unanswered')) {
self.repliedValue(0);
}
}
}

View file

@ -11,6 +11,8 @@ export class AskPopupView extends AbstractViewPopup {
askDesc: '',
yesButton: '',
noButton: '',
username: '',
askUsername: false,
passphrase: '',
askPass: false
});
@ -40,15 +42,19 @@ export class AskPopupView extends AbstractViewPopup {
* @param {boolean=} focusOnShow = true
* @returns {void}
*/
onShow(sAskDesc, fYesFunc = null, fNoFunc = null, focusOnShow = true, askPass = false, btnText = '') {
onShow(sAskDesc, fYesFunc = null, fNoFunc = null, focusOnShow = true, ask = 0, btnText = '') {
this.askDesc(sAskDesc || '');
this.askPass(askPass);
this.askUsername(ask & 2);
this.askPass(ask & 1);
this.username('');
this.passphrase('');
this.yesButton(i18n(btnText || 'POPUPS_ASK/BUTTON_YES'));
this.noButton(i18n(askPass ? 'GLOBAL/CANCEL' : 'POPUPS_ASK/BUTTON_NO'));
this.yesButton(i18n(btnText || 'GLOBAL/YES'));
this.noButton(i18n(ask ? 'GLOBAL/CANCEL' : 'GLOBAL/NO'));
this.fYesAction = fYesFunc;
this.fNoAction = fNoFunc;
this.focusOnShow = focusOnShow ? (askPass ? 'input[type="password"]' : '.buttonYes') : '';
this.focusOnShow = focusOnShow
? (ask ? 'input[type="'+(ask&2?'text':'password')+'"]' : '.buttonYes')
: '';
}
afterShow() {
@ -63,12 +69,15 @@ export class AskPopupView extends AbstractViewPopup {
onBuild() {
// shortcuts.add('tab', 'shift', 'Ask', () => {
shortcuts.add('tab,arrowright,arrowleft', '', 'Ask', () => {
let btn = this.querySelector('.buttonYes');
if (btn.matches(':focus')) {
btn = this.querySelector('.buttonNo');
let yes = this.querySelector('.buttonYes'),
no = this.querySelector('.buttonNo');
if (yes.matches(':focus')) {
no.focus();
return false;
} else if (no.matches(':focus')) {
yes.focus();
return false;
}
btn.focus();
return false;
});
}
}
@ -80,7 +89,20 @@ AskPopupView.password = function(sAskDesc, btnText) {
() => resolve(this.__vm.passphrase()),
() => resolve(null),
true,
true,
1,
btnText
]);
});
}
AskPopupView.credentials = function(sAskDesc, btnText) {
return new Promise(resolve => {
this.showModal([
sAskDesc,
() => resolve({username:this.__vm.username(), password:this.__vm.passphrase()}),
() => resolve(null),
true,
3,
btnText
]);
});

View file

@ -16,11 +16,11 @@ import { encodeHtml, HtmlEditor, htmlToPlain } from 'Common/Html';
import { koArrayWithDestroy } from 'External/ko';
import { UNUSED_OPTION_VALUE } from 'Common/Consts';
import { messagesDeleteHelper } from 'Common/Folders';
import { folderInformation, messagesDeleteHelper } from 'Common/Folders';
import { serverRequest } from 'Common/Links';
import { i18n, getNotification, getUploadErrorDescByCode, timestampToString } from 'Common/Translator';
import { MessageFlagsCache, setFolderHash } from 'Common/Cache';
import { Settings, SettingsCapa, SettingsGet, elementById, addShortcut } from 'Common/Globals';
import { Settings, SettingsCapa, SettingsGet, elementById, addShortcut, createElement } from 'Common/Globals';
//import { exitFullscreen, isFullscreen, toggleFullscreen } from 'Common/Fullscreen';
import { AppUserStore } from 'Stores/User/App';
@ -50,9 +50,13 @@ import { ThemeStore } from 'Stores/Theme';
let alreadyFullscreen;
*/
let oLastMessage;
const
ScopeCompose = 'Compose',
tpl = createElement('template'),
base64_encode = text => btoa(unescape(encodeURIComponent(text))).match(/.{1,76}/g).join('\r\n'),
email = new EmailModel(),
@ -77,7 +81,7 @@ const
if (FolderUserStore.currentFolderFullName() === draftsFolder) {
MessagelistUserStore.reload(true);
} else {
rl.app.folderInformation(draftsFolder);
folderInformation(draftsFolder);
}
}
},
@ -209,8 +213,8 @@ export class ComposePopupView extends AbstractViewPopup {
super('Compose');
const fEmailOutInHelper = (context, identity, name, isIn) => {
if (identity && context && identity[name]() && (isIn ? true : context[name]())) {
const identityEmail = identity[name]();
const identityEmail = context && identity?.[name]();
if (identityEmail && (isIn ? true : context[name]())) {
let list = context[name]().trim().split(',');
list = list.filter(email => {
@ -218,15 +222,12 @@ export class ComposePopupView extends AbstractViewPopup {
return email && identityEmail.trim() !== email;
});
if (isIn) {
list.push(identityEmail);
}
isIn && list.push(identityEmail);
context[name](list.join(','));
}
};
this.oLastMessage = null;
this.oEditor = null;
this.aDraftInfo = null;
this.sInReplyTo = '';
@ -352,7 +353,7 @@ export class ComposePopupView extends AbstractViewPopup {
},
attachmentsInProcess: () => this.attachments.filter(item => item && !item.complete()),
attachmentsInError: () => this.attachments.filter(item => item && item.error()),
attachmentsInError: () => this.attachments.filter(item => item?.error()),
attachmentsCount: () => this.attachments.length,
attachmentsInErrorCount: () => this.attachmentsInError.length,
@ -508,7 +509,7 @@ export class ComposePopupView extends AbstractViewPopup {
alternative.children.push(data);
data = alternative;
}
if (sign && !draft && sign[1]) {
if (!draft && sign?.[1]) {
if ('openpgp' == sign[0]) {
// Doesn't sign attachments
params.Html = params.Text = '';
@ -608,7 +609,7 @@ export class ComposePopupView extends AbstractViewPopup {
this.savedErrorDesc(i18n('COMPOSE/SAVED_ERROR_ON_SEND').trim());
} else {
this.sendError(true);
this.sendErrorDesc(getNotification(iError, data && data.ErrorMessage)
this.sendErrorDesc(getNotification(iError, data?.ErrorMessage)
|| getNotification(Notification.CantSendMessage));
}
} else {
@ -755,11 +756,11 @@ export class ComposePopupView extends AbstractViewPopup {
// getAutocomplete
emailsSource(value, fResponse) {
Remote.request('Suggestions',
Remote.abort('Suggestions').request('Suggestions',
(iError, data) => {
if (!iError && isArray(data.Result)) {
fResponse(
data.Result.map(item => (item && item[0] ? (new EmailModel(item[0], item[1])).toLine() : null))
data.Result.map(item => (item?.[0] ? (new EmailModel(item[0], item[1])).toLine() : null))
.filter(v => v)
);
} else if (Notification.RequestAborted !== iError) {
@ -769,15 +770,12 @@ export class ComposePopupView extends AbstractViewPopup {
{
Query: value
// ,Page: 1
},
null,
'',
['Suggestions']
}
);
}
selectIdentity(identity) {
identity = identity && identity.item;
identity = identity?.item;
if (identity) {
this.currentIdentity(identity);
this.setSignatureFromIdentity(identity);
@ -828,7 +826,7 @@ export class ComposePopupView extends AbstractViewPopup {
this.editor(editor => {
let signature = identity.signature() || '',
isHtml = ':HTML:' === signature.slice(0, 6),
fromLine = this.oLastMessage ? emailArrayToStringLineHelper(this.oLastMessage.from, true) : '';
fromLine = oLastMessage ? emailArrayToStringLineHelper(oLastMessage.from, true) : '';
if (fromLine) {
signature = signature.replace(/{{FROM-FULL}}/g, fromLine);
if (!fromLine.includes(' ') && 0 < fromLine.indexOf('@')) {
@ -909,24 +907,20 @@ export class ComposePopupView extends AbstractViewPopup {
sText = '',
identity = null,
aDraftInfo = null,
message = null;
message = 1 === arrayLength(oMessageOrArray)
? oMessageOrArray[0]
: (isArray(oMessageOrArray) ? null : oMessageOrArray);
const excludeEmail = {},
const
// excludeEmail = new Set(),
excludeEmail = {},
mEmail = AccountUserStore.email(),
lineComposeType = sType || ComposeType.Empty;
if (oMessageOrArray) {
message =
1 === arrayLength(oMessageOrArray)
? oMessageOrArray[0]
: isArray(oMessageOrArray)
? null
: oMessageOrArray;
}
oLastMessage = message;
this.oLastMessage = message;
if (null !== mEmail) {
if (mEmail) {
// excludeEmail.add(mEmail);
excludeEmail[mEmail] = true;
}
@ -934,6 +928,7 @@ export class ComposePopupView extends AbstractViewPopup {
identity = findIdentityByMessage(lineComposeType, message);
if (identity) {
// excludeEmail.add(identity.email());
excludeEmail[identity.email()] = true;
}
@ -1030,16 +1025,22 @@ export class ComposePopupView extends AbstractViewPopup {
// no default
}
sText = message.bodyAsHTML();
let encrypted;
// https://github.com/the-djmaze/snappymail/issues/491
tpl.innerHTML = message.bodyAsHTML();
tpl.content.querySelectorAll('img').forEach(img =>
img.dataset.xSrcCid || img.dataset.xSrc || img.replaceWith(img.alt || img.title)
);
sText = tpl.innerHTML.trim();
switch (lineComposeType) {
case ComposeType.Reply:
case ComposeType.ReplyAll:
sFrom = message.fromToLine(false, true);
sText = '<br><br><p>' + i18n('COMPOSE/REPLY_MESSAGE_TITLE', { DATETIME: sDate, EMAIL: sFrom })
+ ':</p><blockquote>'
+ sText.replace(/<img[^>]+>/g, '').replace(/<a\s[^>]+><\/a>/g, '').trim()
+ sText.trim()
+ '</blockquote>';
break;
@ -1068,6 +1069,7 @@ export class ComposePopupView extends AbstractViewPopup {
case ComposeType.ForwardAsAttachment:
sText = '';
break;
default:
encrypted = PgpUserStore.isEncrypted(sText);
if (encrypted) {
@ -1131,7 +1133,8 @@ export class ComposePopupView extends AbstractViewPopup {
this.setFocusInPopup();
}
const downloads = this.getAttachmentsDownloadsForUpload();
// item.CID item.isInline item.isLinked
const downloads = this.attachments.filter(item => item && !item.tempName()).map(item => item.id);
if (arrayLength(downloads)) {
Remote.request('MessageUploadAttachments',
(iError, oData) => {
@ -1148,7 +1151,7 @@ export class ComposePopupView extends AbstractViewPopup {
});
} else {
this.attachments.forEach(attachment => {
if (attachment && attachment.fromMessage) {
if (attachment?.fromMessage) {
attachment
.waiting(false)
.uploading(false)
@ -1175,7 +1178,7 @@ export class ComposePopupView extends AbstractViewPopup {
if (!this.to()) {
this.to.focused(true);
} else if (!this.to.focused()) {
this.oEditor && this.oEditor.focus();
this.oEditor?.focus();
}
}, 100);
}
@ -1261,7 +1264,7 @@ export class ComposePopupView extends AbstractViewPopup {
})
.on('onComplete', (id, result, data) => {
const attachment = this.getAttachmentById(id),
response = (data && data.Result) || {},
response = data?.Result || {},
errorCode = response.ErrorCode,
attachmentJson = result && response.Attachment;
@ -1349,7 +1352,7 @@ export class ComposePopupView extends AbstractViewPopup {
prepareAttachmentsForSendOrSave() {
const result = {};
this.attachments.forEach(item => {
if (item && item.complete() && item.tempName() && item.enabled()) {
if (item?.complete() && item?.tempName() && item?.enabled()) {
result[item.tempName()] = [item.fileName(), item.isInline ? '1' : '0', item.CID, item.contentLocation];
}
});
@ -1376,7 +1379,7 @@ export class ComposePopupView extends AbstractViewPopup {
oJua || attachment.waiting(false).uploading(true);
attachment.cancel = () => {
this.attachments.remove(attachment);
oJua && oJua.cancel(attachment.id);
oJua?.cancel(attachment.id);
};
this.attachments.push(attachment);
view && this.attachmentsArea();
@ -1402,35 +1405,22 @@ export class ComposePopupView extends AbstractViewPopup {
if (message) {
if (ComposeType.ForwardAsAttachment === type) {
this.addMessageAsAttachment(message);
} else {
} else if ([
ComposeType.Reply, ComposeType.ReplyAll,
ComposeType.Forward, ComposeType.Draft, ComposeType.EditAsNew
].includes(type)) {
message.attachments.forEach(item => {
let add = false;
switch (type) {
case ComposeType.Reply:
case ComposeType.ReplyAll:
break;
case ComposeType.Forward:
case ComposeType.Draft:
case ComposeType.EditAsNew:
add = true;
break;
// no default
}
if (add) {
const attachment = new ComposeAttachmentModel(
item.download,
item.fileName,
item.estimatedSize,
item.isInline(),
item.isLinked(),
item.cid,
item.contentLocation
);
attachment.fromMessage = true;
this.addAttachment(attachment);
}
const attachment = new ComposeAttachmentModel(
item.download,
item.fileName,
item.estimatedSize,
item.isInline(),
item.isLinked(),
item.cid,
item.contentLocation
);
attachment.fromMessage = true;
this.addAttachment(attachment);
});
}
}
@ -1443,7 +1433,7 @@ export class ComposePopupView extends AbstractViewPopup {
isEmptyForm(includeAttachmentInProgress = true) {
const withoutAttachment = includeAttachmentInProgress
? !this.attachments.length
: !this.attachments.some(item => item && item.complete());
: !this.attachments.some(item => item?.complete());
return (
!this.to.length &&
@ -1463,8 +1453,8 @@ export class ComposePopupView extends AbstractViewPopup {
this.replyTo('');
this.subject('');
this.requestDsn(false);
this.requestReadReceipt(false);
this.requestDsn(SettingsUserStore.requestDsn());
this.requestReadReceipt(SettingsUserStore.requestReadReceipt());
this.markAsImportant(false);
this.bodyArea();
@ -1485,8 +1475,8 @@ export class ComposePopupView extends AbstractViewPopup {
this.showBcc(false);
this.showReplyTo(false);
this.pgpSign(false);
this.pgpEncrypt(false);
this.pgpSign(SettingsUserStore.pgpSign());
this.pgpEncrypt(SettingsUserStore.pgpEncrypt());
this.attachments([]);
@ -1499,20 +1489,11 @@ export class ComposePopupView extends AbstractViewPopup {
this.sending(false);
this.saving(false);
this.oEditor && this.oEditor.clear();
this.oEditor?.clear();
this.dropMailvelope();
}
/**
* @returns {Array}
*/
getAttachmentsDownloadsForUpload() {
return this.attachments.filter(item => item && !item.tempName()).map(
item => item.id
);
}
mailvelopeArea() {
if (!this.mailvelope) {
/**

View file

@ -78,7 +78,7 @@ export class ContactsPopupView extends AbstractViewPopup {
contactsPaginator: computedPaginatorHelper(this.contactsPage, pagecount),
contactsCheckedOrSelected: () => {
const checked = ContactUserStore.filter(item => item.checked && item.checked()),
const checked = ContactUserStore.filter(item => item.checked?.()),
selected = this.selectorContact();
return selected
@ -128,7 +128,7 @@ export class ContactsPopupView extends AbstractViewPopup {
const data = oItem.getNameAndEmailHelper(),
email = data ? new EmailModel(data[0], data[1]) : null;
if (email && email.validate()) {
if (email?.validate()) {
return email;
}
}
@ -235,7 +235,7 @@ export class ContactsPopupView extends AbstractViewPopup {
if (this.contactsCheckedOrSelected().length) {
Remote.request('ContactsDelete',
(iError, oData) => {
if (500 < (!iError && oData && oData.Time ? pInt(oData.Time) : 0)) {
if (500 < (!iError && oData?.Time) ? pInt(oData.Time) : 0) {
this.reloadContactList(this.bDropPageAfterDelete);
} else {
setTimeout(() => this.reloadContactList(this.bDropPageAfterDelete), 500);
@ -274,7 +274,7 @@ export class ContactsPopupView extends AbstractViewPopup {
}
ContactUserStore.loading(true);
Remote.request('Contacts',
Remote.abort('Contacts').request('Contacts',
(iError, data) => {
let count = 0,
list = [];
@ -299,10 +299,7 @@ export class ContactsPopupView extends AbstractViewPopup {
Offset: offset,
Limit: CONTACTS_PER_PAGE,
Search: this.search()
},
null,
'',
['Contacts']
}
);
}

View file

@ -8,6 +8,8 @@ import { AbstractViewPopup } from 'Knoin/AbstractViews';
import { DomainAdminStore } from 'Stores/Admin/Domain';
import { AskPopupView } from 'View/Popup/Ask';
const domainToParams = oDomain => ({
Name: oDomain.name(),
@ -179,39 +181,46 @@ export class DomainPopupView extends AbstractViewPopup {
testConnectionCommand() {
this.clearTesting(false);
this.testing(true);
// https://github.com/the-djmaze/snappymail/issues/477
AskPopupView.credentials('IMAP', 'GLOBAL/TEST').then(credentials => {
if (credentials) {
this.testing(true);
const params = domainToParams(this);
params.username = credentials.username;
params.password = credentials.password;
Remote.request('AdminDomainTest',
(iError, oData) => {
this.testing(false);
if (iError) {
this.testingImapError(true);
this.testingSieveError(true);
this.testingSmtpError(true);
} else {
this.testingDone(true);
this.testingImapError(true !== oData.Result.Imap);
this.testingSieveError(true !== oData.Result.Sieve);
this.testingSmtpError(true !== oData.Result.Smtp);
Remote.request('AdminDomainTest',
(iError, oData) => {
this.testing(false);
if (iError) {
this.testingImapError(true);
this.testingSieveError(true);
this.testingSmtpError(true);
} else {
this.testingDone(true);
this.testingImapError(true !== oData.Result.Imap);
this.testingSieveError(true !== oData.Result.Sieve);
this.testingSmtpError(true !== oData.Result.Smtp);
if (this.testingImapError() && oData.Result.Imap) {
this.testingImapErrorDesc('');
this.testingImapErrorDesc(oData.Result.Imap);
}
if (this.testingImapError() && oData.Result.Imap) {
this.testingImapErrorDesc('');
this.testingImapErrorDesc(oData.Result.Imap);
}
if (this.testingSieveError() && oData.Result.Sieve) {
this.testingSieveErrorDesc('');
this.testingSieveErrorDesc(oData.Result.Sieve);
}
if (this.testingSieveError() && oData.Result.Sieve) {
this.testingSieveErrorDesc('');
this.testingSieveErrorDesc(oData.Result.Sieve);
}
if (this.testingSmtpError() && oData.Result.Smtp) {
this.testingSmtpErrorDesc('');
this.testingSmtpErrorDesc(oData.Result.Smtp);
}
}
},
domainToParams(this)
);
if (this.testingSmtpError() && oData.Result.Smtp) {
this.testingSmtpErrorDesc('');
this.testingSmtpErrorDesc(oData.Result.Smtp);
}
}
},
params
);
}
});
}
onDomainCreateOrSaveResponse(iError) {
@ -239,7 +248,7 @@ export class DomainPopupView extends AbstractViewPopup {
if (oDomain) {
this.enableSmartPorts(false);
this.edit(true);
forEachObjectEntry(oDomain, (key, value) => this[key] && this[key](value));
forEachObjectEntry(oDomain, (key, value) => this[key]?.(value));
this.enableSmartPorts(true);
}
}

View file

@ -19,7 +19,7 @@ export class FolderSystemPopupView extends AbstractViewPopup {
this.folderSelectList = koComputable(() =>
folderListOptionsBuilder(
FolderUserStore.folderListSystemNames(),
FolderUserStore.systemFoldersNames(),
[
['', i18n('POPUPS_SYSTEM_FOLDERS/SELECT_CHOOSE_ONE')],
[UNUSED_OPTION_VALUE, i18n('POPUPS_SYSTEM_FOLDERS/SELECT_UNUSE_NAME')]

View file

@ -63,9 +63,7 @@ export class IdentityPopupView extends AbstractViewPopup {
submitForm() {
if (!this.submitRequest()) {
if (this.signature && this.signature.__fetchEditorValue) {
this.signature.__fetchEditorValue();
}
this.signature?.__fetchEditorValue?.();
if (!this.emailHasError()) {
this.emailHasError(!this.email().trim());

View file

@ -51,7 +51,7 @@ export class LanguagesPopupView extends AbstractViewPopup {
}
changeLanguage(lang) {
this.fLang && this.fLang(lang);
this.fLang?.(lang);
this.close();
}
}

View file

@ -94,7 +94,7 @@ export class OpenPgpGeneratePopupView extends AbstractViewPopup {
showError(e) {
console.log(e);
if (e && e.message) {
if (e?.message) {
this.submitError(e.message);
}
}

View file

@ -114,9 +114,9 @@ export class LoginUserView extends AbstractViewLogin {
if (Notification.InvalidInputArgument == iError) {
iError = Notification.AuthError;
}
this.submitError(getNotification(iError, (oData ? oData.ErrorMessage : ''),
this.submitError(getNotification(iError, oData?.ErrorMessage,
Notification.UnknownNotification));
this.submitErrorAdditional((oData && oData.ErrorMessageAdditional) || '');
this.submitErrorAdditional(oData?.ErrorMessageAdditional);
} else {
rl.setData(oData.Result);
}

View file

@ -31,9 +31,7 @@ export class MailFolderList extends AbstractViewLeft {
this.composeInEdit = AppUserStore.composeInEdit;
this.folderList = FolderUserStore.folderList;
this.folderListSystem = FolderUserStore.folderListSystem;
this.foldersChanging = FolderUserStore.foldersChanging;
this.systemFolders = FolderUserStore.systemFolders;
this.moveAction = moveAction;
@ -84,7 +82,7 @@ export class MailFolderList extends AbstractViewLeft {
}
el = eqs(event, 'a');
if (el && el.matches('.selectable')) {
if (el?.matches('.selectable')) {
event.preventDefault();
const folder = ko.dataFor(el);
if (folder) {
@ -172,10 +170,10 @@ export class MailFolderList extends AbstractViewLeft {
AppUserStore.focusedState.subscribe(value => {
let el = qs('li a.focused');
el && el.classList.remove('focused');
el?.classList.remove('focused');
if (Scope.FolderList === value) {
el = qs('li a.selected');
el && el.classList.add('focused');
el?.classList.add('focused');
}
});
}

View file

@ -20,7 +20,7 @@ import { isFullscreen, toggleFullscreen } from 'Common/Fullscreen';
import { mailBox, serverRequest } from 'Common/Links';
import { Selector } from 'Common/Selector';
import { i18n, initOnStartOrLangChange } from 'Common/Translator';
import { i18n } from 'Common/Translator';
import {
getFolderFromCacheList,
@ -55,14 +55,14 @@ const
*/
listAction = (...args) => MessagelistUserStore.setAction(...args);
let
iGoToUpOrDownTimeout = 0,
sLastSearchValue = '';
export class MailMessageList extends AbstractViewRight {
constructor() {
super();
this.emptySubjectValue = '';
this.iGoToUpOrDownTimeout = 0;
this.newMoveToFolder = !!SettingsGet('NewMoveToFolder');
this.allowSearch = SettingsCapa('Search');
@ -76,19 +76,10 @@ export class MailMessageList extends AbstractViewRight {
this.isMobile = ThemeStore.isMobile;
this.leftPanelDisabled = leftPanelDisabled;
this.messageListSearch = MessagelistUserStore.listSearch;
this.messageListError = MessagelistUserStore.error;
this.popupVisibility = arePopupsVisible;
this.useCheckboxesInList = SettingsUserStore.useCheckboxesInList;
this.messageListThreadUid = MessagelistUserStore.endThreadUid;
this.messageListIsLoading = MessagelistUserStore.isLoading;
initOnStartOrLangChange(() => this.emptySubjectValue = i18n('MESSAGE_LIST/EMPTY_SUBJECT_TEXT'));
this.userUsageProc = FolderUserStore.quotaPercentage;
this.addObservables({
@ -99,15 +90,13 @@ export class MailMessageList extends AbstractViewRight {
dragOverArea: null,
dragOverBodyArea: null,
inputMessageListSearchFocus: false
focusSearch: false
});
// append drag and drop
this.dragOver = ko.observable(false).extend({ throttle: 1 });
this.dragOverEnter = ko.observable(false).extend({ throttle: 1 });
this.sLastSearchValue = '';
this.addComputables({
sortSupported: () =>
@ -136,9 +125,9 @@ export class MailMessageList extends AbstractViewRight {
}
},
inputProxyMessageListSearch: {
inputSearch: {
read: MessagelistUserStore.mainSearch,
write: value => this.sLastSearchValue = value
write: value => sLastSearchValue = value
},
isIncompleteChecked: () => {
@ -146,33 +135,28 @@ export class MailMessageList extends AbstractViewRight {
return c && MessagelistUserStore().length > c;
},
hasMessages: () => 0 < MessagelistUserStore().length,
isSpamFolder: () => (FolderUserStore.spamFolder() || 0) === MessagelistUserStore().Folder,
isSpamDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.spamFolder(),
isTrashFolder: () => (FolderUserStore.trashFolder() || 0) === MessagelistUserStore().Folder,
isDraftFolder: () => (FolderUserStore.draftsFolder() || 0) === MessagelistUserStore().Folder,
isSentFolder: () => (FolderUserStore.sentFolder() || 0) === MessagelistUserStore().Folder,
archiveAllowed: () =>
(FolderUserStore.archiveFolder() || 0) !== MessagelistUserStore().Folder
&& UNUSED_OPTION_VALUE !== FolderUserStore.archiveFolder()
&& !this.isDraftFolder(),
isArchiveFolder: () => (FolderUserStore.archiveFolder() || 0) === MessagelistUserStore().Folder,
spamAllowed: () => UNUSED_OPTION_VALUE !== FolderUserStore.spamFolder()
&& (FolderUserStore.sentFolder() || 0) !== MessagelistUserStore().Folder
&& !this.isDraftFolder(),
isArchiveDisabled: () => UNUSED_OPTION_VALUE === FolderUserStore.archiveFolder(),
isSpamVisible: () => !this.isSpamFolder() && this.spamAllowed(),
isArchiveVisible: () => !this.isArchiveFolder() && !this.isArchiveDisabled() && !this.isDraftFolder(),
isUnSpamVisible: () => this.isSpamFolder() && this.spamAllowed(),
isSpamVisible: () =>
!this.isSpamFolder() && !this.isSpamDisabled() && !this.isDraftFolder() && !this.isSentFolder(),
mobileCheckedStateShow: () => ThemeStore.isMobile() ? MessagelistUserStore.listChecked().length : 1,
isUnSpamVisible: () =>
this.isSpamFolder() && !this.isSpamDisabled() && !this.isDraftFolder() && !this.isSentFolder(),
mobileCheckedStateShow: () => ThemeStore.isMobile() ? 0 < MessagelistUserStore.listChecked().length : true,
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessagelistUserStore.listChecked().length : true,
mobileCheckedStateHide: () => ThemeStore.isMobile() ? !MessagelistUserStore.listChecked().length : 1,
sortText: () => {
let mode = FolderUserStore.sortMode(),
@ -188,8 +172,6 @@ export class MailMessageList extends AbstractViewRight {
}
});
this.hasCheckedOrSelectedLines = MessagelistUserStore.hasCheckedOrSelected,
this.selector = new Selector(
MessagelistUserStore,
MessagelistUserStore.selectedMessage,
@ -228,7 +210,7 @@ export class MailMessageList extends AbstractViewRight {
const sFolder = e.detail.Folder, iUid = e.detail.Uid;
const message = MessagelistUserStore.find(
item => item && sFolder === item.folder && iUid == item.uid
item => sFolder === item?.folder && iUid == item?.uid
);
if ('INBOX' === sFolder) {
@ -344,7 +326,7 @@ export class MailMessageList extends AbstractViewRight {
moveNewCommand(vm, event) {
if (this.newMoveToFolder && this.mobileCheckedStateShow()) {
if (vm && event && event.preventDefault) {
if (vm && event?.preventDefault) {
event.preventDefault();
event.stopPropagation();
}
@ -364,8 +346,8 @@ export class MailMessageList extends AbstractViewRight {
return false;
}
clearTimeout(this.iGoToUpOrDownTimeout);
this.iGoToUpOrDownTimeout = setTimeout(() => {
clearTimeout(iGoToUpOrDownTimeout);
iGoToUpOrDownTimeout = setTimeout(() => {
let prev, next, temp, current;
this.messageListPaginator().find(item => {
@ -412,7 +394,7 @@ export class MailMessageList extends AbstractViewRight {
cancelSearch() {
MessagelistUserStore.mainSearch('');
this.inputMessageListSearchFocus(false);
this.focusSearch(false);
}
cancelThreadUid() {
@ -446,7 +428,7 @@ export class MailMessageList extends AbstractViewRight {
getDragData(event) {
const item = ko.dataFor(doc.elementFromPoint(event.clientX, event.clientY));
item && item.checked && item.checked(true);
item?.checked?.(true);
const uids = MessagelistUserStore.listCheckedOrSelectedUidsWithSubMails();
item && !uids.includes(item.uid) && uids.push(item.uid);
return uids.length ? {
@ -589,7 +571,7 @@ export class MailMessageList extends AbstractViewRight {
}
gotoThread(message) {
if (message && 0 < message.threadsLen()) {
if (0 < message?.threadsLen()) {
MessagelistUserStore.pageBeforeThread(MessagelistUserStore.page());
hasher.setHash(
@ -643,7 +625,7 @@ export class MailMessageList extends AbstractViewRight {
el && this.gotoThread(ko.dataFor(el));
},
dblclick: event => {
let el = eqs(event, '.actionHandle');
let el = eqs(event, '.actionHandle');
el && this.gotoThread(ko.dataFor(el));
}
});
@ -684,7 +666,7 @@ export class MailMessageList extends AbstractViewRight {
addShortcut('enter,open', '', Scope.MessageList, () => {
if (formFieldFocused()) {
MessagelistUserStore.mainSearch(this.sLastSearchValue);
MessagelistUserStore.mainSearch(sLastSearchValue);
return false;
}
if (MessageUserStore.message() && this.useAutoSelect()) {
@ -735,15 +717,10 @@ export class MailMessageList extends AbstractViewRight {
});
registerShortcut('t', '', [Scope.MessageList], () => {
let message = MessagelistUserStore.selectedMessage();
if (!message) {
message = MessagelistUserStore.focusedMessage();
}
if (message && 0 < message.threadsLen()) {
let message = MessagelistUserStore.selectedMessage() || MessagelistUserStore.focusedMessage();
if (0 < message?.threadsLen()) {
this.gotoThread(message);
}
return false;
});
@ -778,7 +755,7 @@ export class MailMessageList extends AbstractViewRight {
if (SettingsCapa('Search')) {
// search input focus
addShortcut('/', '', [Scope.MessageList, Scope.MessageView], () => {
this.inputMessageListSearchFocus(true);
this.focusSearch(true);
return false;
});
}

View file

@ -7,7 +7,6 @@ import { Scope } from 'Common/Enums';
import {
ComposeType,
ClientSideKeyNameLastReplyAction,
ClientSideKeyNameMessageHeaderFullInfo,
ClientSideKeyNameMessageAttachmentControls,
FolderType,
@ -94,10 +93,11 @@ export class MailMessageView extends AbstractViewRight {
}
}, this.messageVisibility);
this.msgDefaultAction = SettingsUserStore.msgDefaultAction;
this.addObservables({
showAttachmentControls: !!Local.get(ClientSideKeyNameMessageAttachmentControls),
downloadAsZipLoading: false,
lastReplyAction_: '',
showFullInfo: '1' === Local.get(ClientSideKeyNameMessageHeaderFullInfo),
moreDropdownTrigger: false,
@ -134,19 +134,10 @@ export class MailMessageView extends AbstractViewRight {
allowAttachmentControls: () => arrayLength(attachmentsActions) && SettingsCapa('AttachmentsActions'),
downloadAsZipAllowed: () => this.attachmentsActions.includes('zip')
&& (currentMessage() ? currentMessage().attachments : [])
.filter(item => item && !item.isLinked() && item.checked() && item.download)
&& (currentMessage()?.attachments || [])
.filter(item => item?.download && !item?.isLinked() && item?.checked())
.length,
lastReplyAction: {
read: this.lastReplyAction_,
write: value => this.lastReplyAction_(
[ComposeType.Reply, ComposeType.ReplyAll, ComposeType.Forward].includes(value)
? ComposeType.Reply
: value
)
},
tagsAllowed: () => FolderUserStore.currentFolder() ? FolderUserStore.currentFolder().tagsAllowed() : false,
messageVisibility: () => !MessageUserStore.loading() && !!currentMessage(),
@ -182,8 +173,6 @@ export class MailMessageView extends AbstractViewRight {
});
this.addSubscribables({
lastReplyAction_: value => Local.set(ClientSideKeyNameLastReplyAction, value),
message: message => {
MessageUserStore.activeDom(null);
@ -207,8 +196,6 @@ export class MailMessageView extends AbstractViewRight {
showFullInfo: value => Local.set(ClientSideKeyNameMessageHeaderFullInfo, value ? '1' : '0')
});
this.lastReplyAction(Local.get(ClientSideKeyNameLastReplyAction) || ComposeType.Reply);
// commands
this.replyCommand = createCommandReplyHelper(ComposeType.Reply);
this.replyAllCommand = createCommandReplyHelper(ComposeType.ReplyAll);
@ -256,7 +243,6 @@ export class MailMessageView extends AbstractViewRight {
* @returns {void}
*/
replyOrforward(sType) {
this.lastReplyAction(sType);
showMessageComposer([sType, currentMessage()]);
}
@ -300,7 +286,7 @@ export class MailMessageView extends AbstractViewRight {
el = eqs(event, '.attachmentsPlace .attachmentItem .attachmentNameParent');
if (el) {
const attachment = ko.dataFor(el);
if (attachment && attachment.linkDownload()) {
if (attachment?.linkDownload()) {
if ('message/rfc822' == attachment.mimeType) {
// TODO
rl.fetch(attachment.linkDownload()).then(response => {
@ -407,7 +393,7 @@ export class MailMessageView extends AbstractViewRight {
// toggle message blockquotes
registerShortcut('b', '', [Scope.MessageList, Scope.MessageView], () => {
const message = currentMessage();
if (message && message.body) {
if (message?.body) {
message.body.querySelectorAll('.rlBlockquoteSwitcher').forEach(node => node.click());
return false;
}
@ -425,7 +411,7 @@ export class MailMessageView extends AbstractViewRight {
// print
addShortcut('p,printscreen', 'meta', [Scope.MessageView, Scope.MessageList], () => {
currentMessage() && currentMessage().printMessage();
currentMessage()?.printMessage();
return false;
});
@ -524,7 +510,7 @@ export class MailMessageView extends AbstractViewRight {
downloadAsZip() {
const hashes = (currentMessage() ? currentMessage().attachments : [])
.map(item => (item && !item.isLinked() && item.checked() ? item.download : ''))
.map(item => item?.checked() && !item?.isLinked() ? item.download : '')
.filter(v => v);
if (hashes.length) {
Remote.post('AttachmentsActions', this.downloadAsZipLoading, {
@ -532,7 +518,7 @@ export class MailMessageView extends AbstractViewRight {
Hashes: hashes
})
.then(result => {
let hash = result && result.Result && result.Result.FileHash;
let hash = result?.Result?.FileHash;
if (hash) {
download(attachmentDownload(hash), hash+'.zip');
} else {
@ -591,7 +577,7 @@ export class MailMessageView extends AbstractViewRight {
if (result.data) {
MimeToMessage(result.data, oMessage);
oMessage.html() ? oMessage.viewHtml() : oMessage.viewPlain();
if (result.signatures && result.signatures.length) {
if (result.signatures?.length) {
oMessage.pgpSigned(true);
oMessage.pgpVerified({
signatures: result.signatures,
@ -610,7 +596,7 @@ export class MailMessageView extends AbstractViewRight {
oMessage.pgpVerified(result);
}
/*
if (result && result.success) {
if (result?.success) {
i18n('OPENPGP/GOOD_SIGNATURE', {
USER: validKey.user + ' (' + validKey.id + ')'
});
@ -618,7 +604,7 @@ export class MailMessageView extends AbstractViewRight {
} else {
const keyIds = arrayLength(signingKeyIds) ? signingKeyIds : null,
additional = keyIds
? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ')
? keyIds.map(item => item?.toHex?.()).filter(v => v).join(', ')
: '';
i18n('OPENPGP/ERROR', {

View file

@ -52,7 +52,7 @@ export class SystemDropDownUserView extends AbstractViewRight {
}
accountClick(account, event) {
let email = account && account.email;
let email = account?.email;
if (email && 0 === event.button && AccountUserStore.email() != email) {
AccountUserStore.loading(true);
event.preventDefault();

View file

@ -70,7 +70,7 @@
htmlDrag = b => doc.documentElement.classList.toggle('firefox-drag', b),
setDragImage = (src, xOffset, yOffset) => {
img && img.remove();
img?.remove();
if (src) {
// create drag image from custom element or drag source
img = src.cloneNode(true);
@ -85,7 +85,7 @@
if (dragSource) {
clearInterval(holdInterval);
// dispose of drag image element
img && img.remove();
img?.remove();
isDragging && dispatchEvent(lastTouch, 'dragend', dragSource);
img = dragSource = lastTouch = lastTarget = dataTransfer = holdInterval = null;
isDragging = allowDrop = false;
@ -163,6 +163,17 @@
return false;
};
/*
doc.addEventListener('pointerdown', e => {
doc.addEventListener('pointermove', e => {
e.clientX
});
doc.setPointerCapture(e.pointerId);
});
doc.addEventListener('pointerup', e => {
doc.releasePointerCapture(e.pointerId);
});
*/
doc.addEventListener('touchstart', e => {
// clear all variables
reset();

View file

@ -64,9 +64,8 @@ const
keydown = event => {
let key = (event.key || '').toLowerCase().replace(' ','space'),
modifiers = ['alt','ctrl','meta','shift'].filter(v => event[v+'Key']).join('+');
scope[key] && scope[key][modifiers] && scope[key][modifiers].forEach(cmd => exec(event, cmd));
!event.defaultPrevented && _scope !== 'all' && _scopes.all[key] && _scopes.all[key][modifiers]
&& _scopes.all[key][modifiers].forEach(cmd => exec(event, cmd));
scope[key]?.[modifiers]?.forEach(cmd => exec(event, cmd));
!event.defaultPrevented && _scope !== 'all' && _scopes.all[key]?.[modifiers]?.forEach(cmd => exec(event, cmd));
};
win.shortcuts = shortcuts;

View file

@ -17,7 +17,6 @@ import { SieveScriptPopupView } from 'Sieve/View/Script';
window.Sieve = {
capa: capa,
scripts: scripts,
setError: setError,
loading: loading,
serverError: serverError,
serverErrorDesc: serverErrorDesc,