mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-08-30 04:29:21 +03:00
Merge remote-tracking branch 'origin/master' into addressbook
This commit is contained in:
commit
1a5b8819fa
204 changed files with 2050 additions and 2773 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ export const
|
|||
ClientSideKeyNameExpandedFolders = 3,
|
||||
ClientSideKeyNameFolderListSize = 4,
|
||||
ClientSideKeyNameMessageListSize = 5,
|
||||
ClientSideKeyNameLastReplyAction = 6,
|
||||
ClientSideKeyNameLastSignMe = 7,
|
||||
ClientSideKeyNameMessageHeaderFullInfo = 9,
|
||||
ClientSideKeyNameMessageAttachmentControls = 10;
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
}
|
||||
);
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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)))),
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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'});
|
||||
|
|
|
|||
33
dev/External/SquireUI.js
vendored
33
dev/External/SquireUI.js
vendored
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
16
dev/External/User/ko.js
vendored
16
dev/External/User/ko.js
vendored
|
|
@ -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
8
dev/External/ko.js
vendored
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export class AbstractCollectionModel extends Array
|
|||
}
|
||||
|
||||
onDestroy() {
|
||||
this.forEach(item => item.onDestroy && item.onDestroy());
|
||||
this.forEach(item => item.onDestroy?.());
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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]]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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]) + "')"
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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__):
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
|
|
|
|||
|
|
@ -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 + '; '
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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') },
|
||||
];
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -50,6 +50,6 @@ export class UserSettingsFilters /*extends AbstractViewSettings*/ {
|
|||
}
|
||||
|
||||
onShow() {
|
||||
window.Sieve && window.Sieve.updateList();
|
||||
window.Sieve?.updateList();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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) => () => {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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('');
|
||||
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ PackageAdminStore.fetch = () => {
|
|||
|
||||
const loading = {};
|
||||
PackageAdminStore.forEach(item => {
|
||||
if (item && item.loading()) {
|
||||
if (item?.loading()) {
|
||||
loading[item.file] = item;
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 }),
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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'));
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,10 +1,4 @@
|
|||
|
||||
.UserBackground body {
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
#rl-content {
|
||||
display:flex;
|
||||
height: 100%;
|
||||
|
|
|
|||
|
|
@ -5,4 +5,9 @@
|
|||
padding: 3em 15px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
box-shadow: 0 0 1px inset;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,9 +86,8 @@
|
|||
}
|
||||
|
||||
input, .btn {
|
||||
background: none !important;
|
||||
background: none;
|
||||
border: 1px solid @glass-m-color;
|
||||
color: @glass-color;
|
||||
}
|
||||
|
||||
.fontastic + input {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
|
||||
.UserBackground body {
|
||||
background-size: contain;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -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']
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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')]
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export class LanguagesPopupView extends AbstractViewPopup {
|
|||
}
|
||||
|
||||
changeLanguage(lang) {
|
||||
this.fLang && this.fLang(lang);
|
||||
this.fLang?.(lang);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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', {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import { SieveScriptPopupView } from 'Sieve/View/Script';
|
|||
window.Sieve = {
|
||||
capa: capa,
|
||||
scripts: scripts,
|
||||
setError: setError,
|
||||
loading: loading,
|
||||
serverError: serverError,
|
||||
serverErrorDesc: serverErrorDesc,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue