From cc035464840267c731e9bdfe00bb6b0de6b9a6a5 Mon Sep 17 00:00:00 2001 From: djmaze Date: Fri, 20 Aug 2021 15:40:07 +0200 Subject: [PATCH] Reduce JavaScript footprint --- dev/Common/Cache.js | 237 +++++++------- dev/Common/EnumsUser.js | 4 +- dev/Common/Globals.js | 65 ++-- dev/Common/Html.js | 4 +- dev/Common/Links.js | 258 +++++++-------- dev/Common/Translator.js | 289 +++++++++-------- dev/Common/Utils.js | 209 +++++------- dev/Knoin/Knoin.js | 575 +++++++++++++++++----------------- dev/Model/FolderCollection.js | 8 +- dev/Stores/User/Folder.js | 4 +- dev/View/Popup/Domain.js | 91 +++--- dev/bootstrap.js | 9 +- 12 files changed, 811 insertions(+), 942 deletions(-) diff --git a/dev/Common/Cache.js b/dev/Common/Cache.js index bde23bd7d..fd18c57bc 100644 --- a/dev/Common/Cache.js +++ b/dev/Common/Cache.js @@ -11,152 +11,129 @@ let FOLDERS_CACHE = {}, const REQUESTED_MESSAGE_CACHE = {}; -/** - * @returns {void} - */ -export function clear() { - FOLDERS_CACHE = {}; - FOLDERS_NAME_CACHE = {}; - FOLDERS_HASH_CACHE = {}; - FOLDERS_UID_NEXT_CACHE = {}; - MESSAGE_FLAGS_CACHE = {}; -} +export const + /** + * @returns {void} + */ + clear = () => { + FOLDERS_CACHE = {}; + FOLDERS_NAME_CACHE = {}; + FOLDERS_HASH_CACHE = {}; + FOLDERS_UID_NEXT_CACHE = {}; + MESSAGE_FLAGS_CACHE = {}; + }, -/** - * @param {string} folderFullNameRaw - * @param {string} uid - * @returns {string} - */ -export function getMessageKey(folderFullNameRaw, uid) { - return `${folderFullNameRaw}#${uid}`; -} + /** + * @param {string} folderFullNameRaw + * @param {string} uid + * @returns {string} + */ + getMessageKey = (folderFullNameRaw, uid) => `${folderFullNameRaw}#${uid}`, -/** - * @param {string} folder - * @param {string} uid - */ -export function addRequestedMessage(folder, uid) { - REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)] = true; -} + /** + * @param {string} folder + * @param {string} uid + */ + addRequestedMessage = (folder, uid) => REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)] = true, -/** - * @param {string} folder - * @param {string} uid - * @returns {boolean} - */ -export function hasRequestedMessage(folder, uid) { - return true === REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)]; -} + /** + * @param {string} folder + * @param {string} uid + * @returns {boolean} + */ + hasRequestedMessage = (folder, uid) => true === REQUESTED_MESSAGE_CACHE[getMessageKey(folder, uid)], -/** - * @param {string} folderFullNameRaw - * @param {string} uid - */ -export function addNewMessageCache(folderFullNameRaw, uid) { - NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = true; -} + /** + * @param {string} folderFullNameRaw + * @param {string} uid + */ + addNewMessageCache = (folderFullNameRaw, uid) => NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = true, -/** - * @param {string} folderFullNameRaw - * @param {string} uid - */ -export function hasNewMessageAndRemoveFromCache(folderFullNameRaw, uid) { - if (NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)]) { - NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = null; - return true; - } - return false; -} + /** + * @param {string} folderFullNameRaw + * @param {string} uid + */ + hasNewMessageAndRemoveFromCache = (folderFullNameRaw, uid) => { + if (NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)]) { + NEW_MESSAGE_CACHE[getMessageKey(folderFullNameRaw, uid)] = null; + return true; + } + return false; + }, -/** - * @returns {void} - */ -export function clearNewMessageCache() { - NEW_MESSAGE_CACHE = {}; -} + /** + * @returns {void} + */ + clearNewMessageCache = () => NEW_MESSAGE_CACHE = {}, -/** - * @returns {string} - */ -export function getFolderInboxName() { - return inboxFolderName; -} + /** + * @returns {string} + */ + getFolderInboxName = () => inboxFolderName, -/** - * @returns {string} - */ -export function setFolderInboxName(name) { - inboxFolderName = name; -} + /** + * @returns {string} + */ + setFolderInboxName = name => inboxFolderName = name, -/** - * @param {string} folderHash - * @returns {string} - */ -export function getFolderFullNameRaw(folderHash) { - return folderHash && FOLDERS_NAME_CACHE[folderHash] ? FOLDERS_NAME_CACHE[folderHash] : ''; -} + /** + * @param {string} folderHash + * @returns {string} + */ + getFolderFullNameRaw = folderHash => + folderHash && FOLDERS_NAME_CACHE[folderHash] ? FOLDERS_NAME_CACHE[folderHash] : '', -/** - * @param {string} folderHash - * @param {string} folderFullNameRaw - * @param {?FolderModel} folder - */ -export function setFolder(folderHash, folderFullNameRaw, folder) { - FOLDERS_CACHE[folderFullNameRaw] = folder; - FOLDERS_NAME_CACHE[folderHash] = folderFullNameRaw; -} + /** + * @param {string} folderHash + * @param {string} folderFullNameRaw + * @param {?FolderModel} folder + */ + setFolder = (folderHash, folderFullNameRaw, folder) => { + FOLDERS_CACHE[folderFullNameRaw] = folder; + FOLDERS_NAME_CACHE[folderHash] = folderFullNameRaw; + }, -/** - * @param {string} folderFullNameRaw - * @returns {string} - */ -export function getFolderHash(folderFullNameRaw) { - return folderFullNameRaw && FOLDERS_HASH_CACHE[folderFullNameRaw] ? FOLDERS_HASH_CACHE[folderFullNameRaw] : ''; -} + /** + * @param {string} folderFullNameRaw + * @returns {string} + */ + getFolderHash = folderFullNameRaw => + folderFullNameRaw && FOLDERS_HASH_CACHE[folderFullNameRaw] ? FOLDERS_HASH_CACHE[folderFullNameRaw] : '', -/** - * @param {string} folderFullNameRaw - * @param {string} folderHash - */ -export function setFolderHash(folderFullNameRaw, folderHash) { - if (folderFullNameRaw) { - FOLDERS_HASH_CACHE[folderFullNameRaw] = folderHash; - } -} + /** + * @param {string} folderFullNameRaw + * @param {string} folderHash + */ + setFolderHash = (folderFullNameRaw, folderHash) => + folderFullNameRaw && (FOLDERS_HASH_CACHE[folderFullNameRaw] = folderHash), -/** - * @param {string} folderFullNameRaw - * @returns {string} - */ -export function getFolderUidNext(folderFullNameRaw) { - return folderFullNameRaw && FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] - ? FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] - : ''; -} + /** + * @param {string} folderFullNameRaw + * @returns {string} + */ + getFolderUidNext = folderFullNameRaw => + folderFullNameRaw && FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] + ? FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] + : '', -/** - * @param {string} folderFullNameRaw - * @param {string} uidNext - */ -export function setFolderUidNext(folderFullNameRaw, uidNext) { - FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] = uidNext; -} + /** + * @param {string} folderFullNameRaw + * @param {string} uidNext + */ + setFolderUidNext = (folderFullNameRaw, uidNext) => + FOLDERS_UID_NEXT_CACHE[folderFullNameRaw] = uidNext, -/** - * @param {string} folderFullNameRaw - * @returns {?FolderModel} - */ -export function getFolderFromCacheList(folderFullNameRaw) { - return folderFullNameRaw && FOLDERS_CACHE[folderFullNameRaw] ? FOLDERS_CACHE[folderFullNameRaw] : null; -} + /** + * @param {string} folderFullNameRaw + * @returns {?FolderModel} + */ + getFolderFromCacheList = folderFullNameRaw => + folderFullNameRaw && FOLDERS_CACHE[folderFullNameRaw] ? FOLDERS_CACHE[folderFullNameRaw] : null, -/** - * @param {string} folderFullNameRaw - */ -export function removeFolderFromCacheList(folderFullNameRaw) { - delete FOLDERS_CACHE[folderFullNameRaw]; -} + /** + * @param {string} folderFullNameRaw + */ + removeFolderFromCacheList = folderFullNameRaw => delete FOLDERS_CACHE[folderFullNameRaw]; export class MessageFlagsCache { diff --git a/dev/Common/EnumsUser.js b/dev/Common/EnumsUser.js index 9ede882ee..f2d7c29d6 100644 --- a/dev/Common/EnumsUser.js +++ b/dev/Common/EnumsUser.js @@ -5,8 +5,8 @@ */ export const FolderType = { Inbox: 10, - SentItems: 11, - Draft: 12, + Sent: 11, + Drafts: 12, Trash: 13, Spam: 14, Archive: 15, diff --git a/dev/Common/Globals.js b/dev/Common/Globals.js index 00e6eee77..7b86dc3a8 100644 --- a/dev/Common/Globals.js +++ b/dev/Common/Globals.js @@ -1,46 +1,33 @@ import ko from 'ko'; 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; -export const SettingsGet = rl.settings.get; + $htmlCL = doc.documentElement.classList, -export const dropdownVisibility = ko.observable(false).extend({ rateLimit: 0 }); + elementById = id => doc.getElementById(id), -export const moveAction = ko.observable(false); -export const leftPanelDisabled = ko.observable(false); + Settings = rl.settings, + SettingsGet = Settings.get, -export const createElement = (name, attr) => { - let el = doc.createElement(name); - attr && Object.entries(attr).forEach(([k,v]) => el.setAttribute(k,v)); - return el; -}; + dropdownVisibility = ko.observable(false).extend({ rateLimit: 0 }), -leftPanelDisabled.subscribe(value => { - value && moveAction() && moveAction(false); - $htmlCL.toggle('rl-left-panel-disabled', value); -}); + moveAction = ko.observable(false), + leftPanelDisabled = ko.observable(false), -moveAction.subscribe(value => value && leftPanelDisabled() && leftPanelDisabled(false)); + createElement = (name, attr) => { + let el = doc.createElement(name); + attr && Object.entries(attr).forEach(([k,v]) => el.setAttribute(k,v)); + return el; + }, -// 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 => { + // keys + keyScopeReal = ko.observable(Scope.All), + keyScope = value => { if (value) { if (Scope.Menu !== value) { keyScopeFake = value; @@ -54,4 +41,18 @@ export const keyScope = (()=>{ 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)); diff --git a/dev/Common/Html.js b/dev/Common/Html.js index 0d54bb489..3a58939a1 100644 --- a/dev/Common/Html.js +++ b/dev/Common/Html.js @@ -16,7 +16,7 @@ export function encodeHtml(text) { return (text && text.toString ? text.toString() : ''+text).replace(htmlre, m => htmlmap[m]); } -class HtmlEditor { +export class HtmlEditor { /** * @param {Object} element * @param {Function=} onBlur @@ -206,5 +206,3 @@ class HtmlEditor { this.setHtml(''); } } - -export { HtmlEditor, HtmlEditor as default }; diff --git a/dev/Common/Links.js b/dev/Common/Links.js index b5edfa99f..5d88f7ae6 100644 --- a/dev/Common/Links.js +++ b/dev/Common/Links.js @@ -8,174 +8,140 @@ const VERSION = Settings.app('version'), VERSION_PREFIX = Settings.app('webVersionPath') || 'snappymail/v/' + VERSION + '/'; -/** - * @returns {string} - */ -export const SUB_QUERY_PREFIX = '&q[]='; +export const + SUB_QUERY_PREFIX = '&q[]=', -/** - * @param {string=} startupUrl - * @returns {string} - */ -export function root(startupUrl = '') { - return HASH_PREFIX + pString(startupUrl); -} + /** + * @param {string=} startupUrl + * @returns {string} + */ + root = (startupUrl = '') => HASH_PREFIX + pString(startupUrl), -/** - * @returns {string} - */ -export function logoutLink() { - return (rl.adminArea() && !Settings.app('adminHostUse')) + /** + * @returns {string} + */ + logoutLink = () => (rl.adminArea() && !Settings.app('adminHostUse')) ? SERVER_PREFIX + (Settings.app('adminPath') || 'admin') - : ROOT; -} + : ROOT, -/** - * @param {string} type - * @param {string} hash - * @param {string=} customSpecSuffix - * @returns {string} - */ -export function serverRequestRaw(type, hash, customSpecSuffix) { - return SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/' + /** + * @param {string} type + * @param {string} hash + * @param {string=} customSpecSuffix + * @returns {string} + */ + serverRequestRaw = (type, hash, customSpecSuffix) => + SERVER_PREFIX + '/Raw/' + SUB_QUERY_PREFIX + '/' + (null == customSpecSuffix ? '0' : customSpecSuffix) + '/' + (type ? type + '/' + (hash ? SUB_QUERY_PREFIX + '/' + hash : '') - : '') - ; -} + : ''), -/** - * @param {string} download - * @param {string=} customSpecSuffix - * @returns {string} - */ -export function attachmentDownload(download, customSpecSuffix) { - return serverRequestRaw('Download', download, customSpecSuffix); -} + /** + * @param {string} download + * @param {string=} customSpecSuffix + * @returns {string} + */ + attachmentDownload = (download, customSpecSuffix) => + serverRequestRaw('Download', download, customSpecSuffix), -/** - * @param {string} type - * @returns {string} - */ -export function serverRequest(type) { - return SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/'; -} + /** + * @param {string} type + * @returns {string} + */ + serverRequest = type => SERVER_PREFIX + '/' + type + '/' + SUB_QUERY_PREFIX + '/0/', -/** - * @param {string} email - * @returns {string} - */ -export function change(email) { - return serverRequest('Change') + encodeURIComponent(email) + '/'; -} + /** + * @param {string} email + * @returns {string} + */ + change = email => serverRequest('Change') + encodeURIComponent(email) + '/', -/** - * @param {string} lang - * @param {boolean} isAdmin - * @returns {string} - */ -export function langLink(lang, isAdmin) { - return SERVER_PREFIX + '/Lang/0/' + (isAdmin ? 'Admin' : 'App') + '/' + encodeURI(lang) + '/' + VERSION + '/'; -} + /** + * @param {string} lang + * @param {boolean} isAdmin + * @returns {string} + */ + langLink = (lang, isAdmin) => + SERVER_PREFIX + '/Lang/0/' + (isAdmin ? 'Admin' : 'App') + '/' + encodeURI(lang) + '/' + VERSION + '/', -/** - * @param {string} path - * @returns {string} - */ -export function staticLink(path) { - return VERSION_PREFIX + 'static/' + path; -} + /** + * @param {string} path + * @returns {string} + */ + staticLink = path => VERSION_PREFIX + 'static/' + path, -/** - * @returns {string} - */ -export function openPgpJs() { - return staticLink('js/min/openpgp.min.js'); -} + /** + * @returns {string} + */ + openPgpJs = () => staticLink('js/min/openpgp.min.js'), -/** - * @returns {string} - */ -export function openPgpWorkerJs() { - return staticLink('js/min/openpgp.worker.min.js'); -} + /** + * @returns {string} + */ + openPgpWorkerJs = () => staticLink('js/min/openpgp.worker.min.js'), -/** - * @param {string} theme - * @returns {string} - */ -export function themePreviewLink(theme) { - let prefix = VERSION_PREFIX; - if ('@custom' === theme.substr(-7)) { - theme = theme.substr(0, theme.length - 7).trim(); - prefix = Settings.app('webPath') || ''; - } + /** + * @param {string} theme + * @returns {string} + */ + themePreviewLink = theme => { + let prefix = VERSION_PREFIX; + if ('@custom' === theme.substr(-7)) { + theme = theme.substr(0, theme.length - 7).trim(); + prefix = Settings.app('webPath') || ''; + } - return prefix + 'themes/' + encodeURI(theme) + '/images/preview.png'; -} + return prefix + 'themes/' + encodeURI(theme) + '/images/preview.png'; + }, -/** - * @param {string} inboxFolderName = 'INBOX' - * @returns {string} - */ -export function mailbox(inboxFolderName = 'INBOX') { - return HASH_PREFIX + 'mailbox/' + inboxFolderName; -} + /** + * @param {string} inboxFolderName = 'INBOX' + * @returns {string} + */ + mailbox = (inboxFolderName = 'INBOX') => HASH_PREFIX + 'mailbox/' + inboxFolderName, -/** - * @param {string=} screenName = '' - * @returns {string} - */ -export function settings(screenName = '') { - return HASH_PREFIX + 'settings' + (screenName ? '/' + screenName : ''); -} + /** + * @param {string=} screenName = '' + * @returns {string} + */ + settings = (screenName = '') => HASH_PREFIX + 'settings' + (screenName ? '/' + screenName : ''), -/** - * @param {string} screenName - * @returns {string} - */ -export function admin(screenName) { - let result = HASH_PREFIX; - switch (screenName) { - case 'AdminDomains': - result += 'domains'; - break; - case 'AdminSecurity': - result += 'security'; - break; - // no default - } + /** + * @param {string=} screenName + * @returns {string} + */ + admin = screenName => HASH_PREFIX + ( + 'AdminDomains' == screenName ? 'domains' + : 'AdminSecurity' == screenName ? 'security' + : '' + ), - return result; -} + /** + * @param {string} folder + * @param {number=} page = 1 + * @param {string=} search = '' + * @param {string=} threadUid = '' + * @returns {string} + */ + mailBox = (folder, page = 1, search = '', threadUid = '') => { + page = pInt(page, 1); + search = pString(search); -/** - * @param {string} folder - * @param {number=} page = 1 - * @param {string=} search = '' - * @param {string=} threadUid = '' - * @returns {string} - */ -export function mailBox(folder, page = 1, search = '', threadUid = '') { - page = pInt(page, 1); - search = pString(search); + let result = HASH_PREFIX + 'mailbox/'; - let result = HASH_PREFIX + 'mailbox/'; + if (folder) { + const resultThreadUid = pInt(threadUid); + result += encodeURI(folder) + (0 < resultThreadUid ? '~' + resultThreadUid : ''); + } - if (folder) { - const resultThreadUid = pInt(threadUid); - result += encodeURI(folder) + (0 < resultThreadUid ? '~' + resultThreadUid : ''); - } + if (1 < page) { + result = result.replace(/\/+$/, '') + '/p' + page; + } - if (1 < page) { - result = result.replace(/\/+$/, '') + '/p' + page; - } - - if (search) { - result = result.replace(/\/+$/, '') + '/' + encodeURI(search); - } - - return result; -} + if (search) { + result = result.replace(/\/+$/, '') + '/' + encodeURI(search); + } + return result; + }; diff --git a/dev/Common/Translator.js b/dev/Common/Translator.js index dd2d81bb6..011791117 100644 --- a/dev/Common/Translator.js +++ b/dev/Common/Translator.js @@ -5,49 +5,28 @@ import { doc, createElement } from 'Common/Globals'; let I18N_DATA = {}; -export const trigger = ko.observable(false); - -/** - * @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; - if (key) { - if ('[' === key.substr(0, 1)) { - switch (key.substr(0, 6)) { - case '[html]': - element.innerHTML = i18n(key.substr(6)); - break; - case '[place': - element.placeholder = i18n(key.substr(13)); - break; - case '[title': - element.title = i18n(key.substr(7)); - break; - // no default +const + i18nToNode = element => { + const key = element.dataset.i18n; + if (key) { + if ('[' === key.substr(0, 1)) { + switch (key.substr(0, 6)) { + case '[html]': + element.innerHTML = i18n(key.substr(6)); + break; + case '[place': + element.placeholder = i18n(key.substr(13)); + break; + case '[title': + element.title = i18n(key.substr(7)); + break; + // no default + } + } else { + element.textContent = i18n(key); } - } else { - element.textContent = i18n(key); } - } -}, + }, init = () => { if (rl.I18N) { @@ -60,109 +39,129 @@ const i18nToNode = element => { 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), + + getNotificationMessage = code => { + let key = getKeyByValue(Notification, code); + if (key) { + key = i18nKey(key).replace('_NOTIFICATION', '_ERROR'); + 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 {*=} message = '' + * @param {*=} defCode = null + * @returns {string} + */ + getNotification = (code, message = '', defCode = 0) => { + code = parseInt(code, 10) || 0; + if (Notification.ClientViewError === code && message) { + return message; + } + + return getNotificationMessage(code) + || getNotificationMessage(parseInt(defCode, 10)) + || ''; + }, + + /** + * @param {*} code + * @returns {string} + */ + getUploadErrorDescByCode = code => { + let result = 'UNKNOWN'; + code = parseInt(code, 10) || 0; + switch (code) { + case UploadErrorCode.FileIsTooBig: + case UploadErrorCode.FilePartiallyUploaded: + case UploadErrorCode.NoFileUploaded: + case UploadErrorCode.MissingTempFolder: + case UploadErrorCode.OnSavingFile: + case UploadErrorCode.FileType: + result = i18nKey(getKeyByValue(UploadErrorCode, code)); + break; + } + return i18n('UPLOAD/ERROR_' + result); + }, + + /** + * @param {boolean} admin + * @param {string} language + */ + reload = (admin, language) => + new Promise((resolve, reject) => { + const script = createElement('script'); + script.onload = () => { + // reload the data + if (init()) { + i18nToNodes(doc); + admin || rl.app.reloadTime(); + trigger(!trigger()); + } + script.remove(); + resolve(); + }; + script.onerror = () => reject(new Error('Language '+language+' failed')); + script.src = langLink(language, admin); + // script.async = true; + doc.head.append(script); + }), + + /** + * + * @param {string} language + * @param {boolean=} isEng = false + * @returns {string} + */ + convertLangName = (language, isEng = false) => + i18n( + 'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language, + null, + language + ); init(); - -/** - * @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); - if (key) { - key = i18nKey(key).replace('_NOTIFICATION', '_ERROR'); - return I18N_DATA.NOTIFICATIONS[key]; - } -} - -/** - * @param {number} code - * @param {*=} message = '' - * @param {*=} defCode = null - * @returns {string} - */ -export function getNotification(code, message = '', defCode = 0) { - code = parseInt(code, 10) || 0; - if (Notification.ClientViewError === code && message) { - return message; - } - - return getNotificationMessage(code) - || getNotificationMessage(parseInt(defCode, 10)) - || ''; -} - -/** - * @param {*} code - * @returns {string} - */ -export function getUploadErrorDescByCode(code) { - let result = 'UNKNOWN'; - code = parseInt(code, 10) || 0; - switch (code) { - case UploadErrorCode.FileIsTooBig: - case UploadErrorCode.FilePartiallyUploaded: - case UploadErrorCode.NoFileUploaded: - case UploadErrorCode.MissingTempFolder: - case UploadErrorCode.OnSavingFile: - case UploadErrorCode.FileType: - result = i18nKey(getKeyByValue(UploadErrorCode, code)); - break; - } - return i18n('UPLOAD/ERROR_' + result); -} - -/** - * @param {boolean} admin - * @param {string} language - */ -export function reload(admin, language) { - return new Promise((resolve, reject) => { - const script = createElement('script'); - script.onload = () => { - // reload the data - if (init()) { - i18nToNodes(doc); - admin || rl.app.reloadTime(); - trigger(!trigger()); - } - script.remove(); - resolve(); - }; - script.onerror = () => reject(new Error('Language '+language+' failed')); - script.src = langLink(language, admin); -// script.async = true; - doc.head.append(script); - }); -} - -/** - * - * @param {string} language - * @param {boolean=} isEng = false - * @returns {string} - */ -export function convertLangName(language, isEng = false) { - return i18n( - 'LANGS_NAMES' + (true === isEng ? '_EN' : '') + '/' + language, - null, - language - ); -} diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index 0382ebe54..2688eaf3b 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -1,141 +1,96 @@ import { SaveSettingsStep } from 'Common/Enums'; import { doc, elementById } from 'Common/Globals'; -export const - isArray = Array.isArray, - arrayLength = array => isArray(array) && array.length, - isFunction = v => typeof v === 'function'; - -/** - * @param {*} value - * @param {number=} defaultValue = 0 - * @returns {number} - */ -export function pInt(value, defaultValue = 0) { - value = parseInt(value, 10); - return isNaN(value) || !isFinite(value) ? defaultValue : value; -} - -/** - * @param {*} value - * @returns {string} - */ -export function pString(value) { - return null != value ? '' + value : ''; -} - -/** - * @returns {boolean} - */ -export function inFocus() { - try { - return doc.activeElement && doc.activeElement.matches( - 'input,textarea,[contenteditable]' - ); - } catch (e) { - return false; - } -} - -/** - * @param {string} theme - * @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); - setTimeout(() => koTrigger.call(context, SaveSettingsStep.Idle), 1000); - }; -} - let __themeTimer = 0, __themeJson = null; -/** - * @param {string} value - * @param {function=} themeTrigger = noop - * @returns {void} - */ -export function changeTheme(value, themeTrigger = ()=>0) { - const themeStyle = elementById('app-theme-style'), - clearTimer = () => { - __themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000); - __themeJson = null; - }; +export const + isArray = Array.isArray, + arrayLength = array => isArray(array) && array.length, + isFunction = v => typeof v === 'function', + pString = value => null != value ? '' + value : '', - let url = themeStyle.dataset.href; + pInt = (value, defaultValue = 0) => { + value = parseInt(value, 10); + return isNaN(value) || !isFinite(value) ? defaultValue : value; + }, - if (url) { - url = url.toString() - .replace(/\/-\/[^/]+\/-\//, '/-/' + value + '/-/') - .replace(/\/Css\/[^/]+\/User\//, '/Css/0/User/') - .replace(/\/Hash\/[^/]+\//, '/Hash/-/'); + convertThemeName = theme => theme + .replace(/@custom$/, '') + .replace(/([A-Z])/g, ' $1') + .replace(/[^a-zA-Z0-9]+/g, ' ') + .trim(), - if ('Json/' !== url.substr(-5)) { - url += 'Json/'; + defaultOptionsAfterRender = (domItem, item) => + domItem && item && undefined !== item.disabled + && domItem.classList.toggle('disabled', domItem.disabled = item.disabled), + + 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 { + return doc.activeElement && doc.activeElement.matches( + 'input,textarea,[contenteditable]' + ); + } catch (e) { + return false; } + }, - clearTimeout(__themeTimer); + settingsSaveHelperSimpleFunction = (koTrigger, context) => + iError => { + koTrigger.call(context, iError ? SaveSettingsStep.FalseResult : SaveSettingsStep.TrueResult); + setTimeout(() => koTrigger.call(context, SaveSettingsStep.Idle), 1000); + }, - themeTrigger(SaveSettingsStep.Animate); + changeTheme = (value, themeTrigger = ()=>0) => { + const themeStyle = elementById('app-theme-style'), + clearTimer = () => { + __themeTimer = setTimeout(() => themeTrigger(SaveSettingsStep.Idle), 1000); + __themeJson = null; + }; - if (__themeJson) { - __themeJson.abort(); + let url = themeStyle.dataset.href; + + if (url) { + url = url.toString() + .replace(/\/-\/[^/]+\/-\//, '/-/' + value + '/-/') + .replace(/\/Css\/[^/]+\/User\//, '/Css/0/User/') + .replace(/\/Hash\/[^/]+\//, '/Hash/-/'); + + if ('Json/' !== url.substr(-5)) { + url += 'Json/'; + } + + clearTimeout(__themeTimer); + + themeTrigger(SaveSettingsStep.Animate); + + if (__themeJson) { + __themeJson.abort(); + } + let init = {}; + if (window.AbortController) { + __themeJson = new AbortController(); + init.signal = __themeJson.signal; + } + rl.fetchJSON(url, init) + .then(data => { + if (2 === arrayLength(data)) { + themeStyle.textContent = data[1]; + themeStyle.dataset.href = url; + themeStyle.dataset.theme = data[0]; + themeTrigger(SaveSettingsStep.TrueResult); + } + }) + .then(clearTimer, clearTimer); } - let init = {}; - if (window.AbortController) { - __themeJson = new AbortController(); - init.signal = __themeJson.signal; - } - rl.fetchJSON(url, init) - .then(data => { - if (2 === arrayLength(data)) { - themeStyle.textContent = data[1]; - themeStyle.dataset.href = url; - themeStyle.dataset.theme = data[0]; - themeTrigger(SaveSettingsStep.TrueResult); - } - }) - .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)); -} + }; diff --git a/dev/Knoin/Knoin.js b/dev/Knoin/Knoin.js index e97250004..36d0c6e81 100644 --- a/dev/Knoin/Knoin.js +++ b/dev/Knoin/Knoin.js @@ -10,321 +10,318 @@ const SCREENS = {}, autofocus = dom => { const af = dom.querySelector('[autofocus]'); af && af.focus(); - }; + }, -export const popupVisibilityNames = ko.observableArray([]); + /** + * @param {string} screenName + * @returns {?Object} + */ + screen = screenName => screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null, -export const ViewType = { - Popup: 'Popups', - Left: 'Left', - Right: 'Right', - Content: 'Content' -}; + /** + * @param {Function} ViewModelClass + * @param {Object=} vmScreen + * @returns {*} + */ + buildViewModel = (ViewModelClass, vmScreen) => { + if (ViewModelClass && !ViewModelClass.__builded) { + let vmDom = null; + const vm = new ViewModelClass(vmScreen), + position = vm.viewModelPosition || '', + vmPlace = position ? doc.getElementById('rl-' + position.toLowerCase()) : null; -/** - * @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; + ViewModelClass.__builded = true; + ViewModelClass.__vm = vm; - if (isFunction(fCanExecute)) { - fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && fCanExecute.call(null)); - } else { - fResult.canExecute = ko.computed(() => fResult && fResult.enabled() && !!fCanExecute); - } + if (vmPlace) { + vmDom = Element.fromHTML(''); + vmPlace.append(vmDom); - return fResult; -} + vm.viewModelDom = vmDom; + ViewModelClass.__dom = vmDom; -/** - * @param {string} screenName - * @returns {?Object} - */ -function screen(screenName) { - return screenName && null != SCREENS[screenName] ? SCREENS[screenName] : null; -} + if (ViewType.Popup === position) { + vm.cancelCommand = vm.closeCommand = createCommand(() => { + hideScreenPopup(ViewModelClass); + }); -/** - * @param {Function} ViewModelClassToHide - * @returns {void} - */ -export function hideScreenPopup(ViewModelClassToHide) { - if (ViewModelClassToHide && ViewModelClassToHide.__vm && ViewModelClassToHide.__dom) { - ViewModelClassToHide.__vm.modalVisibility(false); - } -} - -/** - * @param {Function} ViewModelClass - * @param {Object=} vmScreen - * @returns {*} - */ -function buildViewModel(ViewModelClass, vmScreen) { - if (ViewModelClass && !ViewModelClass.__builded) { - let vmDom = null; - const vm = new ViewModelClass(vmScreen), - position = vm.viewModelPosition || '', - vmPlace = position ? doc.getElementById('rl-' + position.toLowerCase()) : null; - - ViewModelClass.__builded = true; - ViewModelClass.__vm = vm; - - if (vmPlace) { - vmDom = Element.fromHTML(''); - vmPlace.append(vmDom); - - vm.viewModelDom = vmDom; - ViewModelClass.__dom = vmDom; - - if (ViewType.Popup === position) { - vm.cancelCommand = vm.closeCommand = createCommand(() => { - hideScreenPopup(ViewModelClass); - }); - - // show/hide popup/modal - const endShowHide = e => { - if (e.target === vmDom) { - if (vmDom.classList.contains('show')) { - autofocus(vmDom); - vm.onShowWithDelay && vm.onShowWithDelay(); - } else { - vmDom.hidden = true; - vm.onHideWithDelay && vm.onHideWithDelay(); + // show/hide popup/modal + const endShowHide = e => { + if (e.target === vmDom) { + if (vmDom.classList.contains('show')) { + autofocus(vmDom); + vm.onShowWithDelay && vm.onShowWithDelay(); + } else { + vmDom.hidden = true; + vm.onHideWithDelay && vm.onHideWithDelay(); + } } - } - }; + }; - vm.modalVisibility.subscribe(value => { - if (value) { - vmDom.style.zIndex = 3000 + popupVisibilityNames().length + 10; - vmDom.hidden = false; - vm.storeAndSetScope(); - popupVisibilityNames.push(vm.viewModelName); - requestAnimationFrame(() => { // wait just before the next paint - vmDom.offsetHeight; // force a reflow - vmDom.classList.add('show'); // trigger the transitions - }); + vm.modalVisibility.subscribe(value => { + if (value) { + vmDom.style.zIndex = 3000 + popupVisibilityNames().length + 10; + vmDom.hidden = false; + vm.storeAndSetScope(); + popupVisibilityNames.push(vm.viewModelName); + requestAnimationFrame(() => { // wait just before the next paint + vmDom.offsetHeight; // force a reflow + vmDom.classList.add('show'); // trigger the transitions + }); + } else { + vm.onHide && vm.onHide(); + vmDom.classList.remove('show'); + vm.restoreScope(); + popupVisibilityNames(popupVisibilityNames.filter(v=>v!==vm.viewModelName)); + } + vmDom.setAttribute('aria-hidden', !value); + }); + if ('ontransitionend' in vmDom) { + vmDom.addEventListener('transitionend', endShowHide); } else { - vm.onHide && vm.onHide(); - vmDom.classList.remove('show'); - vm.restoreScope(); - popupVisibilityNames(popupVisibilityNames.filter(v=>v!==vm.viewModelName)); + // For Edge < 79 and mobile browsers + vm.modalVisibility.subscribe(() => ()=>setTimeout(endShowHide({target:vmDom}), 500)); } - vmDom.setAttribute('aria-hidden', !value); - }); - if ('ontransitionend' in vmDom) { - vmDom.addEventListener('transitionend', endShowHide); - } else { - // For Edge < 79 and mobile browsers - vm.modalVisibility.subscribe(() => ()=>setTimeout(endShowHide({target:vmDom}), 500)); } - } - ko.applyBindingAccessorsToNode( - vmDom, - { - i18nInit: true, - template: () => ({ name: vm.viewModelTemplateID }) - }, - vm - ); - - vm.onBuild && vm.onBuild(vmDom); - if (vm && ViewType.Popup === position) { - vm.registerPopupKeyDown(); - } - - dispatchEvent(new CustomEvent('rl-view-model', {detail:vm})); - } else { - console.log('Cannot find view model position: ' + position); - } - } - - 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} subPart - * @returns {void} - */ -function screenOnRoute(screenName, subPart) { - let vmScreen = null, - isSameScreen = false; - - if (null == screenName || '' == screenName) { - screenName = defaultScreenName; - } - - if (screenName) { - vmScreen = screen(screenName); - if (!vmScreen) { - vmScreen = screen(defaultScreenName); - if (vmScreen) { - subPart = screenName + '/' + subPart; - screenName = defaultScreenName; - } - } - - if (vmScreen && vmScreen.__started) { - isSameScreen = currentScreen && vmScreen === currentScreen; - - if (!vmScreen.__builded) { - vmScreen.__builded = true; - - vmScreen.viewModels.forEach(ViewModelClass => - buildViewModel(ViewModelClass, vmScreen) + ko.applyBindingAccessorsToNode( + vmDom, + { + i18nInit: true, + template: () => ({ name: vm.viewModelTemplateID }) + }, + vm ); - vmScreen.onBuild && vmScreen.onBuild(); - } - - setTimeout(() => { - // hide screen - if (currentScreen && !isSameScreen) { - currentScreen.onHide && currentScreen.onHide(); - currentScreen.onHideWithDelay && setTimeout(()=>currentScreen.onHideWithDelay(), 500); - - if (arrayLength(currentScreen.viewModels)) { - currentScreen.viewModels.forEach(ViewModelClass => { - if ( - ViewModelClass.__vm && - ViewModelClass.__dom && - ViewType.Popup !== ViewModelClass.__vm.viewModelPosition - ) { - ViewModelClass.__dom.hidden = true; - ViewModelClass.__vm.viewModelVisible = false; - - ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide(); - ViewModelClass.__vm.onHideWithDelay && setTimeout(()=>ViewModelClass.__vm.onHideWithDelay(), 500); - } - }); - } + vm.onBuild && vm.onBuild(vmDom); + if (vm && ViewType.Popup === position) { + vm.registerPopupKeyDown(); } - // -- - currentScreen = vmScreen; - - // show screen - if (currentScreen && !isSameScreen) { - currentScreen.onShow && currentScreen.onShow(); - - if (arrayLength(currentScreen.viewModels)) { - currentScreen.viewModels.forEach(ViewModelClass => { - if ( - ViewModelClass.__vm && - ViewModelClass.__dom && - ViewType.Popup !== ViewModelClass.__vm.viewModelPosition - ) { - ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow(); - - ViewModelClass.__dom.hidden = false; - ViewModelClass.__vm.viewModelVisible = true; - - ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow(); - - autofocus(ViewModelClass.__dom); - - ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200); - } - }); - } - } - // -- - - vmScreen && vmScreen.__cross && vmScreen.__cross.parse(subPart); - }, 1); - } - } -} - -/** - * @param {Array} screensClasses - * @returns {void} - */ -export function startScreens(screensClasses) { - screensClasses.forEach(CScreen => { - if (CScreen) { - const vmScreen = new CScreen(), - screenName = vmScreen && vmScreen.screenName(); - - if (screenName) { - defaultScreenName || (defaultScreenName = screenName); - - SCREENS[screenName] = vmScreen; + dispatchEvent(new CustomEvent('rl-view-model', {detail:vm})); + } else { + console.log('Cannot find view model position: ' + position); } } - }); - Object.values(SCREENS).forEach(vmScreen => - vmScreen && vmScreen.onStart && vmScreen.onStart() - ); + return ViewModelClass && ViewModelClass.__vm; + }, - const cross = new Crossroads(); - cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute); + /** + * @param {string} screenName + * @param {string} subPart + * @returns {void} + */ + screenOnRoute = (screenName, subPart) => { + let vmScreen = null, + isSameScreen = false; - hasher.add(cross.parse.bind(cross)); - hasher.init(); + if (null == screenName || '' == screenName) { + screenName = defaultScreenName; + } - setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100); - setTimeout(() => $htmlCL.add('rl-started-delay'), 200); -} + if (screenName) { + vmScreen = screen(screenName); + if (!vmScreen) { + vmScreen = screen(defaultScreenName); + if (vmScreen) { + subPart = screenName + '/' + subPart; + screenName = defaultScreenName; + } + } -export function decorateKoCommands(thisArg, commands) { - Object.entries(commands).forEach(([key, canExecute]) => { - let command = thisArg[key], - fn = (...args) => fn.enabled() && fn.canExecute() && command.apply(thisArg, args); + if (vmScreen && vmScreen.__started) { + isSameScreen = currentScreen && vmScreen === currentScreen; -// fn.__realCanExecute = canExecute; -// fn.isCommand = true; + if (!vmScreen.__builded) { + vmScreen.__builded = true; - fn.enabled = ko.observable(true); + vmScreen.viewModels.forEach(ViewModelClass => + buildViewModel(ViewModelClass, vmScreen) + ); - fn.canExecute = (typeof canExecute === 'function') - ? ko.computed(() => fn.enabled() && canExecute.call(thisArg, thisArg)) - : ko.computed(() => fn.enabled()); + vmScreen.onBuild && vmScreen.onBuild(); + } + + setTimeout(() => { + // hide screen + if (currentScreen && !isSameScreen) { + currentScreen.onHide && currentScreen.onHide(); + currentScreen.onHideWithDelay && setTimeout(()=>currentScreen.onHideWithDelay(), 500); + + if (arrayLength(currentScreen.viewModels)) { + currentScreen.viewModels.forEach(ViewModelClass => { + if ( + ViewModelClass.__vm && + ViewModelClass.__dom && + ViewType.Popup !== ViewModelClass.__vm.viewModelPosition + ) { + ViewModelClass.__dom.hidden = true; + ViewModelClass.__vm.viewModelVisible = false; + + ViewModelClass.__vm.onHide && ViewModelClass.__vm.onHide(); + ViewModelClass.__vm.onHideWithDelay && setTimeout(()=>ViewModelClass.__vm.onHideWithDelay(), 500); + } + }); + } + } + // -- + + currentScreen = vmScreen; + + // show screen + if (currentScreen && !isSameScreen) { + currentScreen.onShow && currentScreen.onShow(); + + if (arrayLength(currentScreen.viewModels)) { + currentScreen.viewModels.forEach(ViewModelClass => { + if ( + ViewModelClass.__vm && + ViewModelClass.__dom && + ViewType.Popup !== ViewModelClass.__vm.viewModelPosition + ) { + ViewModelClass.__vm.onBeforeShow && ViewModelClass.__vm.onBeforeShow(); + + ViewModelClass.__dom.hidden = false; + ViewModelClass.__vm.viewModelVisible = true; + + ViewModelClass.__vm.onShow && ViewModelClass.__vm.onShow(); + + autofocus(ViewModelClass.__dom); + + ViewModelClass.__vm.onShowWithDelay && setTimeout(()=>ViewModelClass.__vm.onShowWithDelay, 200); + } + }); + } + } + // -- + + vmScreen && vmScreen.__cross && vmScreen.__cross.parse(subPart); + }, 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 + * @returns {void} + */ + startScreens = screensClasses => { + screensClasses.forEach(CScreen => { + if (CScreen) { + const vmScreen = new CScreen(), + screenName = vmScreen && vmScreen.screenName(); + + if (screenName) { + defaultScreenName || (defaultScreenName = screenName); + + SCREENS[screenName] = vmScreen; + } + } + }); + + Object.values(SCREENS).forEach(vmScreen => + vmScreen && vmScreen.onStart && vmScreen.onStart() + ); + + const cross = new Crossroads(); + cross.addRoute(/^([a-zA-Z0-9-]*)\/?(.*)$/, screenOnRoute); + + hasher.add(cross.parse.bind(cross)); + hasher.init(); + + setTimeout(() => $htmlCL.remove('rl-started-trigger'), 100); + setTimeout(() => $htmlCL.add('rl-started-delay'), 200); + }, + + decorateKoCommands = (thisArg, commands) => + Object.entries(commands).forEach(([key, canExecute]) => { + let command = thisArg[key], + fn = (...args) => fn.enabled() && fn.canExecute() && command.apply(thisArg, args); + + // fn.__realCanExecute = canExecute; + // fn.isCommand = true; + + fn.enabled = ko.observable(true); + + fn.canExecute = (typeof canExecute === 'function') + ? ko.computed(() => fn.enabled() && canExecute.call(thisArg, thisArg)) + : ko.computed(() => fn.enabled()); + + thisArg[key] = fn; + }); - thisArg[key] = fn; - }); -} ko.decorateCommands = decorateKoCommands; diff --git a/dev/Model/FolderCollection.js b/dev/Model/FolderCollection.js index 90cbda50e..3fd1cd792 100644 --- a/dev/Model/FolderCollection.js +++ b/dev/Model/FolderCollection.js @@ -160,9 +160,9 @@ function getSystemFolderName(type, def) switch (type) { case FolderType.Inbox: return i18n('FOLDER_LIST/INBOX_NAME'); - case FolderType.SentItems: + case FolderType.Sent: return i18n('FOLDER_LIST/SENT_NAME'); - case FolderType.Draft: + case FolderType.Drafts: return i18n('FOLDER_LIST/DRAFTS_NAME'); case FolderType.Spam: return i18n('GLOBAL/SPAM'); @@ -281,14 +281,14 @@ export class FolderModel extends AbstractModel { type = folder.type(); if (count) { - if (FolderType.Draft === type) { + if (FolderType.Drafts === type) { return count; } if ( unread && FolderType.Trash !== type && FolderType.Archive !== type && - FolderType.SentItems !== type + FolderType.Sent !== type ) { return unread; } diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index 64c9519c9..f796348e7 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -127,8 +127,8 @@ export const FolderUserStore = new class { this.archiveFolder.subscribe(fRemoveSystemFolderType(this.archiveFolder), this, 'beforeChange'); addSubscribablesTo(this, { - sentFolder: fSetSystemFolderType(FolderType.SentItems), - draftFolder: fSetSystemFolderType(FolderType.Draft), + sentFolder: fSetSystemFolderType(FolderType.Sent), + draftFolder: fSetSystemFolderType(FolderType.Drafts), spamFolder: fSetSystemFolderType(FolderType.Spam), trashFolder: fSetSystemFolderType(FolderType.Trash), archiveFolder: fSetSystemFolderType(FolderType.Archive) diff --git a/dev/View/Popup/Domain.js b/dev/View/Popup/Domain.js index b03890349..08176683c 100644 --- a/dev/View/Popup/Domain.js +++ b/dev/View/Popup/Domain.js @@ -12,12 +12,11 @@ class DomainPopupView extends AbstractViewPopup { constructor() { super('Domain'); + this.addObservables(this.getDefaults()); this.addObservables({ edit: false, + saving: false, - savingError: '', - page: 'main', - sieveSettings: false, testing: false, testingDone: false, @@ -31,28 +30,6 @@ class DomainPopupView extends AbstractViewPopup { imapServerFocus: false, sieveServerFocus: 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({ @@ -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() { this.edit(false); - - 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(''); + Object.entries(this.getDefaults()).forEach(([key, value]) => this[key](value)); this.enableSmartPorts(true); } } diff --git a/dev/bootstrap.js b/dev/bootstrap.js index 234a1b8f3..b8abd2fa2 100644 --- a/dev/bootstrap.js +++ b/dev/bootstrap.js @@ -45,16 +45,11 @@ export default App => { */ setHash: (hash, silence = false, replace = false) => { hash = hash.replace(/^[#/]+/, ''); - - const cmd = replace ? 'replaceHash' : 'setHash'; - + hasher.active = !silence; + hasher[replace ? 'replaceHash' : 'setHash'](hash); if (silence) { - hasher.active = false; - hasher[cmd](hash); hasher.active = true; } else { - hasher.active = true; - hasher[cmd](hash); hasher.setHash(hash); } }