Reduce JavaScript footprint

This commit is contained in:
djmaze 2021-08-20 15:40:07 +02:00
parent fea65b7ebf
commit cc03546484
12 changed files with 811 additions and 942 deletions

View file

@ -11,152 +11,129 @@ let FOLDERS_CACHE = {},
const REQUESTED_MESSAGE_CACHE = {}; const REQUESTED_MESSAGE_CACHE = {};
/** export const
/**
* @returns {void} * @returns {void}
*/ */
export function clear() { clear = () => {
FOLDERS_CACHE = {}; FOLDERS_CACHE = {};
FOLDERS_NAME_CACHE = {}; FOLDERS_NAME_CACHE = {};
FOLDERS_HASH_CACHE = {}; FOLDERS_HASH_CACHE = {};
FOLDERS_UID_NEXT_CACHE = {}; FOLDERS_UID_NEXT_CACHE = {};
MESSAGE_FLAGS_CACHE = {}; MESSAGE_FLAGS_CACHE = {};
} },
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @param {string} uid * @param {string} uid
* @returns {string} * @returns {string}
*/ */
export function getMessageKey(folderFullNameRaw, uid) { getMessageKey = (folderFullNameRaw, uid) => `${folderFullNameRaw}#${uid}`,
return `${folderFullNameRaw}#${uid}`;
}
/** /**
* @param {string} folder * @param {string} folder
* @param {string} uid * @param {string} uid
*/ */
export function addRequestedMessage(folder, uid) { addRequestedMessage = (folder, uid) => REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)] = true,
REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)] = true;
}
/** /**
* @param {string} folder * @param {string} folder
* @param {string} uid * @param {string} uid
* @returns {boolean} * @returns {boolean}
*/ */
export function hasRequestedMessage(folder, uid) { hasRequestedMessage = (folder, uid) => true === REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)],
return true === REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)];
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @param {string} uid * @param {string} uid
*/ */
export function addNewMessageCache(folderFullNameRaw, uid) { addNewMessageCache = (folderFullNameRaw, uid) => NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = true,
NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = true;
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @param {string} uid * @param {string} uid
*/ */
export function hasNewMessageAndRemoveFromCache(folderFullNameRaw, uid) { hasNewMessageAndRemoveFromCache = (folderFullNameRaw, uid) => {
if (NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)]) { if (NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)]) {
NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = null; NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = null;
return true; return true;
} }
return false; return false;
} },
/** /**
* @returns {void} * @returns {void}
*/ */
export function clearNewMessageCache() { clearNewMessageCache = () => NEW_MESSAGE_CACHE = {},
NEW_MESSAGE_CACHE = {};
}
/** /**
* @returns {string} * @returns {string}
*/ */
export function getFolderInboxName() { getFolderInboxName = () => inboxFolderName,
return inboxFolderName;
}
/** /**
* @returns {string} * @returns {string}
*/ */
export function setFolderInboxName(name) { setFolderInboxName = name => inboxFolderName = name,
inboxFolderName = name;
}
/** /**
* @param {string} folderHash * @param {string} folderHash
* @returns {string} * @returns {string}
*/ */
export function getFolderFullNameRaw(folderHash) { getFolderFullNameRaw = folderHash =>
return folderHash && FOLDERS_NAME_CACHE[folderHash] ? FOLDERS_NAME_CACHE[folderHash] : ''; folderHash && FOLDERS_NAME_CACHE[folderHash] ? FOLDERS_NAME_CACHE[folderHash] : '',
}
/** /**
* @param {string} folderHash * @param {string} folderHash
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @param {?FolderModel} folder * @param {?FolderModel} folder
*/ */
export function setFolder(folderHash, folderFullNameRaw, folder) { setFolder = (folderHash, folderFullNameRaw, folder) => {
FOLDERS_CACHE[folderFullNameRaw] = folder; FOLDERS_CACHE[folderFullNameRaw] = folder;
FOLDERS_NAME_CACHE[folderHash] = folderFullNameRaw; FOLDERS_NAME_CACHE[folderHash] = folderFullNameRaw;
} },
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @returns {string} * @returns {string}
*/ */
export function getFolderHash(folderFullNameRaw) { getFolderHash = folderFullNameRaw =>
return folderFullNameRaw && FOLDERS_HASH_CACHE[folderFullNameRaw] ? FOLDERS_HASH_CACHE[folderFullNameRaw] : ''; folderFullNameRaw && FOLDERS_HASH_CACHE[folderFullNameRaw] ? FOLDERS_HASH_CACHE[folderFullNameRaw] : '',
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @param {string} folderHash * @param {string} folderHash
*/ */
export function setFolderHash(folderFullNameRaw, folderHash) { setFolderHash = (folderFullNameRaw, folderHash) =>
if (folderFullNameRaw) { folderFullNameRaw && (FOLDERS_HASH_CACHE[folderFullNameRaw] = folderHash),
FOLDERS_HASH_CACHE[folderFullNameRaw] = folderHash;
}
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @returns {string} * @returns {string}
*/ */
export function getFolderUidNext(folderFullNameRaw) { getFolderUidNext = folderFullNameRaw =>
return folderFullNameRaw && FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] folderFullNameRaw && FOLDERS_UID_NEXT_CACHE[folderFullNameRaw]
? FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] ? FOLDERS_UID_NEXT_CACHE[folderFullNameRaw]
: ''; : '',
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @param {string} uidNext * @param {string} uidNext
*/ */
export function setFolderUidNext(folderFullNameRaw, uidNext) { setFolderUidNext = (folderFullNameRaw, uidNext) =>
FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] = uidNext; FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] = uidNext,
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
* @returns {?FolderModel} * @returns {?FolderModel}
*/ */
export function getFolderFromCacheList(folderFullNameRaw) { getFolderFromCacheList = folderFullNameRaw =>
return folderFullNameRaw && FOLDERS_CACHE[folderFullNameRaw] ? FOLDERS_CACHE[folderFullNameRaw] : null; folderFullNameRaw && FOLDERS_CACHE[folderFullNameRaw] ? FOLDERS_CACHE[folderFullNameRaw] : null,
}
/** /**
* @param {string} folderFullNameRaw * @param {string} folderFullNameRaw
*/ */
export function removeFolderFromCacheList(folderFullNameRaw) { removeFolderFromCacheList = folderFullNameRaw => delete FOLDERS_CACHE[folderFullNameRaw];
delete FOLDERS_CACHE[folderFullNameRaw];
}
export class MessageFlagsCache export class MessageFlagsCache
{ {

View file

@ -5,8 +5,8 @@
*/ */
export const FolderType = { export const FolderType = {
Inbox: 10, Inbox: 10,
SentItems: 11, Sent: 11,
Draft: 12, Drafts: 12,
Trash: 13, Trash: 13,
Spam: 14, Spam: 14,
Archive: 15, Archive: 15,

View file

@ -1,46 +1,33 @@
import ko from 'ko'; import ko from 'ko';
import { Scope } from 'Common/Enums'; import { Scope } from 'Common/Enums';
export const doc = document; let keyScopeFake = Scope.All;
export const $htmlCL = doc.documentElement.classList; export const
export const elementById = id => doc.getElementById(id); doc = document,
export const Settings = rl.settings; $htmlCL = doc.documentElement.classList,
export const SettingsGet = rl.settings.get;
export const dropdownVisibility = ko.observable(false).extend({ rateLimit: 0 }); elementById = id => doc.getElementById(id),
export const moveAction = ko.observable(false); Settings = rl.settings,
export const leftPanelDisabled = ko.observable(false); SettingsGet = Settings.get,
export const createElement = (name, attr) => { dropdownVisibility = ko.observable(false).extend({ rateLimit: 0 }),
moveAction = ko.observable(false),
leftPanelDisabled = ko.observable(false),
createElement = (name, attr) => {
let el = doc.createElement(name); let el = doc.createElement(name);
attr && Object.entries(attr).forEach(([k,v]) => el.setAttribute(k,v)); attr && Object.entries(attr).forEach(([k,v]) => el.setAttribute(k,v));
return el; return el;
}; },
leftPanelDisabled.subscribe(value => { // keys
value && moveAction() && moveAction(false); keyScopeReal = ko.observable(Scope.All),
$htmlCL.toggle('rl-left-panel-disabled', value); keyScope = value => {
});
moveAction.subscribe(value => value && leftPanelDisabled() && leftPanelDisabled(false));
// keys
export const keyScopeReal = ko.observable(Scope.All);
export const keyScope = (()=>{
let keyScopeFake = Scope.All;
dropdownVisibility.subscribe(value => {
if (value) {
keyScope(Scope.Menu);
} else if (Scope.Menu === shortcuts.getScope()) {
keyScope(keyScopeFake);
}
});
return value => {
if (value) { if (value) {
if (Scope.Menu !== value) { if (Scope.Menu !== value) {
keyScopeFake = value; keyScopeFake = value;
@ -54,4 +41,18 @@ export const keyScope = (()=>{
return keyScopeFake; return keyScopeFake;
} }
}; };
})();
dropdownVisibility.subscribe(value => {
if (value) {
keyScope(Scope.Menu);
} else if (Scope.Menu === shortcuts.getScope()) {
keyScope(keyScopeFake);
}
});
leftPanelDisabled.subscribe(value => {
value && moveAction() && moveAction(false);
$htmlCL.toggle('rl-left-panel-disabled', value);
});
moveAction.subscribe(value => value && leftPanelDisabled() && leftPanelDisabled(false));

View file

@ -16,7 +16,7 @@ export function encodeHtml(text) {
return (text && text.toString ? text.toString() : ''+text).replace(htmlre, m => htmlmap[m]); return (text && text.toString ? text.toString() : ''+text).replace(htmlre, m => htmlmap[m]);
} }
class HtmlEditor { export class HtmlEditor {
/** /**
* @param {Object} element * @param {Object} element
* @param {Function=} onBlur * @param {Function=} onBlur
@ -206,5 +206,3 @@ class HtmlEditor {
this.setHtml(''); this.setHtml('');
} }
} }
export { HtmlEditor, HtmlEditor as default };

View file

@ -8,104 +8,84 @@ const
VERSION = Settings.app('version'), VERSION = Settings.app('version'),
VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/'; VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/';
/** export const
* @returns {string} SUB_QUERY_PREFIX = '&q[]=',
*/
export const SUB_QUERY_PREFIX = '&q[]=';
/** /**
* @param {string=} startupUrl * @param {string=} startupUrl
* @returns {string} * @returns {string}
*/ */
export function root(startupUrl = '') { root = (startupUrl = '') => HASH_PREFIX + pString(startupUrl),
return HASH_PREFIX + pString(startupUrl);
}
/** /**
* @returns {string} * @returns {string}
*/ */
export function logoutLink() { logoutLink = () => (rl.adminArea() && !Settings.app('adminHostUse'))
return (rl.adminArea() && !Settings.app('adminHostUse'))
? SERVER_PREFIX + (Settings.app('adminPath') || 'admin') ? SERVER_PREFIX + (Settings.app('adminPath') || 'admin')
: ROOT; : ROOT,
}
/** /**
* @param {string} type * @param {string} type
* @param {string} hash * @param {string} hash
* @param {string=} customSpecSuffix * @param {string=} customSpecSuffix
* @returns {string} * @returns {string}
*/ */
export function serverRequestRaw(type, hash, customSpecSuffix) { serverRequestRaw = (type, hash, customSpecSuffix) =>
return SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/' SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/'
+ (null == customSpecSuffix ? '0' : customSpecSuffix) + '/' + (null == customSpecSuffix ? '0' : customSpecSuffix) + '/'
+ (type + (type
? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '') ? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '')
: '') : ''),
;
}
/** /**
* @param {string} download * @param {string} download
* @param {string=} customSpecSuffix * @param {string=} customSpecSuffix
* @returns {string} * @returns {string}
*/ */
export function attachmentDownload(download, customSpecSuffix) { attachmentDownload = (download, customSpecSuffix) =>
return serverRequestRaw('Download', download, customSpecSuffix); serverRequestRaw('Download', download, customSpecSuffix),
}
/** /**
* @param {string} type * @param {string} type
* @returns {string} * @returns {string}
*/ */
export function serverRequest(type) { serverRequest = type => SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/',
return SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/';
}
/** /**
* @param {string} email * @param {string} email
* @returns {string} * @returns {string}
*/ */
export function change(email) { change = email => serverRequest('Change') + encodeURIComponent(email) + '/',
return serverRequest('Change') + encodeURIComponent(email) + '/';
}
/** /**
* @param {string} lang * @param {string} lang
* @param {boolean} isAdmin * @param {boolean} isAdmin
* @returns {string} * @returns {string}
*/ */
export function langLink(lang, isAdmin) { langLink = (lang, isAdmin) =>
return SERVER_PREFIX + '/Lang/0/' + (isAdmin ? 'Admin' : 'App') + '/' + encodeURI(lang) + '/' + VERSION + '/'; SERVER_PREFIX + '/Lang/0/' + (isAdmin ? 'Admin' : 'App') + '/' + encodeURI(lang) + '/' + VERSION + '/',
}
/** /**
* @param {string} path * @param {string} path
* @returns {string} * @returns {string}
*/ */
export function staticLink(path) { staticLink = path => VERSION_PREFIX + 'static/' + path,
return VERSION_PREFIX + 'static/' + path;
}
/** /**
* @returns {string} * @returns {string}
*/ */
export function openPgpJs() { openPgpJs = () => staticLink('js/min/openpgp.min.js'),
return staticLink('js/min/openpgp.min.js');
}
/** /**
* @returns {string} * @returns {string}
*/ */
export function openPgpWorkerJs() { openPgpWorkerJs = () => staticLink('js/min/openpgp.worker.min.js'),
return staticLink('js/min/openpgp.worker.min.js');
}
/** /**
* @param {string} theme * @param {string} theme
* @returns {string} * @returns {string}
*/ */
export function themePreviewLink(theme) { themePreviewLink = theme => {
let prefix = VERSION_PREFIX; let prefix = VERSION_PREFIX;
if ('@custom' === theme.substr(-7)) { if ('@custom' === theme.substr(-7)) {
theme = theme.substr(0, theme.length - 7).trim(); theme = theme.substr(0, theme.length - 7).trim();
@ -113,51 +93,38 @@ export function themePreviewLink(theme) {
} }
return prefix + 'themes/' + encodeURI(theme) + '/images/preview.png'; return prefix + 'themes/' + encodeURI(theme) + '/images/preview.png';
} },
/** /**
* @param {string} inboxFolderName = 'INBOX' * @param {string} inboxFolderName = 'INBOX'
* @returns {string} * @returns {string}
*/ */
export function mailbox(inboxFolderName = 'INBOX') { mailbox = (inboxFolderName = 'INBOX') => HASH_PREFIX + 'mailbox/' + inboxFolderName,
return HASH_PREFIX + 'mailbox/' + inboxFolderName;
}
/** /**
* @param {string=} screenName = '' * @param {string=} screenName = ''
* @returns {string} * @returns {string}
*/ */
export function settings(screenName = '') { settings = (screenName = '') => HASH_PREFIX + 'settings' + (screenName ? '/' + screenName : ''),
return HASH_PREFIX + 'settings' + (screenName ? '/' + screenName : '');
}
/** /**
* @param {string} screenName * @param {string=} screenName
* @returns {string} * @returns {string}
*/ */
export function admin(screenName) { admin = screenName => HASH_PREFIX + (
let result = HASH_PREFIX; 'AdminDomains' == screenName ? 'domains'
switch (screenName) { : 'AdminSecurity' == screenName ? 'security'
case 'AdminDomains': : ''
result += 'domains'; ),
break;
case 'AdminSecurity':
result += 'security';
break;
// no default
}
return result; /**
}
/**
* @param {string} folder * @param {string} folder
* @param {number=} page = 1 * @param {number=} page = 1
* @param {string=} search = '' * @param {string=} search = ''
* @param {string=} threadUid = '' * @param {string=} threadUid = ''
* @returns {string} * @returns {string}
*/ */
export function mailBox(folder, page = 1, search = '', threadUid = '') { mailBox = (folder, page = 1, search = '', threadUid = '') => {
page = pInt(page, 1); page = pInt(page, 1);
search = pString(search); search = pString(search);
@ -177,5 +144,4 @@ export function mailBox(folder, page = 1, search = '', threadUid = '') {
} }
return result; return result;
} };

View file

@ -5,29 +5,8 @@ import { doc, createElement } from 'Common/Globals';
let I18N_DATA = {}; let I18N_DATA = {};
export const trigger = ko.observable(false); const
i18nToNode = element => {
/**
* @param {string} key
* @param {Object=} valueList
* @param {string=} defaulValue
* @returns {string}
*/
export function i18n(key, valueList, defaulValue) {
let path = key.split('/');
if (!I18N_DATA[path[0]] || !path[1]) {
return defaulValue || key;
}
let result = I18N_DATA[path[0]][path[1]] || defaulValue || key;
if (valueList) {
Object.entries(valueList).forEach(([key, value]) => {
result = result.replace('%' + key + '%', value);
});
}
return result;
}
const i18nToNode = element => {
const key = element.dataset.i18n; const key = element.dataset.i18n;
if (key) { if (key) {
if ('[' === key.substr(0, 1)) { if ('[' === key.substr(0, 1)) {
@ -47,7 +26,7 @@ const i18nToNode = element => {
element.textContent = i18n(key); element.textContent = i18n(key);
} }
} }
}, },
init = () => { init = () => {
if (rl.I18N) { if (rl.I18N) {
@ -60,45 +39,65 @@ const i18nToNode = element => {
i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(), i18nKey = key => key.replace(/([a-z])([A-Z])/g, '$1_$2').toUpperCase(),
getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v); getKeyByValue = (o, v) => Object.keys(o).find(key => o[key] === v),
init(); getNotificationMessage = code => {
/**
* @param {Object} elements
* @param {boolean=} animate = false
*/
export function i18nToNodes(element) {
setTimeout(() =>
element.querySelectorAll('[data-i18n]').forEach(item => i18nToNode(item))
, 1);
}
/**
* @param {Function} startCallback
* @param {Function=} langCallback = null
*/
export function initOnStartOrLangChange(startCallback, langCallback = null) {
startCallback && startCallback();
startCallback && trigger.subscribe(startCallback);
langCallback && trigger.subscribe(langCallback);
}
function getNotificationMessage(code) {
let key = getKeyByValue(Notification, code); let key = getKeyByValue(Notification, code);
if (key) { if (key) {
key = i18nKey(key).replace('_NOTIFICATION', '_ERROR'); key = i18nKey(key).replace('_NOTIFICATION', '_ERROR');
return I18N_DATA.NOTIFICATIONS[key]; return I18N_DATA.NOTIFICATIONS[key];
} }
} };
/** export const
trigger = ko.observable(false),
/**
* @param {string} key
* @param {Object=} valueList
* @param {string=} defaulValue
* @returns {string}
*/
i18n = (key, valueList, defaulValue) => {
let path = key.split('/');
if (!I18N_DATA[path[0]] || !path[1]) {
return defaulValue || key;
}
let result = I18N_DATA[path[0]][path[1]] || defaulValue || key;
if (valueList) {
Object.entries(valueList).forEach(([key, value]) => {
result = result.replace('%' + key + '%', value);
});
}
return result;
},
/**
* @param {Object} elements
* @param {boolean=} animate = false
*/
i18nToNodes = element =>
setTimeout(() =>
element.querySelectorAll('[data-i18n]').forEach(item => i18nToNode(item))
, 1),
/**
* @param {Function} startCallback
* @param {Function=} langCallback = null
*/
initOnStartOrLangChange = (startCallback, langCallback = null) => {
startCallback && startCallback();
startCallback && trigger.subscribe(startCallback);
langCallback && trigger.subscribe(langCallback);
},
/**
* @param {number} code * @param {number} code
* @param {*=} message = '' * @param {*=} message = ''
* @param {*=} defCode = null * @param {*=} defCode = null
* @returns {string} * @returns {string}
*/ */
export function getNotification(code, message = '', defCode = 0) { getNotification = (code, message = '', defCode = 0) => {
code = parseInt(code, 10) || 0; code = parseInt(code, 10) || 0;
if (Notification.ClientViewError === code && message) { if (Notification.ClientViewError === code && message) {
return message; return message;
@ -107,13 +106,13 @@ export function getNotification(code, message = '', defCode = 0) {
return getNotificationMessage(code) return getNotificationMessage(code)
|| getNotificationMessage(parseInt(defCode, 10)) || getNotificationMessage(parseInt(defCode, 10))
|| ''; || '';
} },
/** /**
* @param {*} code * @param {*} code
* @returns {string} * @returns {string}
*/ */
export function getUploadErrorDescByCode(code) { getUploadErrorDescByCode = code => {
let result = 'UNKNOWN'; let result = 'UNKNOWN';
code = parseInt(code, 10) || 0; code = parseInt(code, 10) || 0;
switch (code) { switch (code) {
@ -127,14 +126,14 @@ export function getUploadErrorDescByCode(code) {
break; break;
} }
return i18n('UPLOAD/ERROR_' + result); return i18n('UPLOAD/ERROR_' + result);
} },
/** /**
* @param {boolean} admin * @param {boolean} admin
* @param {string} language * @param {string} language
*/ */
export function reload(admin, language) { reload = (admin, language) =>
return new Promise((resolve, reject) => { new Promise((resolve, reject) => {
const script = createElement('script'); const script = createElement('script');
script.onload = () => { script.onload = () => {
// reload the data // reload the data
@ -148,21 +147,21 @@ export function reload(admin, language) {
}; };
script.onerror = () => reject(new Error('Language '+language+' failed')); script.onerror = () => reject(new Error('Language '+language+' failed'));
script.src = langLink(language, admin); script.src = langLink(language, admin);
// script.async = true; // script.async = true;
doc.head.append(script); doc.head.append(script);
}); }),
}
/** /**
* *
* @param {string} language * @param {string} language
* @param {boolean=} isEng = false * @param {boolean=} isEng = false
* @returns {string} * @returns {string}
*/ */
export function convertLangName(language, isEng = false) { convertLangName = (language, isEng = false) =>
return i18n( i18n(
'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language, 'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language,
null, null,
language language
); );
}
init();

View file

@ -1,33 +1,41 @@
import { SaveSettingsStep } from 'Common/Enums'; import { SaveSettingsStep } from 'Common/Enums';
import { doc, elementById } from 'Common/Globals'; import { doc, elementById } from 'Common/Globals';
let __themeTimer = 0,
__themeJson = null;
export const export const
isArray = Array.isArray, isArray = Array.isArray,
arrayLength = array => isArray(array) && array.length, arrayLength = array => isArray(array) && array.length,
isFunction = v => typeof v === 'function'; isFunction = v => typeof v === 'function',
pString = value => null != value ? '' + value : '',
/** pInt = (value, defaultValue = 0) => {
* @param {*} value
* @param {number=} defaultValue = 0
* @returns {number}
*/
export function pInt(value, defaultValue = 0) {
value = parseInt(value, 10); value = parseInt(value, 10);
return isNaN(value) || !isFinite(value) ? defaultValue : value; return isNaN(value) || !isFinite(value) ? defaultValue : value;
} },
/** convertThemeName = theme => theme
* @param {*} value .replace(/@custom$/, '')
* @returns {string} .replace(/([A-Z])/g, ' $1')
*/ .replace(/[^a-zA-Z0-9]+/g, ' ')
export function pString(value) { .trim(),
return null != value ? '' + value : '';
}
/** defaultOptionsAfterRender = (domItem, item) =>
* @returns {boolean} domItem && item && undefined !== item.disabled
*/ && domItem.classList.toggle('disabled', domItem.disabled = item.disabled),
export function inFocus() {
addObservablesTo = (target, observables) =>
Object.entries(observables).forEach(([key, value]) =>
target[key] = /*isArray(value) ? ko.observableArray(value) :*/ ko.observable(value) ),
addComputablesTo = (target, computables) =>
Object.entries(computables).forEach(([key, fn]) => target[key] = ko.computed(fn)),
addSubscribablesTo = (target, subscribables) =>
Object.entries(subscribables).forEach(([key, fn]) => target[key].subscribe(fn)),
inFocus = () => {
try { try {
return doc.activeElement && doc.activeElement.matches( return doc.activeElement && doc.activeElement.matches(
'input,textarea,[contenteditable]' 'input,textarea,[contenteditable]'
@ -35,55 +43,15 @@ export function inFocus() {
} catch (e) { } catch (e) {
return false; return false;
} }
} },
/** settingsSaveHelperSimpleFunction = (koTrigger, context) =>
* @param {string} theme iError => {
* @returns {string}
*/
export const convertThemeName = theme => {
if ('@custom' === theme.substr(-7)) {
theme = theme.substr(0, theme.length - 7).trim();
}
return theme
.replace(/([A-Z])/g, ' $1')
.replace(/[^a-zA-Z0-9]+/g, ' ')
.trim();
};
/**
* @param {object} domOption
* @param {object} item
* @returns {void}
*/
export function defaultOptionsAfterRender(domItem, item) {
if (item && undefined !== item.disabled && domItem) {
domItem.classList.toggle('disabled', domItem.disabled = item.disabled);
}
}
/**
* @param {object} koTrigger
* @param {mixed} context
* @returns {mixed}
*/
export function settingsSaveHelperSimpleFunction(koTrigger, context) {
return iError => {
koTrigger.call(context, iError ? SaveSettingsStep.FalseResult : SaveSettingsStep.TrueResult); koTrigger.call(context, iError ? SaveSettingsStep.FalseResult : SaveSettingsStep.TrueResult);
setTimeout(() => koTrigger.call(context, SaveSettingsStep.Idle), 1000); setTimeout(() => koTrigger.call(context, SaveSettingsStep.Idle), 1000);
}; },
}
let __themeTimer = 0, changeTheme = (value, themeTrigger = ()=>0) => {
__themeJson = null;
/**
* @param {string} value
* @param {function=} themeTrigger = noop
* @returns {void}
*/
export function changeTheme(value, themeTrigger = ()=>0) {
const themeStyle = elementById('app-theme-style'), const themeStyle = elementById('app-theme-style'),
clearTimer = () => { clearTimer = () => {
__themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000); __themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000);
@ -125,17 +93,4 @@ export function changeTheme(value, themeTrigger = ()=>0) {
}) })
.then(clearTimer, clearTimer); .then(clearTimer, clearTimer);
} }
} };
export function addObservablesTo(target, observables) {
Object.entries(observables).forEach(([key, value]) =>
target[key] = /*isArray(value) ? ko.observableArray(value) :*/ ko.observable(value) );
}
export function addComputablesTo(target, computables) {
Object.entries(computables).forEach(([key, fn]) => target[key] = ko.computed(fn));
}
export function addSubscribablesTo(target, subscribables) {
Object.entries(subscribables).forEach(([key, fn]) => target[key].subscribe(fn));
}

View file

@ -10,66 +10,20 @@ const SCREENS = {},
autofocus = dom => { autofocus = dom => {
const af = dom.querySelector('[autofocus]'); const af = dom.querySelector('[autofocus]');
af && af.focus(); af && af.focus();
}; },
export const popupVisibilityNames = ko.observableArray([]); /**
export const ViewType = {
Popup: 'Popups',
Left: 'Left',
Right: 'Right',
Content: 'Content'
};
/**
* @param {Function} fExecute
* @param {(Function|boolean|null)=} fCanExecute = true
* @returns {Function}
*/
export function createCommand(fExecute, fCanExecute = true) {
let fResult = fExecute
? (...args) => {
if (fResult && fResult.canExecute && fResult.canExecute()) {
fExecute.apply(null, args);
}
return false;
} : ()=>0;
fResult.enabled = ko.observable(true);
fResult.isCommand = true;
if (isFunction(fCanExecute)) {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null));
} else {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
}
return fResult;
}
/**
* @param {string} screenName * @param {string} screenName
* @returns {?Object} * @returns {?Object}
*/ */
function screen(screenName) { screen = screenName => screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null,
return screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null;
}
/** /**
* @param {Function} ViewModelClassToHide
* @returns {void}
*/
export function hideScreenPopup(ViewModelClassToHide) {
if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) {
ViewModelClassToHide.__vm.modalVisibility(false);
}
}
/**
* @param {Function} ViewModelClass * @param {Function} ViewModelClass
* @param {Object=} vmScreen * @param {Object=} vmScreen
* @returns {*} * @returns {*}
*/ */
function buildViewModel(ViewModelClass, vmScreen) { buildViewModel = (ViewModelClass, vmScreen) => {
if (ViewModelClass && !ViewModelClass.__builded) { if (ViewModelClass && !ViewModelClass.__builded) {
let vmDom = null; let vmDom = null;
const vm = new ViewModelClass(vmScreen), const vm = new ViewModelClass(vmScreen),
@ -151,44 +105,14 @@ function buildViewModel(ViewModelClass, vmScreen) {
} }
return ViewModelClass && ViewModelClass.__vm; return ViewModelClass && ViewModelClass.__vm;
} },
export function getScreenPopupViewModel(ViewModelClassToShow) { /**
return (buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm;
}
/**
* @param {Function} ViewModelClassToShow
* @param {Array=} params
* @returns {void}
*/
export function showScreenPopup(ViewModelClassToShow, params = []) {
const vm = getScreenPopupViewModel(ViewModelClassToShow);
if (vm) {
params = params || [];
vm.onBeforeShow && vm.onBeforeShow(...params);
vm.modalVisibility(true);
vm.onShow && vm.onShow(...params);
}
}
/**
* @param {Function} ViewModelClassToShow
* @returns {boolean}
*/
export function isPopupVisible(ViewModelClassToShow) {
return ViewModelClassToShow && ViewModelClassToShow.__vm && ViewModelClassToShow.__vm.modalVisibility();
}
/**
* @param {string} screenName * @param {string} screenName
* @param {string} subPart * @param {string} subPart
* @returns {void} * @returns {void}
*/ */
function screenOnRoute(screenName, subPart) { screenOnRoute = (screenName, subPart) => {
let vmScreen = null, let vmScreen = null,
isSameScreen = false; isSameScreen = false;
@ -276,13 +200,86 @@ function screenOnRoute(screenName, subPart) {
}, 1); }, 1);
} }
} }
} };
/** export const
popupVisibilityNames = ko.observableArray([]),
ViewType = {
Popup: 'Popups',
Left: 'Left',
Right: 'Right',
Content: 'Content'
},
/**
* @param {Function} fExecute
* @param {(Function|boolean|null)=} fCanExecute = true
* @returns {Function}
*/
createCommand = (fExecute, fCanExecute = true) => {
let fResult = fExecute
? (...args) => {
if (fResult && fResult.canExecute && fResult.canExecute()) {
fExecute.apply(null, args);
}
return false;
} : ()=>0;
fResult.enabled = ko.observable(true);
fResult.isCommand = true;
if (isFunction(fCanExecute)) {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null));
} else {
fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute);
}
return fResult;
},
/**
* @param {Function} ViewModelClassToHide
* @returns {void}
*/
hideScreenPopup = ViewModelClassToHide => {
if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) {
ViewModelClassToHide.__vm.modalVisibility(false);
}
},
getScreenPopupViewModel = ViewModelClassToShow =>
(buildViewModel(ViewModelClassToShow) && ViewModelClassToShow.__dom) && ViewModelClassToShow.__vm,
/**
* @param {Function} ViewModelClassToShow
* @param {Array=} params
* @returns {void}
*/
showScreenPopup = (ViewModelClassToShow, params = []) => {
const vm = getScreenPopupViewModel(ViewModelClassToShow);
if (vm) {
params = params || [];
vm.onBeforeShow && vm.onBeforeShow(...params);
vm.modalVisibility(true);
vm.onShow && vm.onShow(...params);
}
},
/**
* @param {Function} ViewModelClassToShow
* @returns {boolean}
*/
isPopupVisible = ViewModelClassToShow =>
ViewModelClassToShow && ViewModelClassToShow.__vm && ViewModelClassToShow.__vm.modalVisibility(),
/**
* @param {Array} screensClasses * @param {Array} screensClasses
* @returns {void} * @returns {void}
*/ */
export function startScreens(screensClasses) { startScreens = screensClasses => {
screensClasses.forEach(CScreen => { screensClasses.forEach(CScreen => {
if (CScreen) { if (CScreen) {
const vmScreen = new CScreen(), const vmScreen = new CScreen(),
@ -308,15 +305,15 @@ export function startScreens(screensClasses) {
setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100); setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100);
setTimeout(() => $htmlCL.add('rl-started-delay'), 200); setTimeout(() => $htmlCL.add('rl-started-delay'), 200);
} },
export function decorateKoCommands(thisArg, commands) { decorateKoCommands = (thisArg, commands) =>
Object.entries(commands).forEach(([key, canExecute]) => { Object.entries(commands).forEach(([key, canExecute]) => {
let command = thisArg[key], let command = thisArg[key],
fn = (...args) => fn.enabled() && fn.canExecute() && command.apply(thisArg, args); fn = (...args) => fn.enabled() && fn.canExecute() && command.apply(thisArg, args);
// fn.__realCanExecute = canExecute; // fn.__realCanExecute = canExecute;
// fn.isCommand = true; // fn.isCommand = true;
fn.enabled = ko.observable(true); fn.enabled = ko.observable(true);
@ -326,5 +323,5 @@ export function decorateKoCommands(thisArg, commands) {
thisArg[key] = fn; thisArg[key] = fn;
}); });
}
ko.decorateCommands = decorateKoCommands; ko.decorateCommands = decorateKoCommands;

View file

@ -160,9 +160,9 @@ function getSystemFolderName(type, def)
switch (type) { switch (type) {
case FolderType.Inbox: case FolderType.Inbox:
return i18n('FOLDER_LIST/INBOX_NAME'); return i18n('FOLDER_LIST/INBOX_NAME');
case FolderType.SentItems: case FolderType.Sent:
return i18n('FOLDER_LIST/SENT_NAME'); return i18n('FOLDER_LIST/SENT_NAME');
case FolderType.Draft: case FolderType.Drafts:
return i18n('FOLDER_LIST/DRAFTS_NAME'); return i18n('FOLDER_LIST/DRAFTS_NAME');
case FolderType.Spam: case FolderType.Spam:
return i18n('GLOBAL/SPAM'); return i18n('GLOBAL/SPAM');
@ -281,14 +281,14 @@ export class FolderModel extends AbstractModel {
type = folder.type(); type = folder.type();
if (count) { if (count) {
if (FolderType.Draft === type) { if (FolderType.Drafts === type) {
return count; return count;
} }
if ( if (
unread && unread &&
FolderType.Trash !== type && FolderType.Trash !== type &&
FolderType.Archive !== type && FolderType.Archive !== type &&
FolderType.SentItems !== type FolderType.Sent !== type
) { ) {
return unread; return unread;
} }

View file

@ -127,8 +127,8 @@ export const FolderUserStore = new class {
this.archiveFolder.subscribe(fRemoveSystemFolderType(this.archiveFolder), this, 'beforeChange'); this.archiveFolder.subscribe(fRemoveSystemFolderType(this.archiveFolder), this, 'beforeChange');
addSubscribablesTo(this, { addSubscribablesTo(this, {
sentFolder: fSetSystemFolderType(FolderType.SentItems), sentFolder: fSetSystemFolderType(FolderType.Sent),
draftFolder: fSetSystemFolderType(FolderType.Draft), draftFolder: fSetSystemFolderType(FolderType.Drafts),
spamFolder: fSetSystemFolderType(FolderType.Spam), spamFolder: fSetSystemFolderType(FolderType.Spam),
trashFolder: fSetSystemFolderType(FolderType.Trash), trashFolder: fSetSystemFolderType(FolderType.Trash),
archiveFolder: fSetSystemFolderType(FolderType.Archive) archiveFolder: fSetSystemFolderType(FolderType.Archive)

View file

@ -12,12 +12,11 @@ class DomainPopupView extends AbstractViewPopup {
constructor() { constructor() {
super('Domain'); super('Domain');
this.addObservables(this.getDefaults());
this.addObservables({ this.addObservables({
edit: false, edit: false,
saving: false, saving: false,
savingError: '',
page: 'main',
sieveSettings: false,
testing: false, testing: false,
testingDone: false, testingDone: false,
@ -31,28 +30,6 @@ class DomainPopupView extends AbstractViewPopup {
imapServerFocus: false, imapServerFocus: false,
sieveServerFocus: false, sieveServerFocus: false,
smtpServerFocus: false, smtpServerFocus: false,
name: '',
imapServer: '',
imapPort: '143',
imapSecure: 0,
imapShortLogin: false,
useSieve: false,
sieveServer: '',
sievePort: '4190',
sieveSecure: 0,
smtpServer: '',
smtpPort: '25',
smtpSecure: 0,
smtpShortLogin: false,
smtpAuth: true,
smtpSetSender: false,
smtpPhpMail: false,
whiteList: '',
aliasName: '',
enableSmartPorts: false
}); });
this.addComputables({ this.addComputables({
@ -306,38 +283,42 @@ class DomainPopupView extends AbstractViewPopup {
} }
} }
getDefaults() {
return {
savingError: '',
page: 'main',
sieveSettings: false,
name: '',
imapServer: '',
imapPort: '143',
imapSecure: 0,
imapShortLogin: false,
useSieve: false,
sieveServer: '',
sievePort: '4190',
sieveSecure: 0,
smtpServer: '',
smtpPort: '25',
smtpSecure: 0,
smtpShortLogin: false,
smtpAuth: true,
smtpSetSender: false,
smtpPhpMail: false,
whiteList: '',
aliasName: '',
enableSmartPorts: false
};
}
clearForm() { clearForm() {
this.edit(false); this.edit(false);
Object.entries(this.getDefaults()).forEach(([key, value]) => this[key](value));
this.page('main');
this.sieveSettings(false);
this.enableSmartPorts(false);
this.savingError('');
this.name('');
this.imapServer('');
this.imapPort('143');
this.imapSecure(0);
this.imapShortLogin(false);
this.useSieve(false);
this.sieveServer('');
this.sievePort('4190');
this.sieveSecure(0);
this.smtpServer('');
this.smtpPort('25');
this.smtpSecure(0);
this.smtpShortLogin(false);
this.smtpAuth(true);
this.smtpSetSender(true);
this.smtpPhpMail(false);
this.whiteList('');
this.aliasName('');
this.enableSmartPorts(true); this.enableSmartPorts(true);
} }
} }

9
dev/bootstrap.js vendored
View file

@ -45,16 +45,11 @@ export default App => {
*/ */
setHash: (hash, silence = false, replace = false) => { setHash: (hash, silence = false, replace = false) => {
hash = hash.replace(/^[#/]+/, ''); hash = hash.replace(/^[#/]+/, '');
hasher.active = !silence;
const cmd = replace ? 'replaceHash' : 'setHash'; hasher[replace ? 'replaceHash' : 'setHash'](hash);
if (silence) { if (silence) {
hasher.active = false;
hasher[cmd](hash);
hasher.active = true; hasher.active = true;
} else { } else {
hasher.active = true;
hasher[cmd](hash);
hasher.setHash(hash); hasher.setHash(hash);
} }
} }