From aaf4933b0ac506acd5601af7c9a471e2e3bb0433 Mon Sep 17 00:00:00 2001 From: djmaze Date: Wed, 12 Aug 2020 01:47:24 +0200 Subject: [PATCH] Embed cleaner node modules --- README.md | 16 +- dev/Common/ClientStorageDriver/Cookie.js | 110 +++++++++- dev/Model/Email.js | 263 ++++++++++++++++++++++- dev/Model/Message.js | 7 +- package.json | 3 - yarn.lock | 4 - 6 files changed, 383 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 6d685df62..590b21e06 100644 --- a/README.md +++ b/README.md @@ -75,23 +75,23 @@ Things might work in Edge 15-18, Firefox 47-62 and Chrome 54-68 due to one polyf |js/* |1.14.0 |native |gzip 1.14 |gzip | |----------- |--------: |--------: |--------: |--------: | -|admin.js |2.130.942 |1.172.413 | 485.481 | 291.715 | -|app.js |4.184.455 |2.888.668 | 932.725 | 682.438 | -|boot.js | 671.522 | 44.394 | 169.502 | 15.460 | +|admin.js |2.130.942 |1.133.275 | 485.481 | 274.632 | +|app.js |4.184.455 |2.844.189 | 932.725 | 664.075 | +|boot.js | 671.522 | 44.029 | 169.502 | 15.460 | |libs.js | 647.614 | 431.049 | 194.728 | 132.315 | |polyfills.js | 325.834 | 0 | 71.825 | 0 | -|TOTAL js |7.960.367 |4.537.200 |1.854.261 |1.121.928 | +|TOTAL js |7.960.367 |4.452.542 |1.854.261 |1.086.482 | |js/min/* |1.14.0 |native |gzip 1.14 |gzip | |--------------- |--------: |--------: |--------: |--------: | -|admin.min.js | 252.147 | 153.689 | 73.657 | 44.000 | -|app.min.js | 511.202 | 380.294 |140.462 |100.236 | +|admin.min.js | 252.147 | 153.095 | 73.657 | 43.734 | +|app.min.js | 511.202 | 378.695 |140.462 | 99.511 | |boot.min.js | 66.007 | 5.589 | 22.567 | 2.333 | |libs.min.js | 572.545 | 387.636 |176.720 |122.519 | |polyfills.min.js | 32.452 | 0 | 11.312 | 0 | -|TOTAL js/min |1.434.353 | 927.208 |424.718 |269.088 | +|TOTAL js/min |1.434.353 | 925.015 |424.718 |268.097 | -507.145 bytes (155.630 gzip) is not much, but it feels faster. +509.338 bytes (156.621 gzip) is not much, but it feels faster. |css/* |1.14.0 |native | diff --git a/dev/Common/ClientStorageDriver/Cookie.js b/dev/Common/ClientStorageDriver/Cookie.js index f14be4dc7..a90702c33 100644 --- a/dev/Common/ClientStorageDriver/Cookie.js +++ b/dev/Common/ClientStorageDriver/Cookie.js @@ -1,6 +1,114 @@ -import Cookies from 'js-cookie'; import { CLIENT_SIDE_STORAGE_INDEX_NAME } from 'Common/Consts'; +var Cookies = (() => { + function extend (...args) { + var result = {}; + args.forEach(attributes => { + for (var key in attributes) { + result[key] = attributes[key]; + } + }); + return result; + } + + function decode (s) { + return s.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent); + } + + function set (key, value, attributes) { + if (typeof document === 'undefined') { + return; + } + + attributes = extend({ + path: '/' + }, attributes); + + if (typeof attributes.expires === 'number') { + attributes.expires = new Date(new Date() * 1 + attributes.expires * 864e+5); + } + + // We're using "expires" because "max-age" is not supported by IE + attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; + + try { + var result = JSON.stringify(value); + if (/^[{[]/.test(result)) { + value = result; + } + } catch (e) {} // eslint-disable-line no-empty + + value = encodeURIComponent(String(value)) + .replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent); + + key = encodeURIComponent(String(key)) + .replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent) + .replace(/[()]/g, escape); + + var stringifiedAttributes = ''; + for (var attributeName in attributes) { + if (!attributes[attributeName]) { + continue; + } + stringifiedAttributes += '; ' + attributeName; + if (attributes[attributeName] === true) { + continue; + } + + // Considers RFC 6265 section 5.2: + // ... + // 3. If the remaining unparsed-attributes contains a %x3B (";") + // character: + // Consume the characters of the unparsed-attributes up to, + // not including, the first %x3B (";") character. + // ... + stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; + } + + return (document.cookie = key + '=' + value + stringifiedAttributes); + } + + function get (key) { + if (typeof document === 'undefined') { + return; + } + + var jar = {}; + // To prevent the for loop in the first place assign an empty array + // in case there are no cookies at all. + var cookies = document.cookie ? document.cookie.split('; ') : []; + var i = 0; + + for (; i < cookies.length; i++) { + var parts = cookies[i].split('='); + var cookie = parts.slice(1).join('='); + + try { + var name = decode(parts[0]); + cookie = decode(cookie); + + try { + cookie = JSON.parse(cookie); + } catch (e) {} // eslint-disable-line no-empty + + jar[name] = cookie; + + if (key === name) { + break; + } + } catch (e) {} // eslint-disable-line no-empty + } + + return key ? jar[key] : jar; + } + + return { + set: set, + getJSON: key => get(key) + }; +})(); + + class CookieDriver { /** * @param {string} key diff --git a/dev/Model/Email.js b/dev/Model/Email.js index a0a0dedc8..147b29cc1 100644 --- a/dev/Model/Email.js +++ b/dev/Model/Email.js @@ -1,6 +1,267 @@ -import addressparser from 'emailjs-addressparser'; import { encodeHtml, isNonEmptyArray } from 'Common/Utils'; +'use strict'; + +/** + * Parses structured e-mail addresses from an address field + * + * Example: + * + * "Name " + * + * will be converted to + * + * [{name: "Name", address: "address@domain"}] + * + * @param {String} str Address field + * @return {Array} An array of address objects + */ +function addressparser(str) { + var tokenizer = new Tokenizer(str); + var tokens = tokenizer.tokenize(); + var addresses = []; + var address = []; + var parsedAddresses = []; + + tokens.forEach(token => { + if (token.type === 'operator' && (token.value === ',' || token.value === ';')) { + if (address.length) { + addresses.push(address); + } + address = []; + } else { + address.push(token); + } + }); + + if (address.length) { + addresses.push(address); + } + + addresses.forEach(address => { + address = _handleAddress(address); + if (address.length) { + parsedAddresses = parsedAddresses.concat(address); + } + }); + + return parsedAddresses; +} + +/** + * Converts tokens for a single address into an address object + * + * @param {Array} tokens Tokens object + * @return {Object} Address object + */ +function _handleAddress(tokens) { + var isGroup = false; + var state = 'text'; + var address = void 0; + var addresses = []; + var data = { + address: [], + comment: [], + group: [], + text: [] + + // Filter out , (comments) and regular text + }; + for (var i = 0, len = tokens.length; i < len; i++) { + var token = tokens[i]; + + if (token.type === 'operator') { + switch (token.value) { + case '<': + state = 'address'; + break; + case '(': + state = 'comment'; + break; + case ':': + state = 'group'; + isGroup = true; + break; + default: + state = 'text'; + } + } else { + if (token.value) { + data[state].push(token.value); + } + } + } + + // If there is no text but a comment, replace the two + if (!data.text.length && data.comment.length) { + data.text = data.comment; + data.comment = []; + } + + if (isGroup) { + // http://tools.ietf.org/html/rfc2822#appendix-A.1.3 + data.text = data.text.join(' '); + addresses.push({ + name: data.text || address && address.name, + group: data.group.length ? addressparser(data.group.join(',')) : [] + }); + } else { + // If no address was found, try to detect one from regular text + if (!data.address.length && data.text.length) { + for (var _i = data.text.length - 1; _i >= 0; _i--) { + if (data.text[_i].match(/^[^@\s]+@[^@\s]+$/)) { + data.address = data.text.splice(_i, 1); + break; + } + } + + var _regexHandler = function _regexHandler(address) { + if (!data.address.length) { + data.address = [address.trim()]; + return ' '; + } + return address; + }; + + // still no address + if (!data.address.length) { + for (var _i2 = data.text.length - 1; _i2 >= 0; _i2--) { + data.text[_i2] = data.text[_i2].replace(/\s*\b[^@\s]+@[^@\s]+\b\s*/, _regexHandler).trim(); + if (data.address.length) { + break; + } + } + } + } + + // If there's still is no text but a comment exixts, replace the two + if (!data.text.length && data.comment.length) { + data.text = data.comment; + data.comment = []; + } + + // Keep only the first address occurence, push others to regular text + if (data.address.length > 1) { + data.text = data.text.concat(data.address.splice(1)); + } + + // Join values with spaces + data.text = data.text.join(' '); + data.address = data.address.join(' '); + + if (!data.address && isGroup) { + return []; + } + address = { + address: data.address || data.text || '', + name: data.text || data.address || '' + }; + + if (address.address === address.name) { + if ((address.address || '').match(/@/)) { + address.name = ''; + } else { + address.address = ''; + } + } + + addresses.push(address); + } + + return addresses; +} + +/* + * Operator tokens and which tokens are expected to end the sequence + */ +var OPERATORS = { + '"': '"', + '(': ')', + '<': '>', + ',': '', + // Groups are ended by semicolons + ':': ';', + // Semicolons are not a legal delimiter per the RFC2822 grammar other + // than for terminating a group, but they are also not valid for any + // other use in this context. Given that some mail clients have + // historically allowed the semicolon as a delimiter equivalent to the + // comma in their UI, it makes sense to treat them the same as a comma + // when used outside of a group. + ';': '' +}; + +class Tokenizer +{ + constructor(str) { + this.str = (str || '').toString(); + this.operatorCurrent = ''; + this.operatorExpecting = ''; + this.node = null; + this.escaped = false; + this.list = []; + } + + tokenize() { + var list = [], i = this.str.length; + while (i--) this.checkChar(this.str[i]); + + this.list.forEach(node => { + node.value = (node.value || '').toString().trim(); + if (node.value) { + list.push(node); + } + }); + + return list; + } + + checkChar(chr) { + if ((chr in OPERATORS || chr === '\\') && this.escaped) { + this.escaped = false; + } else if (this.operatorExpecting && chr === this.operatorExpecting) { + this.node = { + type: 'operator', + value: chr + }; + this.list.push(this.node); + this.node = null; + this.operatorExpecting = ''; + this.escaped = false; + return; + } else if (!this.operatorExpecting && chr in OPERATORS) { + this.node = { + type: 'operator', + value: chr + }; + this.list.push(this.node); + this.node = null; + this.operatorExpecting = OPERATORS[chr]; + this.escaped = false; + return; + } + + if (!this.escaped && chr === '\\') { + this.escaped = true; + return; + } + + if (!this.node) { + this.node = { + type: 'text', + value: '' + }; + this.list.push(this.node); + } + + if (this.escaped && chr !== '\\') { + this.node.value += '\\'; + } + + this.node.value += chr; + this.escaped = false; + } +} + class EmailModel { email = ''; name = ''; diff --git a/dev/Model/Message.js b/dev/Model/Message.js index 68217189e..b61fedcf0 100644 --- a/dev/Model/Message.js +++ b/dev/Model/Message.js @@ -1,5 +1,4 @@ import ko from 'ko'; -import classnames from 'classnames'; import { MessagePriority, SignedVerifyStatus } from 'Common/Enums'; import { i18n } from 'Common/Translator'; @@ -456,7 +455,8 @@ class MessageModel extends AbstractModel { * @return string */ lineAsCss() { - return classnames({ + let classes = []; + Object.entries({ 'deleted': this.deleted(), 'deleted-mark': this.deletedMark(), 'selected': this.selected(), @@ -473,7 +473,8 @@ class MessageModel extends AbstractModel { // 'hasChildrenMessage': 1 < this.threadsLen(), 'hasUnseenSubMessage': this.hasUnseenSubMessage(), 'hasFlaggedSubMessage': this.hasFlaggedSubMessage() - }); + }).forEach(([key, value]) => value && classes.push(key)); + return classes.join(' '); } /** diff --git a/package.json b/package.json index 8d29446aa..e4d48e4ad 100644 --- a/package.json +++ b/package.json @@ -55,10 +55,8 @@ "autolinker": "3.13.0", "babel-eslint": "10.1.0", "babel-loader": "8.0.6", - "classnames": "2.2.6", "copy-webpack-plugin": "5.1.1", "css-loader": "3.4.2", - "emailjs-addressparser": "2.0.2", "eslint": "6.8.0", "eslint-config-prettier": "6.10.0", "eslint-plugin-prettier": "3.1.2", @@ -89,7 +87,6 @@ "gulp-zip": "5.0.1", "ifvisible.js": "1.0.6", "jquery": "^3.5.1", - "js-cookie": "2.2.1", "knockout": "3.4.2", "knockout-sortable": "1.2.0", "moment": "2.24.0", diff --git a/yarn.lock b/yarn.lock index baa60c0d1..8344847bc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1856,10 +1856,6 @@ class-utils@^0.3.5: isobject "^3.0.0" static-extend "^0.1.1" -classnames@2.2.6: - version "2.2.6" - resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce" - clean-css@4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/clean-css/-/clean-css-4.2.3.tgz#507b5de7d97b48ee53d84adb0160ff6216380f78"