diff --git a/README.md b/README.md index b5397166f..96f23bfd6 100644 --- a/README.md +++ b/README.md @@ -106,22 +106,22 @@ RainLoop 1.14 vs SnappyMail |js/* |RainLoop |Snappy | |----------- |--------: |--------: | -|admin.js |2.130.942 | 798.252 | -|app.js |4.184.455 |2.450.286 | +|admin.js |2.130.942 | 776.615 | +|app.js |4.184.455 |2.430.639 | |boot.js | 671.522 | 5.285 | -|libs.js | 647.614 | 254.910 | +|libs.js | 647.614 | 255.041 | |polyfills.js | 325.834 | 0 | -|TOTAL |7.960.367 |3.508.733 | +|TOTAL |7.960.367 |3.467.580 | |js/min/* |RainLoop |Snappy |Rain gzip |gzip |brotli | |--------------- |--------: |--------: |--------: |--------: |--------: | -|admin.min.js | 252.147 | 106.871 | 73.657 | 28.834 | 25.063 | -|app.min.js | 511.202 | 332.055 |140.462 | 85.613 | 69.336 | +|admin.min.js | 252.147 | 104.046 | 73.657 | 27.739 | 24.058 | +|app.min.js | 511.202 | 329.887 |140.462 | 84.844 | 68.843 | |boot.min.js | 66.007 | 2.935 | 22.567 | 1.510 | 1.285 | -|libs.min.js | 572.545 | 149.580 |176.720 | 52.695 | 46.823 | +|libs.min.js | 572.545 | 149.712 |176.720 | 52.703 | 46.853 | |polyfills.min.js | 32.452 | 0 | 11.312 | 0 | 0 | -|TOTAL |1.434.353 | 591.441 |424.718 |168.652 |142.507 | -|TOTAL (no admin) |1.182.206 | 484.570 |351.061 |139.818 |117.444 | +|TOTAL |1.434.353 | 586.580 |424.718 |166.796 |141.039 | +|TOTAL (no admin) |1.182.206 | 482.534 |351.061 |139.057 |116.981 | For a user its around 60% smaller and faster than traditional RainLoop. diff --git a/dev/App/Admin.js b/dev/App/Admin.js index 5ae00477b..f54b4ab47 100644 --- a/dev/App/Admin.js +++ b/dev/App/Admin.js @@ -82,7 +82,7 @@ class AdminApp extends AbstractApp { return 'core' === item.type && !item.canBeInstalled ? null : item; } return null; - }).filter(value => !!value); + }).filter(v => v); } PackageStore.packages(list); diff --git a/dev/App/User.js b/dev/App/User.js index f66ff4a2f..c45557e76 100644 --- a/dev/App/User.js +++ b/dev/App/User.js @@ -255,8 +255,7 @@ class AppUser extends AbstractApp { }; } - this.moveCache[hash].Uid = this.moveCache[hash].Uid.concat(uidsForMove) - .filter((value, index, self) => self.indexOf(value) == index); + this.moveCache[hash].Uid = this.moveCache[hash].Uid.concat(uidsForMove).unique(); this.messagesMoveTrigger(); } @@ -457,7 +456,7 @@ class AppUser extends AbstractApp { .toLowerCase(), oItem.getKeyIds() .map(item => (item && item.toHex ? item.toHex() : null)) - .filter((value, index, self) => !!value && self.indexOf(value) == index), + .validUnique(), aUsers, aEmails, oItem.isPrivate(), @@ -537,7 +536,7 @@ class AppUser extends AbstractApp { data.Result.Templates.map(templateData => { const template = new TemplateModel(); return template.parse(templateData) ? template : null; - }).filter(value => !!value) + }).filter(v => v) ); } }); @@ -718,7 +717,7 @@ class AppUser extends AbstractApp { } rootUids = messages.map(oMessage => oMessage && oMessage.uid ? oMessage.uid : null) - .filter((value, index, self) => !!value && self.indexOf(value) == index); + .validUnique(); if (sFolderFullNameRaw && rootUids.length) { switch (iSetAction) { @@ -779,7 +778,7 @@ class AppUser extends AbstractApp { Remote.suggestions((result, data) => { if (StorageResultType.Success === result && data && Array.isArray(data.Result)) { autocompleteCallback( - data.Result.map(item => (item && item[0] ? new EmailModel(item[0], item[1]) : null)).filter(value => !!value) + data.Result.map(item => (item && item[0] ? new EmailModel(item[0], item[1]) : null)).filter(v => v) ); } else if (StorageResultType.Abort !== result) { autocompleteCallback([]); diff --git a/dev/Common/File.js b/dev/Common/File.js index e7b45a835..2e41449a0 100644 --- a/dev/Common/File.js +++ b/dev/Common/File.js @@ -301,7 +301,7 @@ export const File = { if (Array.isNotEmpty(data)) { let icons = data .map(item => item ? File.getIconClass(File.getExtension(item[0]), item[1])[0] : '') - .filter((value, index, self) => value && self.indexOf(value) == index); + .validUnique(); return (icons && 1 === icons.length && 'icon-file' !== icons[0]) ? icons[0] diff --git a/dev/Common/Utils.js b/dev/Common/Utils.js index 484279fe9..9e9f5ef82 100644 --- a/dev/Common/Utils.js +++ b/dev/Common/Utils.js @@ -40,38 +40,6 @@ export function pString(value) { return null != value ? '' + value : ''; } -/** - * @param {string} queryString - * @returns {Object} - */ -export function simpleQueryParser(queryString) { - const queries = queryString.split('&'), - params = {}; - - queries.forEach(temp => { - temp = temp.split('='); - params[decodeURIComponent(temp[0])] = decodeURIComponent(temp[1]); - }); - - return params; -} - -/** - * @param {number=} len = 32 - * @returns {string} - */ -export function fakeMd5(len = 32) { - const line = '0123456789abcdefghijklmnopqrstuvwxyz'; - - len = pInt(len); - - let result = ''; - while (len--) - result += line.substr(Math.round(Math.random() * 36), 1); - - return result; -} - /** * @param {string} text * @returns {string} @@ -85,7 +53,7 @@ export function encodeHtml(text) { * @param {number=} len = 100 * @returns {string} */ -export function splitPlainText(text, len = 100) { +function splitPlainText(text, len = 100) { let prefix = '', subText = '', result = text, @@ -139,72 +107,6 @@ export function inFocus() { return false; } -/** - * @param {boolean} force - * @returns {void} - */ -export function removeInFocus(force) { - if (doc.activeElement && doc.activeElement.blur) { - try { - if (force || doc.activeElement.matches('input,textarea')) { - doc.activeElement.blur(); - } - } catch (e) {} // eslint-disable-line no-empty - } -} - -/** - * @returns {void} - */ -export function removeSelection() { - try { - getSelection().removeAllRanges(); - } catch (e) {} // eslint-disable-line no-empty -} - -/** - * @param {string} prefix - * @param {string} subject - * @returns {string} - */ -export function replySubjectAdd(prefix, subject) { - prefix = prefix.toUpperCase().trim(); - subject = subject.replace(/[\s]+/g, ' ').trim(); - - let drop = false, - re = 'RE' === prefix, - fwd = 'FWD' === prefix; - - const parts = [], - prefixIsRe = !fwd; - - if (subject) { - subject.split(':').forEach(part => { - const trimmedPart = part.trim(); - if (!drop && (/^(RE|FWD)$/i.test(trimmedPart) || /^(RE|FWD)[[(][\d]+[\])]$/i.test(trimmedPart))) { - if (!re) { - re = !!/^RE/i.test(trimmedPart); - } - - if (!fwd) { - fwd = !!/^FWD/i.test(trimmedPart); - } - } else { - parts.push(part); - drop = true; - } - }); - } - - if (prefixIsRe) { - re = false; - } else { - fwd = false; - } - - return ((prefixIsRe ? 'Re: ' : 'Fwd: ') + (re ? 'Re: ' : '') + (fwd ? 'Fwd: ' : '') + parts.join(':').trim()).trim(); -} - /** * @param {(number|string)} sizeInBytes * @returns {string} @@ -256,88 +158,18 @@ export function defautOptionsAfterRender(domItem, item) { } } -/** - * @param {Function} fCallback - * @param {?} koTrigger - * @param {?} context = null - * @param {number=} timer = 1000 - * @returns {Function} - */ -export function settingsSaveHelperFunction(fCallback, koTrigger, context = null, timer = 1000) { - timer = pInt(timer); - return (type, data, cached, requestAction, requestParameters) => { - koTrigger.call(context, data && data.Result ? SaveSettingsStep.TrueResult : SaveSettingsStep.FalseResult); - if (fCallback) { - fCallback.call(context, type, data, cached, requestAction, requestParameters); - } - setTimeout(() => { - koTrigger.call(context, SaveSettingsStep.Idle); - }, timer); - }; -} - /** * @param {object} koTrigger * @param {mixed} context * @returns {mixed} */ export function settingsSaveHelperSimpleFunction(koTrigger, context) { - return settingsSaveHelperFunction(null, koTrigger, context, 1000); -} - -/** - * @param {object} remote - * @param {string} settingName - * @param {string} type - * @param {function} fTriggerFunction - * @returns {function} - */ -export function settingsSaveHelperSubscribeFunction(remote, settingName, type, fTriggerFunction) { - return (value) => { - if (remote) { - switch (type) { - case 'bool': - case 'boolean': - value = value ? '1' : '0'; - break; - case 'int': - case 'integer': - case 'number': - value = pInt(value); - break; - case 'trim': - value = value.trim(); - break; - default: - value = pString(value); - break; - } - - const data = {}; - data[settingName] = value; - - if (remote.saveAdminConfig) { - remote.saveAdminConfig(fTriggerFunction || null, data); - } else if (remote.saveSettings) { - remote.saveSettings(fTriggerFunction || null, data); - } - } + return (type, data) => { + koTrigger.call(context, data && data.Result ? SaveSettingsStep.TrueResult : SaveSettingsStep.FalseResult); + setTimeout(() => koTrigger.call(context, SaveSettingsStep.Idle), 1000); }; } -/** - * @param {string} html - * @returns {string} - */ -/*eslint-disable max-len*/ -const url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi, - email = /(^|[\s\n]|\/?>)((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x21\x23-\x5b\x5d-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x21-\x5a\x53-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])+)\]))/gi; -export function findEmailAndLinks(html) { - return html - .replace(url, '$1$2') - .replace(email, '$1$2'); -} - /** * @param {string} html * @returns {string} @@ -644,18 +476,6 @@ export function folderListOptionsBuilder( return aResult; } -/** - * @param {object} element - * @returns {void} - */ -export function selectElement(element) { - let sel = getSelection(), - range = doc.createRange(); - sel.removeAllRanges(); - range.selectNodeContents(element); - sel.addRange(range); -} - /** * @param {Object|Array} objectOrObjects * @returns {void} @@ -827,38 +647,6 @@ export function isTransparent(color) { return 'rgba(0, 0, 0, 0)' === color || 'transparent' === color; } -/** - * @param {string} url - * @param {number} value - * @param {Function} fCallback - */ -export function resizeAndCrop(url, value, fCallback) { - const img = new Image(); - img.onload = function() { - let diff = [0, 0]; - - const canvas = doc.createElement('canvas'), - ctx = canvas.getContext('2d'); - - canvas.width = value; - canvas.height = value; - - if (this.width > this.height) { - diff = [this.width - this.height, 0]; - } else { - diff = [0, this.height - this.width]; - } - - ctx.fillStyle = '#fff'; - ctx.fillRect(0, 0, value, value); - ctx.drawImage(this, diff[0] / 2, diff[1] / 2, this.width - diff[0], this.height - diff[1], 0, 0, value, value); - - fCallback(canvas.toDataURL('image/jpeg')); - }; - - img.src = url; -} - /** * @param {string} mailToUrl * @param {Function} PopupComposeViewModel @@ -888,7 +676,10 @@ export function mailToHelper(mailToUrl, PopupComposeViewModel) { query = mailToUrl.replace(/^[^?]*\?/, ''), EmailModel = require('Model/Email').default; - params = simpleQueryParser(query); + query.split('&').forEach(temp => { + temp = temp.split('='); + params[decodeURIComponent(temp[0])] = decodeURIComponent(temp[1]); + }); if (undefined !== params.to) { to = EmailModel.parseEmailLine(decodeURIComponent(email + ',' + params.to)); diff --git a/dev/Model/Email.js b/dev/Model/Email.js index 115cbe353..634dc2dda 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -440,7 +440,7 @@ class EmailModel { if (parsedResult.length) { return parsedResult.map(item => item.address ? new EmailModel(item.address.replace(/^[<]+(.*)[>]+$/g, '$1'), item.name || '') : null - ).filter(value => !!value); + ).filter(v => v); } return []; diff --git a/dev/Model/Filter.js b/dev/Model/Filter.js index 381281f6b..e2a3cee78 100644 --- a/dev/Model/Filter.js +++ b/dev/Model/Filter.js @@ -1,7 +1,7 @@ import ko from 'ko'; import { FilterRulesType, FiltersAction } from 'Common/Enums'; -import { pString, fakeMd5, delegateRunOnDestroy } from 'Common/Utils'; +import { pString, delegateRunOnDestroy } from 'Common/Utils'; import { i18n } from 'Common/Translator'; import { getFolderFromCacheList } from 'Common/Cache'; @@ -133,7 +133,7 @@ class FilterModel extends AbstractModel { } generateID() { - this.id = fakeMd5(); + this.id = Jua.randomId(); } verify() { @@ -230,7 +230,7 @@ class FilterModel extends AbstractModel { json.Conditions.map(aData => { const filterCondition = new FilterConditionModel(); return filterCondition && filterCondition.parse(aData) ? filterCondition : null; - }).filter(value => !!value) + }).filter(v => v) ); } diff --git a/dev/Model/Message.js b/dev/Model/Message.js index ead03274b..b03e2c109 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -182,7 +182,7 @@ class MessageModel extends AbstractModel { getEmails(properties) { return properties.reduce((carry, property) => carry.concat(this[property]), []).map( oItem => oItem ? oItem.email : '' - ).filter((value, index, self) => !!value && self.indexOf(value) == index); + ).validUnique(); } /** diff --git a/dev/Settings/User/Filters.js b/dev/Settings/User/Filters.js index 35585d6b1..54c4a0498 100644 --- a/dev/Settings/User/Filters.js +++ b/dev/Settings/User/Filters.js @@ -105,7 +105,7 @@ class FiltersUserSettings { data.Result.Filters.map(aItem => { const filter = new FilterModel(); return filter && filter.parse(aItem) ? filter : null; - }).filter(value => !!value) + }).filter(v => v) ); this.modules(data.Result.Modules ? data.Result.Modules : {}); diff --git a/dev/Stores/User/Account.js b/dev/Stores/User/Account.js index d70b51776..7d362cd9e 100644 --- a/dev/Stores/User/Account.js +++ b/dev/Stores/User/Account.js @@ -10,7 +10,7 @@ class AccountUserStore { this.accounts = ko.observableArray([]); this.accounts.loading = ko.observable(false).extend({ throttle: 100 }); - this.getEmailAddresses = () => this.accounts().map(item => item ? item.email : null).filter(value => !!value); + this.getEmailAddresses = () => this.accounts().map(item => item ? item.email : null).filter(v => v); this.accountsUnreadCount = ko.computed(() => 0); // this.accountsUnreadCount = ko.computed(() => { diff --git a/dev/Stores/User/Folder.js b/dev/Stores/User/Folder.js index 2331577d6..59fdcbf9e 100644 --- a/dev/Stores/User/Folder.js +++ b/dev/Stores/User/Folder.js @@ -85,7 +85,7 @@ class FolderUserStore { }); this.folderListSystem = ko.computed(() => - this.folderListSystemNames().map(name => getFolderFromCacheList(name)).filter(value => !!value) + this.folderListSystemNames().map(name => getFolderFromCacheList(name)).filter(v => v) ); this.folderMenuForMove = ko.computed(() => diff --git a/dev/Stores/User/Message.js b/dev/Stores/User/Message.js index 11faf255a..17f57d1a0 100644 --- a/dev/Stores/User/Message.js +++ b/dev/Stores/User/Message.js @@ -5,8 +5,7 @@ import { Layout, Focused, MessageSetAction, StorageResultType, Notification } fr import { pInt, pString, - plainToHtml, - findEmailAndLinks + plainToHtml } from 'Common/Utils'; import { @@ -44,6 +43,14 @@ const const result = hcont.clientHeight; hcont.innerHTML = ''; return result; + }, + /*eslint-disable max-len*/ + url = /(^|[\s\n]|\/?>)(https:\/\/[-A-Z0-9+\u0026\u2019#/%?=()~_|!:,.;]*[-A-Z0-9+\u0026#/%=~()_|])/gi, + email = /(^|[\s\n]|\/?>)((?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|"(?:[\x21\x23-\x5b\x5d-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])*")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\[(?:(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9]))\.){3}(?:(2(5[0-5]|[0-4][0-9])|1[0-9][0-9]|[1-9]?[0-9])|[a-z0-9-]*[a-z0-9]:(?:[\x21-\x5a\x53-\x7f]|\\[\x21\x23-\x5b\x5d-\x7f])+)\]))/gi, + findEmailAndLinks = html => { + return html + .replace(url, '$1$2') + .replace(email, '$1$2'); }; let iMessageBodyCacheCount = 0; @@ -142,7 +149,7 @@ class MessageUserStore { if (checked.length) { return selectedMessage - ? checked.concat([selectedMessage]).filter((value, index, self) => self.indexOf(value) == index) + ? checked.concat([selectedMessage]).unique() : checked; } @@ -155,7 +162,7 @@ class MessageUserStore { if (message) { result.push(message.uid); if (1 < message.threadsLen()) { - result = result.concat(message.threads()).filter((value, index, self) => self.indexOf(value) == index); + result = result.concat(message.threads()).unique(); } } }); diff --git a/dev/Stores/User/Pgp.js b/dev/Stores/User/Pgp.js index 5b5dc2b65..516dc8e6f 100644 --- a/dev/Stores/User/Pgp.js +++ b/dev/Stores/User/Pgp.js @@ -53,7 +53,7 @@ function domControlEncryptedClickHelper(store, dom, armoredMessage, recipients) } else if (validPrivateKey) { const keyIds = Array.isNotEmpty(signingKeyIds) ? signingKeyIds : null, additional = keyIds - ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(value => !!value).join(', ') + ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ') : ''; controlsHelper( @@ -109,7 +109,7 @@ function domControlSignedClickHelper(store, dom, armoredMessage) { } else { const keyIds = Array.isNotEmpty(signingKeyIds) ? signingKeyIds : null, additional = keyIds - ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(value => !!value).join(', ') + ? keyIds.map(item => (item && item.toHex ? item.toHex() : null)).filter(v => v).join(', ') : ''; controlsHelper( @@ -138,8 +138,8 @@ class PgpUserStore { this.openpgpkeys = ko.observableArray([]); this.openpgpKeyring = null; - this.openpgpkeysPublic = ko.computed(() => this.openpgpkeys().filter(item => !!(item && !item.isPrivate))); - this.openpgpkeysPrivate = ko.computed(() => this.openpgpkeys().filter(item => !!(item && item.isPrivate))); + this.openpgpkeysPublic = ko.computed(() => this.openpgpkeys().filter(item => item && !item.isPrivate)); + this.openpgpkeysPrivate = ko.computed(() => this.openpgpkeys().filter(item => item && item.isPrivate)); } /** @@ -165,14 +165,14 @@ class PgpUserStore { return this.openpgpkeysPublic().map(item => { const key = item && item.emails.includes(email) ? item : null; return key ? key.getNativeKeys() : [null]; - }).flat().filter(value => !!value); + }).flat().filter(v => v); } findPublicKeysBySigningKeyIds(signingKeyIds) { return signingKeyIds.map(id => { const key = id && id.toHex ? this.findPublicKeyByHex(id.toHex()) : null; return key ? key.getNativeKeys() : [null]; - }).flat().filter(value => !!value); + }).flat().filter(v => v); } findPrivateKeysByEncryptionKeyIds(encryptionKeyIds, recipients, returnWrapKeys) { @@ -180,7 +180,7 @@ class PgpUserStore { ? encryptionKeyIds.map(id => { const key = id && id.toHex ? this.findPrivateKeyByHex(id.toHex()) : null; return key ? (returnWrapKeys ? [key] : key.getNativeKeys()) : [null]; - }).flat().filter(value => !!value) + }).flat().filter(v => v) : []; if (!result.length && Array.isNotEmpty(recipients)) { @@ -191,7 +191,7 @@ class PgpUserStore { ? keys : keys.map(key => key.getNativeKeys()).flat() : [null]; - }).flat().filter((key, index, self) => key => !!key.id && self.indexOf(key) == index); + }).flat().validUnique(key => key.id); } return result; diff --git a/dev/Stores/User/Template.js b/dev/Stores/User/Template.js index 8d392312f..21fc7bf15 100644 --- a/dev/Stores/User/Template.js +++ b/dev/Stores/User/Template.js @@ -15,7 +15,7 @@ class TemplateUserStore { subscribers() { this.templates.subscribe((list) => { - this.templatesNames(list.map(item => (item ? item.name : null)).filter(value => !!value)); + this.templatesNames(list.map(item => (item ? item.name : null)).filter(v => v)); }); // this.templatesNames.subscribe((aList) => { diff --git a/dev/View/Popup/Compose.js b/dev/View/Popup/Compose.js index 832dcf995..280e71a6a 100644 --- a/dev/View/Popup/Compose.js +++ b/dev/View/Popup/Compose.js @@ -12,7 +12,6 @@ import { } from 'Common/Enums'; import { - replySubjectAdd, encodeHtml, inFocus, delegateRunOnDestroy, @@ -42,7 +41,50 @@ import { ComposeAttachmentModel } from 'Model/ComposeAttachment'; import { popup, command, isPopupVisible, showScreenPopup, hideScreenPopup } from 'Knoin/Knoin'; import { AbstractViewNext } from 'Knoin/AbstractViewNext'; -const Settings = rl.settings; +const Settings = rl.settings, + /** + * @param {string} prefix + * @param {string} subject + * @returns {string} + */ + replySubjectAdd = (prefix, subject) => { + prefix = prefix.toUpperCase().trim(); + subject = subject.replace(/[\s]+/g, ' ').trim(); + + let drop = false, + re = 'RE' === prefix, + fwd = 'FWD' === prefix; + + const parts = [], + prefixIsRe = !fwd; + + if (subject) { + subject.split(':').forEach(part => { + const trimmedPart = part.trim(); + if (!drop && (/^(RE|FWD)$/i.test(trimmedPart) || /^(RE|FWD)[[(][\d]+[\])]$/i.test(trimmedPart))) { + if (!re) { + re = !!/^RE/i.test(trimmedPart); + } + + if (!fwd) { + fwd = !!/^FWD/i.test(trimmedPart); + } + } else { + parts.push(part); + drop = true; + } + }); + } + + if (prefixIsRe) { + re = false; + } else { + fwd = false; + } + + return ((prefixIsRe ? 'Re: ' : 'Fwd: ') + (re ? 'Re: ' : '') + + (fwd ? 'Fwd: ' : '') + parts.join(':').trim()).trim(); + }; ko.extenders.toggleSubscribe = (target, options) => { target.subscribe(options[1], options[0], 'beforeChange'); @@ -773,7 +815,7 @@ class ComposePopupView extends AbstractViewNext { if (Array.isNotEmpty(emails)) { const value = fKoValue().trim(), values = emails.map(item => item ? item.toLine(false) : null) - .filter((value, index, self) => !!value && self.indexOf(value) == index); + .validUnique(); fKoValue(value + (value ? ', ' : '') + values.join(', ').trim()); } diff --git a/dev/View/Popup/ComposeOpenPgp.js b/dev/View/Popup/ComposeOpenPgp.js index 26168b2e2..77158bb04 100644 --- a/dev/View/Popup/ComposeOpenPgp.js +++ b/dev/View/Popup/ComposeOpenPgp.js @@ -40,7 +40,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext { this.encryptKeys = ko.observableArray([]); this.encryptKeysView = ko.computed( - () => this.encryptKeys().map(oKey => (oKey ? oKey.key : null)).filter(value => !!value) + () => this.encryptKeys().map(oKey => (oKey ? oKey.key : null)).filter(v => v) ); this.privateKeysOptions = ko.computed(() => { @@ -56,7 +56,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext { })); }); - return opts.flat().filter(value => !!value); + return opts.flat().filter(v => v); }); this.publicKeysOptions = ko.computed(() => { @@ -71,7 +71,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext { 'class': index % 2 ? 'odd' : 'even' })); }); - return opts.flat().filter(value => !!value); + return opts.flat().filter(v => v); }); this.submitRequest = ko.observable(false); @@ -156,7 +156,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext { this.encryptKeys().forEach(oKey => { if (oKey && oKey.key) { - aPublicKeys = aPublicKeys.concat(oKey.key.getNativeKeys().flat(Infinity).filter(value => !!value)); + aPublicKeys = aPublicKeys.concat(oKey.key.getNativeKeys().flat(Infinity).filter(v => v)); } else if (oKey && oKey.email) { this.notification( i18n('PGP_NOTIFICATIONS/NO_PUBLIC_KEYS_FOUND_FOR', { @@ -349,7 +349,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext { email.clear(); email.parse(value.trim()); return email.email || false; - }).filter(value => !!value); + }).filter(v => v); if (identity && identity.email()) { emailLine = identity.email(); @@ -385,9 +385,7 @@ class ComposeOpenPgpPopupView extends AbstractViewNext { 'key': publicKey })) : []; - }).flat().filter( - (encryptKey, index, self) => encryptKey => !!encryptKey.hash && self.indexOf(encryptKey) == index - ) + }).flat().validUnique(encryptKey => encryptKey.hash) ); if (this.encryptKeys().length) { diff --git a/dev/View/Popup/Contacts.js b/dev/View/Popup/Contacts.js index 3140adc3f..2e549aac2 100644 --- a/dev/View/Popup/Contacts.js +++ b/dev/View/Popup/Contacts.js @@ -13,7 +13,6 @@ import { import { delegateRunOnDestroy, computedPagenatorHelper, - fakeMd5, pInt } from 'Common/Utils'; @@ -154,7 +153,7 @@ class ContactsPopupView extends AbstractViewNext { selected = this.currentContact(); return selected - ? checked.concat([selected]).filter((value, index, self) => self.indexOf(value) == index) + ? checked.concat([selected]).unique() : checked; }); @@ -282,7 +281,7 @@ class ContactsPopupView extends AbstractViewNext { this.viewSaving(true); this.viewSaveTrigger(SaveSettingsStep.Animate); - const requestUid = fakeMd5(), + const requestUid = Jua.randomId(), properties = []; this.viewProperties().forEach(oItem => { @@ -579,7 +578,7 @@ class ContactsPopupView extends AbstractViewNext { return contact.parse(item) ? contact : null; }); - list = list.filter(value => !!value); + list = list.filter(v => v); count = pInt(data.Result.Count); count = 0 < count ? count : 0; diff --git a/dev/View/Popup/Identity.js b/dev/View/Popup/Identity.js index cee7d3083..ab98e11e2 100644 --- a/dev/View/Popup/Identity.js +++ b/dev/View/Popup/Identity.js @@ -1,7 +1,6 @@ import ko from 'ko'; import { StorageResultType, Notification } from 'Common/Enums'; -import { fakeMd5 } from 'Common/Utils'; import { getNotification } from 'Common/Translator'; import Remote from 'Remote/User/Fetch'; @@ -158,7 +157,7 @@ class IdentityPopupView extends AbstractViewNext { this.owner(!this.id); } else { - this.id = fakeMd5(); + this.id = Jua.randomId(); } } diff --git a/dev/View/Popup/ViewOpenPgpKey.js b/dev/View/Popup/ViewOpenPgpKey.js index 1d2ab94cb..aa3bf8230 100644 --- a/dev/View/Popup/ViewOpenPgpKey.js +++ b/dev/View/Popup/ViewOpenPgpKey.js @@ -1,7 +1,6 @@ import ko from 'ko'; import { KeyState } from 'Common/Enums'; -import { selectElement } from 'Common/Utils'; import { popup } from 'Knoin/Knoin'; import { AbstractViewNext } from 'Knoin/AbstractViewNext'; @@ -27,7 +26,11 @@ class ViewOpenPgpKeyPopupView extends AbstractViewNext { selectKey() { const el = this.keyDom(); if (el) { - selectElement(el); + let sel = getSelection(), + range = document.createRange(); + sel.removeAllRanges(); + range.selectNodeContents(el); + sel.addRange(range); } } diff --git a/dev/View/User/MailBox/MessageView.js b/dev/View/User/MailBox/MessageView.js index bafed6799..0a4d9095e 100644 --- a/dev/View/User/MailBox/MessageView.js +++ b/dev/View/User/MailBox/MessageView.js @@ -17,7 +17,6 @@ import { $htmlCL, leftPanelDisabled, keyScopeReal, moveAction } from 'Common/Glo import { inFocus, - removeSelection, mailToHelper, isTransparent } from 'Common/Utils'; @@ -336,7 +335,9 @@ class MessageViewMailBoxUserView extends AbstractViewNext { } toggleFullScreen() { - removeSelection(); + try { + getSelection().removeAllRanges(); + } catch (e) {} // eslint-disable-line no-empty this.fullScreenMode(!this.fullScreenMode()); } @@ -365,7 +366,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext { // var oEmailModel = new EmailModel(); // oEmailModel.parse(sItem); // return oEmailModel.email ? oEmailModel : null; - // }).filter(value => !!value) : null; + // }).filter(v => v) : null; // } // ; // @@ -395,7 +396,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext { }; } return null; - }).filter(value => !!value); + }).filter(v => v); if (items.length) { } @@ -712,7 +713,7 @@ class MessageViewMailBoxUserView extends AbstractViewNext { getAttachmentsHashes() { const atts = this.message() ? this.message().attachments() : []; - return atts.map(item => (item && !item.isLinked && item.checked() ? item.download : '')).filter(value => !!value); + return atts.map(item => (item && !item.isLinked && item.checked() ? item.download : '')).filter(v => v); } downloadAsZip() { diff --git a/dev/prototype.js b/dev/prototype.js index 0b2057486..e14f1fa7a 100644 --- a/dev/prototype.js +++ b/dev/prototype.js @@ -2,6 +2,9 @@ (w=>{ Array.isNotEmpty = array => Array.isArray(array) && array.length; Array.prototype.unique = function() { return this.filter((v, i, a) => a.indexOf(v) === i); }; + Array.prototype.validUnique = function(fn) { + return this.filter((v, i, a) => (fn ? fn(v) : v) && a.indexOf(v) === i); + }; // Import momentjs locales function w.moment = { @@ -45,7 +48,6 @@ } }, pad2 = v => 10 > v ? '0' + v : v, - pad3 = v => 10 > v ? '00' + v : (100 > v ? '0' + v : v), getISODay = x => x.getDay() || 7, getDayOfYear = x => Math.floor((Date.UTC(x.getFullYear(),x.getMonth(),x.getDate()) - Date.UTC(x.getFullYear(),0,1)) / 86400000), @@ -149,7 +151,7 @@ case 'H': return pad2(d.H); case 'i': return pad2(UTC?x.getUTCMinutes():x.getMinutes()); case 's': return pad2(UTC?x.getUTCSeconds():x.getSeconds()); - case 'u': return pad3(UTC?x.getUTCMilliseconds():x.getMilliseconds()); + case 'u': return (UTC?x.getUTCMilliseconds():x.getMilliseconds()).toString().padStart(3,'0'); // Timezone case 'I': return UTC ? 0 : isDST(x) ? 1 : 0; case 'O': return UTC ? 'Z' : (d.Z > 0 ? '+' : '-') + pad2(Math.abs(d.Z / 60)) + '00'; diff --git a/vendors/jua/jua.js b/vendors/jua/jua.js index 19e1f3b58..81caef030 100644 --- a/vendors/jua/jua.js +++ b/vendors/jua/jua.js @@ -488,13 +488,7 @@ */ addNewFile(oFileInfo) { - let iLen = 16, - fakeMd5 = ''; - - while (iLen--) - fakeMd5 += '0123456789abcdefghijklmnopqrstuvwxyz'.substr(Math.round(Math.random() * 36), 1); - - this.addFile('jua-uid-' + fakeMd5 + '-' + (Date.now().toString()), oFileInfo); + this.addFile('jua-uid-' + Jua.randomId(16) + '-' + (Date.now().toString()), oFileInfo); } /** @@ -516,6 +510,12 @@ } } + Jua.randomId = len => { + let arr = new Uint8Array((len || 32) / 2); + crypto.getRandomValues(arr); + return arr.map(dec => dec.toString(16).padStart(2,'0')).join(''); + } + /** * @type {number} */ diff --git a/vendors/jua/jua.min.js b/vendors/jua/jua.min.js index e8cfe4c54..71c9bc03b 100644 --- a/vendors/jua/jua.min.js +++ b/vendors/jua/jua.min.js @@ -1,2 +1,2 @@ /* RainLoop Webmail (c) RainLoop Team | MIT */ -(e=>{const t=20,n=e=>void 0!==e,r=(e,r,i,o)=>{if(e&&e.length){let a=i=n(i)?parseInt(i||0,10):t,s=null,u=0{e&&(!u||0<=--i?(s=l(e))&&r(s):u&&!d&&0>i&&o&&(d=!0,o(a)))})}},i=(e,t)=>Object.entries(t).forEach(([t,n])=>e.addEventListener(t,n)),l=e=>{let t=n(e.fileName)?e.fileName:n(e.name)?e.name:null,r=n(e.fileSize)?e.fileSize:n(e.size)?e.size:null,i=n(e.type)?e.type:null;return"/"===t.charAt(0)&&(t=t.substr(1)),i||0!==r?{FileName:t,Size:r,Type:i,Folder:"",File:e}:null},o=e=>{try{return e.dataTransfer.types.includes("Files")}catch(e){return!1}};class a{constructor(e,t){this.oXhrs={},this.oUids={},this.oJua=e,this.oOptions=Object.assign({action:"",name:"juaFile",hidden:{},disableMultiple:!1},t)}regTaskUid(e){this.oUids[e]=!0}uploadTask(e,t){if(!1===this.oUids[e]||!t||!t.File)return!1;try{const r=this,i=new XMLHttpRequest,l=new FormData,o=this.oOptions.action,a=this.oOptions.hidden,s=this.oJua.getEvent("onStart"),u=this.oJua.getEvent("onComplete"),d=this.oJua.getEvent("onProgress");return i.open("POST",o,!0),d&&i.upload&&(i.upload.onprogress=function(t){t&&t.lengthComputable&&n(t.loaded)&&n(t.total)&&d(e,t.loaded,t.total)}),i.onreadystatechange=function(){if(4===i.readyState&&200===i.status){if(u){let t=!1,n=null;try{n=JSON.parse(i.responseText),t=!0}catch(e){n=null}u(e,t,n)}n(r.oXhrs[e])&&(r.oXhrs[e]=null)}else 4===i.readyState&&u(e,!1,null)},s&&s(e),l.append("jua-post-type","ajax"),l.append(this.oOptions.name,t.File),Object.entries(a).forEach(([e,n])=>l.append(e,("function"==typeof n?n(t):n).toString())),i.send(l),this.oXhrs[e]=i,!0}catch(e){console.error(e)}return!1}generateNewInput(t){if(t){const n=this,i=e.createElement("input"),l=()=>i.click();i.type="file",i.tabIndex=-1,i.style.display="none",i.multiple=!n.oOptions.disableMultiple,t.addEventListener("click",l),i.addEventListener("input",()=>{const e=e=>{n.oJua.addNewFile(e),setTimeout(()=>{i.remove(),t.removeEventListener("click",l),n.generateNewInput(t)},10)};i.files&&i.files.length?r(i.files,e,n.oOptions.multipleSizeLimit,n.oJua.getEvent("onLimitReached")):e({FileName:i.value.split("\\").pop().split("/").pop(),Size:null,Type:null,Folder:"",File:null})})}}cancel(e){if(this.oUids[e]=!1,this.oXhrs[e]){try{this.oXhrs[e].abort&&this.oXhrs[e].abort()}catch(e){console.error(e)}this.oXhrs[e]=null}}}class s extends Array{constructor(e){super(),this.limit=parseInt(e||0,10)}push(e,...t){this.limit>this.length&&(super.push([e,t]),this.call())}call(){if(!this.running){let e;for(this.running=!0;e=this.shift();)e[0](...e[1]);this.running=!1}}}class u{constructor(n){const l=this;l.oEvents={onSelect:null,onStart:null,onComplete:null,onProgress:null,onDragEnter:null,onDragLeave:null,onBodyDragEnter:null,onBodyDragLeave:null,onLimitReached:null},n=Object.assign({queueSize:10,clickElement:null,dragAndDropElement:null,dragAndDropBodyElement:null,disableDocumentDropPrevent:!1,multipleSizeLimit:t},n||{}),l.oQueue=new s(n.queueSize),l.oDriver=new a(l,n);let u=n.clickElement;if(u&&(u.style.position="relative",u.style.overflow="hidden","inline"===u.style.display&&(u.style.display="inline-block"),l.oDriver.generateNewInput(u)),u=n.dragAndDropElement){let t=n.dragAndDropBodyElement||e;n.disableDocumentDropPrevent||e.addEventListener("dragover",e=>{if(o(e))try{e.dataTransfer.dropEffect="none",e.preventDefault()}catch(e){console.error(e)}}),t&&i(t,{dragover:()=>l.docTimer.clear(),dragenter:e=>{o(e)&&(l.docTimer.clear(),e.preventDefault(),l.runEvent("onBodyDragEnter",[e]))},dragleave:e=>e.dataTransfer&&l.docTimer.start(()=>l.runEvent("onBodyDragLeave",[e])),drop:e=>{if(e.dataTransfer){let t=o(e);return t&&e.preventDefault(),l.runEvent("onBodyDragLeave",[e]),!t}return!1}}),i(u,{dragenter:e=>{o(e)&&(l.docTimer.clear(),e.preventDefault(),l.runEvent("onDragEnter",[u,e]))},dragover:e=>{if(o(e))try{let t=e.dataTransfer.effectAllowed;l.docTimer.clear(),e.dataTransfer.dropEffect="move"===t||"linkMove"===t?"move":"copy",e.stopPropagation(),e.preventDefault()}catch(e){console.error(e)}},dragleave:t=>{if(t.dataTransfer){let n=e.elementFromPoint(t.clientX,t.clientY);n&&u.contains(n)||(l.docTimer.clear(),l.runEvent("onDragLeave",[u,t]))}},drop:e=>{o(e)&&(e.preventDefault(),r(e.files||e.dataTransfer.files,e=>{e&&(l.addNewFile(e),l.docTimer.clear())},n.multipleSizeLimit,l.getEvent("onLimitReached"))),l.runEvent("onDragLeave",[e])}})}}on(e,t){return this.oEvents[e]=t,this}runEvent(e,t){this.oEvents[e]&&this.oEvents[e].apply(null,t||[])}getEvent(e){return this.oEvents[e]||null}cancel(e){this.oDriver.cancel(e)}addNewFile(e){let t=16,n="";for(;t--;)n+="0123456789abcdefghijklmnopqrstuvwxyz".substr(Math.round(36*Math.random()),1);this.addFile("jua-uid-"+n+"-"+Date.now().toString(),e)}addFile(e,t){const n=this.getEvent("onSelect");!t||n&&!1===n(e,t)?this.oDriver.cancel(e):(this.oDriver.regTaskUid(e),this.oQueue.push((...e)=>this.oDriver.uploadTask(...e),e,t))}}u.prototype.docTimer={start:function(e){this.clear(),this.timer=setTimeout(e,200)},clear:function(){this.timer&&clearTimeout(this.timer),this.timer=0}},this.Jua=u})(document); +(e=>{const t=20,n=e=>void 0!==e,r=(e,r,i,o)=>{if(e&&e.length){let a=i=n(i)?parseInt(i||0,10):t,s=null,u=0{e&&(!u||0<=--i?(s=l(e))&&r(s):u&&!d&&0>i&&o&&(d=!0,o(a)))})}},i=(e,t)=>Object.entries(t).forEach(([t,n])=>e.addEventListener(t,n)),l=e=>{let t=n(e.fileName)?e.fileName:n(e.name)?e.name:null,r=n(e.fileSize)?e.fileSize:n(e.size)?e.size:null,i=n(e.type)?e.type:null;return"/"===t.charAt(0)&&(t=t.substr(1)),i||0!==r?{FileName:t,Size:r,Type:i,Folder:"",File:e}:null},o=e=>{try{return e.dataTransfer.types.includes("Files")}catch(e){return!1}};class a{constructor(e,t){this.oXhrs={},this.oUids={},this.oJua=e,this.oOptions=Object.assign({action:"",name:"juaFile",hidden:{},disableMultiple:!1},t)}regTaskUid(e){this.oUids[e]=!0}uploadTask(e,t){if(!1===this.oUids[e]||!t||!t.File)return!1;try{const r=this,i=new XMLHttpRequest,l=new FormData,o=this.oOptions.action,a=this.oOptions.hidden,s=this.oJua.getEvent("onStart"),u=this.oJua.getEvent("onComplete"),d=this.oJua.getEvent("onProgress");return i.open("POST",o,!0),d&&i.upload&&(i.upload.onprogress=function(t){t&&t.lengthComputable&&n(t.loaded)&&n(t.total)&&d(e,t.loaded,t.total)}),i.onreadystatechange=function(){if(4===i.readyState&&200===i.status){if(u){let t=!1,n=null;try{n=JSON.parse(i.responseText),t=!0}catch(e){n=null}u(e,t,n)}n(r.oXhrs[e])&&(r.oXhrs[e]=null)}else 4===i.readyState&&u(e,!1,null)},s&&s(e),l.append("jua-post-type","ajax"),l.append(this.oOptions.name,t.File),Object.entries(a).forEach(([e,n])=>l.append(e,("function"==typeof n?n(t):n).toString())),i.send(l),this.oXhrs[e]=i,!0}catch(e){console.error(e)}return!1}generateNewInput(t){if(t){const n=this,i=e.createElement("input"),l=()=>i.click();i.type="file",i.tabIndex=-1,i.style.display="none",i.multiple=!n.oOptions.disableMultiple,t.addEventListener("click",l),i.addEventListener("input",()=>{const e=e=>{n.oJua.addNewFile(e),setTimeout(()=>{i.remove(),t.removeEventListener("click",l),n.generateNewInput(t)},10)};i.files&&i.files.length?r(i.files,e,n.oOptions.multipleSizeLimit,n.oJua.getEvent("onLimitReached")):e({FileName:i.value.split("\\").pop().split("/").pop(),Size:null,Type:null,Folder:"",File:null})})}}cancel(e){if(this.oUids[e]=!1,this.oXhrs[e]){try{this.oXhrs[e].abort&&this.oXhrs[e].abort()}catch(e){console.error(e)}this.oXhrs[e]=null}}}class s extends Array{constructor(e){super(),this.limit=parseInt(e||0,10)}push(e,...t){this.limit>this.length&&(super.push([e,t]),this.call())}call(){if(!this.running){let e;for(this.running=!0;e=this.shift();)e[0](...e[1]);this.running=!1}}}class u{constructor(n){const l=this;l.oEvents={onSelect:null,onStart:null,onComplete:null,onProgress:null,onDragEnter:null,onDragLeave:null,onBodyDragEnter:null,onBodyDragLeave:null,onLimitReached:null},n=Object.assign({queueSize:10,clickElement:null,dragAndDropElement:null,dragAndDropBodyElement:null,disableDocumentDropPrevent:!1,multipleSizeLimit:t},n||{}),l.oQueue=new s(n.queueSize),l.oDriver=new a(l,n);let u=n.clickElement;if(u&&(u.style.position="relative",u.style.overflow="hidden","inline"===u.style.display&&(u.style.display="inline-block"),l.oDriver.generateNewInput(u)),u=n.dragAndDropElement){let t=n.dragAndDropBodyElement||e;n.disableDocumentDropPrevent||e.addEventListener("dragover",e=>{if(o(e))try{e.dataTransfer.dropEffect="none",e.preventDefault()}catch(e){console.error(e)}}),t&&i(t,{dragover:()=>l.docTimer.clear(),dragenter:e=>{o(e)&&(l.docTimer.clear(),e.preventDefault(),l.runEvent("onBodyDragEnter",[e]))},dragleave:e=>e.dataTransfer&&l.docTimer.start(()=>l.runEvent("onBodyDragLeave",[e])),drop:e=>{if(e.dataTransfer){let t=o(e);return t&&e.preventDefault(),l.runEvent("onBodyDragLeave",[e]),!t}return!1}}),i(u,{dragenter:e=>{o(e)&&(l.docTimer.clear(),e.preventDefault(),l.runEvent("onDragEnter",[u,e]))},dragover:e=>{if(o(e))try{let t=e.dataTransfer.effectAllowed;l.docTimer.clear(),e.dataTransfer.dropEffect="move"===t||"linkMove"===t?"move":"copy",e.stopPropagation(),e.preventDefault()}catch(e){console.error(e)}},dragleave:t=>{if(t.dataTransfer){let n=e.elementFromPoint(t.clientX,t.clientY);n&&u.contains(n)||(l.docTimer.clear(),l.runEvent("onDragLeave",[u,t]))}},drop:e=>{o(e)&&(e.preventDefault(),r(e.files||e.dataTransfer.files,e=>{e&&(l.addNewFile(e),l.docTimer.clear())},n.multipleSizeLimit,l.getEvent("onLimitReached"))),l.runEvent("onDragLeave",[e])}})}}on(e,t){return this.oEvents[e]=t,this}runEvent(e,t){this.oEvents[e]&&this.oEvents[e].apply(null,t||[])}getEvent(e){return this.oEvents[e]||null}cancel(e){this.oDriver.cancel(e)}addNewFile(e){this.addFile("jua-uid-"+u.randomId(16)+"-"+Date.now().toString(),e)}addFile(e,t){const n=this.getEvent("onSelect");!t||n&&!1===n(e,t)?this.oDriver.cancel(e):(this.oDriver.regTaskUid(e),this.oQueue.push((...e)=>this.oDriver.uploadTask(...e),e,t))}}u.randomId=(e=>{let t=new Uint8Array((e||32)/2);return crypto.getRandomValues(t),t.map(e=>e.toString(16).padStart(2,"0")).join("")}),u.prototype.docTimer={start:function(e){this.clear(),this.timer=setTimeout(e,200)},clear:function(){this.timer&&clearTimeout(this.timer),this.timer=0}},this.Jua=u})(document);