From 1f09c03d4897206e7ed4a3d90cac6c577c486aed Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Fri, 15 Feb 2019 14:23:16 +0000 Subject: [PATCH 001/228] Add De Bruijn Operation --- src/core/config/Categories.json | 1 + .../operations/GenerateDeBruijnSequence.mjs | 87 +++++++++++++++++++ tests/operations/index.mjs | 1 + .../tests/GenerateDeBruijnSequence.mjs | 33 +++++++ 4 files changed, 122 insertions(+) create mode 100644 src/core/operations/GenerateDeBruijnSequence.mjs create mode 100644 tests/operations/tests/GenerateDeBruijnSequence.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8235ab10..238c7282 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -370,6 +370,7 @@ "Chi Square", "Disassemble x86", "Pseudo-Random Number Generator", + "Generate De Bruijn Sequence", "Generate UUID", "Generate TOTP", "Generate HOTP", diff --git a/src/core/operations/GenerateDeBruijnSequence.mjs b/src/core/operations/GenerateDeBruijnSequence.mjs new file mode 100644 index 00000000..647d3c7f --- /dev/null +++ b/src/core/operations/GenerateDeBruijnSequence.mjs @@ -0,0 +1,87 @@ +/** + * @author gchq77703 [gchq77703@gchq.gov.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Generate De Bruijn Sequence operation + */ +class GenerateDeBruijnSequence extends Operation { + + /** + * GenerateDeBruijnSequence constructor + */ + constructor() { + super(); + + this.name = "Generate De Bruijn Sequence"; + this.module = "Default"; + this.description = "Generates rolling keycode combinations given a certain alphabet size and key length."; + this.infoURL = "https://wikipedia.org/wiki/De_Bruijn_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet size (k)", + type: "number", + value: 2 + }, + { + name: "Key length (n)", + type: "number", + value: 3 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [k, n] = args; + + if (k < 2 || k > 9) { + throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive)."); + } + + if (n < 2) { + throw new OperationError("Invalid key length, required to be at least 2."); + } + + if (Math.pow(k, n) > 50000) { + throw new OperationError("Too many permutations, please reduce k^n to under 50,000."); + } + + const a = []; + for (let i = 0; i < k * n; i++) a.push(0); + + const sequence = []; + + (function db(t = 1, p = 1) { + if (t > n) { + if (n % p !== 0) return; + for (let j = 1; j <= p; j++) { + sequence.push(a[j]); + } + return; + } + + a[t] = a[t - p]; + db(t + 1, p); + for (let j = a[t - p] + 1; j < k; j++) { + a[t] = j; + db(t + 1, t); + } + })(); + + return sequence.join(""); + } +} + +export default GenerateDeBruijnSequence; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index fb68ed9c..316e934c 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -45,6 +45,7 @@ import "./tests/DateTime"; import "./tests/ExtractEmailAddresses"; import "./tests/Fork"; import "./tests/FromDecimal"; +import "./tests/GenerateDeBruijnSequence"; import "./tests/Hash"; import "./tests/HaversineDistance"; import "./tests/Hexdump"; diff --git a/tests/operations/tests/GenerateDeBruijnSequence.mjs b/tests/operations/tests/GenerateDeBruijnSequence.mjs new file mode 100644 index 00000000..b68a843f --- /dev/null +++ b/tests/operations/tests/GenerateDeBruijnSequence.mjs @@ -0,0 +1,33 @@ +/** + * De Brujin Sequence tests. + * + * @author gchq77703 [gchq77703@gchq.gov.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +import TestRegister from "../TestRegister"; + +TestRegister.addTests([ + { + name: "Small Sequence", + input: "", + expectedOutput: "00010111", + recipeConfig: [ + { + "op": "Generate De Bruijn Sequence", + "args": [2, 3] + } + ] + }, + { + name: "Long Sequence", + input: "", + expectedOutput: "0000010000200003000110001200013000210002200023000310003200033001010010200103001110011200113001210012200123001310013200133002010020200203002110021200213002210022200223002310023200233003010030200303003110031200313003210032200323003310033200333010110101201013010210102201023010310103201033011020110301111011120111301121011220112301131011320113301202012030121101212012130122101222012230123101232012330130201303013110131201313013210132201323013310133201333020210202202023020310203202033021030211102112021130212102122021230213102132021330220302211022120221302221022220222302231022320223302303023110231202313023210232202323023310233202333030310303203033031110311203113031210312203123031310313203133032110321203213032210322203223032310323203233033110331203313033210332203323033310333203333111112111131112211123111321113311212112131122211223112321123311312113131132211323113321133312122121231213212133122131222212223122321223312313123221232312332123331313213133132221322313232132331332213323133321333322222322233223232233323233233333", + recipeConfig: [ + { + "op": "Generate De Bruijn Sequence", + "args": [4, 5] + } + ] + } +]) \ No newline at end of file From 44a164ed2825ddd799b656459b89b1a4ee5a9f0a Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Tue, 19 Feb 2019 09:56:38 +0000 Subject: [PATCH 002/228] Fix test script linter --- tests/operations/tests/GenerateDeBruijnSequence.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/operations/tests/GenerateDeBruijnSequence.mjs b/tests/operations/tests/GenerateDeBruijnSequence.mjs index b68a843f..48e8c4ff 100644 --- a/tests/operations/tests/GenerateDeBruijnSequence.mjs +++ b/tests/operations/tests/GenerateDeBruijnSequence.mjs @@ -30,4 +30,4 @@ TestRegister.addTests([ } ] } -]) \ No newline at end of file +]); From 822a4fab86572817fcd2e6218d8c736d1e22bbf4 Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Tue, 19 Feb 2019 10:16:51 +0000 Subject: [PATCH 003/228] Fix operation linting --- src/core/operations/GenerateDeBruijnSequence.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/GenerateDeBruijnSequence.mjs b/src/core/operations/GenerateDeBruijnSequence.mjs index 647d3c7f..af788585 100644 --- a/src/core/operations/GenerateDeBruijnSequence.mjs +++ b/src/core/operations/GenerateDeBruijnSequence.mjs @@ -71,7 +71,7 @@ class GenerateDeBruijnSequence extends Operation { } return; } - + a[t] = a[t - p]; db(t + 1, p); for (let j = a[t - p] + 1; j < k; j++) { From 846e84d3a471513287f25d1e4071dbc5e970e272 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karsten=20Silkenb=C3=A4umer?= Date: Sun, 3 Mar 2019 16:18:31 +0100 Subject: [PATCH 004/228] Add fernet encryption/decryption operation --- package-lock.json | 161 +++++++++++++++----------- package.json | 1 + src/core/config/Categories.json | 2 + src/core/operations/FernetDecrypt.mjs | 64 ++++++++++ src/core/operations/FernetEncrypt.mjs | 54 +++++++++ tests/operations/tests/Fernet.mjs | 80 +++++++++++++ 6 files changed, 292 insertions(+), 70 deletions(-) create mode 100644 src/core/operations/FernetDecrypt.mjs create mode 100644 src/core/operations/FernetEncrypt.mjs create mode 100644 tests/operations/tests/Fernet.mjs diff --git a/package-lock.json b/package-lock.json index 55ad6303..18da5b7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1631,7 +1631,7 @@ }, "array-equal": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, @@ -1716,7 +1716,7 @@ }, "util": { "version": "0.10.3", - "resolved": "http://registry.npmjs.org/util/-/util-0.10.3.tgz", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", "dev": true, "requires": { @@ -1864,7 +1864,7 @@ }, "axios": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/axios/-/axios-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.0.tgz", "integrity": "sha1-MtU+SFHv3AoRmTts0AB4nXDAUQI=", "dev": true, "requires": { @@ -2334,7 +2334,7 @@ }, "browserify-aes": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, "requires": { @@ -2371,7 +2371,7 @@ }, "browserify-rsa": { "version": "4.0.1", - "resolved": "http://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", "dev": true, "requires": { @@ -2436,7 +2436,7 @@ }, "buffer": { "version": "4.9.1", - "resolved": "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "integrity": "sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg=", "dev": true, "requires": { @@ -2590,7 +2590,7 @@ }, "camelcase-keys": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "dev": true, "requires": { @@ -2639,7 +2639,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", @@ -3172,7 +3172,7 @@ }, "create-hash": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, "requires": { @@ -3185,7 +3185,7 @@ }, "create-hmac": { "version": "1.1.7", - "resolved": "http://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, "requires": { @@ -3332,7 +3332,7 @@ }, "css-select": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", "dev": true, "requires": { @@ -3700,7 +3700,7 @@ }, "diffie-hellman": { "version": "5.0.3", - "resolved": "http://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "dev": true, "requires": { @@ -3764,7 +3764,7 @@ "dependencies": { "domelementtype": { "version": "1.1.3", - "resolved": "http://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.1.3.tgz", "integrity": "sha1-vSh3PiZCiBrsUVRJJCmcXNgiGFs=", "dev": true }, @@ -3969,7 +3969,7 @@ }, "entities": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/entities/-/entities-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.0.0.tgz", "integrity": "sha1-sph6o4ITR/zeZCsk/fyeT7cSvyY=", "dev": true }, @@ -4392,7 +4392,7 @@ }, "eventemitter2": { "version": "0.4.14", - "resolved": "http://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", + "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-0.4.14.tgz", "integrity": "sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas=", "dev": true }, @@ -4404,7 +4404,7 @@ }, "events": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/events/-/events-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", "dev": true }, @@ -4720,6 +4720,22 @@ "pend": "~1.2.0" } }, + "fernet": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/fernet/-/fernet-0.3.1.tgz", + "integrity": "sha512-7KnsrcpLkUsKy6aH6Ow68hrMWhvE25rTDd3370+xVGkpqZta05cUCmdJQPyLBKTsNdPUB5NumJZBgJIJ60aQqw==", + "requires": { + "crypto-js": "~3.1.2-1", + "urlsafe-base64": "1.0.0" + }, + "dependencies": { + "crypto-js": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.1.8.tgz", + "integrity": "sha1-cV8HC/YBTyrpkqmLOSkli3E/CNU=" + } + } + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -4821,7 +4837,7 @@ }, "finalhandler": { "version": "1.1.1", - "resolved": "http://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", "dev": true, "requires": { @@ -5057,7 +5073,7 @@ }, "fs-extra": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-1.0.0.tgz", "integrity": "sha1-zTzl9+fLYUWIP8rjGR6Yd/hYeVA=", "dev": true, "requires": { @@ -5726,7 +5742,7 @@ }, "get-stream": { "version": "3.0.0", - "resolved": "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", "dev": true }, @@ -5868,7 +5884,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -5945,7 +5961,7 @@ }, "grunt-cli": { "version": "1.2.0", - "resolved": "http://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/grunt-cli/-/grunt-cli-1.2.0.tgz", "integrity": "sha1-VisRnrsGndtGSs4oRVAb6Xs1tqg=", "dev": true, "requires": { @@ -5993,7 +6009,7 @@ "dependencies": { "shelljs": { "version": "0.5.3", - "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.5.3.tgz", "integrity": "sha1-xUmCuZbHbvDB5rWfvcWCX1txMRM=", "dev": true } @@ -6013,7 +6029,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -6058,7 +6074,7 @@ }, "grunt-contrib-jshint": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/grunt-contrib-jshint/-/grunt-contrib-jshint-1.1.0.tgz", "integrity": "sha1-Np2QmyWTxA6L55lAshNAhQx5Oaw=", "dev": true, "requires": { @@ -6157,7 +6173,7 @@ "dependencies": { "colors": { "version": "1.1.2", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true } @@ -6221,7 +6237,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -6482,7 +6498,7 @@ }, "html-webpack-plugin": { "version": "3.2.0", - "resolved": "http://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", + "resolved": "https://registry.npmjs.org/html-webpack-plugin/-/html-webpack-plugin-3.2.0.tgz", "integrity": "sha1-sBq71yOsqqeze2r0SS69oD2d03s=", "dev": true, "requires": { @@ -6538,7 +6554,7 @@ }, "htmlparser2": { "version": "3.8.3", - "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.8.3.tgz", "integrity": "sha1-mWwosZFRaovoZQGn15dX5ccMEGg=", "dev": true, "requires": { @@ -6557,7 +6573,7 @@ }, "http-errors": { "version": "1.6.3", - "resolved": "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { @@ -6607,7 +6623,7 @@ }, "http-proxy-middleware": { "version": "0.18.0", - "resolved": "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", + "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz", "integrity": "sha512-Fs25KVMPAIIcgjMZkVHJoKg9VcXcC1C8yb9JUgeDvVXY0S/zgVIhMb+qVswDIgtJe2DfckMSY2d6TuTEutlk6Q==", "dev": true, "requires": { @@ -7053,7 +7069,7 @@ }, "is-builtin-module": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", "dev": true, "requires": { @@ -7614,7 +7630,7 @@ }, "jsonfile": { "version": "2.4.0", - "resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", "dev": true, "requires": { @@ -7725,7 +7741,7 @@ }, "kew": { "version": "0.7.0", - "resolved": "http://registry.npmjs.org/kew/-/kew-0.7.0.tgz", + "resolved": "https://registry.npmjs.org/kew/-/kew-0.7.0.tgz", "integrity": "sha1-edk9LTM2PW/dKXCzNdkUGtWR15s=", "dev": true }, @@ -7844,7 +7860,7 @@ }, "load-json-file": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "dev": true, "requires": { @@ -7857,7 +7873,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -8221,7 +8237,7 @@ }, "media-typer": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", "dev": true }, @@ -8280,7 +8296,7 @@ }, "meow": { "version": "3.7.0", - "resolved": "http://registry.npmjs.org/meow/-/meow-3.7.0.tgz", + "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "dev": true, "requires": { @@ -8501,7 +8517,7 @@ }, "mkdirp": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" @@ -8711,7 +8727,7 @@ }, "ncp": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", "dev": true }, @@ -8810,7 +8826,7 @@ "dependencies": { "semver": { "version": "5.3.0", - "resolved": "http://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", "dev": true } @@ -8993,7 +9009,7 @@ "dependencies": { "colors": { "version": "0.5.1", - "resolved": "http://registry.npmjs.org/colors/-/colors-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" }, "underscore": { @@ -9287,13 +9303,13 @@ }, "os-homedir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true }, "os-locale": { "version": "1.4.0", - "resolved": "http://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", "dev": true, "requires": { @@ -9302,7 +9318,7 @@ }, "os-tmpdir": { "version": "1.0.2", - "resolved": "http://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true }, @@ -9338,7 +9354,7 @@ }, "p-is-promise": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-1.1.0.tgz", "integrity": "sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=", "dev": true }, @@ -9526,7 +9542,7 @@ }, "parse-asn1": { "version": "5.1.1", - "resolved": "http://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", "dev": true, "requires": { @@ -9612,7 +9628,7 @@ }, "path-is-absolute": { "version": "1.0.1", - "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true }, @@ -9653,7 +9669,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -9836,7 +9852,7 @@ "dependencies": { "async": { "version": "1.5.2", - "resolved": "http://registry.npmjs.org/async/-/async-1.5.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", "dev": true } @@ -10207,7 +10223,7 @@ }, "progress": { "version": "1.1.8", - "resolved": "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=" }, "promise-inflight": { @@ -10232,13 +10248,13 @@ "dependencies": { "async": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", "dev": true }, "winston": { "version": "2.1.1", - "resolved": "http://registry.npmjs.org/winston/-/winston-2.1.1.tgz", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", "dev": true, "requires": { @@ -10253,7 +10269,7 @@ "dependencies": { "colors": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true }, @@ -10476,7 +10492,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -10665,7 +10681,7 @@ "dependencies": { "jsesc": { "version": "0.5.0", - "resolved": "http://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", "dev": true } @@ -10716,7 +10732,7 @@ }, "htmlparser2": { "version": "3.3.0", - "resolved": "http://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.3.0.tgz", "integrity": "sha1-zHDQWln2VC5D8OaFyYLhTJJKnv4=", "dev": true, "requires": { @@ -10728,7 +10744,7 @@ }, "readable-stream": { "version": "1.0.34", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { @@ -10995,7 +11011,7 @@ }, "safe-regex": { "version": "1.1.0", - "resolved": "http://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { @@ -11315,7 +11331,7 @@ }, "sha.js": { "version": "2.4.11", - "resolved": "http://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", "dev": true, "requires": { @@ -11359,7 +11375,7 @@ }, "shelljs": { "version": "0.3.0", - "resolved": "http://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.3.0.tgz", "integrity": "sha1-NZbmMHp4FUT1kfN9phg2DzHbV7E=", "dev": true }, @@ -12080,7 +12096,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" @@ -12097,7 +12113,7 @@ }, "strip-eof": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, @@ -12190,7 +12206,7 @@ }, "tar": { "version": "2.2.1", - "resolved": "http://registry.npmjs.org/tar/-/tar-2.2.1.tgz", + "resolved": "https://registry.npmjs.org/tar/-/tar-2.2.1.tgz", "integrity": "sha1-jk0qJWwOIYXGsYrWlK7JaLg8sdE=", "dev": true, "requires": { @@ -12348,7 +12364,7 @@ }, "through": { "version": "2.3.8", - "resolved": "http://registry.npmjs.org/through/-/through-2.3.8.tgz", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -12942,6 +12958,11 @@ "requires-port": "^1.0.0" } }, + "urlsafe-base64": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", + "integrity": "sha1-I/iQaabGL0bPOh07ABac77kL4MY=" + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -13008,7 +13029,7 @@ "dependencies": { "async": { "version": "0.9.2", - "resolved": "http://registry.npmjs.org/async/-/async-0.9.2.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", "dev": true }, @@ -13034,7 +13055,7 @@ }, "valid-data-url": { "version": "0.1.6", - "resolved": "http://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", + "resolved": "https://registry.npmjs.org/valid-data-url/-/valid-data-url-0.1.6.tgz", "integrity": "sha512-FXg2qXMzfAhZc0y2HzELNfUeiOjPr+52hU1DNBWiJJ2luXD+dD1R9NA48Ug5aj0ibbxroeGDc/RJv6ThiGgkDw==", "dev": true }, @@ -13050,7 +13071,7 @@ }, "validator": { "version": "9.4.1", - "resolved": "http://registry.npmjs.org/validator/-/validator-9.4.1.tgz", + "resolved": "https://registry.npmjs.org/validator/-/validator-9.4.1.tgz", "integrity": "sha512-YV5KjzvRmSyJ1ee/Dm5UED0G+1L4GZnLN3w6/T+zZm8scVua4sOhYKWTUrKa0H/tMiJyO9QLHMPN+9mB/aMunA==", "dev": true }, @@ -13736,14 +13757,14 @@ "dependencies": { "async": { "version": "1.0.0", - "resolved": "http://registry.npmjs.org/async/-/async-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", "dev": true, "optional": true }, "colors": { "version": "1.0.3", - "resolved": "http://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", "dev": true, "optional": true @@ -13776,7 +13797,7 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { diff --git a/package.json b/package.json index cb59db38..35901453 100644 --- a/package.json +++ b/package.json @@ -96,6 +96,7 @@ "esmangle": "^1.0.1", "esprima": "^4.0.1", "exif-parser": "^0.1.12", + "fernet": "^0.3.1", "file-saver": "^2.0.0", "geodesy": "^1.1.3", "highlight.js": "^9.13.1", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8235ab10..2db5af51 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -73,6 +73,8 @@ "DES Decrypt", "Triple DES Encrypt", "Triple DES Decrypt", + "Fernet Encrypt", + "Fernet Decrypt", "RC2 Encrypt", "RC2 Decrypt", "RC4", diff --git a/src/core/operations/FernetDecrypt.mjs b/src/core/operations/FernetDecrypt.mjs new file mode 100644 index 00000000..76d4fd16 --- /dev/null +++ b/src/core/operations/FernetDecrypt.mjs @@ -0,0 +1,64 @@ +/** + * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import fernet from "fernet"; + +/** + * FernetDecrypt operation + */ +class FernetDecrypt extends Operation { + /** + * FernetDecrypt constructor + */ + constructor() { + super(); + + this.name = "Fernet Decrypt"; + this.module = "Default"; + this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().

Key: The key must be 32 bytes (256 bits) encoded with Base64."; + this.infoURL = "https://asecuritysite.com/encryption/fer"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + }, + ]; + this.patterns = [ + { + match: "^[A-Z\\d\\-_=]{20,}$", + flags: "i", + args: [] + }, + ]; + } + /** + * @param {String} input + * @param {Object[]} args + * @returns {String} + */ + run(input, args) { + const [secretInput] = args; + // const fernet = require("fernet"); + try { + const secret = new fernet.Secret(secretInput); + const token = new fernet.Token({ + secret: secret, + token: input, + ttl: 0 + }); + return token.decode(); + } catch (err) { + throw new OperationError(err); + } + } +} + +export default FernetDecrypt; diff --git a/src/core/operations/FernetEncrypt.mjs b/src/core/operations/FernetEncrypt.mjs new file mode 100644 index 00000000..ac8c64cb --- /dev/null +++ b/src/core/operations/FernetEncrypt.mjs @@ -0,0 +1,54 @@ +/** + * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; +import fernet from "fernet"; + +/** + * FernetEncrypt operation + */ +class FernetEncrypt extends Operation { + /** + * FernetEncrypt constructor + */ + constructor() { + super(); + + this.name = "Fernet Encrypt"; + this.module = "Default"; + this.description = "Fernet is a symmetric encryption method which makes sure that the message encrypted cannot be manipulated/read without the key. It uses URL safe encoding for the keys. Fernet uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256 for authentication. The IV is created from os.random().

Key: The key must be 32 bytes (256 bits) encoded with Base64."; + this.infoURL = "https://asecuritysite.com/encryption/fer"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "" + }, + ]; + } + /** + * @param {String} input + * @param {Object[]} args + * @returns {String} + */ + run(input, args) { + const [secretInput] = args; + try { + const secret = new fernet.Secret(secretInput); + const token = new fernet.Token({ + secret: secret, + }); + return token.encode(input); + } catch (err) { + throw new OperationError(err); + } + } +} + +export default FernetEncrypt; diff --git a/tests/operations/tests/Fernet.mjs b/tests/operations/tests/Fernet.mjs new file mode 100644 index 00000000..0632fca9 --- /dev/null +++ b/tests/operations/tests/Fernet.mjs @@ -0,0 +1,80 @@ +/** + * Fernet tests. + * + * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @copyright Karsten Silkenbäumer 2019 + * @license Apache-2.0 + */ +import TestRegister from "../TestRegister"; + +TestRegister.addTests([ + { + name: "Fernet Decrypt: no input", + input: "", + expectedOutput: "Error: Invalid version", + recipeConfig: [ + { + op: "Fernet Decrypt", + args: ["MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="] + } + ], + }, + { + name: "Fernet Decrypt: no secret", + input: "gAAAAABce-Tycae8klRxhDX2uenJ-uwV8-A1XZ2HRnfOXlNzkKKfRxviNLlgtemhT_fd1Fw5P_zFUAjd69zaJBQyWppAxVV00SExe77ql8c5n62HYJOnoIU=", + expectedOutput: "Error: Secret must be 32 url-safe base64-encoded bytes.", + recipeConfig: [ + { + op: "Fernet Decrypt", + args: [""] + } + ], + }, + { + name: "Fernet Decrypt: valid arguments", + input: "gAAAAABce-Tycae8klRxhDX2uenJ-uwV8-A1XZ2HRnfOXlNzkKKfRxviNLlgtemhT_fd1Fw5P_zFUAjd69zaJBQyWppAxVV00SExe77ql8c5n62HYJOnoIU=", + expectedOutput: "This is a secret message.\n", + recipeConfig: [ + { + op: "Fernet Decrypt", + args: ["VGhpc0lzVGhpcnR5VHdvQ2hhcmFjdGVyc0xvbmdLZXk="] + } + ], + } +]); + +TestRegister.addTests([ + { + name: "Fernet Encrypt: no input", + input: "", + expectedMatch: /^gAAAAABce-[\w-]+={0,2}$/, + recipeConfig: [ + { + op: "Fernet Encrypt", + args: ["MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="] + } + ], + }, + { + name: "Fernet Encrypt: no secret", + input: "This is a secret message.\n", + expectedOutput: "Error: Secret must be 32 url-safe base64-encoded bytes.", + recipeConfig: [ + { + op: "Fernet Encrypt", + args: [""] + } + ], + }, + { + name: "Fernet Encrypt: valid arguments", + input: "This is a secret message.\n", + expectedMatch: /^gAAAAABce-[\w-]+={0,2}$/, + recipeConfig: [ + { + op: "Fernet Encrypt", + args: ["MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTI="] + } + ], + } +]); From 55cac174564cf71da857f6aee0941e06635d445d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Karsten=20Silkenb=C3=A4umer?= Date: Sun, 3 Mar 2019 17:19:07 +0100 Subject: [PATCH 005/228] Change author URL --- src/core/operations/FernetDecrypt.mjs | 2 +- src/core/operations/FernetEncrypt.mjs | 2 +- tests/operations/tests/Fernet.mjs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/operations/FernetDecrypt.mjs b/src/core/operations/FernetDecrypt.mjs index 76d4fd16..d68593d8 100644 --- a/src/core/operations/FernetDecrypt.mjs +++ b/src/core/operations/FernetDecrypt.mjs @@ -1,5 +1,5 @@ /** - * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ diff --git a/src/core/operations/FernetEncrypt.mjs b/src/core/operations/FernetEncrypt.mjs index ac8c64cb..2f98449f 100644 --- a/src/core/operations/FernetEncrypt.mjs +++ b/src/core/operations/FernetEncrypt.mjs @@ -1,5 +1,5 @@ /** - * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ diff --git a/tests/operations/tests/Fernet.mjs b/tests/operations/tests/Fernet.mjs index 0632fca9..ee9ba2f1 100644 --- a/tests/operations/tests/Fernet.mjs +++ b/tests/operations/tests/Fernet.mjs @@ -1,7 +1,7 @@ /** * Fernet tests. * - * @author Karsten Silkenbäumer [kassi@users.noreply.github.com] + * @author Karsten Silkenbäumer [github.com/kassi] * @copyright Karsten Silkenbäumer 2019 * @license Apache-2.0 */ From be2080259ec9ae7d64945fc5640188ec4b773ba6 Mon Sep 17 00:00:00 2001 From: Kyle Parrish Date: Wed, 2 Oct 2019 09:57:50 -0400 Subject: [PATCH 006/228] Add Fang URL to categories --- src/core/config/Categories.json | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 94f7fd30..18fc19ff 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -183,6 +183,7 @@ "Encode NetBIOS Name", "Decode NetBIOS Name", "Defang URL", + "Fang URL", "Defang IP Addresses" ] }, From cd15a8c406726bf06d55b879d271ac3f79b3ba99 Mon Sep 17 00:00:00 2001 From: Kyle Parrish Date: Wed, 2 Oct 2019 09:58:28 -0400 Subject: [PATCH 007/228] Create FangURL.mjs --- src/core/operations/FangURL.mjs | 77 +++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 src/core/operations/FangURL.mjs diff --git a/src/core/operations/FangURL.mjs b/src/core/operations/FangURL.mjs new file mode 100644 index 00000000..5badaae7 --- /dev/null +++ b/src/core/operations/FangURL.mjs @@ -0,0 +1,77 @@ +/** + * @author arnydo [github@arnydo.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * FangURL operation + */ +class FangURL extends Operation { + + /** + * FangURL constructor + */ + constructor() { + super(); + + this.name = "Fang URL"; + this.module = "Default"; + this.description = "Takes a 'Defanged' Universal Resource Locator (URL) and 'Fangs' it. Meaning, it removes the alterations (defanged) that render it useless so that it can be used again."; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Escape [.]", + type: "boolean", + value: true + }, + { + name: "Escape hxxp", + type: "boolean", + value: true + }, + { + name: "Escape ://", + type: "boolean", + value: true + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [dots, http, slashes] = args; + + input = fangURL(input, dots, http, slashes); + + return input; + } + +} + + +/** + * Defangs a given URL + * + * @param {string} url + * @param {boolean} dots + * @param {boolean} http + * @param {boolean} slashes + * @returns {string} + */ +function fangURL(url, dots, http, slashes) { + if (dots) url = url.replace(/\[\.\]/g, "."); + if (http) url = url.replace(/hxxp/g, "http"); + if (slashes) url = url.replace(/\[\:\/\/\]/g, "://"); + + return url; +} + +export default FangURL; From 794e0effba5ed4193265ddc6429ba55f6dac33d4 Mon Sep 17 00:00:00 2001 From: Alan C Date: Mon, 7 Oct 2019 20:02:28 +0800 Subject: [PATCH 008/228] Add "To Float" and "From Float" operations --- package-lock.json | 6 +- package.json | 1 + src/core/config/Categories.json | 2 + src/core/operations/FromFloat.mjs | 78 ++++++++++++++ src/core/operations/ToFloat.mjs | 80 +++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/Float.mjs | 164 ++++++++++++++++++++++++++++++ 7 files changed, 329 insertions(+), 3 deletions(-) create mode 100644 src/core/operations/FromFloat.mjs create mode 100644 src/core/operations/ToFloat.mjs create mode 100644 tests/operations/tests/Float.mjs diff --git a/package-lock.json b/package-lock.json index 11c80ca0..930dfc40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7657,9 +7657,9 @@ "integrity": "sha512-slx8Q6oywCCSfKgPgL0sEsXtPVnSbTLWpyiDcu6msHOyKOLari1TD1qocXVCft80umnkk3/Qqh3lwoFt8T/BPQ==" }, "ieee754": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.12.tgz", - "integrity": "sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA==" + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" }, "iferr": { "version": "0.1.5", diff --git a/package.json b/package.json index e9c33484..1283f545 100644 --- a/package.json +++ b/package.json @@ -112,6 +112,7 @@ "file-saver": "^2.0.2", "geodesy": "^1.1.3", "highlight.js": "^9.15.10", + "ieee754": "^1.1.13", "jimp": "^0.6.4", "jquery": "3.4.1", "js-crc": "^0.2.0", diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 94f7fd30..939aa22e 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -14,6 +14,8 @@ "From Charcode", "To Decimal", "From Decimal", + "To Float", + "From Float", "To Binary", "From Binary", "To Octal", diff --git a/src/core/operations/FromFloat.mjs b/src/core/operations/FromFloat.mjs new file mode 100644 index 00000000..4fe5990e --- /dev/null +++ b/src/core/operations/FromFloat.mjs @@ -0,0 +1,78 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * From Float operation + */ +class FromFloat extends Operation { + + /** + * FromFloat constructor + */ + constructor() { + super(); + + this.name = "From Float"; + this.module = "Default"; + this.description = "Convert from EEE754 Floating Point Numbers"; + this.infoURL = "https://en.wikipedia.org/wiki/IEEE_754"; + this.inputType = "string"; + this.outputType = "byteArray"; + this.args = [ + { + "name": "Endianness", + "type": "option", + "value": [ + "Big Endian", + "Little Endian" + ] + }, + { + "name": "Size", + "type": "option", + "value": [ + "Float (4 bytes)", + "Double (8 bytes)" + ] + }, + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + if (input.length === 0) return []; + + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "Space"); + const byteSize = size === "Double (8 bytes)" ? 8 : 4; + const isLE = endianness === "Little Endian"; + const mLen = byteSize === 4 ? 23 : 52; + const floats = input.split(delim); + + const output = new Array(floats.length*byteSize); + for (let i = 0; i < floats.length; i++) { + ieee754.write(output, parseFloat(floats[i]), i*byteSize, isLE, mLen, byteSize); + } + return output; + } + +} + +export default FromFloat; diff --git a/src/core/operations/ToFloat.mjs b/src/core/operations/ToFloat.mjs new file mode 100644 index 00000000..b9aef638 --- /dev/null +++ b/src/core/operations/ToFloat.mjs @@ -0,0 +1,80 @@ +/** + * @author tcode2k16 [tcode2k16@gmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import Utils from "../Utils.mjs"; +import ieee754 from "ieee754"; +import {DELIM_OPTIONS} from "../lib/Delim.mjs"; + +/** + * To Float operation + */ +class ToFloat extends Operation { + + /** + * ToFloat constructor + */ + constructor() { + super(); + + this.name = "To Float"; + this.module = "Default"; + this.description = "Convert to EEE754 Floating Point Numbers"; + this.infoURL = "https://en.wikipedia.org/wiki/IEEE_754"; + this.inputType = "byteArray"; + this.outputType = "string"; + this.args = [ + { + "name": "Endianness", + "type": "option", + "value": [ + "Big Endian", + "Little Endian" + ] + }, + { + "name": "Size", + "type": "option", + "value": [ + "Float (4 bytes)", + "Double (8 bytes)" + ] + }, + { + "name": "Delimiter", + "type": "option", + "value": DELIM_OPTIONS + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [endianness, size, delimiterName] = args; + const delim = Utils.charRep(delimiterName || "Space"); + const byteSize = size === "Double (8 bytes)" ? 8 : 4; + const isLE = endianness === "Little Endian"; + const mLen = byteSize === 4 ? 23 : 52; + + if (input.length % byteSize !== 0) { + throw new OperationError(`Input is not a multiple of ${byteSize}`); + } + + const output = []; + for (let i = 0; i < input.length; i+=byteSize) { + output.push(ieee754.read(input, i, isLE, mLen, byteSize)); + } + return output.join(delim); + } + +} + +export default ToFloat; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 14c7408e..b77f16a9 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -39,6 +39,7 @@ import "./tests/Crypt.mjs"; import "./tests/CSV.mjs"; import "./tests/DateTime.mjs"; import "./tests/ExtractEmailAddresses.mjs"; +import "./tests/Float.mjs"; import "./tests/Fork.mjs"; import "./tests/FromDecimal.mjs"; import "./tests/Hash.mjs"; diff --git a/tests/operations/tests/Float.mjs b/tests/operations/tests/Float.mjs new file mode 100644 index 00000000..3977834c --- /dev/null +++ b/tests/operations/tests/Float.mjs @@ -0,0 +1,164 @@ +/** + * Float tests. + * + * @author tcode2k16 [tcode2k16@gmail.com] + * + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + + +TestRegister.addTests([ + { + name: "To Float: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "From Hex", + args: ["Auto"] + }, + { + op: "To Float", + args: ["Big Endian", "Float (4 bytes)", "Space"] + } + ], + }, + { + name: "To Float (Big Endian, 4 bytes): 0.5", + input: "3f0000003f000000", + expectedOutput: "0.5 0.5", + recipeConfig: [ + { + op: "From Hex", + args: ["Auto"] + }, + { + op: "To Float", + args: ["Big Endian", "Float (4 bytes)", "Space"] + } + ] + }, + { + name: "To Float (Little Endian, 4 bytes): 0.5", + input: "0000003f0000003f", + expectedOutput: "0.5 0.5", + recipeConfig: [ + { + op: "From Hex", + args: ["Auto"] + }, + { + op: "To Float", + args: ["Little Endian", "Float (4 bytes)", "Space"] + } + ] + }, + { + name: "To Float (Big Endian, 8 bytes): 0.5", + input: "3fe00000000000003fe0000000000000", + expectedOutput: "0.5 0.5", + recipeConfig: [ + { + op: "From Hex", + args: ["Auto"] + }, + { + op: "To Float", + args: ["Big Endian", "Double (8 bytes)", "Space"] + } + ] + }, + { + name: "To Float (Little Endian, 8 bytes): 0.5", + input: "000000000000e03f000000000000e03f", + expectedOutput: "0.5 0.5", + recipeConfig: [ + { + op: "From Hex", + args: ["Auto"] + }, + { + op: "To Float", + args: ["Little Endian", "Double (8 bytes)", "Space"] + } + ] + }, + { + name: "From Float: nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "From Float", + args: ["Big Endian", "Float (4 bytes)", "Space"] + }, + { + op: "To Hex", + args: ["None"] + } + ] + }, + { + name: "From Float (Big Endian, 4 bytes): 0.5", + input: "0.5 0.5", + expectedOutput: "3f0000003f000000", + recipeConfig: [ + { + op: "From Float", + args: ["Big Endian", "Float (4 bytes)", "Space"] + }, + { + op: "To Hex", + args: ["None"] + } + ] + }, + { + name: "From Float (Little Endian, 4 bytes): 0.5", + input: "0.5 0.5", + expectedOutput: "0000003f0000003f", + recipeConfig: [ + { + op: "From Float", + args: ["Little Endian", "Float (4 bytes)", "Space"] + }, + { + op: "To Hex", + args: ["None"] + } + ] + }, + { + name: "From Float (Big Endian, 8 bytes): 0.5", + input: "0.5 0.5", + expectedOutput: "3fe00000000000003fe0000000000000", + recipeConfig: [ + { + op: "From Float", + args: ["Big Endian", "Double (8 bytes)", "Space"] + }, + { + op: "To Hex", + args: ["None"] + } + ] + }, + { + name: "From Float (Little Endian, 8 bytes): 0.5", + input: "0.5 0.5", + expectedOutput: "000000000000e03f000000000000e03f", + recipeConfig: [ + { + op: "From Float", + args: ["Little Endian", "Double (8 bytes)", "Space"] + }, + { + op: "To Hex", + args: ["None"] + } + ] + } +]); From 3546ee30a22611f6af16c00532a31eb08fdd2501 Mon Sep 17 00:00:00 2001 From: Kyle Parrish Date: Mon, 7 Oct 2019 16:09:22 -0400 Subject: [PATCH 009/228] Update escaped chars --- src/core/operations/FangURL.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/FangURL.mjs b/src/core/operations/FangURL.mjs index 5badaae7..7390c1a9 100644 --- a/src/core/operations/FangURL.mjs +++ b/src/core/operations/FangURL.mjs @@ -69,7 +69,7 @@ class FangURL extends Operation { function fangURL(url, dots, http, slashes) { if (dots) url = url.replace(/\[\.\]/g, "."); if (http) url = url.replace(/hxxp/g, "http"); - if (slashes) url = url.replace(/\[\:\/\/\]/g, "://"); + if (slashes) url = url.replace(/[://]/g, "://"); return url; } From c689cf7f134df8e8309302c88e8b9bf1a22e94f8 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Thu, 9 Jan 2020 15:14:33 +0000 Subject: [PATCH 010/228] Fix #930 by allowing variable key sizes --- src/core/operations/BlowfishDecrypt.mjs | 8 ++++++-- src/core/operations/BlowfishEncrypt.mjs | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/core/operations/BlowfishDecrypt.mjs b/src/core/operations/BlowfishDecrypt.mjs index 07b6a0ff..83236327 100644 --- a/src/core/operations/BlowfishDecrypt.mjs +++ b/src/core/operations/BlowfishDecrypt.mjs @@ -70,10 +70,14 @@ class BlowfishDecrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 8) { + if (key.length < 4 || key.length > 56) { throw new OperationError(`Invalid key length: ${key.length} bytes -Blowfish uses a key length of 8 bytes (64 bits).`); +Blowfish's key length needs to between 4 and 56 bytes (32-448 bits).`); + } + + if (iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`); } input = Utils.convertToByteString(input, inputType); diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs index e7e558cd..ebf5e5c2 100644 --- a/src/core/operations/BlowfishEncrypt.mjs +++ b/src/core/operations/BlowfishEncrypt.mjs @@ -70,10 +70,14 @@ class BlowfishEncrypt extends Operation { inputType = args[3], outputType = args[4]; - if (key.length !== 8) { + if (key.length < 4 || key.length > 56) { throw new OperationError(`Invalid key length: ${key.length} bytes + +Blowfish's key length needs to between 4 and 56 bytes (32-448 bits).`); + } -Blowfish uses a key length of 8 bytes (64 bits).`); + if (iv.length !== 8) { + throw new OperationError(`Invalid IV length: ${iv.length} bytes. Expected 8 bytes`); } input = Utils.convertToByteString(input, inputType); From 9e17825b53b371ed1c8671472ef6585f96a29d86 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Thu, 9 Jan 2020 15:15:01 +0000 Subject: [PATCH 011/228] Add variable key size tests --- tests/operations/tests/Crypt.mjs | 34 ++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/operations/tests/Crypt.mjs b/tests/operations/tests/Crypt.mjs index b56f9cf8..a6b9e2ac 100644 --- a/tests/operations/tests/Crypt.mjs +++ b/tests/operations/tests/Crypt.mjs @@ -1751,4 +1751,38 @@ DES uses a key length of 8 bytes (64 bits).`, } ], }, + { + name: "Blowfish Encrypt with variable key length: CBC, ASCII, 4 bytes", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "823f337a53ecf121aa9ec1b111bd5064d1d7586abbdaaa0c8fd0c6cc43c831c88bf088ee3e07287e3f36cf2e45f9c7e6", + recipeConfig: [ + { + "op": "Blowfish Encrypt", + "args": [ + {"option": "Hex", "string": "00112233"}, // Key + {"option": "Hex", "string": "0000000000000000"}, // IV + "CBC", // Mode + "Raw", // Input + "Hex" // Output + ] + } + ], + }, + { + name: "Blowfish Encrypt with variable key length: CBC, ASCII, 42 bytes", + input: "The quick brown fox jumps over the lazy dog.", + expectedOutput: "19f5a68145b34321cfba72226b0f33922ce44dd6e7869fe328db64faae156471216f12ed2a37fd0bdd7cebf867b3cff0", + recipeConfig: [ + { + "op": "Blowfish Encrypt", + "args": [ + {"option": "Hex", "string": "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead"}, // Key + {"option": "Hex", "string": "0000000000000000"}, // IV + "CBC", // Mode + "Raw", // Input + "Hex" // Output + ] + } + ], + } ]); From 81605b2222e2a4b9b41198651da3abc9f2156082 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Sat, 11 Jan 2020 10:47:40 +0000 Subject: [PATCH 012/228] Grammar typo --- src/core/operations/BlowfishDecrypt.mjs | 2 +- src/core/operations/BlowfishEncrypt.mjs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/operations/BlowfishDecrypt.mjs b/src/core/operations/BlowfishDecrypt.mjs index 83236327..a80fdb2b 100644 --- a/src/core/operations/BlowfishDecrypt.mjs +++ b/src/core/operations/BlowfishDecrypt.mjs @@ -73,7 +73,7 @@ class BlowfishDecrypt extends Operation { if (key.length < 4 || key.length > 56) { throw new OperationError(`Invalid key length: ${key.length} bytes -Blowfish's key length needs to between 4 and 56 bytes (32-448 bits).`); +Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`); } if (iv.length !== 8) { diff --git a/src/core/operations/BlowfishEncrypt.mjs b/src/core/operations/BlowfishEncrypt.mjs index ebf5e5c2..7d550d46 100644 --- a/src/core/operations/BlowfishEncrypt.mjs +++ b/src/core/operations/BlowfishEncrypt.mjs @@ -73,7 +73,7 @@ class BlowfishEncrypt extends Operation { if (key.length < 4 || key.length > 56) { throw new OperationError(`Invalid key length: ${key.length} bytes -Blowfish's key length needs to between 4 and 56 bytes (32-448 bits).`); +Blowfish's key length needs to be between 4 and 56 bytes (32-448 bits).`); } if (iv.length !== 8) { From cd4e70b24b7f11bbdfdebb374650781f42479370 Mon Sep 17 00:00:00 2001 From: Evan Reichard Date: Mon, 3 May 2021 21:07:58 -0400 Subject: [PATCH 013/228] Fixed right shift 32 problem --- src/core/vendor/DisassembleX86-64.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/vendor/DisassembleX86-64.mjs b/src/core/vendor/DisassembleX86-64.mjs index 5f0ac65d..bbaf7a9b 100644 --- a/src/core/vendor/DisassembleX86-64.mjs +++ b/src/core/vendor/DisassembleX86-64.mjs @@ -4054,7 +4054,7 @@ function DecodeImmediate( type, BySize, SizeSetting ) //Sign bit adjust. - if( V32 >= ( n >> 1 ) ) { V32 -= n; } + if( V32 >= ( n / 2 ) ) { V32 -= n; } //Add position. From 3ea12a2e1be11805837dad8b620b73662f5fa8ce Mon Sep 17 00:00:00 2001 From: Luis Martinez Date: Sat, 28 May 2022 00:17:59 -0500 Subject: [PATCH 014/228] xxtea encryption added --- src/core/config/Categories.json | 3 +- src/core/operations/XXTEA.mjs | 182 ++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/XXTEA.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 9540f8a3..a55d5ddd 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -123,7 +123,8 @@ "Typex", "Lorenz", "Colossus", - "SIGABA" + "SIGABA", + "XXTEA" ] }, { diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs new file mode 100644 index 00000000..e8264c4d --- /dev/null +++ b/src/core/operations/XXTEA.mjs @@ -0,0 +1,182 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {toBase64} from "../lib/Base64.mjs"; +import Utils from "../Utils.mjs"; + +/** + * XXTEA Encrypt operation + */ +class XXTEAEncrypt extends Operation { + + /** + * XXTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA"; + this.module = "Default"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "", + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let key = args[0]; + + if (input === undefined || input === null || input.length === 0) { + throw new OperationError("Invalid input length (0)"); + } + + if (key === undefined || key === null || key.length === 0) { + throw new OperationError("Invalid key length (0)"); + } + + input = Utils.convertToByteString(input, "utf8"); + key = Utils.convertToByteString(key, "utf8"); + + input = this.convertToUint32Array(input, true); + key = this.fixLength(this.convertToUint32Array(key, false)); + + let encrypted = this.encryptUint32Array(input, key); + + encrypted = toBase64(this.toBinaryString(encrypted, false)); + + return encrypted; + } + + /** + * Convert Uint32Array to binary string + * + * @param {Uint32Array} v + * @param {Boolean} includeLength + * @returns {string} + */ + toBinaryString(v, includeLENGTH) { + const LENGTH = v.LENGTH; + let n = LENGTH << 2; + if (includeLENGTH) { + const M = v[LENGTH - 1]; + n -= 4; + if ((M < n - 3) || (M > n)) { + return null; + } + n = M; + } + for (let i = 0; i < LENGTH; i++) { + v[i] = String.fromCharCode( + v[i] & 0xFF, + v[i] >>> 8 & 0xFF, + v[i] >>> 16 & 0xFF, + v[i] >>> 24 & 0xFF + ); + } + const RESULT = v.join(""); + if (includeLENGTH) { + return RESULT.substring(0, n); + } + return RESULT; + } + + /** + * @param {number} sum + * @param {number} y + * @param {number} z + * @param {number} p + * @param {number} e + * @param {number} k + * @returns {number} + */ + mx(sum, y, z, p, e, k) { + return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z)); + } + + + /** + * Encrypt Uint32Array + * + * @param {Uint32Array} v + * @param {number} k + * @returns {Uint32Array} + */ + encryptUint32Array(v, k) { + const LENGTH = v.LENGTH; + const N = LENGTH - 1; + let y, z, sum, e, p, q; + z = v[N]; + sum = 0; + for (q = Math.floor(6 + 52 / LENGTH) | 0; q > 0; --q) { + sum = (sum + 0x9E3779B9) & 0xFFFFFFFF; + e = sum >>> 2 & 3; + for (p = 0; p < N; ++p) { + y = v[p + 1]; + z = v[p] = (v[p] + this.mx(sum, y, z, p, e, k)) & 0xFFFFFFFF; + } + y = v[0]; + z = v[N] = (v[N] + this.mx(sum, y, z, N, e, k)) & 0xFFFFFFFF; + } + return v; + } + + /** + * Fixes the Uint32Array lenght to 4 + * + * @param {Uint32Array} k + * @returns {Uint32Array} + */ + fixLength(k) { + if (k.length < 4) { + k.length = 4; + } + return k; + } + + /** + * Convert string to Uint32Array + * + * @param {string} bs + * @param {Boolean} includeLength + * @returns {Uint32Array} + */ + convertToUint32Array(bs, includeLength) { + const LENGTH = bs.LENGTH; + let n = LENGTH >> 2; + if ((LENGTH & 3) !== 0) { + ++n; + } + let v; + if (includeLength) { + v = new Array(n + 1); + v[n] = LENGTH; + } else { + v = new Array(n); + } + for (let i = 0; i < LENGTH; ++i) { + v[i >> 2] |= bs.charCodeAt(i) << ((i & 3) << 3); + } + return v; + } + +} + +export default XXTEAEncrypt; From 19423cc4372ed5d901d65afb21b8620ca188d881 Mon Sep 17 00:00:00 2001 From: Luis Martinez Date: Sat, 28 May 2022 00:20:51 -0500 Subject: [PATCH 015/228] xxtea encryption added --- src/core/operations/XXTEA.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs index e8264c4d..4fa0706d 100644 --- a/src/core/operations/XXTEA.mjs +++ b/src/core/operations/XXTEA.mjs @@ -98,7 +98,7 @@ class XXTEAEncrypt extends Operation { return RESULT; } - /** + /** * @param {number} sum * @param {number} y * @param {number} z From 85ffe487432a21dd56c6133e16db4c828d718009 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 29 Jun 2022 18:02:49 +0100 Subject: [PATCH 016/228] Input now uses CodeMirror editor --- package-lock.json | 197 +++++++++++ package.json | 5 + src/core/operations/ParseColourCode.mjs | 2 +- src/web/Manager.mjs | 4 +- src/web/extensions/statusBar.mjs | 190 +++++++++++ src/web/html/index.html | 4 +- .../static/fonts/MaterialIcons-Regular.ttf | Bin 0 -> 354228 bytes .../static/fonts/MaterialIcons-Regular.woff2 | Bin 44300 -> 0 bytes src/web/stylesheets/layout/_io.css | 101 +++++- src/web/stylesheets/utils/_overrides.css | 2 +- src/web/waiters/ControlsWaiter.mjs | 2 +- src/web/waiters/HighlighterWaiter.mjs | 15 +- src/web/waiters/InputWaiter.mjs | 307 +++++++++--------- src/web/waiters/OptionsWaiter.mjs | 6 +- src/web/waiters/OutputWaiter.mjs | 5 +- tests/browser/nightwatch.js | 2 +- tests/browser/ops.js | 6 +- 17 files changed, 666 insertions(+), 182 deletions(-) create mode 100644 src/web/extensions/statusBar.mjs create mode 100644 src/web/static/fonts/MaterialIcons-Regular.ttf delete mode 100644 src/web/static/fonts/MaterialIcons-Regular.woff2 diff --git a/package-lock.json b/package-lock.json index ffde1368..18759956 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,6 +95,11 @@ "@babel/plugin-transform-runtime": "^7.18.2", "@babel/preset-env": "^7.18.2", "@babel/runtime": "^7.18.3", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.1.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.1", + "@codemirror/view": "^6.0.2", "autoprefixer": "^10.4.7", "babel-loader": "^8.2.5", "babel-plugin-dynamic-import-node": "^2.3.3", @@ -1782,6 +1787,60 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/commands": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.0.0.tgz", + "integrity": "sha512-nVJDPiCQXWXj5AZxqNVXyIM3nOYauF4Dko9NGPSwgVdK+lXWJQhI5LGhS/AvdG5b7u7/pTQBkrQmzkLWRBF62A==", + "dev": true, + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.1.0.tgz", + "integrity": "sha512-CeqY80nvUFrJcXcBW115aNi06D0PS8NSW6nuJRSwbrYFkE0SfJnPfyLGrcM90AV95lqg5+4xUi99BCmzNaPGJg==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.0.0.tgz", + "integrity": "sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.0.1.tgz", + "integrity": "sha512-6vYgaXc4KjSY0BUfSVDJooGcoswg/RJZpq/ZGjsUYmY0KN1lmB8u03nv+jiG1ncUV5qoggyxFT5AGD5Ak+5Zrw==", + "dev": true + }, + "node_modules/@codemirror/view": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz", + "integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2370,6 +2429,30 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lezer/common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.0.tgz", + "integrity": "sha512-ohydQe+Hb+w4oMDvXzs8uuJd2NoA3D8YDcLiuDsLqH+yflDTPEpgCsWI3/6rH5C3BAedtH1/R51dxENldQceEA==", + "dev": true + }, + "node_modules/@lezer/highlight": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.0.0.tgz", + "integrity": "sha512-nsCnNtim90UKsB5YxoX65v3GEIw3iCHw9RM2DtdgkiqAbKh9pCdvi8AWNwkYf10Lu6fxNhXPpkpHbW6mihhvJA==", + "dev": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.0.0.tgz", + "integrity": "sha512-k6DEqBh4HxqO/cVGedb6Ern6LS7K6IOzfydJ5WaqCR26v6UR9sIFyb6PS+5rPUs/mXgnBR/QQCW7RkyjSCMoQA==", + "dev": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@nightwatch/chai": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz", @@ -5059,6 +5142,12 @@ "sha.js": "^2.4.8" } }, + "node_modules/crelt": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", + "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -14244,6 +14333,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", + "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "dev": true + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -14982,6 +15077,12 @@ "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==" }, + "node_modules/w3c-keyname": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", + "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==", + "dev": true + }, "node_modules/watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", @@ -17001,6 +17102,60 @@ "to-fast-properties": "^2.0.0" } }, + "@codemirror/commands": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.0.0.tgz", + "integrity": "sha512-nVJDPiCQXWXj5AZxqNVXyIM3nOYauF4Dko9NGPSwgVdK+lXWJQhI5LGhS/AvdG5b7u7/pTQBkrQmzkLWRBF62A==", + "dev": true, + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "@codemirror/language": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.1.0.tgz", + "integrity": "sha512-CeqY80nvUFrJcXcBW115aNi06D0PS8NSW6nuJRSwbrYFkE0SfJnPfyLGrcM90AV95lqg5+4xUi99BCmzNaPGJg==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/search": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.0.0.tgz", + "integrity": "sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/state": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.0.1.tgz", + "integrity": "sha512-6vYgaXc4KjSY0BUfSVDJooGcoswg/RJZpq/ZGjsUYmY0KN1lmB8u03nv+jiG1ncUV5qoggyxFT5AGD5Ak+5Zrw==", + "dev": true + }, + "@codemirror/view": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz", + "integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -17452,6 +17607,30 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "@lezer/common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.0.tgz", + "integrity": "sha512-ohydQe+Hb+w4oMDvXzs8uuJd2NoA3D8YDcLiuDsLqH+yflDTPEpgCsWI3/6rH5C3BAedtH1/R51dxENldQceEA==", + "dev": true + }, + "@lezer/highlight": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.0.0.tgz", + "integrity": "sha512-nsCnNtim90UKsB5YxoX65v3GEIw3iCHw9RM2DtdgkiqAbKh9pCdvi8AWNwkYf10Lu6fxNhXPpkpHbW6mihhvJA==", + "dev": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.0.0.tgz", + "integrity": "sha512-k6DEqBh4HxqO/cVGedb6Ern6LS7K6IOzfydJ5WaqCR26v6UR9sIFyb6PS+5rPUs/mXgnBR/QQCW7RkyjSCMoQA==", + "dev": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, "@nightwatch/chai": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz", @@ -19640,6 +19819,12 @@ "sha.js": "^2.4.8" } }, + "crelt": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", + "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -26720,6 +26905,12 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-mod": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", + "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -27303,6 +27494,12 @@ "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==" }, + "w3c-keyname": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", + "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==", + "dev": true + }, "watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", diff --git a/package.json b/package.json index cea3fb19..8e89248a 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,11 @@ "@babel/plugin-transform-runtime": "^7.18.2", "@babel/preset-env": "^7.18.2", "@babel/runtime": "^7.18.3", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.1.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.1", + "@codemirror/view": "^6.0.2", "autoprefixer": "^10.4.7", "babel-loader": "^8.2.5", "babel-plugin-dynamic-import-node": "^2.3.3", diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs index 9cf40ba7..045d8f05 100644 --- a/src/core/operations/ParseColourCode.mjs +++ b/src/core/operations/ParseColourCode.mjs @@ -112,7 +112,7 @@ CMYK: ${cmyk} useAlpha: true }).on('colorpickerChange', function(e) { var color = e.color.string('rgba'); - document.getElementById('input-text').value = color; + window.app.manager.input.setInput(color); window.app.manager.input.debounceInputChange(new Event("keyup")); }); `; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index e1e07dfd..08a35d75 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -146,8 +146,7 @@ class Manager { this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); // Input - this.addMultiEventListener("#input-text", "keyup", this.input.debounceInputChange, this.input); - this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input); + document.getElementById("input-text").addEventListener("keyup", this.input.debounceInputChange.bind(this.input)); document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); @@ -179,6 +178,7 @@ class Manager { this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); + this.addDynamicListener(".eol-select a", "click", this.input.eolSelectClick, this.input); // Output diff --git a/src/web/extensions/statusBar.mjs b/src/web/extensions/statusBar.mjs new file mode 100644 index 00000000..8a837a51 --- /dev/null +++ b/src/web/extensions/statusBar.mjs @@ -0,0 +1,190 @@ +/** + * A Status bar extension for CodeMirror + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {showPanel} from "@codemirror/view"; + +/** + * Counts the stats of a document + * @param {element} el + * @param {Text} doc + */ +function updateStats(el, doc) { + const length = el.querySelector("#stats-length-value"), + lines = el.querySelector("#stats-lines-value"); + length.textContent = doc.length; + lines.textContent = doc.lines; +} + +/** + * Gets the current selection info + * @param {element} el + * @param {EditorState} state + * @param {boolean} selectionSet + */ +function updateSelection(el, state, selectionSet) { + const selLen = state.selection && state.selection.main ? + state.selection.main.to - state.selection.main.from : + 0; + + const selInfo = el.querySelector("#sel-info"), + curOffsetInfo = el.querySelector("#cur-offset-info"); + + if (!selectionSet) { + selInfo.style.display = "none"; + curOffsetInfo.style.display = "none"; + return; + } + + if (selLen > 0) { // Range + const start = el.querySelector("#sel-start-value"), + end = el.querySelector("#sel-end-value"), + length = el.querySelector("#sel-length-value"); + + selInfo.style.display = "inline-block"; + curOffsetInfo.style.display = "none"; + + start.textContent = state.selection.main.from; + end.textContent = state.selection.main.to; + length.textContent = state.selection.main.to - state.selection.main.from; + } else { // Position + const offset = el.querySelector("#cur-offset-value"); + + selInfo.style.display = "none"; + curOffsetInfo.style.display = "inline-block"; + + offset.textContent = state.selection.main.from; + } +} + +/** + * Gets the current character encoding of the document + * @param {element} el + * @param {EditorState} state + */ +function updateCharEnc(el, state) { + // const charenc = el.querySelector("#char-enc-value"); + // TODO + // charenc.textContent = "TODO"; +} + +/** + * Returns what the current EOL separator is set to + * @param {element} el + * @param {EditorState} state + */ +function updateEOL(el, state) { + const eolLookup = { + "\u000a": "LF", + "\u000b": "VT", + "\u000c": "FF", + "\u000d": "CR", + "\u000d\u000a": "CRLF", + "\u0085": "NEL", + "\u2028": "LS", + "\u2029": "PS" + }; + + const val = el.querySelector("#eol-value"); + val.textContent = eolLookup[state.lineBreak]; +} + +/** + * Builds the Left-hand-side widgets + * @returns {string} + */ +function constructLHS() { + return ` + abc + + + + sort + + + + + highlight_alt + \u279E + ( selected) + + + location_on + + `; +} + +/** + * Builds the Right-hand-side widgets + * Event listener set up in Manager + * @returns {string} + */ +function constructRHS() { + return ` + language + UTF-16 + + + `; +} + +/** + * A panel constructor building a panel that re-counts the stats every time the document changes. + * @param {EditorView} view + * @returns {Panel} + */ +function wordCountPanel(view) { + const dom = document.createElement("div"); + const lhs = document.createElement("div"); + const rhs = document.createElement("div"); + + dom.className = "cm-status-bar"; + lhs.innerHTML = constructLHS(); + rhs.innerHTML = constructRHS(); + + dom.appendChild(lhs); + dom.appendChild(rhs); + + updateEOL(rhs, view.state); + updateCharEnc(rhs, view.state); + updateStats(lhs, view.state.doc); + updateSelection(lhs, view.state, false); + + return { + dom, + update(update) { + updateEOL(rhs, update.state); + updateSelection(lhs, update.state, update.selectionSet); + updateCharEnc(rhs, update.state); + if (update.docChanged) { + updateStats(lhs, update.state.doc); + } + } + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @returns {Extension} + */ +export function statusBar() { + return showPanel.of(wordCountPanel); +} diff --git a/src/web/html/index.html b/src/web/html/index.html index d222fee1..3d237bdd 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -219,8 +219,6 @@
-
-
@@ -267,7 +265,7 @@
- +
diff --git a/src/web/static/fonts/MaterialIcons-Regular.ttf b/src/web/static/fonts/MaterialIcons-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..54938737932514a89614069b081163cb34826768 GIT binary patch literal 354228 zcmb@v2b2_5*S5W@s!yFFNFIb?fEkh?h-3+hh!HaqR73$W4!;&y3*vJn#3df30tNU45TAb;92J>#yr32~wKvi0!hMck9u)#RWI|A_E499PD&mw{u&R9$KeA z`!}+`Z~uY)2CrQH$}y4iUKMGzx&Me^$x_j~gniUW?CyR;2cJC2Orn!YboYQ!cU4;V zZ9VaNEt1$7=AJwI-BE39!EyFK&;B;|P!aQdZ9kapdiM+*_F&fgyHbe{k6 zM_U5OpD!m*oIL5f-lsPV;^TR-UN@xPUcXQI^Z$@iuE+K_=A5Su_pB&Ah`z`Dcx)tc zR)7X2(hl&?-&kpeJ@HNV9=D@pcd|65Xr&j? z@M5JX_mvgJWlxgijpDK|<>ZCpav(RzCB@~~X>E)o%Gt&BAtjM(q@N6vJ7tL6OKE^~ zk^XYO43eR84rAOYcgt{U`?0@gywyHcJCPTZUEex6(wuNfBCA@sPDy)EK` z{%0#yQmNHeu4QA+6k1wXT%*0*2->d)dv(8SjQGg9RfoB>Rmm%X?Z#{=uB>Av z5FpkdbI=yk|+Rj?y@8t=6Q*w;HpWy5;_EVU*N3+EU_UYOk{? z=`8x^B9V$}T_@>IeJ6r`Dh{1!RWiP_$yss%?R4D6l2Saow$v6{uWfXn+tS(G7$ttW zde=txmAqyUJ@-|^6C)%%h^omofFKW$yItyXAX z9Z~0YC5@WSr5@>6YLl~a>_3s$w)%2*ISbs#YPFVhBMtn4lePgLbS@)ies(o&*QzbXAvv4dn>aqH1d_=9&`JENcq6DcVx*|GG zHTJn$_2XvhY87ugt0X?JvrR3yI&P2Jbh;I4mBdHX+4MD*8s|sJ_&T5VQcHA?TLEX+ z*m%9}*H?Sc7WzuyisNG5zIeNbbd1;NYPr$0pRTv|)!Kx3X>74Ni)K(agI0|>4{L06 zr8QDIMi2RkE6i)WbQEX(NUn1~;nS1x7S1kbk+V|%v9J@ZbPOF^=hAb6+M@O9M`Ml} zeb?T)U+eTWma@h`J=OQd%-0^(Z8ws>Ygx0uq|VLLqqyGn*E-iuN77!WUT#*m?ZsQU z8lA7P)CDc+oTtAu>)a94gDbmht8-}|^-kyO!g=NN(RH7*#QE>$)V>;N*H_1Iee_*h zyLwlvZQTqKpF?P)aV~?F>~YacWxEsCY~8ObnZR{YBbUIIi(n7#S+td_Z7i+gJ#;o_ zovZH=->>s@qNJ^LzdOpcPRok3&uwc@XN|t=9NI=-THl(x#%@rKs|R%AESefW4|L-0rwiLUY7c5Ozf0^;Es3tH z`+xaf;(@c<+3f1H+zE?I{8ZnyT(ZutmDXsT`l{aQ*-d+C{GxcBmfToca^5@tbp(C6 zSZGOGO#7!!$F2{$XJK3u;~rHbM0FlFziZ!v zadc#7xqB~J!`ktv>e*jEUFzuN=RKcN5AKm` z@v2&UE!J`>wW+AKKV_TRqWPqKTtwV{H>$4C4aM!d#m6X_&qd9}N!xa3jEb~AwLx|j3WwNXC>J5t%FzsY5(~`4AM=D>ul@K3S>)ojC*mPsKKB=_S9&R;KDW}F?+Fs+BfTqU#JL~ja z+n4kxSr@$zaXoeK47N_6+l}CQ>ATwM)>Y%6W9hc8rXCww=eGJ#)`;kSt#e0YNmR6F zE%xhNXP-O~AL&$1l-SdYy{(~#OrgDdX&jU3=VGRHTBFAh$D z`1!+);cQCK&%yEiYPZ&<#%FDb>e)=|#!}WCUr0M$<;ILWgLjR-ZfTwR=~}yd(t0cq!VD|DrFyczL&7ttB&8u8QiE7+$c9djX?%C?TD_Bh`< z(Y8nNc&=XaL05PzEuGy{i~H#KCHA{Lx>a(n7#km7JzU5`M1qn4;$YO&_==`UyHspC|ANR9VsEFW`U<9a()Ynsqo=ho5O%f-%(e|oL9cAo3J z&K~XASgI8FOo)%Jb*{c-JgwJPLi{YQW2TZDdgjtUJ@5OVt@W=K@mMH}*{`pJ_}%>s z&PgRc-9VcX`cWPXW8i6+1&iQI*Z?~q8xHdTEd=KB>Oo8B3|*lw+z*e!Q^2!OZy|gE z>tQ=&!JnAPvpSz=t$tnLd7*z1@NClW1A|~Re9H4q+6Ap)F3;@BKrc8TVxEPsMMB01 zc{Ui{3~%t5?o#-UN37?6zbR5C5gvgb;V6%H@uBP!!1L9z>qO3A zo-=yFDv@&4;YpG50bB_9RerNbg)^Z85Vs0jA&<-DHE>9z5`I*|zDnsLl`8}DR{nrz zwH9b!g*YTIXTl_rs&|W=xk;qjGa}UoiX^hH2C+}#xpUG2k(wQ06zqnREa~O&o=9z; z*4FL{_?4UjAHiQDDG$SPo(W@b9oDwa3-E(TUCtnNhe9TglUc8Nw69OB>whBBfOs^( zw+6#uHZW!b<~pkfOoaU+4Q~@pD1e`5vmR$phNYw$zBT?>qzOBlJP5?}9Be=5T6hj< z-xPb#?F1`Dnh~33jNcslo71K_ZJPfq(t68k4L@ufUeP9}V3am}%*P&44;+tSDkc*epg$sawm*DFq_;YDxxEa{K zYzAQOWyJpSI?xO7>k7ud;!&W!3vIhF$CVY}O88FXs&jytUR5M=HS2ygYk5sok!yPc zeqC1!9s=_0`YV9_T_d<1-h#h5yt}|e_(r4~V|0544vBQ9zWXvxz>L>}oawPnq$hLt z918e!W!(-#^L|C?|IDA z5Izza&luxb|MA#1p4^;(PWS|VhQlHgnQ!7nFi>O?K1}Kkli^p9$&H{tFy|EfnDPPq zE;6+<5dW#iM4ozxOH6sV6UdooQh@R^*fni9EE9RQ9NZ@|o%-q6H2tK=bHw4fCqp@YEc~CfOXM}|eC+|) zCh~f77$Y*9wzEgVA(1(^!=EB^hltDz;0`z;^2W9Bp2(Zjy~$W_ekJl&2+Z}?bMTYM z+ez>ktQMJ14CeO%j>q}iMHbL@!Nsr`Sfh6?1>(Pu`i0|wITvvpyju@m5LwJIxpsbFTm?%-zAXp%^6lRu8*c>iVp9{C0$C!P_lRuSDzddLd@k}Gc76A{$oJUw z{d2Hd5JNr+jBk zo*jG#Scm+9z&hvWh!ha30%B8eK%}q-U{B#nk)js>zkcry*z@~tkwf@%2pbOlE%FC_ z{x~A?=gZs@o(1znj8*I>9{n4o-?!{brz?cr`48;x!r$2gE!32KY?8#sORjPXM+w#_lHgaSky%hk4I=Q@p0EYg76) z-6-CGR@Ub`Q|^@=EX|VA~bg(gk0;Tmh_W7wWFW-z(o0@2X@N3P<=U1o6M7AABj^ zwXE&6lYw=)t_@6rmEv7r4eleXwfM5Oo6z_J{t$#-#F8%YwyMr~qV}p2iHU{E)S1n-QU8^8N zyt}KwMeryPgL@L;S{MyW#JiU~ytgwv3;1_mZFoey)EL|%-T-vK8u13Ez-+*VL4Dv! zpnN}TeE&jzqQks{#{zW^bb*%u`-U_E;yi@>8p?VP9R#!){tRprZ^ZR5N4$}pV1;-O z_5|8LNPI^z&nRqu=mNl|hl%0C-Nk#P1v~`%#2bwtqmS}&dzyG-h}D?$;ZyO(HWBYJ z51tn9aeR3E0bsr-$iF9s!Ezu!#*tIwh~2o`fS5l?`N_xOBiJq8c-Ce-v78VB$KC|& zp0HiKiR8&d;xnl!;KL;BoJ{N{zYlxGn?iZYo#IUuz+UeAys5c75}^Fl2-qUt(|3vY z%(=jEJ?$0oo@Ko072!Mao}0_hpl%RvMtwLe-V42ed0$*9-b=*i<-y|3>?+mJegZd+khEC*JGC@^#`kn^?`^Sf8_6yt#}y_n3I|@c#{beFHz=>;W0#y){j| zw~6oD*gt=Ycniqu1spH$;QKohfp{;(j)k9yw}|y##Q2LAi1#jYzKd^*vFAP7zBd)N ziT8doOouJji6kXvi!iMN(qT1))CDFau-Y{(aHT~`3PD9R>K9HbcC0Y)q%^-g@p6e>?m{50d5gq5a0hTK9LN;!H+(%<4p^6b;>`V)myd4+_rrei3WossP*fM* z5byUEuvEN5wc!Qv{^-bKGJN=Rjd+L2^TXJEn7lsnrg(pm14jqJr{euhEdFMlj$zL+ zY&pi<$JdH?g7Ho~1{va=#Fmqvi!YbLa`C-~#1BfrBJpDt;bZa5d*X+Qz<%3a{OB6k zD1NCPuv`4n=fMu~%e00kV88ff$B2JMO_(5lx$^Li_~p9;S|J%$ieE8=A>vn}P34PW z8IS#{z-!_sw1u7GS51LU;-6UuX2BlutBr@$>Rs;$LwtU~iX+;$L}(_*c~j ze&u=fBJr=mmusGczs0}y7FZ?zb@c#yu0I=|7r!g<>q=a27%qM{Y~`7e-)#U)6Tdq# z=ss8c9?t@P_G|>K_%}l@Vsc9*ARfJmZEx(ql^EUnt@yWL2iJYS z4{`5HT>2gpzaKI0M;`Pi26wcEvEtwPiuiXCtGniie>btX2S4u_1dGMLm+gBuh<_h? z-vsefFM~J4AHW;~R*OH7H5&Mh_=D~c|NiRGA2y0V7#&EhI_$Xk!*7Q1K>m+t0N;r}vK3&{gBEs*KWaJrCjLW7a2@<5{=>xc5n?%- zn2x>|u=&yH;*Tj0?O~?)W9Pvh@gG|u{^R_f_3>uF_7m9n1ip>qSQ$sW$K{LvBsPt| z0J6oO@Ui$4iPPi^@u$2fKG#`)YNq&49TNYU2gIL-ozplzpB*j!bjFze3}E+j9mRj1 z7(V~C_%n#@3uOQsUu*}od8sbUf?eXjjNdOa_sc7RUjWZ+1h)fz%_P3Bz^g;yP1q;? zEOPU;iad5Z?W&KN#egv4Bp-< z{(NGv;0z#63kt=5hqdGxiNDB)djPxNJrh{d#Y2GjzsH)q&s^`LA6x}*iT@$F{ULpq z(Dx(!{3uWSkD2F_7`!e1r&q!%@jtr)mWls)DImU|Gv^oQz@vbFUzP{r_a*UJ+7pQH zvNqx`kKjT0Ui_~v5`P6Yuec9b?-hTG|MhF)^K8Ul*%jsi>$D30RyTsdfFEnh0`1lm ziNCf6^a0}g4P&gM&w66M;Z`6|zQwO^UlM;KYrK)zZXyPoCX2tBeBX>6ThQ+fj1vEc zUhszaKjPDmN5%i?DPV24-3fce{~3RN9t)oXb8o*8eieTQ@!WA({9k?%f9J)pN&H>d zwQH^TyL-d$;_vAq{;!F!LHu;)OUJ(SH1YRwOzi6hd&J*QJToSVpIHmWi=R~&a>dU+ zOZ*)A=d2Px_ex;gys9t-nDf9?@qcRxUxjw zutfadUx%IIa~<^$Jphb<=%Dz2G>74^RQx~tz&!B}b6gzWCjJp(@)zs#7q%WP4L6Ga zH@5w~PyA!#%dux6NBrX*fH<6BjZb8Ye{!@0Tu}r0P696(7E9o_h1=m{34#mZ6$xS$ zpsNI76L?wz+gpO@JPAtG0LCa?T7ohLCP+~3CfFoF`M&VB1QpJKsqmWw6{ky3=~7_6 z$^+p&393|wDA5>#6!LG{}3mIR4sN>GC~HJ+3psSG?L0pBSFHJ35+q*^`z1)plAz8~3F`7o_IhVQkp%UROVD7Z1ZO3}NLV95!^-fX1dXu0 z(akU$c1dvdy%IDo3-?2&1Woz@zmRWoSOUHa3Yy*xdnGs*ZPp0xfo~;fej59b5(#=<1K57!T@u{XQi7Y?Nzm&T32r${g5H-)aBCwVhPN@tZTQ^>|N92; zfdu^yNzk9M?tD{%yXbp2@xJ>>3GQM4y=5f04}S(!lwjaR@H`O9K|Nu$1osaIwg(g2 z2d;qcB^ZL=L&(>m*feyv1jCxZ1MrRn!$-hp5{zJ;5yXCE0*nFH^TBg~xIg$J;QJ`@ zY!q>MD1>( z^Alm41T*f2PbGMP7{2h51TSLii{DA`Qb!=pFINNF%p57ft5twLvmS?|61;XLybbvM z`cS}@*`p+w!#s0(!Ey=a60f=AC76d@^RWF5#(I;Oy*UDQO7IqI@^(Ym%0CIJ4fwX8 zKD;c!J7)qhd*?3+7Bc?AZzNcBBOI3C-L`;zi_ZqyEVXOpS-41N8C?MJOh4~U>A9}>qE$pU^nZ!haBBgB*Cx5;8*f0?GnJ2v>zl$zXs5~ zH%YLs2D~T1{_BBud_NFmCcqeA4YDqT2@+)YfJ_N;h-=Qh66D?ktmlCk9G2j>HWD1< z9L4oB$S0Tck4R8(CA=y@VRiUif})zh9KSD;;Ltn?{$Q>@i0z+`!k-cxP6CdvBaHtS zHXI!)!Li#UI6h2*6WDO_28r?esaWhDiJ2x63m=u3y+mSBLl^-|;H1P#-3fn4tn>pC zE7J%jK!(K1wuaY*e-9_ZNWzs>8Z;>`6Jzn>+d=$as@0RS?2!hMzSHz<-uP~Z-%-W; zNgq_| zwyoj_wQYif?-C@9?=lpAaVlvwAcb;Mv@X=6+!|GznovFu)qTw=pO3Z#^{W%w0lHDX z9PI(aDECE&1K){+YRe>teFJ?DmeOV{x*Wcu{4}}>R#Tpau7v{1d^eNM_d$xi747J- zGf}=jN*_fVzRO9cPx=hXMd-_pNZY^a5WW{mf6WmtL1#Ncz5`01;|Q@geXb*X9-Rl+ z#P>Inj$KOlBD%&A;%ho~D&cN)ox@8+H#kDI>sv>-65Z$s=cAioD-Vx|efoEfa2EQ# zBf1U!!4cL(e{zKPquXI8eQTp>fFE96G|ypK)ASP}dwuE~pvnNsjnVOrh#cI@I_$+S zZwR^;)>D2M#ZJW=gEEg2V%OeGM~Llvvm9Y-G}jT5Lwm772^XLT93kt+J-F?i(8L{9GC;fSt4u}iV6@BZH%VK#cm5#k5uv^Zft%6v*l4(vbduxFs;lM?2jx(`CN z>!`z)LH~Ay#BBdDN64D&Kkl#|s@o72peG$6G2@VrW6PqnQ$ps?U_BK=DH-^$SYnz{ zOvsvKlyZc>p~N`@d&45MoFl~U49*1^6{yGFjEX=!twE~*F||4d>!XDDpONGUiCqS{ zsD#*?Q47dHt9B(jLj21h50ntUGwL~P46W}7S<8%bpbhKQ6>SHjDc^!V;jlW-IEU5w zpLE!&=y-?Kc_zR_Y&a90-{T+5gWPthK`GoE%>_2U_bB`z7$9JV_8 ztix(-W;kpe^d*?dI2ylIkdFPcQH?3^9j#;%N5v9@OyZwOjQp=q-PeUO$5du-M~qly z_JMvPhBeFV4|h>!{LEB`ZG;YYm{#Zq4oh4!mpE(#^fQNHtjyK0hPlYm%wHV#EOfWS zCr>kfb@*xnekeZcm_^(b6QInk#26>5y~8#{&v#hjm(|%}8>1J)CHUSPz0_fwpqDx9 z+34jC+YG$|h>2~A_H|hDA`2fB+Zw&yVOyfaN3m_uI~^UZ)5{KfAv)7xFG62&*iPuH4y*B)3-jnpEVABp zSoQlohgHAdcUXzuvej*9QImtv%_{pw>a$8=vId% z)>%I|>7 zE(F9Zhul_TRZ!M7XEfymbc`cL%yS-h#IPl2B21#qwdfQ_jJ3{r#=&zL$(inmu`W5! zJ7Ub&w;CJeuL=QQv+WZInN&N)$FGq}Z$~oqUO-9LeCC0kt3XmT$awfMF)T7KA=GJ#u ze93J9?WkXflBbGELdjFbkW;zmJ4|Er0*C2@c61olH}^t^8G`D*E{ypVdZoiGK(BI` z&rotsF&uNb!5EVkx8<*;h+(+=AYeZgVXzn2_VZGYKe z??g4eVDCX+aoF3@R~=SupXIRkqpv%x+CJN1H3oAX_C8eC0IbIBEr%V0zU{E;>wJe* zpB6am6X-h*`w+U&VL29Z7s0!XIT~H;u#cecIqZ1!eTU_E$^F1#A4WL`C{|;+#9=v( za#uR6#&4CwK8vn(SdG&+4m$%~=de@J^$t57-Qchq`;87e1>NMZFQQu<_BnK`!%jmv zhbVR;`n|*IT50})orM1Au)5|yIqYO~o5Sk*{Oqv0=Gz@s*Kmi!>bm{nu)5AW9riU; z^A+ry=pKh<{c?YGSY6vRhn<7&g?+TwwcYQqnhTi@tLvHNu)5CK4y$XMad!Fe>?0FelJ=BYI7{NLmN8G2(*#IkOO&59G1MxJI7&J z&%CA%OCIE%>#(cQW)4eE>9MI!;-IgH#qDTw41|zi*|R| zb!bnA-HP7muq)7;9G2rR?`DVP7|QGAu*=Xs4olwU^>tWsF|VJ)rlYqz>`wF!hgJJI z-zk<{&%4WEGtj#ob~}2H!*U$t-RrQs(EA{jzT|J-{SLbg9qh0>&<9`$Z8&c7hQe^l z920q(2Vj3jM>^~;=tGVuKp%G4LX`8GV*f-(J8S{^sKb)Kd1D-rhmLjFqv&G}OaA6P z?yv{ZCmgm29p|uz(I*{|kB)cP-%!pqN+jq+hdqW)cG%z1DGvKLI@Mt{H=c6XJoIUY zJ&8W!uz#V`9QFkItiv8hr#qq;`kce&qt83+0d$5Vs*b+kh^nA3I-)Y@OOB`l`m!TB z6P@XZYNM|>qN?btj;Jy^%Mq1FUvoqRBJXua6r!^o(HZC*m`kjZ(Rq%j8v2GKs)fGk zh!W7Z98n_rwj)YH=R2ZO=mJM%(RUnCO?06ns(~(YMCH&=98qcXGe=Yh{oD~%M89xE z2K~|zRYI3KqO#~RM|2jt+!39Pu69IC(KU{!KDyQsHAB}q^xq8Tt#?EX&<&2L5&Erz zXW5ds(Gk@{^*D#9F}m3ior7+1M9tByj;IOxog=D?e(#9dqCYsIPUw%0=sff%N7N48 z=7>6?KRcrH(d~}tQgnwSYKi{hh%Q5SI-*O^U5=;)y4w-8M)x?P_UNyUs56@8h+3g~ zo&tUgFL`?%Q5$rhBf1#f?}#oyGaS)HXr?3TfMz-PotfliJE9xW97l8|n(K&eLh~F^ zPxOEz>Wcp6h^|EsI->5V9(xdVLkk?y^=P3Zx(+RJL|xF|9nlTwAxCr-`llnh8a?cY zdZ5P~(Jkn4N7M^F;fQV)IS@Ib-e@UD)CVo?h*Hrqj_5wLtRotUp5fqo6FE@M5e-Mn zJECD|1xGX#t>}n`pp_ia188MOG#IVoi0(%d9MK@Osv{bRp6Q4Npw%4Fy=ZkubPt;7 zi0(#fIHJ4IBu8{7TGJ8Tf!1(U6Me;cw^2zrY@y;g~^U0Uk zQS^Kur((oApJPBV9!f6elV8T5jD@wz&UDtV?Day7r3!;nAu zx($YyPz-C4PoCw|&#+8Hnl}+jur! z8Ql)QP_BjUggnZ1&;t%r7yS(mQr{BIhXTrN&_W;w!*=N3aFR0X%Wf$MDYNzk))DqZ zOF=UAtXl#3R8XHXF(_yVttjI|L2Kwlc^rBXkUQbC=p{gohBHu|gZv9wi-N162j$mL zorARw-$7Yt#T-Fd>jKs^d>`!#{V0Eg-VUry$XXN-mjc!(T!peeiYOr*RAo#Y7jp;O@*>hY~$8sJ~3eoS{5Y%O@v z5k7&w3^Qr727Sd5ZbRog%nEdoBm4waKf!#BY9BBfzby_!9u<7&Fi)Z6mSQx{@y;BTStFdfmD!`y(9Yl?XdCEpbDCK@@+O(?mi7}mJ3w8Jz-%Q(#SXjz9Dik{&x z3(;~8Lmm~96N)((t>`eswXl-I5ZA&g4nzD3t2)eWXbp!U?+QmdOi%Pthj|Nq3?65E zwU<0njK*Vv!(5G$ON!xKQ#jdS2B1?M=3bPTDkc>rXY}7Nngnzf5GPX)Wt|k$9A!Ne zb0Ip%VJ<@FI?Sc$JcsFmz5#F3UhO4U6f+)O;4mA|cN}Ins&NF9i7s-O&gi=ivj<)5 zFq_f$97g>nmle|j{lH=Np&vTTYIKRiv_?O2m}Th44zm>H*iej)@u|ba(9ax3`*AK- z%){uH4#V+RxZGhlt_#0%m^SDNhv|lX?Jz6RVvNqe%3<Y&FQhPX0( z9CHooISjd0M0^#~8x0(0KT19+<^j|=%ynq!FywntS%*1*p6M_L(P|EZy+zoq7z`+? z;V}4Alms=Y$B&{~4)YsY+hK@PQL@AQg{C+RF)PA1#Ska{jZGX=fS%>RL@6Ralu-R> zZ?u`i5Wk`pj!ugpxN(sPS&^ zFh|i2j*z$?neB+)LdkI@nu?O+N67DJE=q1I(KG1xj%X(OgCm-QlIKUZ(dH@iXGo(w z4NZqk%CDeVj_3`PTveh;DEX;G6H$(BC3+lXU6p7u%Gw-ZO`<1IVy#3|P}buJYa5M6 zSq~+81Z9nt=wXyRIC7XWd47caJi>ZLqfla}L=U3G^a$~bMoWd0$Ky!sPl=Vof4>ER zill|K<5yn~rNz?DNUNBZkXAh{C9PgsleFe(?b5oX4NDu5HYsgs+E;0-(+bktrguyq zl|Cc=<@8t6UrV2xzAAlf`nvQV(tk?dnZ7qYZ?C_1z~1qDSMOcFcjMljd$aZ)*ca`q zwy*uZm-nUZJGj5{{-*mo?ccC}$Ns#GAR{58T1HAn{fsslqcfh(n2|9vBR%72W~t2N z%r2R|Gy7%Um6@73JoAIhC7GXPuF3o*b9d&indwe3TqQCkspq1JCb%J`^aBMPV!&C*WV5Hk$!3X9x$zZS`~7pMq1sp zhG|XHTBdbK>yb8`oOv&8dD^P9weg&}G<{le&dg4qm%f^u*+9;0PfttFOFyx<0XegD z@7j3IWbVz~XZMxfmq^ac+P8OK;r@jE&0Nmx|2-ojXOc4NWt@}IF5?Mu=B12P@ti4_ zS(lvYliB~YoLT)JInz0-Z`NJp%)qRdvgVRA?`5qcXVSCwXXRua%r28%o}6iw-JYDe zCc9hq@a)gBx90dcQBFm2=JI&XO#D~Q%p_;#kTY+`bLM?==98Q+a#oNt>vF!!*+$N! z73a(Wa;B&_XZS9boN18TKDSfumATjF_R8&>o0>Z~cQ`pSmYjKtoOvnt_1w9+Z{@Dc zU7NcxcT4W>+`YM(xd-!Vc+Pt25ee&+eyFc%tyvgLuXL+mh*5&<~ckDn! z&a@?GHsx>6-H%31q};Y7hF_udBN2MJqvCx7+CO7!PJ6j%Z*Q==cJKNLBDH(M4I=es*1o*<#q~SaZdlvY-&yOE zS|8V1QkV0eNUc$|9;`L8?p3uCYE`LKsaC~W73z+zdw-n;x-VsqzSpc*vkv8_Qbr~1 zPHB`pKFL80=BDUU2d?RsQ;@rg76K5s9 zk~lN*rNkE!XHY*a@#(~;5~n7PuX$bKlWcRQPvi`rI1~noByj~u+6&y#{P({^?g2!q zzy0sMN!?H1P8ueXu(s-Dye=21dIelt^*V)8*Q!LmY7cFnfZYjPw-TyVEuB!Y%9tu| zid5NHg)3DR?iQ+Ct8J=mu5v{cMyWip@*|bkXiepK{L#U>g+2{iAuYbCHJd7L)N+MT zr2J>)KQFH5>hAvhv;Ag%tCFq%yZU9hwv~el|GlkWR`>q*KkulY7{4-}ulxP>|Gm}! zUh6mTTlw_$&-XjWN4U%{m>cGY zf7vs_oUqWA2{XeJ)(Z>51Ga2<*ouw9tnjz6DEvJ<6#fw&36F++DtbTHDoLZM)cB_9lCy zz1j8%&J3yr)q}*KMvxTLv^{Nady6e+Z?g}?ZY5%)%tNvJVo%1#+q+^DViQf0iA-Z6 z+`|kp3(RMxf|+T~Cf4)JE#_u3(v&sHc8Kk7N7}(A-F#v0Hebe0+P-$UO||{(KzpaX z$KGy7*!%5+c96Z#4zOeFDEpXw!aiglx1;T&_7OYYjFJuiDq`EA}<}rk!W!+Bx7+l}@|`<2~nzqa4o4R*Qx z#;&sKV~1jY*fsVCyTyKM*V(Ohg`{Bf9<)XFaOB(HBC*Hp@Ahwd$mZFT_Ah(F9*<%+ z-yVpnM^&OSQHAKtsCHB}svMP%q9}~ch?1j}s9IDjN{AApq^MM6qnc5Ts9aPhsu-E5 zQdBlND>^%B8r6@QMdwBhqDE1}s9w}KIwxu#HHqp*ZKF=nc~QHlV|0FWY1A^hEV?9W z5w(umN1dZq(S=c)=;G*t=%T1YbWL<)bY*l?)HCWDT^n_ex<%JV*F{~T8=|YCtD_## zEm5!N=BRhnCrXX(i$+ExqT$i7XlOJfdLSAc-5(8#21Wy-d!u`zyQ90JJEJ?I{?YAG z-`L^Uk=W7L-?8Jd6UH;fgvOdO=1f!F)G#$oEmPa1m z)8E`-?lSk7`^{i8+&p3)HDk>*^PG9!%rGyQm&_|1&9lrLGuOOf7MaE7J@dZ#)O>C> znXTq~v%~B%X(r3$nz~`juvOS5JTJUFydvxpUKw5$UK?H)ULST1ZwR}E-NT;Yv*Gmc zxo}4KLikR&FkBSA8!istGe^Sr!w0EG`E^_%@*^W>1p0F_n86aUXyAPOjT3Q)Hlt| zh2|o2sp(=KGmo1m%s4aNY%se`rs-__9yULlHl~|d zX+Ac2W{r8<{9;y_o#qqHhF=-qJi~dkiTTm|WTu;m=1Fs>X=KhblQ@G;F>B2?W}V3} zIcB?AZpN6a%zBe(J~Wq^b4(p`jag!PoBieibDeqD95jEJB6G<6X%3s;OuqTc6q?@+ zkMP4-7=$(qLt{>u~nrKbxYV$h?t*cErAs$VQ;n}R7*45CplPclP_?Z7}$G3{- z<+-u02eW$%d9YqCZf!zvi1jWm)4F=R%xw1U_-yJ?0`*CFS2^5O+>iY{@he->mi_9j z=1CQ)#P9S=|7%Zede@9ijYlj+Jz*^8_Zjig)&3MY7#~}`Pv&0_s7-a9PmG?xRt>4> zUx2se*)A8)r6j*2HtKc-?AH9MC=L7zsnI%(hpqpoqfa=Op&^iNG4de@K!v8Rjsr${~eSD|I~VoeEmW4HEq`~Io-55$j%TP47T@-hso zRn?DVUgVQKgRTEjM@>BH{Ck%&#ru*OS&x#cY$x;EA{(#QY)F!7{GK*sTh~k1b6gPD z{j0r3T36#Ye9+^pD*v`j$D_n(G0>y2Hvh`(RIMqg<@;cr#kJLp&|}ygEqmDduY2el z+MIv&b2(T;Bf_s2Oa0SN$0fdAz0>M68;O#Cn7EdV@76MxkxOdy$kz3*7x%j&-x0dA zi278MNW>$_IC{+J88V(J8hNk&X?E*gji0VV+$(+~dup8j*vmNDQb(^H{>poaHUGz! z^wJ)B4CoP4F-+tA|I{{=Z=0jtoaV=g&JCiekUXM6- zdG4%Nr+AtDhTp2YYmOfCTCe9-y^2)gm)xbO;c63)oo0G+u#w!*x@!E+M*C4>6g~Tv z97zpgm6*R|Pu%KQ<~YtWim@4t@*h2E^Di5`zWAxviJI)uHLK6jn#;DX4_5$cUH0iYpsLpK-J6vL zcwCkEH=splK8w}ibKOzy0#|VNIGt;3FYYnhgw;YnetliZed(**yWYbmr?w{9l;tzV zp4iIR`>|JJ55@Y%I>(yCN(V*3wqQjtKNuVI4K52>1&RJKKh0mqXY6VILwFOu*+dI9!>9tPdqA{(+@8$F=qh}rG zn|i71o@~bzTvj}VQCI|sOHub#nc(5^hArto5H@3#L- z+u|!peB3^~>zx1FxH@9ZuoC|K&sG(&M6*zj+Va@RTJno7y*v8%X#ZS`h|q|*r~m7I z{BT!7^|@l~Od|K6qt%R`{Zjm0@$*%AX8R{o-r5 z(>1IXZl}-x)<)M-b2Ei+tlU+gWPEoO>d6SF_f0B3BJ|7`&tPtaPw(fBnM(2q*Tqx) z^v*@2S6zBy*Xi{dU-h!KG~nN{=o!Dn7k5V0Sv0$n!yRPX>3dV+W9l8>jq$UT>!)!^ zl$%a#>uQRx*WP=~S29YKcw5crQ`h>k@uS|2^p$iz-c!=oX2-MgRNu4kY(+fwu1@bW8w6jU z=Br-$^j@v_937i-n#H=V8oS#3zFc#~jZux78mtlT!OGL-(-Al#gH@-sO(a?si558! ztUhhzTGZ*8JB44mXwJBi^&G3~8owI})|@tuJJQwCM1G^L`RRIS?&;A~Y<;lyG<)=F z$^DyuY21H3PwADO^Mp;|3hAyJ<$`bGR_V-|bvjN85n9Jg>dn7unKzzU{%-o{mET>z zitiX>8kbYGN%2o2de0Z{BX43^$zHu4GZ&vG_3Es5h#Ju(xfzd3vP*Mbua@=rOxYRR z{!2YA^lmIgdi{@niSB$Tx5Vvq*{b6?OM1sGIaSLY5&u?A&na5Rc_tn=wOr3l8e#n; zkxZQ5!rq#+I3s>n+<7b4CzDjs?`P7vx<@v8dSXK78EJ`hXZRs|p8Y}JQv-=bKB>6w)-g0hi~BNbw{3bC)#p6u_)rX z`uE{dp1{oyU**Zg?L4<=#&ZkdDbrq_@Ga#D+H{^-_u`(a7WY({v30QzW7A`g@C5tn z*!kR3RSFISYlE4=eL<(7P7wMBc~&nj;7ulC#c^|+@h=Nsn z(7T;`tFw7xaDoin9`IjT(wGH{c-PqJJx~oi*L>1XhkA5W<9}zrN z=gtb;Yw+&}_4qDntC^{1GyM#%E5Y%`FBJ6?|EU&w)F<LGO6=d4itHl4E*(FBd<$-CeWh zyXIOv-~3-V?%bWTn@2yD=rPuS-(2Yv5k0<>L;cjCSLNB1@ zcg63o_?NYMFRJl%*_p_{%hY?3#Q6An=F&U7ioA+Hd+_f(%l>P>)~n}r?8*38`Z-u@ zI6wO1$y}XBqfH(EzESUUx$j5yYNJP(URhXU?{3zxWS_e)*5f}}^vT4jdOgZDmMQ$( z$5UhJe5Z{S)72~Kqbs6ouGym*RWnv5en;Z0`1f8tL!EYy8Y?Sj{+Ds$cXo`WD^VpL zyW*=w%so~3pCkWgd$mF>{7)N8+Lnx{Yw^Fg(kmC&R(^k|YvSfjriN#9y!4u@pF-Up zy^19BU1Q0%9s`wPJMhBYhiH8C%Hz&*dNynjyvvH7zBf7COcrU4u0m4r(UcNyAxr+F zHXb4UbdVG*j`z|#H$AG0eGBw_m%xhrt2W+?eEt8}`|>b3i)!z8e|vhi?#}E>raMa~ z%&?i=7{eH1K$ZZJ42Xyk10uv25fCHdh=_<0F$P487;_OZK@1TiB4XrXE=EM;LPX>$ zmy293AR;0na>Mugom21D(-ZW0zUTYv6O;5?b?Q{rsZ(dG&HE&3sh6uVP;FqX7^<4u!H*QgEG2vbk`Za7_B-fM{M&Ch8AB0)SxEkja|EB!GxBXse zd4;pWy&2LD(Ij^}(rAl9T6v!WC-h;~=)5r+&ASwB$fow<9t4(>7@&*$*6ad0j#aty;b^= zg}@whz9Q-5IC6yAl&BeZnKj`nw)#LRjTlz~+$xBiDozU0Hlm$C&$h$=52S-Dut&s` z)Kc&OIEMM~lG%4)?Eyt7{nB=AFUBuQi)~t*38nT^NR4LDl1{AQe)PCreu80Q27*40 zSj#CKjQb@x(hxBg_xo{+DVHf-dfnUbua=9`kV1`6v9};9U*CUT|C#+G{fGDO-`~~uO5c-x5A|K&cV6F_eJA$K>zmZq*1Na&nchcx zclU1U-Ozh+?~2~Tdl&Xj>Upu}?w*Z3t9wrE8R=QnGo>fr{bKj-?(N+-bYIhbS@+8B z;qJliQn%OjXxCj`H*{UzbxzmPu6bRB&gVP#bnfU}-?_H)q|ODM6FXk+c(~)PjvXCa zI@Whw(6OfD_>O}++S*@gf3kg7`{wpb+D~s^)_z3$toDiRPTPxZPh#)XuC^_0o7ygI zJEQHCwk2%`x0Tz12`^1}c*5NiwolkH;qnRRPdIhL(h0LBbd~m&9xm-F-BP-`baCm7 z(u&d%rD`cxe6jd=@uuRkVo%}a!uG=Y!pg#Mp|ACY*4?ceTUWPswA|5hS<6Vvfh|t+ zp61({w>58QzOZ?9^NQvpn&&l7YI>pRp{84#u5G%!>4K)!O)Hy@YFdb`c`r9U+xSr9 z&c@A+YZ{j}9@tpOKcBxhzd64)e|Wy5;mL-(8a6jv(r`+{yoOxvk=#vK?Y#{94a(Rl zad&tfc8Q#V{VFrU5+bROV8wlXaA|O1aB{E&k<hS%q$Z{-nqs= zSx(7MJw}aLvfClE>cetng!VF}J4VH(bgK`U*x91x>`t^L4Ox?qlg|_Om5+AHnEBtY z=?AG5MI6+}fH#B|(n7dNMuM76`y5I163!lyMp2GxnF>xjM;;2{O)6@p<*QtX-s6ls z$Cq&l>hg$#!LPV|%E8JncPWs6NKH^a*eX!*H@<{#NJqO1?NDA*%Ti*e?q@L8aj*Q_ z026w%{i5f>OA%nD8}4 z`euApuNyt4KS}$Q*2u%sV%os;;kCcr{txiQSvvtK)Hk|r$k}Gf1>eS8)p`9J`30`@ z)=Me!C-Fr8rXoz?wIWY6CyG%G4Int03Qk}8n333qZYX5#;*VN5r%n+I$FJw^SX6ZtmLb-xI1vl3jEBY?K)GLmY$tvDgYO7D zH8u82xYuHSOS;qt&jq*Ng(ijjH>&=Rpm`M{S!L&}Y;(Vgk*JqLTY&t&2{>!>RXE9&ujUIIg`}bKU{zggA%Q+hVGL?J=uEUplZPY%011&l7UAU!&T2*%ls72R?GGb5o zJ5gTqbTo}DK|Udg(ZwHG3AB4&6E=)t2hr|*<7Isn+{B|^u10Z457qKX{Q?KP$^NKA z|KT$6?x|phHS`+o;e4P>Q_Do{e0tF0VH^62ghWb0livTUcoX_@*6y2r+BV9_9^8NN zk3g%n*J5+^>9uf^{@wP(*BnK$QgBZN^$a;oxrmky$3IOe#y0B1B)w*DUIPX=Rkm~m zw}LP0Lz7>N!S_Yp*K5^3GuEM98MhwkeQ+E4UB4YsG>`jtWDcSAac_ekSO@$Q<^HKQ z2e(rr)*X?0ZR(Zz-W~ogWR;ep#s;LlSyHHNjL+iyJ7`OZAE)3KN1gJg68um`Ih$+Z zg)(kj4Mo7+<{qzs2}c-=eoA~?6HrE9Ks(955J30fZ<%^D#vjGuW9rf zN;2w{$mK9V>sbfatONtjWi=p0qYCQ9O|d?RT=EpyE9Zf#rrWWjpb9zz7su;v~a6LnTUxLZNV*PT!$#*wsP*I z)HNN_PQt?f#k@3Ox$$A6$NV8cHCr6yz(2r7-J@V#6R%K(@7rfG0YyEcuS{J zvm@2}GunVP68r7o0j`x$=NTP~+xfYBAWEaeCY@qh4BKVngpmM#APQA#Fije;%;8PMiA17gvXccJ9~4`30vtID5yBT!kZ^rdvt1I9RC-u-Opi&&O!HwiBDKo~SRmI=r zi!$yNAs+LEx?o&u(W^2Uy0-lQZt(si&K@9EP zR8v1^AE_AC|841KQa@uc@Zxd%BgZ?PUIkBbZd1my_OeGEnTFwvXAC03y-h|S!9lx} zyimgW-1lo*O{vUsB295;@PrKJNODE{02%y)WV&; z0nR(oc4kd?V_f1dU^e3x{vJK1C8Lo(^@%FpD84Dhl)B6#Eoot^9H#bUtSk#-{IN7t zxEC{&P@MHx?Z!<_fqZifwv3a&o_r02EodL%SO+BtEig!|;3tqMb~#)O z%5^70Mn$mf3HT1Tsx`%FD)US$gI$1#pvU0CZDR>8Mu|avypZ6V66Nr^_; zqv3sAIFHz&4V^0tjK;g7ol+-*_lS#oBFNqC_)V@MUi1xjg=t;UY#YEic=AM*RB?77 z+QxYLm|mbPX+m7I+eks|Np!x4TK*AuwnuvAejPMr3GVIzjc^wnSA<(Zb(P`$Xv_Oi zLv5Og2U^D)c#gd6bwHo)Gw`W2q=?RQJ`b7^7vtpB;BF}`GErzm8|&@pKUb-7P7AM? zux9y4I%{eKsH@wdS1ZiGsh7g(N;lXhCmflFtwVq;q*fXVPBz4SdGq!Cmn!5k!)TUuw#-zq&et|Jh%EVlZ+uO-e z5w18rF852!vLqiP8Qu>mo=J&e>ER_ghH}6yql347I0li$9PtS)8hRRP)zjLNSXX#F zB+)+%EiHg`;eEoiK&|qq4#SJiL4Zr+WV_TBZ}A^6^odFu`uPu<-bU#$g`9(Ha$=tk zFIO}`3eldT#l@Ie$=L%r&a{HZn56uQ=;r(izoHb%E3RDVZqg!NOi)|}z?k@j^AKu| z;C01Ip?iX#qI5da%NacGnp?0N?fHkHQGc|S_cK^8|4cc~3hsZAS?&Bly|Qn(*5g?+$O;Cr9au%Tg9!$`xzhFJ~!H%w|MG&s2za!=-V=Wf6m z_0_mBVSn80@C0s@xDC6R*Wt#CCD_H>hub}#3m(PE|4q10WDU-QEyDigHveVpW!Q!D zV3+%A<=piQ>|V~x+3Vfj4(|r<3UAaK!P)DwyB9ke?sTtm*Sf3R@8xJA$ubbTjLSh5{|Y zCddt1Z=~iC<~Ov7TfxYvRmZ4fD#n1X*z2JzcPZ`1gA^?TTxzLPOV#3nyaWc?>EzO} z`IO6<`n+?1Ln8>}0opM=O6bD5ToG`+}_VU8eGkd^)p@u;C@_9OXlE3 zn6+XL0+QB*u^oc1L6cAx8QmFdhVG2Md;M<%KP?aH6Z#J*8BWI+({v{rxii8m78BZ5 zaX$&HoKK8BK)d_f(1X;sw3a%h%{mglHAgK^>O{4i%HFwBJ2j-9--Ir6|0uPqC&E_6 zt1tL%R+sSR23t)$^u#0v7IL2-)2jHRwsc##8hxeSCPgdmCFYB^RgZN)q!9D^aEqiw z&mfyLia@^5_yHw!)!8n6)pLlD&B4Eb(z>c{&eNl1l%4-B_(?^|`v;`o(N{*RS`Wf* z({(IDX7kuf#z%}l;GJh`Q>>~N8MixI=#=1BE0j`%{uJ$zlJ_C$Q3AtX;=jmOcx$b#7OE= zzmdLb10E%`qa6lb!);3(OY?=+kduxE*Vm?%ybnuTvyh}HODr`w>5){el?JDN$)nuPhiz3OKCE*UP-970LV z$@_@(p1P7Hph1GK%ZRBI&~}RMaW0oOX*w-a&curI37JbZslcRrP$s#T3O_KPeJOdQ zwM6vv8;}VftM>%na{nfMiAw>>^d;aqu2|N=YuFB|)5_9lAnp8ai@RYXzSM{W_qUhu zatWi98cV3)%@lHt`ND6Gzmej2FM)YF%Czgr@06KnIo?&!Sdz-}OdcRpr*QES2v60e zv1p85+U9=;Jz>nsSp4p%A)ol&B(!uPH_f7(KJ?LAv#0Sjs!!jo$yi(qbKUE(4tcr zm*})E>Eqb^7=!zqP){LLMyZaVEF1{Gh?%E2OZHb~Tq#45`4f1Q`-nL*jIh$OYqi(e7ul=q#n`j<5>9VDVBKn6jq`&maXZh!;OiT~ z9UA8n367V^xvQP^t3*e9#QzrPnoUz1hb+AZ93C2Q>A-wn=mzQ%&Otiv%TMiW_YG5GmSX+S@k^YjA-zosWLHd>kb zGN{CMa$ah0)la4Fr>{iK9@$YFh5qBi8tLlu^h|`4vx&7<@pb}79Ud}X6lA+ORxQ>I z6Y{0!OLt)p^0lQaN@tf&E-fm}D85p>zj#~m+Tv*OoZ`vFMa3D#w!({rU4>1Bb%k>Z zXB0*XiwosKMI{0#vuP}{;~X>`RnqR=FiL@fisMKc?Wxy?`_!Lu)g8Eh7%j+H{^5A<{r!4m)nKA zsV>IdW=nA6nge_PPTVqgIc~N)7B}-1uxt6rU{7#Yup_uSSR1UwO=DfS2kU;^;B_(X zF*_djZ&sN^kZN{O!FDGNiVYuI;eLjPdK9vy!(Oxc?!@ zYzwtY!F_O-@uW! z_!dd;>g8l@!9!*A>ceF$d}{~jLR*7lA8C00R!L{%u6v%ema9{egF8VvSK@E1$DRdE z#}wK|-ToKFJ|maYkK-;6#E$WD5A7w!4AtuR5^_@CfK>lXWqmpP32KR2LOZ9|Z9*+P zVNa=9@)%=`zoayxaxSr?Q#1VycS##$ZJ2#@KO}A72}n{}{a}tS#}3lmJs+vmOqx&2 zo)!i*fWmhJLTAFb5Gc<+gDcz*3m+;rrVy9TJ92_h#a)}Kkz7k@q&6k_&vF->< z9SF%IcA=#E5!4aE>r7xT#J_Ye5E?2jj;`o5_oHZ6R3cu{6`GW>Bk#ggZlvYa-hln+ zw#XPUq77>07-b_S`2q$%_tlz|Ah)R|C3IU&tD;gOv;AvKy_r`}Q7yP}ZcJ+A39qG~;zH%ZuMzxDK>Z~*4ok>MoQghnhi0?FBS_Q^y z_P%K{|=fQai_Z#{c76^V zNgl280Z4RGn!PPL_h!nnkF*e2dzZZtluXJpa%H}pUu1GjbXoA-m&u_-q7GqAzs%%N z>Z%Mc+FyZn9@V39oG$Adsdh(c$}w_YA2&-T^h)wgOKfsiEk^pJ+}rxix+Up3(F@s9 zw_f&uw~_Qalg#+Trf?=hx3U)67doeD{r5OiOiwjEC8PbXf$B+#R1V6UvGzBkuJK^v zwNvNFOdT3k5f9FpYWl9VlQ!`!PR=4|wFEbP3atJCp8Yd+l=Ot!f_o0SokI;DE4J}U z(EA?%^-9j-e+Ztp0SJeOe}fmw7EiU8!C%#hOC|w6iFheUlf0I_QrewFom8gw7%2R3hot3LEOI!8{ z18eFk^c%H1qmVG#3Y8uG;auo@O%W{|&Vy{%Z~o1Q3@nq7Yv=q%+q|=-Rm{;SR5bfj ztqfO5eN&x}qZ~&In#bolD1UQrF2|2^wFe%wrfM9{tak@r1g{g8sVQAxy%}Hi(i7|M z&7jA=A*jQbU}n~b%23z84)sTHHFD5>D3zy@jyj$auPeAZ)qZk1r4pt1VVFmXk26Je zgKF6|al31Cl&wg`5 zr8hm%mW5<1y&N{ahyR_w*(z2 z(K);zOe}~ANFMZ}I|>fg6xwG4-X8^)Dc7Jjjt4c2t~|z8n5l1qGfnUjhE^?XKJR)- zNsW#3G`N`2N?^CYg^@MCfrD+Ab3@KwFn^PDwTM2B5=oj$bz{Y%pSr_$N^lKi7->m9 zr^F+*XkAkx@jQfD)09I>*=LbLc-{A{v?JeXiLEA=wP{|WFS`A2fL1IUdGKEbKN7Fn z&nkJSmrB7#^fQ|y9O!aSfVQOKeNgI(#;<06I4__VgyaZ_EsbwIi_z}iV6gLy4OfM9 zUr~QJ19cOc>C`XeYSM!dCrVa5F^zSf;4A2_T2WjXVvf>H_p7nzQJv%=S_T!wgL$fk zrDzWTC*If9I~UBiw1YWa{x>0)cp8Z`Aun-#yoz_U$h$SEC`F#FxD&v$-1#D0>+_s# z`~?`{3Hv0pAWDUuhu6ormqp9a?xS={$0k!_0t@lupc|u@OeJlM1>$c7&(gkOs;={N z;QbG*-`X$Y#_W4=2h|q)3i|@A;4a1;RC()VtcKibt+y_)Rv;31pyz-NdJ>1DNxCx% z%s%}FHD+@Jzjqd3vgxc{I1{fEQ-`Q$%M@~4H{Mw$M{0aK@!6tz7p=PgkBkhhMdfEw zBeAwG2X%UdzS>HSxRdi@9&n!lg*l#_Pv8{yZ1ja|P^_E1W8cUFxcdsJ19*Fodoa`a z=H6rXgXnuUhqX=#{x$}qqyw|gd)^!!mD7D3?co4wiOwOawW#)J8SmJUBeZ6MFgn=Zl zvh}31>clp@z^?V_iO9F$t%=9*R>elVtucbvLIS)}a(i%1aCvY+aAvRq@yN2dkM>r) zmT)=V6^!*6yWG3bTjkC2x;ziJ(LRQV?)mPBJB<6@3vSMN z0XNd#BYO|dca}N(gKu8~-`;_n)7IK6?IY~zc9-p-&AVVRZ?rbxePkECsyJO}DJ6wi zX%0Cq{=#pLfr*9SO&waiqMEzpkS}WUNf(^JaCj?rI!|Pc^BIhrdXc1Y z3GY5qijv>u8-LeG#NaaUfI{f5eR{Rz#DRdfVWxA0kMOcLS8^i{lCc++44h9KFXm8M z6>aWV<)|9_19@x2{bLw8Fz5j=?}~;Xa^vI!2)m*#}e*L4~}}Tkq(keZmp#whoc@aaV;%9;B%kR}Nn? z(Da76kAU<4LP_IE8*&V>^y2jeV%0S2ecbP97r}G5rvYzzQjW;pO8iop4M|B{-h(I? z<#A>qo)WARX{hi_lVAR*$fufAX)!HOjH##hk}3qP1s&~b@L5PD^#D`*7opq8f10lM zX`~|(VhaSkU6l~V_Y)d`5B!gVlbLRKCU>k@K>VgngdX}Ap5J~=j$mYk1V*S!RISU!pA>z#r8u446%)+xsXwuS#GLsyOv z%bQhXyiBX~RvpK6If<|3j+1m+d(^bt@1uHi8R$#7t?8L)lC&QBHelq2QeSl*-`tsBk!*MRDB%H40 z@ORPqB6_7|Nn6rDrIX4M%36_^c*mR6U>(ZIY$YMrioK5C?gwNPnaXxx6)N~#CIz$1 z=GhlwK?{S&(Ng}_vzYWNOkGiVyxhXN>qF2gjCQzl&R1N@p8C*bHwUgACr@gNtkaUK z5%_v_s?AR=Uvha*%J}%yj^K?s`-16p8}`{M%D5=%MViv)i8zMm#f${RD-awR&13wV zTBibQ^GdNyqJ30lYjxu!EZ_V#zcYVBenbAk{2JWNIwN1oH#O{Sc)Vd(!)*;)8ZK{G z+prp|n+G-oxyN%m@&3*_yr;7gFF!5Dn^DzVAKtBc7;j?TgcZ)UxSMq;EQ$`iW%d}} zKe`V48P5w=;Xc-CQ1G9_TSj-`J*ErsPSLUc0=y&CgZG{u!Yb#D-qqfv-r3&i-ZJcG zoaI%$CihAAes>#Qk~zm+>Q>!0*c6Y!qS!1qvM#{eF_!(j{ixi=dM#EvSJ=nnKHvrR zELg(@>lHkp{9C-c%iVmGWSrSD0@i)tO|hPGXGyMFwUL(m7W9nd3OsExBVEFYQVw$` z+#r@pFJ9^8N-C`kKesXd9pt_e{|f}WFHI$Cz2x92+&%kVpaw%O4-h0HpVyq1_-#bG}15#=t z4r(guSj<+u=}z35t6mG|dX#%6+A%gaUH&ZTy_Q$&U#T5oSOw0EFr@QSIC8CMX#|r> zM>~=hiLYk;5^_)>Q*iDYLo@FChNQz{L95Q?(j1BW_(N9H(^2_|}4xeeQOC3cBEYwLM+Ni9M$kK>v`|AMHJ!C$HN3 zi7k=jtIYh4aAK0mmT?ZVoZ8FmYbm%0EsgJ>0!H#JXN=B{Pl#0jKD7CQL$r^I`Y_s! zT(RE@$SA94pSdX@E8JPukn-d=>LRd8ul@OL|IJ@`zIC zQ*rq?4S6^dE7UIf=ZfXbUWkSW8Q^Nlb1fgFoUz_?a6kCvh7zr*{7n9CiQdb&;Z zMq(S6U022nbZ|XzuGs3Rfu2UuKTmqX*c9f?v`*=Eby}Klnp)&ENUsSooTC`bAVP5XyBhO{Ya>sYCoWas{V)3$MG=%dteEF zkB23;xqR+S9v@Q-2Z}u{lu$h$?U66p`o5CbZy{e7i1vANEbv+b_93#(qz8G6HVdT# zCF|7iF&TT!;||6*$Otg{QF5qnST0&wBCTWIbB+*hj&f5geZgOa;>>}J_0F^$tWUmz z`sh=U8pifPJ5<+#ul$eTE6SHWDfm{4rQfEl73JyNfG>)Cjuy3IpZ5dQDE-IZJx(X6 z#IHooLQ*@tF(XNpF|<}gQYL9^G3RcdwE(y|SJ={cl~QWtj<+|WJ@s;pu0qWV$AO4< zOga~$X2rp|5>wS$)3~B>N#mTxmi%-1NAtV#x8=9xH)3bt`S~;QC*l^<1^EN9Kd{j7 za>HW{cQ@S9a4k*-t;Q<{3vepP!kwl&b6doY9>Ke+ZMfHTPq-5=(_Vo$a!2s8!4$k} z@I1}~-30sjj9_sv-G2q|L+!?!x)%(?AYxHhmm#L~X{4%;)o7Q)eFD!0td)?lDB=w#oa8 z=V8y_GJ7G$@mcFJ_dVb+6Myme`d<`_Ay?M;SzZTLJob@~5y|LjkEa6?co}2&3>i0# z_te0Lcd5p? zlY`|;+CjYx3GP1wz7s24^4KQI^`etU&zP?B3S90q8c3#a=0`heUchfcs?6Z{@eXOu zMz+rQ3jV41&6Ov$0aWgo`_1v@xhLu6fHIsAEU)(O0OlU8moc->I}OyRla7+4vcxGd zGs#8ivgtWln-|Z<;C%3;(P6=h7$5T+*rKyGJhd35sw8X8y$vxM!C&1mOgfg0%(z^C(7;SQkj4-e$4|BwS0ZKEc z%ldDOul{2Fhz~i315#6H5s`}Vo;{Cepwxng4e6dGSlEu+1dH;adPnP}^~wtz1w;#+ zC&i+T^v&ep!-CD|1Ls|+M`fNy;iGtntid?%L`%}Sq@{_yID9EDomYXZe;3-FO^Y;y z(Ywy6nNsA}0q5PIZ&HdqN4(8hnQ0kQX+P5jM|{ya4YDX*hV}#dNIQvt)2fbi7Uqj{ zzUW6{VL!>cgoNyHPRBSVnBqvO|8AK*Q3+~PwQi`BIO`b)qdkQPl=GepofrkBwJ%XM z=M2=A;HRI<`NdI-^^Eggu}z}7^d1}bKz$3s5?{$V6J-)yoZXCws~^(soR#SbbLkUx zJF7B1VWgaLian*y%lMYg`!YSrXk3TugGud_l=M2@pP{VEC5}PaS(9mx>Ui|Y`9P)) zrKO34I%j8kNK22D7O4!clq-8l3)Bw8Omp<^rHFo{$TV8!&bgTun_Ut9%1n#N&+I4Y z?VOhxE9U6z(zYuM&;O;{ppkWrkCNv74)@;HgW#G2^SSpn$tPDSH*htK`mEx7I5SH) zLM3NyhBCy(y>@18$N5O6r{pcjE|+hiX-$dUYt~oorns+39_K}B&!BTb+|mR_cRDIZ zR;@3@v`iqBS{vfAO6TBA2#byo{5_x65_c4!CE-^fefgUfJY~XU z?{r93O@ZIyvrYHP9LIV{a$*{I5F4V8CmDwTw~|H3f%Ap3ey*;2IMiy4ArzzEWr5q!ABsa zG?z1pmU>+{|5ad3t@3yepnhW+p+rjd=4drlr8V?_``t&~``kO+P3|S`>9A)f!k)bk zuXCM>m1PG};2n4qZVgtItBCwQfOk&U<1E)2>kOPpT@bv0(V&e*n@RX2d=F~XyR!5> zAEs)~=U`{TBM<+IcBVXYn|IywJGR4u^8`mK1??AF*`J|~O2bNy*#{@R6mQR7_Zo1t z3qV!-vtpgq)|BAmh>7K5b1%idYQ~t`p94fK40SE%imstT+Syms!9*F#IJ1eBhhIaP zT1=E)dNT~PNO(h?mS-S94c@R*MdYA9F0M_<`di35PU_VbLqxd3D@6BZ=|w%Odverz zOQ>gj@9^{gR4Fqy;d;)unz0_gu6U%vFKE9LelmPfzl|KX&jX^iCdxgGc(@sOYV#=H zjC2jR)Zw&LendHYeH}cMa23!F@l%@;y|y0elU92J{;ti5ZxZnC5^I#+7`+ErvEH+o zUz9WI5%dOsfvn{>^&RV?O+|PgyP1x%<{+&pq`gmXhE@{ek8%@2_&t$zgl068(TDEv zR*}$}#=kj61L5~mkkR<@H3HpNqdVKw1DJ;McB9zeas<|)_}cPs-P^IhY)VL0-Q0LgkVg;%V{xhY}C0GeP!#XzjGkQnfe)tWCZNvYjBVX^x#rHguNX6m&QSx7QSXk zeUVN#5exK#o;j_nE_37@`k^WqTVmqM#4=1U=^O!A(Ke;r#H4c=4#u z&ta$DZxJ251$*^2dg~#}j`tRObG$a3Ab!NX+r0^Q1&!k6qto4^aE5r$?ZfK&vshif z9X8da&KlembhtCgX|i9oAGhy^b#((y6t6{WaE4vBd+eO`BKA`~WZhz26PynIsB7y5 z?*V;iSMI|e^xlP@>T3LSj0)!pBYUI+Pfv2!lBV(8C4PDLOA7bO(A(#zpMt1$G2l8k zXAkA1tPBM21z%-SkjI%u$qyd#kHh$AKK*posF4A-IR)p~&{PU1BO1`pPJP!vJFr@# z+}?mYYU8ggWUwl3v7wOTLKi8t@;Z6E&EeXMrZSQQx&-e79Dng9ReFuoZslM#{vMNx zI%(TQXKt&u-Uq_6>|@2&`(fg5z*CME?3*zk6q~>yU3%~MVi$9Eiv|nc4@`ukkHB?s z9W7EYc^8ZCITD5%8$I%uf&Qs)l&bM9!_R`J6oQhMvPjOXU?o{sm)Rq77Vj15_cns& z@yeEaGB`@2TmD<658M?)=`GP1{5|0Egqc7fJtMD!HBxr6RS5r8+B_AlrWe;6d;l~T zTMK0v{T8hpoQ)cyRL)XbF05$|YEY@Dw*x7?;6>Stdut9UDAtC5yx`>TI1MR6Q}-Qp z^HjFH19jEl0yO7@i2w)BgEBmaEOd8IL7yb`RSBG$Zl z8Ga13;@7@D8fcvnM6!xpbKLKT;H1?iajCO*~<9Kg`_qkMAvp*)#vyJ4;X~3cO zOcVvwS;#0gB}&OZ5x>+w&caeFp}ihoB&(NCyNy(+cz*ze)IOs{-RhhSd&5NH!q*86 zsH<4No{7SmzR%dTau(0u(+VVo*%Hob^xHoPWyV6(%qe$;lp%+v@sL;OCsf^c0ryxe z#-jG$jylKYvi^SWU!@oH`Ah!E;C#YSYLNaMd*QyYPpGds>P1(qXTya6*v`jlMe7p!_&cspsN24Nh1Pb`J!M>uU<E8v&Q*8740kH3=3sCxjXV=l03d4uX!r_P6ACx z0Z_x{3lRES1zMQ*S+Nm(4y`ST$zBnH# zfwi%)yRfCOp>Sd0q{4ziwcxcr*ZOelU9GpY-q3n&>)O^6Tgxp^;vLZI@e=47yahU^ z#lsyt+nO(JUe&y?xzO}X)2^n?O_w&UX*!~*+W1Q21C4hyZfV@ucxmIx#={${^7`kV z{2kbXx-NfC{`CAwSU;bapO_CCo^9CEu)EI0xFg&YUKp+nhr{VuMSlU-@^+kUSu3|{?vGulFZ%cT z+x+!dNnhbF#V*uAzsnE27qFUspLYjV)33(amNUI2UKwXwp2NwO9qwk_t9d%^)jZst zk5@t`LY_Pfd9uy90(((cImbKmogOD|zkrqW+h8YOfK~Jru$33v^WEbxf9N5Xa$k^n zPV0a)-jMnVa=JFHd13CVR9-Ik#ne}W&(qX`&x|zXroIm?QWmM|19RrXf(S-1U-u0! zJmhl@DI8@ptSz6Xc$pIUzO&6v%UWYp`_@YKJN^-7&Q1H4IjMl8CU zQSJ7kH52kA%3-M~zya&u*(fbx3Bq&t(K1P)7T|1wRC4JPGZGZ106W{QcM=RBpRvkp zKIxR~=G_Px$aRqOQ8O(~jd0QaQIAFK6>4p^7U6AEsEm?K4T!)u8twCrQ&&^yNFarLye1shSy~1hqWg4HT7AkDk^AY&H8M;Grmo($BQV zeiBq?y68{)_n>tAg_=D3iJBDO)>U^?0x*Hi_jS{yLi?$jl$`x^O-h6POifDO{zFYl zqy216N|VhKUFo`LUl^~@KMeCQohN=l_#^PZm{gP!D%#K0_6ixT|0M?8hgfmp{BjuX*gi*HX{|`GBeAIqCYk1 z(q6PM+zH-K*A&gjsHYk?PWnR20o-rDXm~Bc%UMP!+N9L`Tvz#P4V5^f)dN7p)m<*V zXFP&^WfaOp$6Q9lL{=eHn<;|<+(SB@`aJg z0sk$6RbhA@NvVy}xl1rFqa6mB9AFb}$bCv^u4SVrS(kUG%%w~Vo$nh+mB^+?-c^#S z)QNZYhAU+j@r){cm#2hU6;c;%rlpYPo1Z}jk_v4h?{Jh~0uje+ecl?)bi6ZRzhr)+ zZT8<#H`7T6_D7;O_RDB{lB!(&cg&k~T5oV7;G-0BhDPrVZv<}xzWTYn!AS-e*G46d z%(!Cw(Oo@T1Y(VJ(*QJ_eANp)}T4@tuHz58l{wkUd{2QS^gF; z0rWTH8Sob3I_U;pZ)zkDMDScgW{i)1SfsmTJcE;AE%e9?V%v(rJ3w{)HoiePg7#=^ zp=w`%5r{15f!s8-BL0#QpgyREKgRELI(0BL59$t|2QQ`bl=dI3!DRG`ePInIjXQF_ z6rH=#-ZZ9Y?S(sSyTdzc>Y2vz^#2TsuncLZ-YaLedcnNep=|w5YNU*zG}S%7geGnN zBq??Cq!gmb{*jm!GImHIMH$0CO{m6JV@~Iq1wv2UkSdUKIeI*_jku427L?2q`}N3U zDs2k3hck(Jlys??ZO=#AcyPQ=OD!0@XKKGedQcyuX^Xu8Z5j_-(zZ9sh$Wb4+rh)K z7lPB$rDY`uEg!HCsi|8>g0OK!$sVq$I}M}sV?^{#`@ls0hV-@bX3RKZl*i;T%5v%n`nyYN#*A<(@pW&bnJ=K| zVdFybR8MqHkly(!@K3s33C+ zUZzI=*1L=ph`xDx-?dI1lgc~U-)*Ew0-N)XGHp%Lv=Pk(~`4&=TwI7@v+bqsa zsAEhjYUaF!6=NTRp6PxggB_)hk<+Y)=cea!;Zxx~c$eeS@VxL8oMT;pHMgeViQu7N zXRrl3@p)J8;$U7dDad1?{t^Ff+>WuyUxzz;XZd}IAnf*T!cP42y;HE#HWAT-C){1` zweDr^g?N=?u{#OxaXjkW>D+*w18Wd9Sd1O`({ZBsy_FC3EjQhFv{S)fS$CT z4pyIvs~F@^+Q^s@!SR5vmoGZbX2g(lqvBj6c<7x|ldwk4FcUdPY<39g0*QY)&XhpP zCj-u9sXXFkz8RfhIZ8$4YVxEe`UrdHl%8JV!3(*T)&m+&=?Tvq{;Uj=bedCMKH}T*7*GnFC zgi?tyNZQztN4eAL=281q{Q2Mm(k)$%{Taxej=yVj;`p)uhPt_G1r_}Tpg?U|jvb;G z;2_SnDL&89)WUGq(H0Tj@)v@-SqQd>dWoDz&x$rh**`?=Zr)bKaUic<4l0r^@Xx{n zKz+7bPlr+#RC7lHb4?oXQf8>^L=V*($h98KjdB;O#p%)})im*s|AQJ`*cPix?~JF_46sA`sp?Oi4;_ zmN4GA{X0{V`{>03wI2lrmW)#XtGl{LEynHs^EDuR;9yiYi-Vj?sm=1^=lb?z7^~W{ zCJHB||7)N5zWq3$YRfAJ_qp7ur038%AH}W-81<`3LyG7jYqaI2mJKcIS}txmQ(i3y zntzKI3odV7+q|ZES@Znn{hQmGo@%Gr0LO&4KZWu$4iY5%4+tgSrOcwghL#%mid zY&@g!q{exT2R63lU&-H|zdC<*{wSPDEH%7DhWaACvg4NLJ(Sr6VVcro`t z?#A3zx$|<1aHGe|;oaeN;U(cR>^o|~j-#EyX58a(VlWuw{ipl~{9FAk{s!zeI>$f7 zU*u2syKs-k9&e|&*<0(Kgng5Pc&p$s_aXOI_j>noceT3=yNtS=m+)4>PMkp8;9P=L z*wxr&G@q-m_H(d*cHji!1@`GUeK?49SH~Lx#mKoDOXEy02aHS8zM&^e4uq%W9wup& z^OSv_#eCv?*60i63gZ6Z0zgKoI%kx3nWh#gcg;i) zDY+fYM{1PL9Wb1$w5DWCgE!z;{oLLhcMU~(>XUHZQKHg{5SeTHsn&8v=!!9KQ__-@ z2bJ6>@s;{Uo$5=_+bOzF7#y63svSVz@L@;_?uTa-XTbY0>-g1sMae|R_BOfwWbQ6AOb7=2reR_YJ+RW@d$E)A^EO1MF55H;mUuM49I`}H# zbLbPj2)XeKxW!iu7GPG8H-$gF`+$wV)V9j-uCq+atK}M7o(?^*+UNrNVLSq7WZZm8 zyGrmzl&)W1@_~td4?Swk1&97zeQ3(pg`m&4xiy%UkAq1(lHibWFiCC=rV(&`y;_yd z^ji-ar!L_{=S1|ZUU|wl_MFyUH5kUP*MzER*`?#u%Lq%abArg0n(+h%+B4Bx$!`Mh z)~huIQ&Z=O<4{9wYVelxwsHEKSly6~!J={cn}MpS`=oJrjTFRNx z4})3=`r17Lu>xqP;80LXPyexHs#98n#ef=<3T&iqC3y2VxoSNu0bISZkWJ3Nnv&{4 zM7qP&idNHmOm^?F(2^9H?7Rwjt&tdTNBA2V zCyhUF<%7|#qV)stV+?~-rC7|j{!DOZI$!O)F%c&BNNE@9b&mqqQj5^;VxPO+SEVLK zkI?&IDQ0MGp4yVe(f}6XEd{(SD+?+0SwF>yQr7bXYT4#qVt&!%(|Ci#w>+M%kJ4F= zJttpC6e=9VNHKSca~G&dE01GJ3j#dq&q9xrG}WifrDXs(?`^Q<_K^?B%m{~!mhBr} zR#3c?(D!<9)H#eKk;iLrdvCA7tsaD0d8s`z{%lAp=vI%fs;HeEt)@k`%*lYjYA;TE zxaj48cMTx<_BZP#?I;Bq6Ds=L?_QfjUh1*FJE5oaa_Py^J*DlX4W;u+CzckJCKg{V zK2*G~xC1ZxT~a)=xU_h1v9Fjfyi|Ci@IYZ_VMk$OVSVBJ!ik09LSLb!^~KgFTJLV% zCNKJ3(0Wws!L22Px%c2rzbjhKX*s!NQOm(CUCl2wKhk_>^9{`x<3+zk&C{D+YPzrK zCV0n}HLY!0)-f`?0Ppzj##V)$n}7oekGDT!feXPQ{DfGvFn+VCUhU+}*j&c-wDvZW(UBoPsrj7qI7W zTR0l73Ri^l!ZPl@Fvsn}Y3CZ2z0kDJjXGfaq`12=FQYtCBwAT#j0e7_`!k`TmQ@R#+|%t(GHs%t z&b~vAd&^{Qvu9=RzlCnJX<_@>qcR`RJNx6PO;-A(H`KJWG{|9;*Tfy=M!J@~N^2^n zZuk*{o4aO9!GD`P(o4CX9V1omA@oXPBh;TU<{TqgOy}Gr_j5iE z;PndLF{P>G73f*}r=nv_&jHQWY8;PaOG>eCZT4=oKBiC&9I-><1v5c6jy3$TU>52! zzX8peSP47OU#5{mD#2`|^GhR;nRvPTCZwnHND;P%ICdC8wtGn1foAImoqE|1J@!l%qY`##hdSPR{0qogZjL}k0vF!g5BhQg0 z-6KweC+Rar7NsZL!`8%6FY7MBBhuIUG^nlcY_I4PXN%-BrdIN&ODp9!@{L!GoOsWK z#pntxbxM?oZgGs$-wzx#ZZ0s^_(tCGz_KrR@gwaInigW8f_)CgnkBerAv4cx-qDog z7>~g|&$L(JnVRx7Y~ECtl+oJG7g|R(aDB1FKj9BE__^W-U1EPY)1H*g94=4QtZ@d7 zOUgKfOJFDWS$@daCpCITYq@X6oR<}g;7Red$YGp`h$jYfz)$iEX{BH;>W{wwL+gdn z`vIF;LUb#>Vrv1bb8i9%)=5WM&Ungb;=sCzeUG$_^dYy9ZxF4pe=aT3R9$st?2{fY zbrqzqKPYXAzXh{MjBqhVEd>cK8p9Lex1@h84GX~28;$ry54U9BYwFVYd&&Jh+DeS( z8~E6E$^qW6c&+FF&84NL^PDmzqhmszI#w(aTlRXTk1VIQMA@dzs1VW)+&)GfM`=S3 zFWP-5mIi8(U2cG&2HFP%T7i-BpTn#nT%>im=1l8{D>~}eYfaP&7)g)2&C~EY6D!u_ z@Mu$Ggej5<8pn-R_RIc|!Jt}^n%r1PxzEW6spZ5}TF7dZaczy(Q`O>2ovh!q2QV5F z>GuCVCWqs~l3xZ?woH5;-P1h@X?%=$@)RIBui|m1e7Y<-k)!z;8Iz=3+4@Obzi=Yz zB;~LZ7^gBL97zso`)_f(qI9;HbSZ`JlG4OKCJ&`Bi-P}&sZ|d`=4E)Q%(wBdP>zyg zVFiSD#Qjl=vSNJ(%d?)cbvIh1`W5N+$I#~Z3)slv^b|S6%-$(?2`~|cT85l! z?CIbsneAiJ0ihE0_28OxxhQ^I3cDbO+27V2qhb6->zQ&yEkCr@{&#^vo9$nMMx+LJ z*?aiiY&F@ugCa@`kmB19q*8K73GD|{DLmJjv-hM@B zzXA`y{xvW~HFF-(?jdiW<@Up=yhtjL=Rh~-FrlJiWqGwlOEzDEi}2~a+d7_nZHePf zO|G$i)4uSJ&~ccjxv3ZlT0?BLN)YNAjuB@k+7$j;I5G>t)pWIu#i|Z(Li=j5uqJAj zLila;s5X~!9{J(6Iwjd_@?0^zxlS(oBer$8eVknSSf%hg=zDFgyq~u>yroWis(?jn zb38N5QluG z*vo+Xo{ilbOM(M~DM1hRaJ=L{ikDb6;tcGFf1uxj-5XC}_v#(k zzp(+4sPnxuaFg^R?_j*|mUExNUDCJUHtF^5*>ack6zt=81p7Fy!%Z=(oMW9?P8ZJ6 zzbJcHx7(W$k2=pj6Zc>(!Wnu-4qU=e=pLAlBHsc$G^vooNjU1N3c={cn zuBK6v)0g4dCjAxdd@s54Azd^LlF;wOIL2SV#gUQm_a27r$#@WSn0F6yjCCcglbI!mSyq?>AnzsV@4+Y-?)@tz~E z#C1l-IR>o1L(0|W@vX&Iq;+X*nY{)c&oxn#>*(=cvpXZGVho^rf@uOlEhb}U`U^e} zrsFrYQ<6h0TrDXE`vGS*FS2x@Fa2(eKXYll!72!Uh1RFOfmbw~a~wDa@(S~Vel_H< z-vho@o2Ae1Ma%Sr1LqexmQo*Zejo7DrsWsf6b#~9{Q&WKbKH+hDT{fRdlxv|{Dws1 zP2uquey60F#XCSa4=^`<>Pqt)zoOG_l&-~K2(yHF^n|Ecn9A3>x$}r?E!xg@hvz

>*gmXw_*^lG!e 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›X6oq95 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 literal 0 HcmV?d00001 diff --git a/src/web/static/fonts/MaterialIcons-Regular.woff2 b/src/web/static/fonts/MaterialIcons-Regular.woff2 deleted file mode 100644 index 9fa211252080046a23b2449dbdced6abc2b0bb34..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44300 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 QW8}wPUeo^@jR61v08@RD2LJ#7 diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 7811144a..625b81f7 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -6,7 +6,24 @@ * @license Apache-2.0 */ -#input-text, +#input-text { + position: relative; + width: 100%; + height: 100%; + margin: 0; + background-color: transparent; +} + +.cm-editor { + height: 100%; +} + +.cm-editor .cm-content { + font-family: var(--fixed-width-font-family); + font-size: var(--fixed-width-font-size); + color: var(--fixed-width-font-colour); +} + #output-text, #output-html { position: relative; @@ -163,14 +180,14 @@ #input-wrapper, #output-wrapper, -#input-wrapper > * , +#input-wrapper > :not(#input-text), #output-wrapper > .textarea-wrapper > div, #output-wrapper > .textarea-wrapper > textarea { height: calc(100% - var(--title-height)); } #input-wrapper.show-tabs, -#input-wrapper.show-tabs > *, +#input-wrapper.show-tabs > :not(#input-text), #output-wrapper.show-tabs, #output-wrapper.show-tabs > .textarea-wrapper > div, #output-wrapper.show-tabs > .textarea-wrapper > textarea { @@ -193,7 +210,9 @@ } .textarea-wrapper textarea, -.textarea-wrapper>div { +.textarea-wrapper #output-text, +.textarea-wrapper #output-html, +.textarea-wrapper #output-highlighter { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); color: var(--fixed-width-font-colour); @@ -292,10 +311,6 @@ align-items: center; } -#input-info { - line-height: 15px; -} - .dropping-file { border: 5px dashed var(--drop-file-border-colour) !important; } @@ -458,3 +473,73 @@ cursor: pointer; filter: brightness(98%); } + + +/* Status bar */ + +.cm-status-bar { + font-family: var(--fixed-width-font-family); + font-weight: normal; + font-size: 8pt; + margin: 0 5px; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; +} + +.cm-status-bar i { + font-size: 12pt; + vertical-align: middle; + margin-left: 8px; +} +.cm-status-bar>div>span:first-child i { + margin-left: 0; +} + +/* Dropup Button */ +.cm-status-bar-select-btn { + border: none; + cursor: pointer; +} + +/* The container

- needed to position the dropup content */ +.cm-status-bar-select { + position: relative; + display: inline-block; +} + +/* Dropup content (Hidden by Default) */ +.cm-status-bar-select-content { + display: none; + position: absolute; + bottom: 20px; + right: 0; + background-color: #f1f1f1; + min-width: 200px; + box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +/* Links inside the dropup */ +.cm-status-bar-select-content a { + color: black; + padding: 2px 5px; + text-decoration: none; + display: block; +} + +/* Change color of dropup links on hover */ +.cm-status-bar-select-content a:hover { + background-color: #ddd +} + +/* Show the dropup menu on hover */ +.cm-status-bar-select:hover .cm-status-bar-select-content { + display: block; +} + +/* Change the background color of the dropup button when the dropup content is shown */ +.cm-status-bar-select:hover .cm-status-bar-select-btn { + background-color: #f1f1f1; +} diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index c06d3b8c..fa216836 100755 --- a/src/web/stylesheets/utils/_overrides.css +++ b/src/web/stylesheets/utils/_overrides.css @@ -13,7 +13,7 @@ font-family: 'Material Icons'; font-style: normal; font-weight: 400; - src: url("../static/fonts/MaterialIcons-Regular.woff2") format('woff2'); + src: url("../static/fonts/MaterialIcons-Regular.ttf") format('truetype'); } .material-icons { diff --git a/src/web/waiters/ControlsWaiter.mjs b/src/web/waiters/ControlsWaiter.mjs index 5a9533f5..426107bb 100755 --- a/src/web/waiters/ControlsWaiter.mjs +++ b/src/web/waiters/ControlsWaiter.mjs @@ -140,7 +140,7 @@ class ControlsWaiter { const params = [ includeRecipe ? ["recipe", recipeStr] : undefined, - includeInput ? ["input", Utils.escapeHtml(input)] : undefined, + includeInput && input.length ? ["input", Utils.escapeHtml(input)] : undefined, ]; const hash = params diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 664daef8..9f83b55c 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -155,12 +155,11 @@ class HighlighterWaiter { this.mouseTarget = INPUT; this.removeHighlights(); - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightOutput([{start: start, end: end}]); } } @@ -248,12 +247,11 @@ class HighlighterWaiter { this.mouseTarget !== INPUT) return; - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightOutput([{start: start, end: end}]); } } @@ -328,7 +326,6 @@ class HighlighterWaiter { removeHighlights() { document.getElementById("input-highlighter").innerHTML = ""; document.getElementById("output-highlighter").innerHTML = ""; - document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index b421d8d8..e8e71b12 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -7,9 +7,19 @@ import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWorker.js"; import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs"; -import Utils, { debounce } from "../../core/Utils.mjs"; -import { toBase64 } from "../../core/lib/Base64.mjs"; -import { isImage } from "../../core/lib/FileType.mjs"; +import Utils, {debounce} from "../../core/Utils.mjs"; +import {toBase64} from "../../core/lib/Base64.mjs"; +import {isImage} from "../../core/lib/FileType.mjs"; + +import { + EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor +} from "@codemirror/view"; +import {EditorState, Compartment} from "@codemirror/state"; +import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@codemirror/commands"; +import {bracketMatching} from "@codemirror/language"; +import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; + +import {statusBar} from "../extensions/statusBar.mjs"; /** @@ -27,6 +37,9 @@ class InputWaiter { this.app = app; this.manager = manager; + this.inputTextEl = document.getElementById("input-text"); + this.initEditor(); + // Define keys that don't change the input so we don't have to autobake when they are pressed this.badKeys = [ 16, // Shift @@ -61,6 +74,135 @@ class InputWaiter { } } + /** + * Sets up the CodeMirror Editor and returns the view + */ + initEditor() { + this.inputEditorConf = { + eol: new Compartment, + lineWrapping: new Compartment + }; + + const initialState = EditorState.create({ + doc: null, + extensions: [ + history(), + highlightSpecialChars({render: this.renderSpecialChar}), + drawSelection(), + rectangularSelection(), + crosshairCursor(), + bracketMatching(), + highlightSelectionMatches(), + search({top: true}), + statusBar(this.inputEditorConf), + this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), + this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + EditorState.allowMultipleSelections.of(true), + keymap.of([ + // Explicitly insert a tab rather than indenting the line + { key: "Tab", run: insertTab }, + // Explicitly insert a new line (using the current EOL char) rather + // than messing around with indenting, which does not respect EOL chars + { key: "Enter", run: insertNewline }, + ...historyKeymap, + ...defaultKeymap, + ...searchKeymap + ]), + ] + }); + + this.inputEditorView = new EditorView({ + state: initialState, + parent: this.inputTextEl + }); + } + + /** + * Override for rendering special characters. + * Should mirror the toDOM function in + * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 + * But reverts the replacement of line feeds with newline control pictures. + * @param {number} code + * @param {string} desc + * @param {string} placeholder + * @returns {element} + */ + renderSpecialChar(code, desc, placeholder) { + const s = document.createElement("span"); + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. + s.textContent = code === 0x0a ? "\u240a" : placeholder; + s.title = desc; + s.setAttribute("aria-label", desc); + s.className = "cm-specialChar"; + return s; + } + + /** + * Handler for EOL Select clicks + * Sets the line separator + * @param {Event} e + */ + eolSelectClick(e) { + e.preventDefault(); + + const eolLookup = { + "LF": "\u000a", + "VT": "\u000b", + "FF": "\u000c", + "CR": "\u000d", + "CRLF": "\u000d\u000a", + "NEL": "\u0085", + "LS": "\u2028", + "PS": "\u2029" + }; + const eolval = eolLookup[e.target.getAttribute("data-val")]; + const oldInputVal = this.getInput(); + + // Update the EOL value + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval)) + }); + + // Reset the input so that lines are recalculated, preserving the old EOL values + this.setInput(oldInputVal); + } + + /** + * Sets word wrap on the input editor + * @param {boolean} wrap + */ + setWordWrap(wrap) { + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.lineWrapping.reconfigure( + wrap ? EditorView.lineWrapping : [] + ) + }); + } + + /** + * Gets the value of the current input + * @returns {string} + */ + getInput() { + const doc = this.inputEditorView.state.doc; + const eol = this.inputEditorView.state.lineBreak; + return doc.sliceString(0, doc.length, eol); + } + + /** + * Sets the value of the current input + * @param {string} data + */ + setInput(data) { + this.inputEditorView.dispatch({ + changes: { + from: 0, + to: this.inputEditorView.state.doc.length, + insert: data + } + }); + } + /** * Calculates the maximum number of tabs to display */ @@ -339,10 +481,8 @@ class InputWaiter { const activeTab = this.manager.tabs.getActiveInputTab(); if (inputData.inputNum !== activeTab) return; - const inputText = document.getElementById("input-text"); - if (typeof inputData.input === "string") { - inputText.value = inputData.input; + this.setInput(inputData.input); const fileOverlay = document.getElementById("input-file"), fileName = document.getElementById("input-file-name"), fileSize = document.getElementById("input-file-size"), @@ -355,17 +495,11 @@ class InputWaiter { fileType.textContent = ""; fileLoaded.textContent = ""; - inputText.style.overflow = "auto"; - inputText.classList.remove("blur"); - inputText.scroll(0, 0); - - const lines = inputData.input.length < (this.app.options.ioDisplayThreshold * 1024) ? - inputData.input.count("\n") + 1 : null; - this.setInputInfo(inputData.input.length, lines); + this.inputTextEl.classList.remove("blur"); // Set URL to current input const inputStr = toBase64(inputData.input, "A-Za-z0-9+/"); - if (inputStr.length > 0 && inputStr.length <= 68267) { + if (inputStr.length >= 0 && inputStr.length <= 68267) { this.setUrl({ includeInput: true, input: inputStr @@ -414,7 +548,6 @@ class InputWaiter { fileLoaded.textContent = inputData.progress + "%"; } - this.setInputInfo(inputData.size, null); this.displayFilePreview(inputData); if (!silent) window.dispatchEvent(this.manager.statechange); @@ -488,12 +621,10 @@ class InputWaiter { */ displayFilePreview(inputData) { const activeTab = this.manager.tabs.getActiveInputTab(), - input = inputData.input, - inputText = document.getElementById("input-text"); + input = inputData.input; if (inputData.inputNum !== activeTab) return; - inputText.style.overflow = "hidden"; - inputText.classList.add("blur"); - inputText.value = Utils.printable(Utils.arrayBufferToStr(input.slice(0, 4096))); + this.inputTextEl.classList.add("blur"); + this.setInput(Utils.arrayBufferToStr(input.slice(0, 4096))); this.renderFileThumb(); @@ -576,7 +707,7 @@ class InputWaiter { */ async getInputValue(inputNum) { return await new Promise(resolve => { - this.getInput(inputNum, false, r => { + this.getInputFromWorker(inputNum, false, r => { resolve(r.data); }); }); @@ -590,7 +721,7 @@ class InputWaiter { */ async getInputObj(inputNum) { return await new Promise(resolve => { - this.getInput(inputNum, true, r => { + this.getInputFromWorker(inputNum, true, r => { resolve(r.data); }); }); @@ -604,7 +735,7 @@ class InputWaiter { * @param {Function} callback - The callback to execute when the input is returned * @returns {ArrayBuffer | string | object} */ - getInput(inputNum, getObj, callback) { + getInputFromWorker(inputNum, getObj, callback) { const id = this.callbackID++; this.callbacks[id] = callback; @@ -647,29 +778,6 @@ class InputWaiter { }); } - - /** - * Displays information about the input. - * - * @param {number} length - The length of the current input string - * @param {number} lines - The number of the lines in the current input string - */ - setInputInfo(length, lines) { - let width = length.toString().length.toLocaleString(); - width = width < 2 ? 2 : width; - - const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); - let msg = "length: " + lengthStr; - - if (typeof lines === "number") { - const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); - msg += "
lines: " + linesStr; - } - - document.getElementById("input-info").innerHTML = msg; - - } - /** * Handler for input change events. * Debounces the input so we don't call autobake too often. @@ -696,17 +804,13 @@ class InputWaiter { // Remove highlighting from input and output panes as the offsets might be different now this.manager.highlighter.removeHighlights(); - const textArea = document.getElementById("input-text"); - const value = (textArea.value !== undefined) ? textArea.value : ""; + const value = this.getInput(); const activeTab = this.manager.tabs.getActiveInputTab(); this.app.progress = 0; - const lines = value.length < (this.app.options.ioDisplayThreshold * 1024) ? - (value.count("\n") + 1) : null; - this.setInputInfo(value.length, lines); this.updateInputValue(activeTab, value); - this.manager.tabs.updateInputTabHeader(activeTab, value.replace(/[\n\r]/g, "").slice(0, 100)); + this.manager.tabs.updateInputTabHeader(activeTab, value.slice(0, 100).replace(/[\n\r]/g, "")); if (e && this.badKeys.indexOf(e.keyCode) < 0) { // Fire the statechange event as the input has been modified @@ -714,62 +818,6 @@ class InputWaiter { } } - /** - * Handler for input paste events - * Checks that the size of the input is below the display limit, otherwise treats it as a file/blob - * - * @param {event} e - */ - async inputPaste(e) { - e.preventDefault(); - e.stopPropagation(); - - const self = this; - /** - * Triggers the input file/binary data overlay - * - * @param {string} pastedData - */ - function triggerOverlay(pastedData) { - const file = new File([pastedData], "PastedData", { - type: "text/plain", - lastModified: Date.now() - }); - - self.loadUIFiles([file]); - } - - const pastedData = e.clipboardData.getData("Text"); - const inputText = document.getElementById("input-text"); - const selStart = inputText.selectionStart; - const selEnd = inputText.selectionEnd; - const startVal = inputText.value.slice(0, selStart); - const endVal = inputText.value.slice(selEnd); - const val = startVal + pastedData + endVal; - - if (val.length >= (this.app.options.ioDisplayThreshold * 1024)) { - // Data too large to display, use overlay - triggerOverlay(val); - return false; - } else if (await this.preserveCarriageReturns(val)) { - // Data contains a carriage return and the user doesn't wish to edit it, use overlay - // We check this in a separate condition to make sure it is not run unless absolutely - // necessary. - triggerOverlay(val); - return false; - } else { - // Pasting normally fires the inputChange() event before - // changing the value, so instead change it here ourselves - // and manually fire inputChange() - inputText.value = val; - inputText.setSelectionRange(selStart + pastedData.length, selStart + pastedData.length); - // Don't debounce here otherwise the keyup event for the Ctrl key will cancel an autobake - // (at least for large inputs) - this.inputChange(e, true); - } - } - - /** * Handler for input dragover events. * Gives the user a visual cue to show that items can be dropped here. @@ -818,7 +866,7 @@ class InputWaiter { if (text) { // Append the text to the current input and fire inputChange() - document.getElementById("input-text").value += text; + this.setInput(this.getInput() + text); this.inputChange(e); return; } @@ -843,44 +891,6 @@ class InputWaiter { } } - /** - * Checks if an input contains carriage returns. - * If a CR is detected, checks if the preserve CR option has been set, - * and if not, asks the user for their preference. - * - * @param {string} input - The input to be checked - * @returns {boolean} - If true, the input contains a CR which should be - * preserved, so display an overlay so it can't be edited - */ - async preserveCarriageReturns(input) { - if (input.indexOf("\r") < 0) return false; - - const optionsStr = "This behaviour can be changed in the
Options pane"; - const preserveStr = `A carriage return (\\r, 0x0d) was detected in your input. To preserve it, editing has been disabled.
${optionsStr}`; - const dontPreserveStr = `A carriage return (\\r, 0x0d) was detected in your input. It has not been preserved.
${optionsStr}`; - - switch (this.app.options.preserveCR) { - case "always": - this.app.alert(preserveStr, 6000); - return true; - case "never": - this.app.alert(dontPreserveStr, 6000); - return false; - } - - // Only preserve for high-entropy inputs - const data = Utils.strToArrayBuffer(input); - const entropy = Utils.calculateShannonEntropy(data); - - if (entropy > 6) { - this.app.alert(preserveStr, 6000); - return true; - } - - this.app.alert(dontPreserveStr, 6000); - return false; - } - /** * Load files from the UI into the inputWorker * @@ -1080,6 +1090,9 @@ class InputWaiter { this.manager.worker.setupChefWorker(); this.addInput(true); this.bakeAll(); + + // Fire the statechange event as the input has been modified + window.dispatchEvent(this.manager.statechange); } /** diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 5ef517d4..52b81ab4 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -53,6 +53,9 @@ class OptionsWaiter { selects[i].selectedIndex = 0; } } + + // Initialise options + this.setWordWrap(); } @@ -136,14 +139,13 @@ class OptionsWaiter { * Sets or unsets word wrap on the input and output depending on the wordWrap option value. */ setWordWrap() { - document.getElementById("input-text").classList.remove("word-wrap"); + this.manager.input.setWordWrap(this.app.options.wordWrap); document.getElementById("output-text").classList.remove("word-wrap"); document.getElementById("output-html").classList.remove("word-wrap"); document.getElementById("input-highlighter").classList.remove("word-wrap"); document.getElementById("output-highlighter").classList.remove("word-wrap"); if (!this.app.options.wordWrap) { - document.getElementById("input-text").classList.add("word-wrap"); document.getElementById("output-text").classList.add("word-wrap"); document.getElementById("output-html").classList.add("word-wrap"); document.getElementById("input-highlighter").classList.add("word-wrap"); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 0eb6baec..8996edb0 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -1019,7 +1019,6 @@ class OutputWaiter { } document.getElementById("output-info").innerHTML = msg; - document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; } @@ -1292,9 +1291,7 @@ class OutputWaiter { if (this.outputs[activeTab].data.type === "string" && active.byteLength <= this.app.options.ioDisplayThreshold * 1024) { const dishString = await this.getDishStr(this.getOutputDish(activeTab)); - if (!await this.manager.input.preserveCarriageReturns(dishString)) { - active = dishString; - } + active = dishString; } else { transferable.push(active); } diff --git a/tests/browser/nightwatch.js b/tests/browser/nightwatch.js index 41aff9b2..ba6f5204 100644 --- a/tests/browser/nightwatch.js +++ b/tests/browser/nightwatch.js @@ -82,7 +82,7 @@ module.exports = { // Enter input browser .useCss() - .setValue("#input-text", "Don't Panic.") + .setValue("#input-text", "Don't Panic.") // TODO .pause(1000) .click("#bake"); diff --git a/tests/browser/ops.js b/tests/browser/ops.js index bb18dc5d..d0933bb6 100644 --- a/tests/browser/ops.js +++ b/tests/browser/ops.js @@ -409,16 +409,16 @@ function bakeOp(browser, opName, input, args=[]) { .click("#clr-recipe") .click("#clr-io") .waitForElementNotPresent("#rec-list li.operation") - .expect.element("#input-text").to.have.property("value").that.equals(""); + .expect.element("#input-text").to.have.property("value").that.equals(""); // TODO browser .perform(function() { console.log(`Current test: ${opName}`); }) .urlHash("recipe=" + recipeConfig) - .setValue("#input-text", input) + .setValue("#input-text", input) // TODO .waitForElementPresent("#rec-list li.operation") - .expect.element("#input-text").to.have.property("value").that.equals(input); + .expect.element("#input-text").to.have.property("value").that.equals(input); // TODO browser .waitForElementVisible("#stale-indicator", 5000) From bc949b47d918fd77142c7fd22c086f5795d1a522 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 1 Jul 2022 12:01:48 +0100 Subject: [PATCH 017/228] Improved Controls CSS --- src/web/App.mjs | 1 + src/web/stylesheets/layout/_controls.css | 16 +++++----------- src/web/stylesheets/layout/_recipe.css | 1 - src/web/waiters/ControlsWaiter.mjs | 11 +++++++++++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/web/App.mjs b/src/web/App.mjs index 9d4813e0..2d45d1f1 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -589,6 +589,7 @@ class App { this.manager.recipe.adjustWidth(); this.manager.input.calcMaxTabs(); this.manager.output.calcMaxTabs(); + this.manager.controls.calcControlsHeight(); } diff --git a/src/web/stylesheets/layout/_controls.css b/src/web/stylesheets/layout/_controls.css index c410704b..1edc41b5 100755 --- a/src/web/stylesheets/layout/_controls.css +++ b/src/web/stylesheets/layout/_controls.css @@ -6,27 +6,20 @@ * @license Apache-2.0 */ -:root { - --controls-height: 75px; -} - #controls { position: absolute; width: 100%; - height: var(--controls-height); bottom: 0; - padding: 0; - padding-top: 12px; + padding: 10px 0; border-top: 1px solid var(--primary-border-colour); background-color: var(--secondary-background-colour); } #controls-content { position: relative; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - transform-origin: center left; + display: flex; + flex-flow: row nowrap; + align-items: center; } #auto-bake-label { @@ -56,6 +49,7 @@ #controls .btn { border-radius: 30px; + margin: 0; } .output-maximised .hide-on-maximised-output { diff --git a/src/web/stylesheets/layout/_recipe.css b/src/web/stylesheets/layout/_recipe.css index bd70d10f..339da074 100755 --- a/src/web/stylesheets/layout/_recipe.css +++ b/src/web/stylesheets/layout/_recipe.css @@ -7,7 +7,6 @@ */ #rec-list { - bottom: var(--controls-height); overflow: auto; } diff --git a/src/web/waiters/ControlsWaiter.mjs b/src/web/waiters/ControlsWaiter.mjs index 426107bb..2879089a 100755 --- a/src/web/waiters/ControlsWaiter.mjs +++ b/src/web/waiters/ControlsWaiter.mjs @@ -410,6 +410,17 @@ ${navigator.userAgent} } } + /** + * Calculates the height of the controls area and adjusts the recipe + * height accordingly. + */ + calcControlsHeight() { + const controls = document.getElementById("controls"), + recList = document.getElementById("rec-list"); + + recList.style.bottom = controls.clientHeight + "px"; + } + } export default ControlsWaiter; From 68733c74cc5dd5067d750c28e32708e9e7a280a0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Sat, 2 Jul 2022 19:23:03 +0100 Subject: [PATCH 018/228] Output now uses CodeMirror editor --- src/core/Utils.mjs | 2 +- src/web/Manager.mjs | 4 - src/web/extensions/statusBar.mjs | 190 ----------------- src/web/html/index.html | 7 +- src/web/stylesheets/layout/_io.css | 48 +---- src/web/utils/editorUtils.mjs | 28 +++ src/web/utils/htmlWidget.mjs | 87 ++++++++ src/web/utils/statusBar.mjs | 271 ++++++++++++++++++++++++ src/web/waiters/HighlighterWaiter.mjs | 173 ++++++---------- src/web/waiters/InputWaiter.mjs | 48 +---- src/web/waiters/OptionsWaiter.mjs | 5 +- src/web/waiters/OutputWaiter.mjs | 285 +++++++++++++++++--------- tests/browser/nightwatch.js | 4 +- tests/browser/ops.js | 8 +- 14 files changed, 665 insertions(+), 495 deletions(-) delete mode 100644 src/web/extensions/statusBar.mjs create mode 100644 src/web/utils/editorUtils.mjs create mode 100644 src/web/utils/htmlWidget.mjs create mode 100644 src/web/utils/statusBar.mjs diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 66a98c36..5f36cae9 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -424,7 +424,7 @@ class Utils { const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { - if (isWorkerEnvironment()) { + if (isWorkerEnvironment() && self && typeof self.setOption === "function") { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { window.app.options.attemptHighlight = false; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 08a35d75..2477bb60 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -178,7 +178,6 @@ class Manager { this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); - this.addDynamicListener(".eol-select a", "click", this.input.eolSelectClick, this.input); // Output @@ -192,10 +191,7 @@ class Manager { document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); - this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); this.addDynamicListener("#output-file-show-all", "click", this.output.showAllFile, this.output); this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output); diff --git a/src/web/extensions/statusBar.mjs b/src/web/extensions/statusBar.mjs deleted file mode 100644 index 8a837a51..00000000 --- a/src/web/extensions/statusBar.mjs +++ /dev/null @@ -1,190 +0,0 @@ -/** - * A Status bar extension for CodeMirror - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2022 - * @license Apache-2.0 - */ - -import {showPanel} from "@codemirror/view"; - -/** - * Counts the stats of a document - * @param {element} el - * @param {Text} doc - */ -function updateStats(el, doc) { - const length = el.querySelector("#stats-length-value"), - lines = el.querySelector("#stats-lines-value"); - length.textContent = doc.length; - lines.textContent = doc.lines; -} - -/** - * Gets the current selection info - * @param {element} el - * @param {EditorState} state - * @param {boolean} selectionSet - */ -function updateSelection(el, state, selectionSet) { - const selLen = state.selection && state.selection.main ? - state.selection.main.to - state.selection.main.from : - 0; - - const selInfo = el.querySelector("#sel-info"), - curOffsetInfo = el.querySelector("#cur-offset-info"); - - if (!selectionSet) { - selInfo.style.display = "none"; - curOffsetInfo.style.display = "none"; - return; - } - - if (selLen > 0) { // Range - const start = el.querySelector("#sel-start-value"), - end = el.querySelector("#sel-end-value"), - length = el.querySelector("#sel-length-value"); - - selInfo.style.display = "inline-block"; - curOffsetInfo.style.display = "none"; - - start.textContent = state.selection.main.from; - end.textContent = state.selection.main.to; - length.textContent = state.selection.main.to - state.selection.main.from; - } else { // Position - const offset = el.querySelector("#cur-offset-value"); - - selInfo.style.display = "none"; - curOffsetInfo.style.display = "inline-block"; - - offset.textContent = state.selection.main.from; - } -} - -/** - * Gets the current character encoding of the document - * @param {element} el - * @param {EditorState} state - */ -function updateCharEnc(el, state) { - // const charenc = el.querySelector("#char-enc-value"); - // TODO - // charenc.textContent = "TODO"; -} - -/** - * Returns what the current EOL separator is set to - * @param {element} el - * @param {EditorState} state - */ -function updateEOL(el, state) { - const eolLookup = { - "\u000a": "LF", - "\u000b": "VT", - "\u000c": "FF", - "\u000d": "CR", - "\u000d\u000a": "CRLF", - "\u0085": "NEL", - "\u2028": "LS", - "\u2029": "PS" - }; - - const val = el.querySelector("#eol-value"); - val.textContent = eolLookup[state.lineBreak]; -} - -/** - * Builds the Left-hand-side widgets - * @returns {string} - */ -function constructLHS() { - return ` - abc - - - - sort - - - - - highlight_alt - \u279E - ( selected) - - - location_on - - `; -} - -/** - * Builds the Right-hand-side widgets - * Event listener set up in Manager - * @returns {string} - */ -function constructRHS() { - return ` - language - UTF-16 - - - `; -} - -/** - * A panel constructor building a panel that re-counts the stats every time the document changes. - * @param {EditorView} view - * @returns {Panel} - */ -function wordCountPanel(view) { - const dom = document.createElement("div"); - const lhs = document.createElement("div"); - const rhs = document.createElement("div"); - - dom.className = "cm-status-bar"; - lhs.innerHTML = constructLHS(); - rhs.innerHTML = constructRHS(); - - dom.appendChild(lhs); - dom.appendChild(rhs); - - updateEOL(rhs, view.state); - updateCharEnc(rhs, view.state); - updateStats(lhs, view.state.doc); - updateSelection(lhs, view.state, false); - - return { - dom, - update(update) { - updateEOL(rhs, update.state); - updateSelection(lhs, update.state, update.selectionSet); - updateCharEnc(rhs, update.state); - if (update.docChanged) { - updateStats(lhs, update.state.doc); - } - } - }; -} - -/** - * A function that build the extension that enables the panel in an editor. - * @returns {Extension} - */ -export function statusBar() { - return showPanel.of(wordCountPanel); -} diff --git a/src/web/html/index.html b/src/web/html/index.html index 3d237bdd..3eb150e5 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -191,7 +191,7 @@
    -
    +
    @@ -289,8 +289,6 @@
    -
    -
    @@ -344,8 +342,7 @@
    -
    - +
    diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 625b81f7..ba670f3d 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -6,7 +6,8 @@ * @license Apache-2.0 */ -#input-text { +#input-text, +#output-text { position: relative; width: 100%; height: 100%; @@ -24,23 +25,6 @@ color: var(--fixed-width-font-colour); } -#output-text, -#output-html { - position: relative; - width: 100%; - height: 100%; - margin: 0; - padding: 3px; - -moz-padding-start: 3px; - -moz-padding-end: 3px; - border: none; - border-width: 0px; - resize: none; - background-color: transparent; - white-space: pre-wrap; - word-wrap: break-word; -} - #output-wrapper{ margin: 0; padding: 0; @@ -54,13 +38,6 @@ pointer-events: auto; } - -#output-html { - display: none; - overflow-y: auto; - -moz-padding-start: 1px; /* Fixes bug in Firefox */ -} - #input-tabs-wrapper #input-tabs, #output-tabs-wrapper #output-tabs { list-style: none; @@ -179,25 +156,15 @@ } #input-wrapper, -#output-wrapper, -#input-wrapper > :not(#input-text), -#output-wrapper > .textarea-wrapper > div, -#output-wrapper > .textarea-wrapper > textarea { +#output-wrapper { height: calc(100% - var(--title-height)); } #input-wrapper.show-tabs, -#input-wrapper.show-tabs > :not(#input-text), -#output-wrapper.show-tabs, -#output-wrapper.show-tabs > .textarea-wrapper > div, -#output-wrapper.show-tabs > .textarea-wrapper > textarea { +#output-wrapper.show-tabs { height: calc(100% - var(--tab-height) - var(--title-height)); } -#output-wrapper > .textarea-wrapper > #output-html { - height: 100%; -} - #show-file-overlay { height: 32px; } @@ -211,7 +178,6 @@ .textarea-wrapper textarea, .textarea-wrapper #output-text, -.textarea-wrapper #output-html, .textarea-wrapper #output-highlighter { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); @@ -477,6 +443,12 @@ /* Status bar */ +.ͼ2 .cm-panels { + background-color: var(--secondary-background-colour); + border-color: var(--secondary-border-colour); + color: var(--primary-font-colour); +} + .cm-status-bar { font-family: var(--fixed-width-font-family); font-weight: normal; diff --git a/src/web/utils/editorUtils.mjs b/src/web/utils/editorUtils.mjs new file mode 100644 index 00000000..fe6b83d4 --- /dev/null +++ b/src/web/utils/editorUtils.mjs @@ -0,0 +1,28 @@ +/** + * CodeMirror utilities that are relevant to both the input and output + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + + +/** + * Override for rendering special characters. + * Should mirror the toDOM function in + * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 + * But reverts the replacement of line feeds with newline control pictures. + * @param {number} code + * @param {string} desc + * @param {string} placeholder + * @returns {element} + */ +export function renderSpecialChar(code, desc, placeholder) { + const s = document.createElement("span"); + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. + s.textContent = code === 0x0a ? "\u240a" : placeholder; + s.title = desc; + s.setAttribute("aria-label", desc); + s.className = "cm-specialChar"; + return s; +} diff --git a/src/web/utils/htmlWidget.mjs b/src/web/utils/htmlWidget.mjs new file mode 100644 index 00000000..fbce9b49 --- /dev/null +++ b/src/web/utils/htmlWidget.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view"; + +/** + * Adds an HTML widget to the Code Mirror editor + */ +class HTMLWidget extends WidgetType { + + /** + * HTMLWidget consructor + */ + constructor(html) { + super(); + this.html = html; + } + + /** + * Builds the DOM node + * @returns {DOMNode} + */ + toDOM() { + const wrap = document.createElement("span"); + wrap.setAttribute("id", "output-html"); + wrap.innerHTML = this.html; + return wrap; + } + +} + +/** + * Decorator function to provide a set of widgets for the editor DOM + * @param {EditorView} view + * @param {string} html + * @returns {DecorationSet} + */ +function decorateHTML(view, html) { + const widgets = []; + if (html.length) { + const deco = Decoration.widget({ + widget: new HTMLWidget(html), + side: 1 + }); + widgets.push(deco.range(0)); + } + return Decoration.set(widgets); +} + + +/** + * An HTML Plugin builder + * @param {Object} htmlOutput + * @returns {ViewPlugin} + */ +export function htmlPlugin(htmlOutput) { + const plugin = ViewPlugin.fromClass( + class { + /** + * Plugin constructor + * @param {EditorView} view + */ + constructor(view) { + this.htmlOutput = htmlOutput; + this.decorations = decorateHTML(view, this.htmlOutput.html); + } + + /** + * Editor update listener + * @param {ViewUpdate} update + */ + update(update) { + if (this.htmlOutput.changed) { + this.decorations = decorateHTML(update.view, this.htmlOutput.html); + this.htmlOutput.changed = false; + } + } + }, { + decorations: v => v.decorations + } + ); + + return plugin; +} diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs new file mode 100644 index 00000000..431d8a3d --- /dev/null +++ b/src/web/utils/statusBar.mjs @@ -0,0 +1,271 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {showPanel} from "@codemirror/view"; + +/** + * A Status bar extension for CodeMirror + */ +class StatusBarPanel { + + /** + * StatusBarPanel constructor + * @param {Object} opts + */ + constructor(opts) { + this.label = opts.label; + this.bakeStats = opts.bakeStats ? opts.bakeStats : null; + this.eolHandler = opts.eolHandler; + + this.dom = this.buildDOM(); + } + + /** + * Builds the status bar DOM tree + * @returns {DOMNode} + */ + buildDOM() { + const dom = document.createElement("div"); + const lhs = document.createElement("div"); + const rhs = document.createElement("div"); + + dom.className = "cm-status-bar"; + lhs.innerHTML = this.constructLHS(); + rhs.innerHTML = this.constructRHS(); + + dom.appendChild(lhs); + dom.appendChild(rhs); + + // Event listeners + dom.addEventListener("click", this.eolSelectClick.bind(this), false); + + return dom; + } + + /** + * Handler for EOL Select clicks + * Sets the line separator + * @param {Event} e + */ + eolSelectClick(e) { + e.preventDefault(); + + const eolLookup = { + "LF": "\u000a", + "VT": "\u000b", + "FF": "\u000c", + "CR": "\u000d", + "CRLF": "\u000d\u000a", + "NEL": "\u0085", + "LS": "\u2028", + "PS": "\u2029" + }; + const eolval = eolLookup[e.target.getAttribute("data-val")]; + + // Call relevant EOL change handler + this.eolHandler(eolval); + } + + /** + * Counts the stats of a document + * @param {Text} doc + */ + updateStats(doc) { + const length = this.dom.querySelector(".stats-length-value"), + lines = this.dom.querySelector(".stats-lines-value"); + length.textContent = doc.length; + lines.textContent = doc.lines; + } + + /** + * Gets the current selection info + * @param {EditorState} state + * @param {boolean} selectionSet + */ + updateSelection(state, selectionSet) { + const selLen = state.selection && state.selection.main ? + state.selection.main.to - state.selection.main.from : + 0; + + const selInfo = this.dom.querySelector(".sel-info"), + curOffsetInfo = this.dom.querySelector(".cur-offset-info"); + + if (!selectionSet) { + selInfo.style.display = "none"; + curOffsetInfo.style.display = "none"; + return; + } + + if (selLen > 0) { // Range + const start = this.dom.querySelector(".sel-start-value"), + end = this.dom.querySelector(".sel-end-value"), + length = this.dom.querySelector(".sel-length-value"); + + selInfo.style.display = "inline-block"; + curOffsetInfo.style.display = "none"; + + start.textContent = state.selection.main.from; + end.textContent = state.selection.main.to; + length.textContent = state.selection.main.to - state.selection.main.from; + } else { // Position + const offset = this.dom.querySelector(".cur-offset-value"); + + selInfo.style.display = "none"; + curOffsetInfo.style.display = "inline-block"; + + offset.textContent = state.selection.main.from; + } + } + + /** + * Gets the current character encoding of the document + * @param {EditorState} state + */ + updateCharEnc(state) { + // const charenc = this.dom.querySelector("#char-enc-value"); + // TODO + // charenc.textContent = "TODO"; + } + + /** + * Returns what the current EOL separator is set to + * @param {EditorState} state + */ + updateEOL(state) { + const eolLookup = { + "\u000a": "LF", + "\u000b": "VT", + "\u000c": "FF", + "\u000d": "CR", + "\u000d\u000a": "CRLF", + "\u0085": "NEL", + "\u2028": "LS", + "\u2029": "PS" + }; + + const val = this.dom.querySelector(".eol-value"); + val.textContent = eolLookup[state.lineBreak]; + } + + /** + * Sets the latest bake duration + */ + updateBakeStats() { + const bakingTime = this.dom.querySelector(".baking-time-value"); + const bakingTimeInfo = this.dom.querySelector(".baking-time-info"); + + if (this.label === "Output" && + this.bakeStats && + typeof this.bakeStats.duration === "number" && + this.bakeStats.duration >= 0) { + bakingTimeInfo.style.display = "inline-block"; + bakingTime.textContent = this.bakeStats.duration; + } else { + bakingTimeInfo.style.display = "none"; + } + } + + /** + * Builds the Left-hand-side widgets + * @returns {string} + */ + constructLHS() { + return ` + + abc + + + + sort + + + + + highlight_alt + \u279E + ( selected) + + + location_on + + `; + } + + /** + * Builds the Right-hand-side widgets + * Event listener set up in Manager + * @returns {string} + */ + constructRHS() { + return ` + + + + language + UTF-16 + + + `; + } + +} + +/** + * A panel constructor factory building a panel that re-counts the stats every time the document changes. + * @param {Object} opts + * @returns {Function} + */ +function makePanel(opts) { + const sbPanel = new StatusBarPanel(opts); + + return (view) => { + sbPanel.updateEOL(view.state); + sbPanel.updateCharEnc(view.state); + sbPanel.updateBakeStats(); + sbPanel.updateStats(view.state.doc); + sbPanel.updateSelection(view.state, false); + + return { + "dom": sbPanel.dom, + update(update) { + sbPanel.updateEOL(update.state); + sbPanel.updateSelection(update.state, update.selectionSet); + sbPanel.updateCharEnc(update.state); + sbPanel.updateBakeStats(); + if (update.docChanged) { + sbPanel.updateStats(update.state.doc); + } + } + }; + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @param {Object} opts + * @returns {Extension} + */ +export function statusBar(opts) { + const panelMaker = makePanel(opts); + return showPanel.of(panelMaker); +} diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 9f83b55c..d1340165 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -176,34 +176,16 @@ class HighlighterWaiter { this.mouseTarget = OUTPUT; this.removeHighlights(); - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightInput([{start: start, end: end}]); } } - /** - * Handler for output HTML mousedown events. - * Calculates the current selection info. - * - * @param {event} e - */ - outputHtmlMousedown(e) { - this.mouseButtonDown = true; - this.mouseTarget = OUTPUT; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } - } - - /** * Handler for input mouseup events. * @@ -224,16 +206,6 @@ class HighlighterWaiter { } - /** - * Handler for output HTML mouseup events. - * - * @param {event} e - */ - outputHtmlMouseup(e) { - this.mouseButtonDown = false; - } - - /** * Handler for input mousemove events. * Calculates the current selection info, and highlights the corresponding data in the output. @@ -270,37 +242,16 @@ class HighlighterWaiter { this.mouseTarget !== OUTPUT) return; - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightInput([{start: start, end: end}]); } } - /** - * Handler for output HTML mousemove events. - * Calculates the current selection info. - * - * @param {event} e - */ - outputHtmlMousemove(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== OUTPUT) - return; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } - } - - /** * Given start and end offsets, writes the HTML for the selection info element with the correct * padding. @@ -326,7 +277,6 @@ class HighlighterWaiter { removeHighlights() { document.getElementById("input-highlighter").innerHTML = ""; document.getElementById("output-highlighter").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; } @@ -379,7 +329,8 @@ class HighlighterWaiter { const io = direction === "forward" ? "output" : "input"; - document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); + // TODO + // document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); this.highlight( document.getElementById(io + "-text"), document.getElementById(io + "-highlighter"), @@ -398,67 +349,67 @@ class HighlighterWaiter { * @param {number} pos.end - The end offset. */ async highlight(textarea, highlighter, pos) { - if (!this.app.options.showHighlighter) return false; - if (!this.app.options.attemptHighlight) return false; + // if (!this.app.options.showHighlighter) return false; + // if (!this.app.options.attemptHighlight) return false; - // Check if there is a carriage return in the output dish as this will not - // be displayed by the HTML textarea and will mess up highlighting offsets. - if (await this.manager.output.containsCR()) return false; + // // Check if there is a carriage return in the output dish as this will not + // // be displayed by the HTML textarea and will mess up highlighting offsets. + // if (await this.manager.output.containsCR()) return false; - const startPlaceholder = "[startHighlight]"; - const startPlaceholderRegex = /\[startHighlight\]/g; - const endPlaceholder = "[endHighlight]"; - const endPlaceholderRegex = /\[endHighlight\]/g; - let text = textarea.value; + // const startPlaceholder = "[startHighlight]"; + // const startPlaceholderRegex = /\[startHighlight\]/g; + // const endPlaceholder = "[endHighlight]"; + // const endPlaceholderRegex = /\[endHighlight\]/g; + // // let text = textarea.value; // TODO - // Put placeholders in position - // If there's only one value, select that - // If there are multiple, ignore the first one and select all others - if (pos.length === 1) { - if (pos[0].end < pos[0].start) return; - text = text.slice(0, pos[0].start) + - startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + - text.slice(pos[0].end, text.length); - } else { - // O(n^2) - Can anyone improve this without overwriting placeholders? - let result = "", - endPlaced = true; + // // Put placeholders in position + // // If there's only one value, select that + // // If there are multiple, ignore the first one and select all others + // if (pos.length === 1) { + // if (pos[0].end < pos[0].start) return; + // text = text.slice(0, pos[0].start) + + // startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + + // text.slice(pos[0].end, text.length); + // } else { + // // O(n^2) - Can anyone improve this without overwriting placeholders? + // let result = "", + // endPlaced = true; - for (let i = 0; i < text.length; i++) { - for (let j = 1; j < pos.length; j++) { - if (pos[j].end < pos[j].start) continue; - if (pos[j].start === i) { - result += startPlaceholder; - endPlaced = false; - } - if (pos[j].end === i) { - result += endPlaceholder; - endPlaced = true; - } - } - result += text[i]; - } - if (!endPlaced) result += endPlaceholder; - text = result; - } + // for (let i = 0; i < text.length; i++) { + // for (let j = 1; j < pos.length; j++) { + // if (pos[j].end < pos[j].start) continue; + // if (pos[j].start === i) { + // result += startPlaceholder; + // endPlaced = false; + // } + // if (pos[j].end === i) { + // result += endPlaceholder; + // endPlaced = true; + // } + // } + // result += text[i]; + // } + // if (!endPlaced) result += endPlaceholder; + // text = result; + // } - const cssClass = "hl1"; + // const cssClass = "hl1"; - // Remove HTML tags - text = text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/\n/g, " ") - // Convert placeholders to tags - .replace(startPlaceholderRegex, "") - .replace(endPlaceholderRegex, "") + " "; + // // Remove HTML tags + // text = text + // .replace(/&/g, "&") + // .replace(//g, ">") + // .replace(/\n/g, " ") + // // Convert placeholders to tags + // .replace(startPlaceholderRegex, "") + // .replace(endPlaceholderRegex, "") + " "; - // Adjust width to allow for scrollbars - highlighter.style.width = textarea.clientWidth + "px"; - highlighter.innerHTML = text; - highlighter.scrollTop = textarea.scrollTop; - highlighter.scrollLeft = textarea.scrollLeft; + // // Adjust width to allow for scrollbars + // highlighter.style.width = textarea.clientWidth + "px"; + // highlighter.innerHTML = text; + // highlighter.scrollTop = textarea.scrollTop; + // highlighter.scrollLeft = textarea.scrollLeft; } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index e8e71b12..0dc44dbe 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -19,7 +19,8 @@ import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@ import {bracketMatching} from "@codemirror/language"; import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; -import {statusBar} from "../extensions/statusBar.mjs"; +import {statusBar} from "../utils/statusBar.mjs"; +import {renderSpecialChar} from "../utils/editorUtils.mjs"; /** @@ -87,14 +88,17 @@ class InputWaiter { doc: null, extensions: [ history(), - highlightSpecialChars({render: this.renderSpecialChar}), + highlightSpecialChars({render: renderSpecialChar}), drawSelection(), rectangularSelection(), crosshairCursor(), bracketMatching(), highlightSelectionMatches(), search({top: true}), - statusBar(this.inputEditorConf), + statusBar({ + label: "Input", + eolHandler: this.eolChange.bind(this) + }), this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), EditorState.allowMultipleSelections.of(true), @@ -118,44 +122,10 @@ class InputWaiter { } /** - * Override for rendering special characters. - * Should mirror the toDOM function in - * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 - * But reverts the replacement of line feeds with newline control pictures. - * @param {number} code - * @param {string} desc - * @param {string} placeholder - * @returns {element} - */ - renderSpecialChar(code, desc, placeholder) { - const s = document.createElement("span"); - // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. - s.textContent = code === 0x0a ? "\u240a" : placeholder; - s.title = desc; - s.setAttribute("aria-label", desc); - s.className = "cm-specialChar"; - return s; - } - - /** - * Handler for EOL Select clicks + * Handler for EOL change events * Sets the line separator - * @param {Event} e */ - eolSelectClick(e) { - e.preventDefault(); - - const eolLookup = { - "LF": "\u000a", - "VT": "\u000b", - "FF": "\u000c", - "CR": "\u000d", - "CRLF": "\u000d\u000a", - "NEL": "\u0085", - "LS": "\u2028", - "PS": "\u2029" - }; - const eolval = eolLookup[e.target.getAttribute("data-val")]; + eolChange(eolval) { const oldInputVal = this.getInput(); // Update the EOL value diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 52b81ab4..7d9a3e2d 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -140,14 +140,11 @@ class OptionsWaiter { */ setWordWrap() { this.manager.input.setWordWrap(this.app.options.wordWrap); - document.getElementById("output-text").classList.remove("word-wrap"); - document.getElementById("output-html").classList.remove("word-wrap"); + this.manager.output.setWordWrap(this.app.options.wordWrap); document.getElementById("input-highlighter").classList.remove("word-wrap"); document.getElementById("output-highlighter").classList.remove("word-wrap"); if (!this.app.options.wordWrap) { - document.getElementById("output-text").classList.add("word-wrap"); - document.getElementById("output-html").classList.add("word-wrap"); document.getElementById("input-highlighter").classList.add("word-wrap"); document.getElementById("output-highlighter").classList.add("word-wrap"); } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 8996edb0..496b0ac5 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -10,6 +10,18 @@ import Dish from "../../core/Dish.mjs"; import FileSaver from "file-saver"; import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; +import { + EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor +} from "@codemirror/view"; +import {EditorState, Compartment} from "@codemirror/state"; +import {defaultKeymap} from "@codemirror/commands"; +import {bracketMatching} from "@codemirror/language"; +import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; + +import {statusBar} from "../utils/statusBar.mjs"; +import {renderSpecialChar} from "../utils/editorUtils.mjs"; +import {htmlPlugin} from "../utils/htmlWidget.mjs"; + /** * Waiter to handle events related to the output */ @@ -25,12 +37,155 @@ class OutputWaiter { this.app = app; this.manager = manager; + this.outputTextEl = document.getElementById("output-text"); + // Object to contain bake statistics - used by statusBar extension + this.bakeStats = { + duration: 0 + }; + // Object to handle output HTML state - used by htmlWidget extension + this.htmlOutput = { + html: "", + changed: false + }; + this.initEditor(); + this.outputs = {}; this.zipWorker = null; this.maxTabs = this.manager.tabs.calcMaxTabs(); this.tabTimeout = null; } + /** + * Sets up the CodeMirror Editor and returns the view + */ + initEditor() { + this.outputEditorConf = { + eol: new Compartment, + lineWrapping: new Compartment + }; + + const initialState = EditorState.create({ + doc: null, + extensions: [ + EditorState.readOnly.of(true), + htmlPlugin(this.htmlOutput), + highlightSpecialChars({render: renderSpecialChar}), + drawSelection(), + rectangularSelection(), + crosshairCursor(), + bracketMatching(), + highlightSelectionMatches(), + search({top: true}), + statusBar({ + label: "Output", + bakeStats: this.bakeStats, + eolHandler: this.eolChange.bind(this) + }), + this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), + this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + EditorState.allowMultipleSelections.of(true), + keymap.of([ + ...defaultKeymap, + ...searchKeymap + ]), + ] + }); + + this.outputEditorView = new EditorView({ + state: initialState, + parent: this.outputTextEl + }); + } + + /** + * Handler for EOL change events + * Sets the line separator + */ + eolChange(eolval) { + const oldOutputVal = this.getOutput(); + + // Update the EOL value + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval)) + }); + + // Reset the output so that lines are recalculated, preserving the old EOL values + this.setOutput(oldOutputVal); + } + + /** + * Sets word wrap on the output editor + * @param {boolean} wrap + */ + setWordWrap(wrap) { + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.lineWrapping.reconfigure( + wrap ? EditorView.lineWrapping : [] + ) + }); + } + + /** + * Gets the value of the current output + * @returns {string} + */ + getOutput() { + const doc = this.outputEditorView.state.doc; + const eol = this.outputEditorView.state.lineBreak; + return doc.sliceString(0, doc.length, eol); + } + + /** + * Sets the value of the current output + * @param {string} data + */ + setOutput(data) { + this.outputEditorView.dispatch({ + changes: { + from: 0, + to: this.outputEditorView.state.doc.length, + insert: data + } + }); + } + + /** + * Sets the value of the current output to a rendered HTML value + * @param {string} html + */ + setHTMLOutput(html) { + this.htmlOutput.html = html; + this.htmlOutput.changed = true; + // This clears the text output, but also fires a View update which + // triggers the htmlWidget to render the HTML. + this.setOutput(""); + + // Execute script sections + const scriptElements = document.getElementById("output-html").querySelectorAll("script"); + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + log.error(err); + } + } + } + + /** + * Clears the HTML output + */ + clearHTMLOutput() { + this.htmlOutput.html = ""; + this.htmlOutput.changed = true; + // Fire a blank change to force the htmlWidget to update and remove any HTML + this.outputEditorView.dispatch({ + changes: { + from: 0, + insert: "" + } + }); + } + /** * Calculates the maximum number of tabs to display */ @@ -245,8 +400,6 @@ class OutputWaiter { activeTab = this.manager.tabs.getActiveOutputTab(); if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10); - const outputText = document.getElementById("output-text"); - const outputHtml = document.getElementById("output-html"); const outputFile = document.getElementById("output-file"); const outputHighlighter = document.getElementById("output-highlighter"); const inputHighlighter = document.getElementById("input-highlighter"); @@ -278,95 +431,68 @@ class OutputWaiter { } else if (output.status === "error") { // style the tab if it's being shown this.toggleLoader(false); - outputText.style.display = "block"; - outputText.classList.remove("blur"); - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; + this.outputTextEl.classList.remove("blur"); outputFile.style.display = "none"; outputHighlighter.display = "none"; inputHighlighter.display = "none"; + this.clearHTMLOutput(); if (output.error) { - outputText.value = output.error; + this.setOutput(output.error); } else { - outputText.value = output.data.result; + this.setOutput(output.data.result); } - outputHtml.innerHTML = ""; } else if (output.status === "baked" || output.status === "inactive") { document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`; this.closeFile(); - let scriptElements, lines, length; if (output.data === null) { - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; - outputText.value = ""; - outputHtml.innerHTML = ""; + this.clearHTMLOutput(); + this.setOutput(""); this.toggleLoader(false); return; } + this.bakeStats.duration = output.data.duration; + switch (output.data.type) { case "html": - outputText.style.display = "none"; - outputHtml.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.style.display = "none"; inputHighlighter.style.display = "none"; - outputText.value = ""; - outputHtml.innerHTML = output.data.result; - - // Execute script sections - scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - log.error(err); - } - } + this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputHighlighter.display = "none"; inputHighlighter.display = "none"; - outputText.value = ""; - outputHtml.innerHTML = ""; + this.clearHTMLOutput(); + this.setOutput(""); - length = output.data.result.byteLength; this.setFile(await this.getDishBuffer(output.data.dish), activeTab); break; case "string": default: - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; - outputText.value = Utils.printable(output.data.result, true); - outputHtml.innerHTML = ""; - - lines = output.data.result.count("\n") + 1; - length = output.data.result.length; + this.clearHTMLOutput(); + this.setOutput(output.data.result); break; } this.toggleLoader(false); - if (output.data.type === "html") { - const dishStr = await this.getDishStr(output.data.dish); - length = dishStr.length; - lines = dishStr.count("\n") + 1; - } - - this.setOutputInfo(length, lines, output.data.duration); debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])(); } }.bind(this)); @@ -383,14 +509,13 @@ class OutputWaiter { // Display file overlay in output area with details const fileOverlay = document.getElementById("output-file"), fileSize = document.getElementById("output-file-size"), - outputText = document.getElementById("output-text"), fileSlice = buf.slice(0, 4096); fileOverlay.style.display = "block"; fileSize.textContent = buf.byteLength.toLocaleString() + " bytes"; - outputText.classList.add("blur"); - outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); + this.outputTextEl.classList.add("blur"); + this.setOutput(Utils.arrayBufferToStr(fileSlice)); } /** @@ -398,7 +523,7 @@ class OutputWaiter { */ closeFile() { document.getElementById("output-file").style.display = "none"; - document.getElementById("output-text").classList.remove("blur"); + this.outputTextEl.classList.remove("blur"); } /** @@ -466,7 +591,6 @@ class OutputWaiter { clearTimeout(this.outputLoaderTimeout); const outputLoader = document.getElementById("output-loader"), - outputElement = document.getElementById("output-text"), animation = document.getElementById("output-loader-animation"); if (value) { @@ -483,7 +607,6 @@ class OutputWaiter { // Show the loading screen this.outputLoaderTimeout = setTimeout(function() { - outputElement.disabled = true; outputLoader.style.visibility = "visible"; outputLoader.style.opacity = 1; }, 200); @@ -494,7 +617,6 @@ class OutputWaiter { animation.removeChild(this.bombeEl); } catch (err) {} }.bind(this), 500); - outputElement.disabled = false; outputLoader.style.opacity = 0; outputLoader.style.visibility = "hidden"; } @@ -717,8 +839,7 @@ class OutputWaiter { debounce(this.set, 50, "setOutput", this, [inputNum])(); - document.getElementById("output-html").scroll(0, 0); - document.getElementById("output-text").scroll(0, 0); + this.outputTextEl.scroll(0, 0); // TODO if (changeInput) { this.manager.input.changeTab(inputNum, false); @@ -996,32 +1117,6 @@ class OutputWaiter { } } - /** - * Displays information about the output. - * - * @param {number} length - The length of the current output string - * @param {number} lines - The number of the lines in the current output string - * @param {number} duration - The length of time (ms) it took to generate the output - */ - setOutputInfo(length, lines, duration) { - if (!length) return; - let width = length.toString().length; - width = width < 4 ? 4 : width; - - const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); - const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); - - let msg = "time: " + timeStr + "
    length: " + lengthStr; - - if (typeof lines === "number") { - const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); - msg += "
    lines: " + linesStr; - } - - document.getElementById("output-info").innerHTML = msg; - document.getElementById("output-selection-info").innerHTML = ""; - } - /** * Triggers the BackgroundWorker to attempt Magic on the current output. */ @@ -1111,9 +1206,7 @@ class OutputWaiter { async displayFileSlice() { document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice..."; this.toggleLoader(true); - const outputText = document.getElementById("output-text"), - outputHtml = document.getElementById("output-html"), - outputFile = document.getElementById("output-file"), + const outputFile = document.getElementById("output-file"), outputHighlighter = document.getElementById("output-highlighter"), inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), @@ -1130,12 +1223,12 @@ class OutputWaiter { str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish).slice(sliceFrom, sliceTo)); } - outputText.classList.remove("blur"); + this.outputTextEl.classList.remove("blur"); showFileOverlay.style.display = "block"; - outputText.value = Utils.printable(str, true); + this.clearHTMLOutput(); + this.setOutput(str); - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; @@ -1149,9 +1242,7 @@ class OutputWaiter { async showAllFile() { document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash..."; this.toggleLoader(true); - const outputText = document.getElementById("output-text"), - outputHtml = document.getElementById("output-html"), - outputFile = document.getElementById("output-file"), + const outputFile = document.getElementById("output-file"), outputHighlighter = document.getElementById("output-highlighter"), inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), @@ -1164,12 +1255,12 @@ class OutputWaiter { str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish)); } - outputText.classList.remove("blur"); + this.outputTextEl.classList.remove("blur"); showFileOverlay.style.display = "none"; - outputText.value = Utils.printable(str, true); + this.clearHTMLOutput(); + this.setOutput(str); - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; @@ -1185,7 +1276,7 @@ class OutputWaiter { showFileOverlayClick(e) { const showFileOverlay = e.target; - document.getElementById("output-text").classList.add("blur"); + this.outputTextEl.classList.add("blur"); showFileOverlay.style.display = "none"; this.set(this.manager.tabs.getActiveOutputTab()); } @@ -1212,7 +1303,7 @@ class OutputWaiter { * Handler for copy click events. * Copies the output to the clipboard */ - async copyClick() { + async copyClick() { // TODO - do we need this? const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab()); if (dish === null) { this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000); diff --git a/tests/browser/nightwatch.js b/tests/browser/nightwatch.js index ba6f5204..e63a8036 100644 --- a/tests/browser/nightwatch.js +++ b/tests/browser/nightwatch.js @@ -90,7 +90,7 @@ module.exports = { browser .useCss() .waitForElementNotVisible("#stale-indicator", 1000) - .expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); + .expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); // TODO // Clear recipe browser @@ -206,7 +206,7 @@ module.exports = { .useCss() .waitForElementVisible(".operation .op-title", 1000) .waitForElementNotVisible("#stale-indicator", 1000) - .expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/); + .expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/); // TODO browser.click("#clr-recipe"); }, diff --git a/tests/browser/ops.js b/tests/browser/ops.js index d0933bb6..64f8e036 100644 --- a/tests/browser/ops.js +++ b/tests/browser/ops.js @@ -443,9 +443,9 @@ function testOp(browser, opName, input, output, args=[]) { bakeOp(browser, opName, input, args); if (typeof output === "string") { - browser.expect.element("#output-text").to.have.property("value").that.equals(output); + browser.expect.element("#output-text").to.have.property("value").that.equals(output); // TODO } else if (output instanceof RegExp) { - browser.expect.element("#output-text").to.have.property("value").that.matches(output); + browser.expect.element("#output-text").to.have.property("value").that.matches(output); // TODO } } @@ -463,8 +463,8 @@ function testOpHtml(browser, opName, input, cssSelector, output, args=[]) { bakeOp(browser, opName, input, args); if (typeof output === "string") { - browser.expect.element("#output-html " + cssSelector).text.that.equals(output); + browser.expect.element("#output-html " + cssSelector).text.that.equals(output); // TODO } else if (output instanceof RegExp) { - browser.expect.element("#output-html " + cssSelector).text.that.matches(output); + browser.expect.element("#output-html " + cssSelector).text.that.matches(output); // TODO } } From 890f645eebd6665f9fffbebcfb200a518190f008 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Sun, 10 Jul 2022 22:01:22 +0100 Subject: [PATCH 019/228] Overhauled Highlighting to work with new editor and support multiple selections --- src/core/ChefWorker.js | 2 +- src/core/operations/ToHex.mjs | 6 +- src/web/Manager.mjs | 8 - src/web/waiters/HighlighterWaiter.mjs | 419 +++++--------------------- src/web/waiters/InputWaiter.mjs | 24 +- src/web/waiters/OutputWaiter.mjs | 33 +- src/web/waiters/TabWaiter.mjs | 3 - src/web/waiters/WorkerWaiter.mjs | 2 +- 8 files changed, 104 insertions(+), 393 deletions(-) diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index f4a17f63..d46a705d 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -186,7 +186,7 @@ async function getDishTitle(data) { * * @param {Object[]} recipeConfig * @param {string} direction - * @param {Object} pos - The position object for the highlight. + * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. */ diff --git a/src/core/operations/ToHex.mjs b/src/core/operations/ToHex.mjs index 71893105..092155a9 100644 --- a/src/core/operations/ToHex.mjs +++ b/src/core/operations/ToHex.mjs @@ -76,7 +76,7 @@ class ToHex extends Operation { } const lineSize = args[1], - len = (delim === "\r\n" ? 1 : delim.length) + commaLen; + len = delim.length + commaLen; const countLF = function(p) { // Count the number of LFs from 0 upto p @@ -105,7 +105,7 @@ class ToHex extends Operation { * @returns {Object[]} pos */ highlightReverse(pos, args) { - let delim, commaLen; + let delim, commaLen = 0; if (args[0] === "0x with comma") { delim = "0x"; commaLen = 1; @@ -114,7 +114,7 @@ class ToHex extends Operation { } const lineSize = args[1], - len = (delim === "\r\n" ? 1 : delim.length) + commaLen, + len = delim.length + commaLen, width = len + 2; const countLF = function(p) { diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 2477bb60..a46379e9 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -153,10 +153,6 @@ class Manager { this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); - document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); - this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); @@ -188,10 +184,6 @@ class Manager { document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); - document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); - this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); this.addDynamicListener("#output-file-show-all", "click", this.output.showAllFile, this.output); this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output); diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index d1340165..8b4375fe 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -4,17 +4,7 @@ * @license Apache-2.0 */ -/** - * HighlighterWaiter data type enum for the input. - * @enum - */ -const INPUT = 0; - -/** - * HighlighterWaiter data type enum for the output. - * @enum - */ -const OUTPUT = 1; +import {EditorSelection} from "@codemirror/state"; /** @@ -32,309 +22,81 @@ class HighlighterWaiter { this.app = app; this.manager = manager; - this.mouseButtonDown = false; - this.mouseTarget = null; + this.currentSelectionRanges = []; } - /** - * Determines if the current text selection is running backwards or forwards. - * StackOverflow answer id: 12652116 + * Handler for selection change events in the input and output * - * @private - * @returns {boolean} - */ - _isSelectionBackwards() { - let backwards = false; - const sel = window.getSelection(); - - if (!sel.isCollapsed) { - const range = document.createRange(); - range.setStart(sel.anchorNode, sel.anchorOffset); - range.setEnd(sel.focusNode, sel.focusOffset); - backwards = range.collapsed; - range.detach(); - } - return backwards; - } - - - /** - * Calculates the text offset of a position in an HTML element, ignoring HTML tags. - * - * @private - * @param {element} node - The parent HTML node. - * @param {number} offset - The offset since the last HTML element. - * @returns {number} - */ - _getOutputHtmlOffset(node, offset) { - const sel = window.getSelection(); - const range = document.createRange(); - - range.selectNodeContents(document.getElementById("output-html")); - range.setEnd(node, offset); - sel.removeAllRanges(); - sel.addRange(range); - - return sel.toString().length; - } - - - /** - * Gets the current selection offsets in the output HTML, ignoring HTML tags. - * - * @private - * @returns {Object} pos - * @returns {number} pos.start - * @returns {number} pos.end - */ - _getOutputHtmlSelectionOffsets() { - const sel = window.getSelection(); - let range, - start = 0, - end = 0, - backwards = false; - - if (sel.rangeCount) { - range = sel.getRangeAt(sel.rangeCount - 1); - backwards = this._isSelectionBackwards(); - start = this._getOutputHtmlOffset(range.startContainer, range.startOffset); - end = this._getOutputHtmlOffset(range.endContainer, range.endOffset); - sel.removeAllRanges(); - sel.addRange(range); - - if (backwards) { - // If selecting backwards, reverse the start and end offsets for the selection to - // prevent deselecting as the drag continues. - sel.collapseToEnd(); - sel.extend(sel.anchorNode, range.startOffset); - } - } - - return { - start: start, - end: end - }; - } - - - /** - * Handler for input scroll events. - * Scrolls the highlighter pane to match the input textarea position. - * - * @param {event} e - */ - inputScroll(e) { - const el = e.target; - document.getElementById("input-highlighter").scrollTop = el.scrollTop; - document.getElementById("input-highlighter").scrollLeft = el.scrollLeft; - } - - - /** - * Handler for output scroll events. - * Scrolls the highlighter pane to match the output textarea position. - * - * @param {event} e - */ - outputScroll(e) { - const el = e.target; - document.getElementById("output-highlighter").scrollTop = el.scrollTop; - document.getElementById("output-highlighter").scrollLeft = el.scrollLeft; - } - - - /** - * Handler for input mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ - inputMousedown(e) { - this.mouseButtonDown = true; - this.mouseTarget = INPUT; - this.removeHighlights(); - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightOutput([{start: start, end: end}]); - } - } - - - /** - * Handler for output mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ - outputMousedown(e) { - this.mouseButtonDown = true; - this.mouseTarget = OUTPUT; - this.removeHighlights(); - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightInput([{start: start, end: end}]); - } - } - - - /** - * Handler for input mouseup events. - * - * @param {event} e - */ - inputMouseup(e) { - this.mouseButtonDown = false; - } - - - /** - * Handler for output mouseup events. - * - * @param {event} e - */ - outputMouseup(e) { - this.mouseButtonDown = false; - } - - - /** - * Handler for input mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ - inputMousemove(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== INPUT) - return; - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightOutput([{start: start, end: end}]); - } - } - - - /** - * Handler for output mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ - outputMousemove(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== OUTPUT) - return; - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightInput([{start: start, end: end}]); - } - } - - - /** - * Given start and end offsets, writes the HTML for the selection info element with the correct - * padding. - * - * @param {number} start - The start offset. - * @param {number} end - The end offset. - * @returns {string} - */ - selectionInfo(start, end) { - const len = end.toString().length; - const width = len < 2 ? 2 : len; - const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); - const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); - const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); - - return "start: " + startStr + "
    end: " + endStr + "
    length: " + lenStr; - } - - - /** - * Removes highlighting and selection information. - */ - removeHighlights() { - document.getElementById("input-highlighter").innerHTML = ""; - document.getElementById("output-highlighter").innerHTML = ""; - } - - - /** - * Highlights the given offsets in the output. + * Highlights the given offsets in the input or output. * We will only highlight if: * - input hasn't changed since last bake * - last bake was a full bake * - all operations in the recipe support highlighting * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. + * @param {string} io + * @param {ViewUpdate} e */ - highlightOutput(pos) { + selectionChange(io, e) { + // Confirm we are not currently baking if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos); + + // Confirm this was a user-generated event to prevent looping + // from setting the selection in this class + if (!e.transactions[0].isUserEvent("select")) return false; + + const view = io === "input" ? + this.manager.output.outputEditorView : + this.manager.input.inputEditorView; + + this.currentSelectionRanges = []; + + // Confirm some non-empty ranges are set + const selectionRanges = e.state.selection.ranges.filter(r => !r.empty); + if (!selectionRanges.length) { + this.resetSelections(view); + return; + } + + // Loop through ranges and send request for output offsets for each one + const direction = io === "input" ? "forward" : "reverse"; + for (const range of selectionRanges) { + const pos = [{ + start: range.from, + end: range.to + }]; + this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos); + } } - /** - * Highlights the given offsets in the input. - * We will only highlight if: - * - input hasn't changed since last bake - * - last bake was a full bake - * - all operations in the recipe support highlighting - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. + * Resets the current set of selections in the given view + * @param {EditorView} view */ - highlightInput(pos) { - if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); + resetSelections(view) { + this.currentSelectionRanges = []; + + // Clear current selection in output or input + view.dispatch({ + selection: EditorSelection.create([EditorSelection.range(0, 0)]) + }); } /** * Displays highlight offsets sent back from the Chef. * - * @param {Object} pos - The position object for the highlight. + * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. * @param {string} direction */ displayHighlights(pos, direction) { if (!pos) return; - if (this.manager.tabs.getActiveInputTab() !== this.manager.tabs.getActiveOutputTab()) return; const io = direction === "forward" ? "output" : "input"; - - // TODO - // document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); - this.highlight( - document.getElementById(io + "-text"), - document.getElementById(io + "-highlighter"), - pos); + this.highlight(io, pos); } @@ -342,74 +104,35 @@ class HighlighterWaiter { * Adds the relevant HTML to the specified highlight element such that highlighting appears * underneath the correct offset. * - * @param {element} textarea - The input or output textarea. - * @param {element} highlighter - The input or output highlighter element. - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. + * @param {string} io - The input or output + * @param {Object[]} ranges - An array of position objects to highlight + * @param {number} ranges.start - The start offset + * @param {number} ranges.end - The end offset */ - async highlight(textarea, highlighter, pos) { - // if (!this.app.options.showHighlighter) return false; - // if (!this.app.options.attemptHighlight) return false; + async highlight(io, ranges) { + if (!this.app.options.showHighlighter) return false; + if (!this.app.options.attemptHighlight) return false; + if (!ranges || !ranges.length) return false; - // // Check if there is a carriage return in the output dish as this will not - // // be displayed by the HTML textarea and will mess up highlighting offsets. - // if (await this.manager.output.containsCR()) return false; + const view = io === "input" ? + this.manager.input.inputEditorView : + this.manager.output.outputEditorView; - // const startPlaceholder = "[startHighlight]"; - // const startPlaceholderRegex = /\[startHighlight\]/g; - // const endPlaceholder = "[endHighlight]"; - // const endPlaceholderRegex = /\[endHighlight\]/g; - // // let text = textarea.value; // TODO + // Add new SelectionRanges to existing ones + for (const range of ranges) { + if (!range.start || !range.end) continue; + this.currentSelectionRanges.push( + EditorSelection.range(range.start, range.end) + ); + } - // // Put placeholders in position - // // If there's only one value, select that - // // If there are multiple, ignore the first one and select all others - // if (pos.length === 1) { - // if (pos[0].end < pos[0].start) return; - // text = text.slice(0, pos[0].start) + - // startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + - // text.slice(pos[0].end, text.length); - // } else { - // // O(n^2) - Can anyone improve this without overwriting placeholders? - // let result = "", - // endPlaced = true; - - // for (let i = 0; i < text.length; i++) { - // for (let j = 1; j < pos.length; j++) { - // if (pos[j].end < pos[j].start) continue; - // if (pos[j].start === i) { - // result += startPlaceholder; - // endPlaced = false; - // } - // if (pos[j].end === i) { - // result += endPlaceholder; - // endPlaced = true; - // } - // } - // result += text[i]; - // } - // if (!endPlaced) result += endPlaceholder; - // text = result; - // } - - // const cssClass = "hl1"; - - // // Remove HTML tags - // text = text - // .replace(/&/g, "&") - // .replace(//g, ">") - // .replace(/\n/g, " ") - // // Convert placeholders to tags - // .replace(startPlaceholderRegex, "") - // .replace(endPlaceholderRegex, "") + " "; - - // // Adjust width to allow for scrollbars - // highlighter.style.width = textarea.clientWidth + "px"; - // highlighter.innerHTML = text; - // highlighter.scrollTop = textarea.scrollTop; - // highlighter.scrollLeft = textarea.scrollLeft; + // Set selection + if (this.currentSelectionRanges.length) { + view.dispatch({ + selection: EditorSelection.create(this.currentSelectionRanges), + scrollIntoView: true + }); + } } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 0dc44dbe..ff512f69 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -87,6 +87,7 @@ class InputWaiter { const initialState = EditorState.create({ doc: null, extensions: [ + // Editor extensions history(), highlightSpecialChars({render: renderSpecialChar}), drawSelection(), @@ -95,13 +96,19 @@ class InputWaiter { bracketMatching(), highlightSelectionMatches(), search({top: true}), + EditorState.allowMultipleSelections.of(true), + + // Custom extensions statusBar({ label: "Input", eolHandler: this.eolChange.bind(this) }), + + // Mutable state this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), - EditorState.allowMultipleSelections.of(true), + + // Keymap keymap.of([ // Explicitly insert a tab rather than indenting the line { key: "Tab", run: insertTab }, @@ -112,6 +119,12 @@ class InputWaiter { ...defaultKeymap, ...searchKeymap ]), + + // Event listeners + EditorView.updateListener.of(e => { + if (e.selectionSet) + this.manager.highlighter.selectionChange("input", e); + }) ] }); @@ -771,9 +784,6 @@ class InputWaiter { const fileOverlay = document.getElementById("input-file"); if (fileOverlay.style.display === "block") return; - // Remove highlighting from input and output panes as the offsets might be different now - this.manager.highlighter.removeHighlights(); - const value = this.getInput(); const activeTab = this.manager.tabs.getActiveInputTab(); @@ -1033,9 +1043,6 @@ class InputWaiter { this.manager.output.removeAllOutputs(); this.manager.output.terminateZipWorker(); - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - const tabsList = document.getElementById("input-tabs"); const tabsListChildren = tabsList.children; @@ -1073,9 +1080,6 @@ class InputWaiter { const inputNum = this.manager.tabs.getActiveInputTab(); if (inputNum === -1) return; - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - this.updateInputValue(inputNum, "", true); this.set({ diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 496b0ac5..d1fd2532 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -67,6 +67,7 @@ class OutputWaiter { const initialState = EditorState.create({ doc: null, extensions: [ + // Editor extensions EditorState.readOnly.of(true), htmlPlugin(this.htmlOutput), highlightSpecialChars({render: renderSpecialChar}), @@ -76,18 +77,30 @@ class OutputWaiter { bracketMatching(), highlightSelectionMatches(), search({top: true}), + EditorState.allowMultipleSelections.of(true), + + // Custom extensiosn statusBar({ label: "Output", bakeStats: this.bakeStats, eolHandler: this.eolChange.bind(this) }), + + // Mutable state this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), - EditorState.allowMultipleSelections.of(true), + + // Keymap keymap.of([ ...defaultKeymap, ...searchKeymap ]), + + // Event listeners + EditorView.updateListener.of(e => { + if (e.selectionSet) + this.manager.highlighter.selectionChange("output", e); + }) ] }); @@ -817,9 +830,6 @@ class OutputWaiter { this.hideMagicButton(); - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - if (!this.manager.tabs.changeOutputTab(inputNum)) { let direction = "right"; if (currentNum > inputNum) { @@ -1343,21 +1353,6 @@ class OutputWaiter { document.body.removeChild(textarea); } - /** - * Returns true if the output contains carriage returns - * - * @returns {boolean} - */ - async containsCR() { - const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab()); - if (dish === null) return; - - if (dish.type === Dish.STRING) { - const data = await dish.get(Dish.STRING); - return data.indexOf("\r") >= 0; - } - } - /** * Handler for switch click events. * Moves the current output into the input textarea. diff --git a/src/web/waiters/TabWaiter.mjs b/src/web/waiters/TabWaiter.mjs index 384b1ab7..f5b0efd4 100644 --- a/src/web/waiters/TabWaiter.mjs +++ b/src/web/waiters/TabWaiter.mjs @@ -305,9 +305,6 @@ class TabWaiter { changeTab(inputNum, io) { const tabsList = document.getElementById(`${io}-tabs`); - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - let found = false; for (let i = 0; i < tabsList.children.length; i++) { const tabNum = parseInt(tabsList.children.item(i).getAttribute("inputNum"), 10); diff --git a/src/web/waiters/WorkerWaiter.mjs b/src/web/waiters/WorkerWaiter.mjs index 7fcaa509..a63bfc1f 100644 --- a/src/web/waiters/WorkerWaiter.mjs +++ b/src/web/waiters/WorkerWaiter.mjs @@ -794,7 +794,7 @@ class WorkerWaiter { * * @param {Object[]} recipeConfig * @param {string} direction - * @param {Object} pos - The position object for the highlight. + * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. */ From 157dacb3a52fd082ffd203d5a88e328217260eb2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 11 Jul 2022 11:43:48 +0100 Subject: [PATCH 020/228] Improved highlighting colours and selection ranges --- src/web/stylesheets/layout/_io.css | 22 +++++++++++ src/web/stylesheets/themes/_classic.css | 10 ++--- src/web/waiters/HighlighterWaiter.mjs | 51 ++++++++++--------------- src/web/waiters/InputWaiter.mjs | 3 +- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index ba670f3d..cb196709 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -440,6 +440,28 @@ filter: brightness(98%); } +/* Highlighting */ +.ͼ2.cm-focused .cm-selectionBackground { + background-color: var(--hl5); +} + +.ͼ2 .cm-selectionBackground { + background-color: var(--hl1); +} + +.ͼ1 .cm-selectionMatch { + background-color: var(--hl2); +} + +.ͼ1.cm-focused .cm-cursor.cm-cursor-primary { + border-color: var(--primary-font-colour); +} + +.ͼ1 .cm-cursor.cm-cursor-primary { + display: block; + border-color: var(--subtext-font-colour); +} + /* Status bar */ diff --git a/src/web/stylesheets/themes/_classic.css b/src/web/stylesheets/themes/_classic.css index 3b3bd555..971c1c57 100755 --- a/src/web/stylesheets/themes/_classic.css +++ b/src/web/stylesheets/themes/_classic.css @@ -110,11 +110,11 @@ /* Highlighter colours */ - --hl1: #fff000; - --hl2: #95dfff; - --hl3: #ffb6b6; - --hl4: #fcf8e3; - --hl5: #8de768; + --hl1: #ffee00aa; + --hl2: #95dfffaa; + --hl3: #ffb6b6aa; + --hl4: #fcf8e3aa; + --hl5: #8de768aa; /* Scrollbar */ diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 8b4375fe..189d3777 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -45,18 +45,10 @@ class HighlighterWaiter { // from setting the selection in this class if (!e.transactions[0].isUserEvent("select")) return false; - const view = io === "input" ? - this.manager.output.outputEditorView : - this.manager.input.inputEditorView; - this.currentSelectionRanges = []; // Confirm some non-empty ranges are set - const selectionRanges = e.state.selection.ranges.filter(r => !r.empty); - if (!selectionRanges.length) { - this.resetSelections(view); - return; - } + const selectionRanges = e.state.selection.ranges; // Loop through ranges and send request for output offsets for each one const direction = io === "input" ? "forward" : "reverse"; @@ -69,19 +61,6 @@ class HighlighterWaiter { } } - /** - * Resets the current set of selections in the given view - * @param {EditorView} view - */ - resetSelections(view) { - this.currentSelectionRanges = []; - - // Clear current selection in output or input - view.dispatch({ - selection: EditorSelection.create([EditorSelection.range(0, 0)]) - }); - } - /** * Displays highlight offsets sent back from the Chef. @@ -120,18 +99,30 @@ class HighlighterWaiter { // Add new SelectionRanges to existing ones for (const range of ranges) { - if (!range.start || !range.end) continue; - this.currentSelectionRanges.push( - EditorSelection.range(range.start, range.end) - ); + if (typeof range.start !== "number" || + typeof range.end !== "number") + continue; + const selection = range.end <= range.start ? + EditorSelection.cursor(range.start) : + EditorSelection.range(range.start, range.end); + + this.currentSelectionRanges.push(selection); } // Set selection if (this.currentSelectionRanges.length) { - view.dispatch({ - selection: EditorSelection.create(this.currentSelectionRanges), - scrollIntoView: true - }); + try { + view.dispatch({ + selection: EditorSelection.create(this.currentSelectionRanges), + scrollIntoView: true + }); + } catch (err) { + // Ignore Range Errors + if (!err.toString().startsWith("RangeError")) { + console.error(err); + } + + } } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index ff512f69..69417b92 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -12,7 +12,7 @@ import {toBase64} from "../../core/lib/Base64.mjs"; import {isImage} from "../../core/lib/FileType.mjs"; import { - EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor + EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, dropCursor } from "@codemirror/view"; import {EditorState, Compartment} from "@codemirror/state"; import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@codemirror/commands"; @@ -93,6 +93,7 @@ class InputWaiter { drawSelection(), rectangularSelection(), crosshairCursor(), + dropCursor(), bracketMatching(), highlightSelectionMatches(), search({top: true}), From 5c8aac5572b687186d390d07c7206e068df25a19 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 11 Jul 2022 13:43:19 +0100 Subject: [PATCH 021/228] Improved input change update responsiveness --- src/core/operations/ParseColourCode.mjs | 2 +- src/web/App.mjs | 9 +++-- src/web/Manager.mjs | 1 - src/web/waiters/InputWaiter.mjs | 53 ++++++------------------- src/web/waiters/OutputWaiter.mjs | 4 +- 5 files changed, 20 insertions(+), 49 deletions(-) diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs index 045d8f05..31e575a1 100644 --- a/src/core/operations/ParseColourCode.mjs +++ b/src/core/operations/ParseColourCode.mjs @@ -113,7 +113,7 @@ CMYK: ${cmyk} }).on('colorpickerChange', function(e) { var color = e.color.string('rgba'); window.app.manager.input.setInput(color); - window.app.manager.input.debounceInputChange(new Event("keyup")); + window.app.manager.input.inputChange(new Event("keyup")); }); `; } diff --git a/src/web/App.mjs b/src/web/App.mjs index 2d45d1f1..4ead8bc4 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -728,10 +728,11 @@ class App { * @param {event} e */ stateChange(e) { - this.progress = 0; - this.autoBake(); - - this.updateTitle(true, null, true); + debounce(function() { + this.progress = 0; + this.autoBake(); + this.updateTitle(true, null, true); + }, 20, "stateChange", this, [])(); } diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index a46379e9..9d03c728 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -146,7 +146,6 @@ class Manager { this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); // Input - document.getElementById("input-text").addEventListener("keyup", this.input.debounceInputChange.bind(this.input)); document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 69417b92..6a1b57df 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -41,24 +41,6 @@ class InputWaiter { this.inputTextEl = document.getElementById("input-text"); this.initEditor(); - // Define keys that don't change the input so we don't have to autobake when they are pressed - this.badKeys = [ - 16, // Shift - 17, // Ctrl - 18, // Alt - 19, // Pause - 20, // Caps - 27, // Esc - 33, 34, 35, 36, // PgUp, PgDn, End, Home - 37, 38, 39, 40, // Directional - 44, // PrntScrn - 91, 92, // Win - 93, // Context - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, // F1-12 - 144, // Num - 145, // Scroll - ]; - this.inputWorker = null; this.loaderWorkers = []; this.workerId = 0; @@ -125,6 +107,8 @@ class InputWaiter { EditorView.updateListener.of(e => { if (e.selectionSet) this.manager.highlighter.selectionChange("input", e); + if (e.docChanged) + this.inputChange(e); }) ] }); @@ -396,7 +380,7 @@ class InputWaiter { this.showLoadingInfo(r.data, true); break; case "setInput": - debounce(this.set, 50, "setInput", this, [r.data.inputObj, r.data.silent])(); + this.set(r.data.inputObj, r.data.silent); break; case "inputAdded": this.inputAdded(r.data.changeTab, r.data.inputNum); @@ -762,41 +746,30 @@ class InputWaiter { }); } - /** - * Handler for input change events. - * Debounces the input so we don't call autobake too often. - * - * @param {event} e - */ - debounceInputChange(e) { - debounce(this.inputChange, 50, "inputChange", this, [e])(); - } - /** * Handler for input change events. * Updates the value stored in the inputWorker + * Debounces the input so we don't call autobake too often. * * @param {event} e * * @fires Manager#statechange */ inputChange(e) { - // Ignore this function if the input is a file - const fileOverlay = document.getElementById("input-file"); - if (fileOverlay.style.display === "block") return; + debounce(function(e) { + // Ignore this function if the input is a file + const fileOverlay = document.getElementById("input-file"); + if (fileOverlay.style.display === "block") return; - const value = this.getInput(); - const activeTab = this.manager.tabs.getActiveInputTab(); + const value = this.getInput(); + const activeTab = this.manager.tabs.getActiveInputTab(); - this.app.progress = 0; + this.updateInputValue(activeTab, value); + this.manager.tabs.updateInputTabHeader(activeTab, value.slice(0, 100).replace(/[\n\r]/g, "")); - this.updateInputValue(activeTab, value); - this.manager.tabs.updateInputTabHeader(activeTab, value.slice(0, 100).replace(/[\n\r]/g, "")); - - if (e && this.badKeys.indexOf(e.keyCode) < 0) { // Fire the statechange event as the input has been modified window.dispatchEvent(this.manager.statechange); - } + }, 20, "inputChange", this, [e])(); } /** diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index d1fd2532..3f031ac7 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -847,9 +847,7 @@ class OutputWaiter { } } - debounce(this.set, 50, "setOutput", this, [inputNum])(); - - this.outputTextEl.scroll(0, 0); // TODO + this.set(inputNum); if (changeInput) { this.manager.input.changeTab(inputNum, false); From 0dc2322269d4fd26bc6b2aa07f6cb0cd9e3cbce6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 11 Jul 2022 13:57:28 +0100 Subject: [PATCH 022/228] Fixed dropping text in the input --- src/web/Manager.mjs | 6 +++--- src/web/waiters/InputWaiter.mjs | 16 ++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 9d03c728..820b1a8d 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -149,9 +149,9 @@ class Manager { document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); - this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); - this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); - this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); + this.addListeners("#input-wrapper", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-wrapper", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-wrapper", "drop", this.input.inputDrop, this.input); document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 6a1b57df..ed8f174b 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -797,7 +797,10 @@ class InputWaiter { inputDragleave(e) { e.stopPropagation(); e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); + // Dragleave often fires when moving between lines in the editor. + // If the target element is within the input-text element, we are still on target. + if (!this.inputTextEl.contains(e.target)) + e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); } /** @@ -813,17 +816,10 @@ class InputWaiter { e.stopPropagation(); e.preventDefault(); - - const text = e.dataTransfer.getData("Text"); - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); - if (text) { - // Append the text to the current input and fire inputChange() - this.setInput(this.getInput() + text); - this.inputChange(e); - return; - } + // Dropped text is handled by the editor itself + if (e.dataTransfer.getData("Text")) return; if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { this.loadUIFiles(e.dataTransfer.files); From 893b84d0426754d0b21647ef25564f6d9b19f95e Mon Sep 17 00:00:00 2001 From: Luis Martinez Date: Sat, 28 May 2022 00:17:59 -0500 Subject: [PATCH 023/228] xxtea encryption added --- src/core/config/Categories.json | 3 +- src/core/operations/XXTEA.mjs | 182 ++++++++++++++++++++++++++++++++ 2 files changed, 184 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/XXTEA.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8ac60048..26e56905 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -131,7 +131,8 @@ "Typex", "Lorenz", "Colossus", - "SIGABA" + "SIGABA", + "XXTEA" ] }, { diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs new file mode 100644 index 00000000..e8264c4d --- /dev/null +++ b/src/core/operations/XXTEA.mjs @@ -0,0 +1,182 @@ +/** + * @author devcydo [devcydo@gmail.com] + * @author Ma Bingyao [mabingyao@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; +import {toBase64} from "../lib/Base64.mjs"; +import Utils from "../Utils.mjs"; + +/** + * XXTEA Encrypt operation + */ +class XXTEAEncrypt extends Operation { + + /** + * XXTEAEncrypt constructor + */ + constructor() { + super(); + + this.name = "XXTEA"; + this.module = "Default"; + this.description = "Corrected Block TEA (often referred to as XXTEA) is a block cipher designed to correct weaknesses in the original Block TEA. XXTEA operates on variable-length blocks that are some arbitrary multiple of 32 bits in size (minimum 64 bits). The number of full cycles depends on the block size, but there are at least six (rising to 32 for small block sizes). The original Block TEA applies the XTEA round function to each word in the block and combines it additively with its leftmost neighbour. Slow diffusion rate of the decryption process was immediately exploited to break the cipher. Corrected Block TEA uses a more involved round function which makes use of both immediate neighbours in processing each word in the block."; + this.infoURL = "https://wikipedia.org/wiki/XXTEA"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + "name": "Key", + "type": "string", + "value": "", + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let key = args[0]; + + if (input === undefined || input === null || input.length === 0) { + throw new OperationError("Invalid input length (0)"); + } + + if (key === undefined || key === null || key.length === 0) { + throw new OperationError("Invalid key length (0)"); + } + + input = Utils.convertToByteString(input, "utf8"); + key = Utils.convertToByteString(key, "utf8"); + + input = this.convertToUint32Array(input, true); + key = this.fixLength(this.convertToUint32Array(key, false)); + + let encrypted = this.encryptUint32Array(input, key); + + encrypted = toBase64(this.toBinaryString(encrypted, false)); + + return encrypted; + } + + /** + * Convert Uint32Array to binary string + * + * @param {Uint32Array} v + * @param {Boolean} includeLength + * @returns {string} + */ + toBinaryString(v, includeLENGTH) { + const LENGTH = v.LENGTH; + let n = LENGTH << 2; + if (includeLENGTH) { + const M = v[LENGTH - 1]; + n -= 4; + if ((M < n - 3) || (M > n)) { + return null; + } + n = M; + } + for (let i = 0; i < LENGTH; i++) { + v[i] = String.fromCharCode( + v[i] & 0xFF, + v[i] >>> 8 & 0xFF, + v[i] >>> 16 & 0xFF, + v[i] >>> 24 & 0xFF + ); + } + const RESULT = v.join(""); + if (includeLENGTH) { + return RESULT.substring(0, n); + } + return RESULT; + } + + /** + * @param {number} sum + * @param {number} y + * @param {number} z + * @param {number} p + * @param {number} e + * @param {number} k + * @returns {number} + */ + mx(sum, y, z, p, e, k) { + return ((z >>> 5 ^ y << 2) + (y >>> 3 ^ z << 4)) ^ ((sum ^ y) + (k[p & 3 ^ e] ^ z)); + } + + + /** + * Encrypt Uint32Array + * + * @param {Uint32Array} v + * @param {number} k + * @returns {Uint32Array} + */ + encryptUint32Array(v, k) { + const LENGTH = v.LENGTH; + const N = LENGTH - 1; + let y, z, sum, e, p, q; + z = v[N]; + sum = 0; + for (q = Math.floor(6 + 52 / LENGTH) | 0; q > 0; --q) { + sum = (sum + 0x9E3779B9) & 0xFFFFFFFF; + e = sum >>> 2 & 3; + for (p = 0; p < N; ++p) { + y = v[p + 1]; + z = v[p] = (v[p] + this.mx(sum, y, z, p, e, k)) & 0xFFFFFFFF; + } + y = v[0]; + z = v[N] = (v[N] + this.mx(sum, y, z, N, e, k)) & 0xFFFFFFFF; + } + return v; + } + + /** + * Fixes the Uint32Array lenght to 4 + * + * @param {Uint32Array} k + * @returns {Uint32Array} + */ + fixLength(k) { + if (k.length < 4) { + k.length = 4; + } + return k; + } + + /** + * Convert string to Uint32Array + * + * @param {string} bs + * @param {Boolean} includeLength + * @returns {Uint32Array} + */ + convertToUint32Array(bs, includeLength) { + const LENGTH = bs.LENGTH; + let n = LENGTH >> 2; + if ((LENGTH & 3) !== 0) { + ++n; + } + let v; + if (includeLength) { + v = new Array(n + 1); + v[n] = LENGTH; + } else { + v = new Array(n); + } + for (let i = 0; i < LENGTH; ++i) { + v[i >> 2] |= bs.charCodeAt(i) << ((i & 3) << 3); + } + return v; + } + +} + +export default XXTEAEncrypt; From 653af6a3005f872d98ca0d59f0aa118e48f88bb7 Mon Sep 17 00:00:00 2001 From: Luis Martinez Date: Sat, 28 May 2022 00:20:51 -0500 Subject: [PATCH 024/228] xxtea encryption added --- src/core/operations/XXTEA.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs index e8264c4d..4fa0706d 100644 --- a/src/core/operations/XXTEA.mjs +++ b/src/core/operations/XXTEA.mjs @@ -98,7 +98,7 @@ class XXTEAEncrypt extends Operation { return RESULT; } - /** + /** * @param {number} sum * @param {number} y * @param {number} z From c14098a27c47bf345f1f119b970248fd573fa9d6 Mon Sep 17 00:00:00 2001 From: Luis Martinez Date: Mon, 11 Jul 2022 19:38:59 -0500 Subject: [PATCH 025/228] tests added and XXTEA not working correctly fixed --- src/core/operations/XXTEA.mjs | 6 ++-- tests/operations/tests/XXTEA.mjs | 62 ++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 3 deletions(-) create mode 100644 tests/operations/tests/XXTEA.mjs diff --git a/src/core/operations/XXTEA.mjs b/src/core/operations/XXTEA.mjs index 4fa0706d..1a0a4368 100644 --- a/src/core/operations/XXTEA.mjs +++ b/src/core/operations/XXTEA.mjs @@ -73,7 +73,7 @@ class XXTEAEncrypt extends Operation { * @returns {string} */ toBinaryString(v, includeLENGTH) { - const LENGTH = v.LENGTH; + const LENGTH = v.length; let n = LENGTH << 2; if (includeLENGTH) { const M = v[LENGTH - 1]; @@ -120,7 +120,7 @@ class XXTEAEncrypt extends Operation { * @returns {Uint32Array} */ encryptUint32Array(v, k) { - const LENGTH = v.LENGTH; + const LENGTH = v.length; const N = LENGTH - 1; let y, z, sum, e, p, q; z = v[N]; @@ -159,7 +159,7 @@ class XXTEAEncrypt extends Operation { * @returns {Uint32Array} */ convertToUint32Array(bs, includeLength) { - const LENGTH = bs.LENGTH; + const LENGTH = bs.length; let n = LENGTH >> 2; if ((LENGTH & 3) !== 0) { ++n; diff --git a/tests/operations/tests/XXTEA.mjs b/tests/operations/tests/XXTEA.mjs new file mode 100644 index 00000000..4787f086 --- /dev/null +++ b/tests/operations/tests/XXTEA.mjs @@ -0,0 +1,62 @@ +/** + * Base64 tests. + * + * @author devcydo [devcydo@gmail.com] + * + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + name: "XXTEA", + input: "Hello World! 你好,中国!", + expectedOutput: "QncB1C0rHQoZ1eRiPM4dsZtRi9pNrp7sqvX76cFXvrrIHXL6", + reecipeConfig: [ + { + args: "1234567890" + }, + ], + }, + { + name: "XXTEA", + input: "ნუ პანიკას", + expectedOutput: "PbWjnbFmP8Apu2MKOGNbjeW/72IZLlLMS/g82ozLxwE=", + reecipeConfig: [ + { + args: "1234567890" + }, + ], + }, + { + name: "XXTEA", + input: "ნუ პანიკას", + expectedOutput: "dHrOJ4ClIx6gH33NPSafYR2GG7UqsazY6Xfb0iekBY4=", + reecipeConfig: [ + { + args: "ll3kj209d2" + }, + ], + }, + { + name: "XXTEA", + input: "", + expectedOutput: "Invalid input length (0)", + reecipeConfig: [ + { + args: "1234567890" + }, + ], + }, + { + name: "XXTEA", + input: "", + expectedOutput: "Invalid input length (0)", + reecipeConfig: [ + { + args: "" + }, + ], + }, +]); From 7c8a185a3d0f48275cad43a9b94a60cbfddc04f6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 18 Jul 2022 18:39:41 +0100 Subject: [PATCH 026/228] HTML outputs can now be selected and handle control characters correctly --- src/core/Utils.mjs | 21 ++- src/core/operations/Magic.mjs | 2 +- src/core/operations/ROT13BruteForce.mjs | 6 +- src/core/operations/ROT47BruteForce.mjs | 6 +- .../operations/TextEncodingBruteForce.mjs | 2 +- src/core/operations/ToHexdump.mjs | 29 ++-- src/core/operations/XORBruteForce.mjs | 6 +- src/web/html/index.html | 2 - src/web/stylesheets/layout/_io.css | 21 +-- src/web/stylesheets/utils/_overrides.css | 8 ++ src/web/utils/copyOverride.mjs | 125 ++++++++++++++++++ src/web/utils/editorUtils.mjs | 70 +++++++++- src/web/utils/htmlWidget.mjs | 47 ++++++- src/web/waiters/OptionsWaiter.mjs | 7 - src/web/waiters/OutputWaiter.mjs | 88 +++++------- src/web/waiters/RecipeWaiter.mjs | 3 +- 16 files changed, 319 insertions(+), 124 deletions(-) create mode 100644 src/web/utils/copyOverride.mjs diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 5f36cae9..b72a6028 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -174,17 +174,13 @@ class Utils { * @returns {string} */ static printable(str, preserveWs=false, onlyAscii=false) { - if (isWebEnvironment() && window.app && !window.app.options.treatAsUtf8) { - str = Utils.byteArrayToChars(Utils.strToByteArray(str)); - } - if (onlyAscii) { return str.replace(/[^\x20-\x7f]/g, "."); } // eslint-disable-next-line no-misleading-character-class const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g; - const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g; + const wsRe = /[\x09-\x10\u2028\u2029]/g; str = str.replace(re, "."); if (!preserveWs) str = str.replace(wsRe, "."); @@ -192,6 +188,21 @@ class Utils { } + /** + * Returns a string with whitespace represented as special characters from the + * Unicode Private Use Area, which CyberChef will display as control characters. + * Private Use Area characters are in the range U+E000..U+F8FF. + * https://en.wikipedia.org/wiki/Private_Use_Areas + * @param {string} str + * @returns {string} + */ + static escapeWhitespace(str) { + return str.replace(/[\x09-\x10]/g, function(c) { + return String.fromCharCode(0xe000 + c.charCodeAt(0)); + }); + } + + /** * Parse a string entered by a user and replace escaped chars with the bytes they represent. * diff --git a/src/core/operations/Magic.mjs b/src/core/operations/Magic.mjs index d5357d95..69cad1db 100644 --- a/src/core/operations/Magic.mjs +++ b/src/core/operations/Magic.mjs @@ -149,7 +149,7 @@ class Magic extends Operation { output += ` ${Utils.generatePrettyRecipe(option.recipe, true)} - ${Utils.escapeHtml(Utils.printable(Utils.truncate(option.data, 99)))} + ${Utils.escapeHtml(Utils.escapeWhitespace(Utils.truncate(option.data, 99)))} ${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy} `; }); diff --git a/src/core/operations/ROT13BruteForce.mjs b/src/core/operations/ROT13BruteForce.mjs index aefe2ab7..7468ee11 100644 --- a/src/core/operations/ROT13BruteForce.mjs +++ b/src/core/operations/ROT13BruteForce.mjs @@ -86,12 +86,12 @@ class ROT13BruteForce extends Operation { } const rotatedString = Utils.byteArrayToUtf8(rotated); if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { - const rotatedStringPrintable = Utils.printable(rotatedString, false); + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); if (printAmount) { const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; - result.push(amountStr + rotatedStringPrintable); + result.push(amountStr + rotatedStringEscaped); } else { - result.push(rotatedStringPrintable); + result.push(rotatedStringEscaped); } } } diff --git a/src/core/operations/ROT47BruteForce.mjs b/src/core/operations/ROT47BruteForce.mjs index 5f346e00..fa1e90dc 100644 --- a/src/core/operations/ROT47BruteForce.mjs +++ b/src/core/operations/ROT47BruteForce.mjs @@ -66,12 +66,12 @@ class ROT47BruteForce extends Operation { } const rotatedString = Utils.byteArrayToUtf8(rotated); if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { - const rotatedStringPrintable = Utils.printable(rotatedString, false); + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); if (printAmount) { const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; - result.push(amountStr + rotatedStringPrintable); + result.push(amountStr + rotatedStringEscaped); } else { - result.push(rotatedStringPrintable); + result.push(rotatedStringEscaped); } } } diff --git a/src/core/operations/TextEncodingBruteForce.mjs b/src/core/operations/TextEncodingBruteForce.mjs index 18eb071e..ef8b7f80 100644 --- a/src/core/operations/TextEncodingBruteForce.mjs +++ b/src/core/operations/TextEncodingBruteForce.mjs @@ -79,7 +79,7 @@ class TextEncodingBruteForce extends Operation { let table = ""; for (const enc in encodings) { - const value = Utils.escapeHtml(Utils.printable(encodings[enc], true)); + const value = Utils.escapeHtml(Utils.escapeWhitespace(encodings[enc])); table += ``; } diff --git a/src/core/operations/ToHexdump.mjs b/src/core/operations/ToHexdump.mjs index c657adeb..a52b0451 100644 --- a/src/core/operations/ToHexdump.mjs +++ b/src/core/operations/ToHexdump.mjs @@ -63,33 +63,32 @@ class ToHexdump extends Operation { if (length < 1 || Math.round(length) !== length) throw new OperationError("Width must be a positive integer"); - let output = ""; + const lines = []; for (let i = 0; i < data.length; i += length) { - const buff = data.slice(i, i+length); - let hexa = ""; - for (let j = 0; j < buff.length; j++) { - hexa += Utils.hex(buff[j], padding) + " "; - } - let lineNo = Utils.hex(i, 8); + const buff = data.slice(i, i+length); + const hex = []; + buff.forEach(b => hex.push(Utils.hex(b, padding))); + let hexStr = hex.join(" ").padEnd(length*(padding+1), " "); + + const ascii = Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat); + const asciiStr = ascii.padEnd(buff.length, " "); + if (upperCase) { - hexa = hexa.toUpperCase(); + hexStr = hexStr.toUpperCase(); lineNo = lineNo.toUpperCase(); } - output += lineNo + " " + - hexa.padEnd(length*(padding+1), " ") + - " |" + - Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat).padEnd(buff.length, " ") + - "|\n"; + lines.push(`${lineNo} ${hexStr} |${asciiStr}|`); + if (includeFinalLength && i+buff.length === data.length) { - output += Utils.hex(i+buff.length, 8) + "\n"; + lines.push(Utils.hex(i+buff.length, 8)); } } - return output.slice(0, -1); + return lines.join("\n"); } /** diff --git a/src/core/operations/XORBruteForce.mjs b/src/core/operations/XORBruteForce.mjs index 9b548df8..8c097731 100644 --- a/src/core/operations/XORBruteForce.mjs +++ b/src/core/operations/XORBruteForce.mjs @@ -126,11 +126,7 @@ class XORBruteForce extends Operation { if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; - if (outputHex) { - record += toHex(result); - } else { - record += Utils.printable(resultUtf8, false); - } + record += outputHex ? toHex(result) : Utils.escapeWhitespace(resultUtf8); output.push(record); } diff --git a/src/web/html/index.html b/src/web/html/index.html index 3eb150e5..a7931de5 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -264,7 +264,6 @@
    -
    @@ -341,7 +340,6 @@
    -
    diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index cb196709..ea15b6ac 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -177,31 +177,12 @@ } .textarea-wrapper textarea, -.textarea-wrapper #output-text, -.textarea-wrapper #output-highlighter { +.textarea-wrapper #output-text { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); color: var(--fixed-width-font-colour); } -#input-highlighter, -#output-highlighter { - position: absolute; - left: 0; - bottom: 0; - width: 100%; - padding: 3px; - margin: 0; - overflow: hidden; - letter-spacing: normal; - white-space: pre-wrap; - word-wrap: break-word; - color: #fff; - background-color: transparent; - border: none; - pointer-events: none; -} - #output-loader { position: absolute; bottom: 0; diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index fa216836..920aab89 100755 --- a/src/web/stylesheets/utils/_overrides.css +++ b/src/web/stylesheets/utils/_overrides.css @@ -232,3 +232,11 @@ optgroup { .colorpicker-color div { height: 100px; } + + +/* CodeMirror */ + +.ͼ2 .cm-specialChar, +.cm-specialChar { + color: red; +} diff --git a/src/web/utils/copyOverride.mjs b/src/web/utils/copyOverride.mjs new file mode 100644 index 00000000..51b2386b --- /dev/null +++ b/src/web/utils/copyOverride.mjs @@ -0,0 +1,125 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * In order to render whitespace characters as control character pictures in the output, even + * when they are the designated line separator, CyberChef sometimes chooses to represent them + * internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). + * See `Utils.escapeWhitespace()` for an example of this. + * + * The `renderSpecialChar()` function understands that it should display these characters as + * control pictures. When copying data from the Output, we need to replace these PUA characters + * with their original values, so we override the DOM "copy" event and modify the copied data + * if required. This handler is based closely on the built-in CodeMirror handler and defers to the + * built-in handler if PUA characters are not present in the copied data, in order to minimise the + * impact of breaking changes. + */ + +import {EditorView} from "@codemirror/view"; + +/** + * Copies the currently selected text from the state doc. + * Based on the built-in implementation with a few unrequired bits taken out: + * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L604 + * + * @param {EditorState} state + * @returns {Object} + */ +function copiedRange(state) { + const content = []; + let linewise = false; + for (const range of state.selection.ranges) if (!range.empty) { + content.push(state.sliceDoc(range.from, range.to)); + } + if (!content.length) { + // Nothing selected, do a line-wise copy + let upto = -1; + for (const {from} of state.selection.ranges) { + const line = state.doc.lineAt(from); + if (line.number > upto) { + content.push(line.text); + } + upto = line.number; + } + linewise = true; + } + + return {text: content.join(state.lineBreak), linewise}; +} + +/** + * Regex to match characters in the Private Use Area of the Unicode table. + */ +const PUARegex = new RegExp("[\ue000-\uf8ff]"); +const PUARegexG = new RegExp("[\ue000-\uf8ff]", "g"); +/** + * Regex tto match Unicode Control Pictures. + */ +const CPRegex = new RegExp("[\u2400-\u243f]"); +const CPRegexG = new RegExp("[\u2400-\u243f]", "g"); + +/** + * Overrides the DOM "copy" handler in the CodeMirror editor in order to return the original + * values of control characters that have been represented in the Unicode Private Use Area for + * visual purposes. + * Based on the built-in copy handler with some modifications: + * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L629 + * + * This handler will defer to the built-in version if no PUA characters are present. + * + * @returns {Extension} + */ +export function copyOverride() { + return EditorView.domEventHandlers({ + copy(event, view) { + const {text, linewise} = copiedRange(view.state); + if (!text && !linewise) return; + + // If there are no PUA chars in the copied text, return false and allow the built-in + // copy handler to fire + if (!PUARegex.test(text)) return false; + + // If PUA chars are detected, modify them back to their original values and copy that instead + const rawText = text.replace(PUARegexG, function(c) { + return String.fromCharCode(c.charCodeAt(0) - 0xe000); + }); + + event.preventDefault(); + event.clipboardData.clearData(); + event.clipboardData.setData("text/plain", rawText); + + // Returning true prevents CodeMirror default handlers from firing + return true; + } + }); +} + + +/** + * Handler for copy events in output-html decorations. If there are control pictures present, + * this handler will convert them back to their raw form before copying. If there are no + * control pictures present, it will do nothing and defer to the default browser handler. + * + * @param {ClipboardEvent} event + * @returns {boolean} + */ +export function htmlCopyOverride(event) { + const text = window.getSelection().toString(); + if (!text) return; + + // If there are no control picture chars in the copied text, return false and allow the built-in + // copy handler to fire + if (!CPRegex.test(text)) return false; + + // If control picture chars are detected, modify them back to their original values and copy that instead + const rawText = text.replace(CPRegexG, function(c) { + return String.fromCharCode(c.charCodeAt(0) - 0x2400); + }); + + event.preventDefault(); + event.clipboardData.clearData(); + event.clipboardData.setData("text/plain", rawText); + + return true; +} diff --git a/src/web/utils/editorUtils.mjs b/src/web/utils/editorUtils.mjs index fe6b83d4..cb0ebed1 100644 --- a/src/web/utils/editorUtils.mjs +++ b/src/web/utils/editorUtils.mjs @@ -6,12 +6,41 @@ * @license Apache-2.0 */ +import Utils from "../../core/Utils.mjs"; + +// Descriptions for named control characters +const Names = { + 0: "null", + 7: "bell", + 8: "backspace", + 10: "line feed", + 11: "vertical tab", + 13: "carriage return", + 27: "escape", + 8203: "zero width space", + 8204: "zero width non-joiner", + 8205: "zero width joiner", + 8206: "left-to-right mark", + 8207: "right-to-left mark", + 8232: "line separator", + 8237: "left-to-right override", + 8238: "right-to-left override", + 8233: "paragraph separator", + 65279: "zero width no-break space", + 65532: "object replacement" +}; + +// Regex for Special Characters to be replaced +const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g"; +const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc\ue000-\uf8ff]", UnicodeRegexpSupport); + /** * Override for rendering special characters. * Should mirror the toDOM function in * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 * But reverts the replacement of line feeds with newline control pictures. + * * @param {number} code * @param {string} desc * @param {string} placeholder @@ -19,10 +48,47 @@ */ export function renderSpecialChar(code, desc, placeholder) { const s = document.createElement("span"); - // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. - s.textContent = code === 0x0a ? "\u240a" : placeholder; + + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back along with its description. + if (code === 0x0a) { + placeholder = "\u240a"; + desc = desc.replace("newline", "line feed"); + } + + // Render CyberChef escaped characters correctly - see Utils.escapeWhitespace + if (code >= 0xe000 && code <= 0xf8ff) { + code = code - 0xe000; + placeholder = String.fromCharCode(0x2400 + code); + desc = "Control character " + (Names[code] || "0x" + code.toString(16)); + } + + s.textContent = placeholder; s.title = desc; s.setAttribute("aria-label", desc); s.className = "cm-specialChar"; return s; } + + +/** + * Given a string, returns that string with any control characters replaced with HTML + * renderings of control pictures. + * + * @param {string} str + * @param {boolean} [preserveWs=false] + * @param {string} [lineBreak="\n"] + * @returns {html} + */ +export function escapeControlChars(str, preserveWs=false, lineBreak="\n") { + if (!preserveWs) + str = Utils.escapeWhitespace(str); + + return str.replace(Specials, function(c) { + if (lineBreak.includes(c)) return c; + const code = c.charCodeAt(0); + const desc = "Control character " + (Names[code] || "0x" + code.toString(16)); + const placeholder = code > 32 ? "\u2022" : String.fromCharCode(9216 + code); + const n = renderSpecialChar(code, desc, placeholder); + return n.outerHTML; + }); +} diff --git a/src/web/utils/htmlWidget.mjs b/src/web/utils/htmlWidget.mjs index fbce9b49..5e5c41c1 100644 --- a/src/web/utils/htmlWidget.mjs +++ b/src/web/utils/htmlWidget.mjs @@ -5,6 +5,9 @@ */ import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view"; +import {escapeControlChars} from "./editorUtils.mjs"; +import {htmlCopyOverride} from "./copyOverride.mjs"; + /** * Adds an HTML widget to the Code Mirror editor @@ -14,9 +17,10 @@ class HTMLWidget extends WidgetType { /** * HTMLWidget consructor */ - constructor(html) { + constructor(html, view) { super(); this.html = html; + this.view = view; } /** @@ -27,9 +31,45 @@ class HTMLWidget extends WidgetType { const wrap = document.createElement("span"); wrap.setAttribute("id", "output-html"); wrap.innerHTML = this.html; + + // Find text nodes and replace unprintable chars with control codes + this.walkTextNodes(wrap); + + // Add a handler for copy events to ensure the control codes are copied correctly + wrap.addEventListener("copy", htmlCopyOverride); return wrap; } + /** + * Walks all text nodes in a given element + * @param {DOMNode} el + */ + walkTextNodes(el) { + for (const node of el.childNodes) { + switch (node.nodeType) { + case Node.TEXT_NODE: + this.replaceControlChars(node); + break; + default: + if (node.nodeName !== "SCRIPT" && + node.nodeName !== "STYLE") + this.walkTextNodes(node); + break; + } + } + } + + /** + * Renders control characters in text nodes + * @param {DOMNode} textNode + */ + replaceControlChars(textNode) { + const val = escapeControlChars(textNode.nodeValue, true, this.view.state.lineBreak); + const node = document.createElement("null"); + node.innerHTML = val; + textNode.parentNode.replaceChild(node, textNode); + } + } /** @@ -42,7 +82,7 @@ function decorateHTML(view, html) { const widgets = []; if (html.length) { const deco = Decoration.widget({ - widget: new HTMLWidget(html), + widget: new HTMLWidget(html, view), side: 1 }); widgets.push(deco.range(0)); @@ -79,7 +119,8 @@ export function htmlPlugin(htmlOutput) { } } }, { - decorations: v => v.decorations + decorations: v => v.decorations, + } ); diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 7d9a3e2d..36beef7e 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -141,13 +141,6 @@ class OptionsWaiter { setWordWrap() { this.manager.input.setWordWrap(this.app.options.wordWrap); this.manager.output.setWordWrap(this.app.options.wordWrap); - document.getElementById("input-highlighter").classList.remove("word-wrap"); - document.getElementById("output-highlighter").classList.remove("word-wrap"); - - if (!this.app.options.wordWrap) { - document.getElementById("input-highlighter").classList.add("word-wrap"); - document.getElementById("output-highlighter").classList.add("word-wrap"); - } } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 3f031ac7..deaeaed3 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -5,7 +5,7 @@ * @license Apache-2.0 */ -import Utils, { debounce } from "../../core/Utils.mjs"; +import Utils, {debounce} from "../../core/Utils.mjs"; import Dish from "../../core/Dish.mjs"; import FileSaver from "file-saver"; import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; @@ -19,8 +19,9 @@ import {bracketMatching} from "@codemirror/language"; import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; import {statusBar} from "../utils/statusBar.mjs"; -import {renderSpecialChar} from "../utils/editorUtils.mjs"; import {htmlPlugin} from "../utils/htmlWidget.mjs"; +import {copyOverride} from "../utils/copyOverride.mjs"; +import {renderSpecialChar} from "../utils/editorUtils.mjs"; /** * Waiter to handle events related to the output @@ -61,7 +62,8 @@ class OutputWaiter { initEditor() { this.outputEditorConf = { eol: new Compartment, - lineWrapping: new Compartment + lineWrapping: new Compartment, + drawSelection: new Compartment }; const initialState = EditorState.create({ @@ -69,9 +71,10 @@ class OutputWaiter { extensions: [ // Editor extensions EditorState.readOnly.of(true), - htmlPlugin(this.htmlOutput), - highlightSpecialChars({render: renderSpecialChar}), - drawSelection(), + highlightSpecialChars({ + render: renderSpecialChar, // Custom character renderer to handle special cases + addSpecialChars: /[\ue000-\uf8ff]/g // Add the Unicode Private Use Area which we use for some whitespace chars + }), rectangularSelection(), crosshairCursor(), bracketMatching(), @@ -79,16 +82,19 @@ class OutputWaiter { search({top: true}), EditorState.allowMultipleSelections.of(true), - // Custom extensiosn + // Custom extensions statusBar({ label: "Output", bakeStats: this.bakeStats, eolHandler: this.eolChange.bind(this) }), + htmlPlugin(this.htmlOutput), + copyOverride(), // Mutable state this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + this.outputEditorConf.drawSelection.of(drawSelection()), // Keymap keymap.of([ @@ -153,6 +159,14 @@ class OutputWaiter { * @param {string} data */ setOutput(data) { + // Turn drawSelection back on + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.drawSelection.reconfigure( + drawSelection() + ) + }); + + // Insert data into editor this.outputEditorView.dispatch({ changes: { from: 0, @@ -173,6 +187,11 @@ class OutputWaiter { // triggers the htmlWidget to render the HTML. this.setOutput(""); + // Turn off drawSelection + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.drawSelection.reconfigure([]) + }); + // Execute script sections const scriptElements = document.getElementById("output-html").querySelectorAll("script"); for (let i = 0; i < scriptElements.length; i++) { @@ -414,8 +433,6 @@ class OutputWaiter { if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10); const outputFile = document.getElementById("output-file"); - const outputHighlighter = document.getElementById("output-highlighter"); - const inputHighlighter = document.getElementById("input-highlighter"); // If pending or baking, show loader and status message // If error, style the tab and handle the error @@ -447,8 +464,6 @@ class OutputWaiter { this.outputTextEl.style.display = "block"; this.outputTextEl.classList.remove("blur"); outputFile.style.display = "none"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; this.clearHTMLOutput(); if (output.error) { @@ -463,8 +478,6 @@ class OutputWaiter { if (output.data === null) { this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.clearHTMLOutput(); this.setOutput(""); @@ -478,15 +491,11 @@ class OutputWaiter { switch (output.data.type) { case "html": outputFile.style.display = "none"; - outputHighlighter.style.display = "none"; - inputHighlighter.style.display = "none"; this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": this.outputTextEl.style.display = "block"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; this.clearHTMLOutput(); this.setOutput(""); @@ -497,8 +506,6 @@ class OutputWaiter { default: this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.clearHTMLOutput(); this.setOutput(output.data.result); @@ -1215,8 +1222,6 @@ class OutputWaiter { document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice..."; this.toggleLoader(true); const outputFile = document.getElementById("output-file"), - outputHighlighter = document.getElementById("output-highlighter"), - inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), sliceFromEl = document.getElementById("output-file-slice-from"), sliceToEl = document.getElementById("output-file-slice-to"), @@ -1238,8 +1243,6 @@ class OutputWaiter { this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.toggleLoader(false); } @@ -1251,8 +1254,6 @@ class OutputWaiter { document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash..."; this.toggleLoader(true); const outputFile = document.getElementById("output-file"), - outputHighlighter = document.getElementById("output-highlighter"), - inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), output = this.outputs[this.manager.tabs.getActiveOutputTab()].data; @@ -1270,8 +1271,6 @@ class OutputWaiter { this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.toggleLoader(false); } @@ -1319,36 +1318,13 @@ class OutputWaiter { } const output = await dish.get(Dish.STRING); + const self = this; - // Create invisible textarea to populate with the raw dish string (not the printable version that - // contains dots instead of the actual bytes) - const textarea = document.createElement("textarea"); - textarea.style.position = "fixed"; - textarea.style.top = 0; - textarea.style.left = 0; - textarea.style.width = 0; - textarea.style.height = 0; - textarea.style.border = "none"; - - textarea.value = output; - document.body.appendChild(textarea); - - let success = false; - try { - textarea.select(); - success = output && document.execCommand("copy"); - } catch (err) { - success = false; - } - - if (success) { - this.app.alert("Copied raw output successfully.", 2000); - } else { - this.app.alert("Sorry, the output could not be copied.", 3000); - } - - // Clean up - document.body.removeChild(textarea); + navigator.clipboard.writeText(output).then(function() { + self.app.alert("Copied raw output successfully.", 2000); + }, function(err) { + self.app.alert("Sorry, the output could not be copied.", 3000); + }); } /** diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs index f4107e66..d907a67c 100755 --- a/src/web/waiters/RecipeWaiter.mjs +++ b/src/web/waiters/RecipeWaiter.mjs @@ -7,6 +7,7 @@ import HTMLOperation from "../HTMLOperation.mjs"; import Sortable from "sortablejs"; import Utils from "../../core/Utils.mjs"; +import {escapeControlChars} from "../utils/editorUtils.mjs"; /** @@ -568,7 +569,7 @@ class RecipeWaiter { const registerList = []; for (let i = 0; i < registers.length; i++) { - registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); + registerList.push(`$R${numPrevRegisters + i} = ${escapeControlChars(Utils.escapeHtml(Utils.truncate(registers[i], 100)))}`); } const registerListEl = `
    ${registerList.join("
    ")} From e93aa42697b5101791b2bc1238f8b687c08cf84f Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 2 Sep 2022 12:56:04 +0100 Subject: [PATCH 027/228] Input and output character encodings can now be set --- src/core/Chef.mjs | 8 +- src/core/ChefWorker.js | 7 +- src/core/Utils.mjs | 8 + src/core/lib/ChrEnc.mjs | 13 +- src/core/operations/DecodeText.mjs | 8 +- src/core/operations/EncodeText.mjs | 8 +- .../operations/TextEncodingBruteForce.mjs | 10 +- src/web/Manager.mjs | 1 - src/web/html/index.html | 3 - src/web/stylesheets/layout/_io.css | 42 ++- src/web/utils/htmlWidget.mjs | 11 +- src/web/utils/statusBar.mjs | 231 +++++++++++++--- src/web/waiters/InputWaiter.mjs | 168 +++++++----- src/web/waiters/OutputWaiter.mjs | 132 ++++----- src/web/workers/InputWorker.mjs | 255 +++++------------- 15 files changed, 482 insertions(+), 423 deletions(-) diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs index 36998cec..140774bc 100755 --- a/src/core/Chef.mjs +++ b/src/core/Chef.mjs @@ -68,16 +68,10 @@ class Chef { // Present the raw result await recipe.present(this.dish); - // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. - // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. - // The threshold is specified in KiB. - const threshold = (options.ioDisplayThreshold || 1024) * 1024; const returnType = this.dish.type === Dish.HTML ? Dish.HTML : - this.dish.size > threshold ? - Dish.ARRAY_BUFFER : - Dish.STRING; + Dish.ARRAY_BUFFER; return { dish: rawDish, diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index d46a705d..8989875a 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -101,14 +101,17 @@ async function bake(data) { // Ensure the relevant modules are loaded self.loadRequiredModules(data.recipeConfig); try { - self.inputNum = (data.inputNum !== undefined) ? data.inputNum : -1; + self.inputNum = data.inputNum === undefined ? -1 : data.inputNum; const response = await self.chef.bake( data.input, // The user's input data.recipeConfig, // The configuration of the recipe data.options // Options set by the user ); - const transferable = (data.input instanceof ArrayBuffer) ? [data.input] : undefined; + const transferable = (response.dish.value instanceof ArrayBuffer) ? + [response.dish.value] : + undefined; + self.postMessage({ action: "bakeComplete", data: Object.assign(response, { diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index b72a6028..604b7b8c 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -406,6 +406,7 @@ class Utils { * Utils.strToArrayBuffer("你好"); */ static strToArrayBuffer(str) { + log.debug("Converting string to array buffer"); const arr = new Uint8Array(str.length); let i = str.length, b; while (i--) { @@ -432,6 +433,7 @@ class Utils { * Utils.strToUtf8ArrayBuffer("你好"); */ static strToUtf8ArrayBuffer(str) { + log.debug("Converting string to UTF8 array buffer"); const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -461,6 +463,7 @@ class Utils { * Utils.strToByteArray("你好"); */ static strToByteArray(str) { + log.debug("Converting string to byte array"); const byteArray = new Array(str.length); let i = str.length, b; while (i--) { @@ -487,6 +490,7 @@ class Utils { * Utils.strToUtf8ByteArray("你好"); */ static strToUtf8ByteArray(str) { + log.debug("Converting string to UTF8 byte array"); const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -515,6 +519,7 @@ class Utils { * Utils.strToCharcode("你好"); */ static strToCharcode(str) { + log.debug("Converting string to charcode"); const charcode = []; for (let i = 0; i < str.length; i++) { @@ -549,6 +554,7 @@ class Utils { * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ static byteArrayToUtf8(byteArray) { + log.debug("Converting byte array to UTF8"); const str = Utils.byteArrayToChars(byteArray); try { const utf8Str = utf8.decode(str); @@ -581,6 +587,7 @@ class Utils { * Utils.byteArrayToChars([20320,22909]); */ static byteArrayToChars(byteArray) { + log.debug("Converting byte array to chars"); if (!byteArray) return ""; let str = ""; // String concatenation appears to be faster than an array join @@ -603,6 +610,7 @@ class Utils { * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); */ static arrayBufferToStr(arrayBuffer, utf8=true) { + log.debug("Converting array buffer to str"); const arr = new Uint8Array(arrayBuffer); return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr); } diff --git a/src/core/lib/ChrEnc.mjs b/src/core/lib/ChrEnc.mjs index c5cb5605..8934d137 100644 --- a/src/core/lib/ChrEnc.mjs +++ b/src/core/lib/ChrEnc.mjs @@ -9,7 +9,7 @@ /** * Character encoding format mappings. */ -export const IO_FORMAT = { +export const CHR_ENC_CODE_PAGES = { "UTF-8 (65001)": 65001, "UTF-7 (65000)": 65000, "UTF-16LE (1200)": 1200, @@ -164,6 +164,17 @@ export const IO_FORMAT = { "Simplified Chinese GB18030 (54936)": 54936, }; + +export const CHR_ENC_SIMPLE_LOOKUP = {}; +export const CHR_ENC_SIMPLE_REVERSE_LOOKUP = {}; + +for (const name in CHR_ENC_CODE_PAGES) { + const simpleName = name.match(/(^.+)\([\d/]+\)$/)[1]; + + CHR_ENC_SIMPLE_LOOKUP[simpleName] = CHR_ENC_CODE_PAGES[name]; + CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName; +} + /** * Unicode Normalisation Forms * diff --git a/src/core/operations/DecodeText.mjs b/src/core/operations/DecodeText.mjs index 9b01b79f..0fc9d2b5 100644 --- a/src/core/operations/DecodeText.mjs +++ b/src/core/operations/DecodeText.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation.mjs"; import cptable from "codepage"; -import {IO_FORMAT} from "../lib/ChrEnc.mjs"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; /** * Decode text operation @@ -26,7 +26,7 @@ class DecodeText extends Operation { "

    ", "Supported charsets are:", "
      ", - Object.keys(IO_FORMAT).map(e => `
    • ${e}
    • `).join("\n"), + Object.keys(CHR_ENC_CODE_PAGES).map(e => `
    • ${e}
    • `).join("\n"), "
    ", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; @@ -36,7 +36,7 @@ class DecodeText extends Operation { { "name": "Encoding", "type": "option", - "value": Object.keys(IO_FORMAT) + "value": Object.keys(CHR_ENC_CODE_PAGES) } ]; } @@ -47,7 +47,7 @@ class DecodeText extends Operation { * @returns {string} */ run(input, args) { - const format = IO_FORMAT[args[0]]; + const format = CHR_ENC_CODE_PAGES[args[0]]; return cptable.utils.decode(format, new Uint8Array(input)); } diff --git a/src/core/operations/EncodeText.mjs b/src/core/operations/EncodeText.mjs index 8fc61fce..8cc1450f 100644 --- a/src/core/operations/EncodeText.mjs +++ b/src/core/operations/EncodeText.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation.mjs"; import cptable from "codepage"; -import {IO_FORMAT} from "../lib/ChrEnc.mjs"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; /** * Encode text operation @@ -26,7 +26,7 @@ class EncodeText extends Operation { "

    ", "Supported charsets are:", "
      ", - Object.keys(IO_FORMAT).map(e => `
    • ${e}
    • `).join("\n"), + Object.keys(CHR_ENC_CODE_PAGES).map(e => `
    • ${e}
    • `).join("\n"), "
    ", ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; @@ -36,7 +36,7 @@ class EncodeText extends Operation { { "name": "Encoding", "type": "option", - "value": Object.keys(IO_FORMAT) + "value": Object.keys(CHR_ENC_CODE_PAGES) } ]; } @@ -47,7 +47,7 @@ class EncodeText extends Operation { * @returns {ArrayBuffer} */ run(input, args) { - const format = IO_FORMAT[args[0]]; + const format = CHR_ENC_CODE_PAGES[args[0]]; const encoded = cptable.utils.encode(format, input); return new Uint8Array(encoded).buffer; } diff --git a/src/core/operations/TextEncodingBruteForce.mjs b/src/core/operations/TextEncodingBruteForce.mjs index ef8b7f80..ae96fd0a 100644 --- a/src/core/operations/TextEncodingBruteForce.mjs +++ b/src/core/operations/TextEncodingBruteForce.mjs @@ -8,7 +8,7 @@ import Operation from "../Operation.mjs"; import Utils from "../Utils.mjs"; import cptable from "codepage"; -import {IO_FORMAT} from "../lib/ChrEnc.mjs"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; /** * Text Encoding Brute Force operation @@ -28,7 +28,7 @@ class TextEncodingBruteForce extends Operation { "

    ", "Supported charsets are:", "
      ", - Object.keys(IO_FORMAT).map(e => `
    • ${e}
    • `).join("\n"), + Object.keys(CHR_ENC_CODE_PAGES).map(e => `
    • ${e}
    • `).join("\n"), "
    " ].join("\n"); this.infoURL = "https://wikipedia.org/wiki/Character_encoding"; @@ -51,15 +51,15 @@ class TextEncodingBruteForce extends Operation { */ run(input, args) { const output = {}, - charsets = Object.keys(IO_FORMAT), + charsets = Object.keys(CHR_ENC_CODE_PAGES), mode = args[0]; charsets.forEach(charset => { try { if (mode === "Decode") { - output[charset] = cptable.utils.decode(IO_FORMAT[charset], input); + output[charset] = cptable.utils.decode(CHR_ENC_CODE_PAGES[charset], input); } else { - output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(IO_FORMAT[charset], input)); + output[charset] = Utils.arrayBufferToStr(cptable.utils.encode(CHR_ENC_CODE_PAGES[charset], input)); } } catch (err) { output[charset] = "Could not decode."; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 820b1a8d..793b61de 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -180,7 +180,6 @@ class Manager { document.getElementById("save-all-to-file").addEventListener("click", this.output.saveAllClick.bind(this.output)); document.getElementById("copy-output").addEventListener("click", this.output.copyClick.bind(this.output)); document.getElementById("switch").addEventListener("click", this.output.switchClick.bind(this.output)); - document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); diff --git a/src/web/html/index.html b/src/web/html/index.html index a7931de5..68d69a78 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -300,9 +300,6 @@ - diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index ea15b6ac..185b3bdb 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -224,7 +224,7 @@ #output-file { position: absolute; left: 0; - bottom: 0; + top: 50%; width: 100%; display: none; } @@ -446,6 +446,10 @@ /* Status bar */ +.cm-panel input::placeholder { + font-size: 12px !important; +} + .ͼ2 .cm-panels { background-color: var(--secondary-background-colour); border-color: var(--secondary-border-colour); @@ -509,12 +513,38 @@ background-color: #ddd } -/* Show the dropup menu on hover */ -.cm-status-bar-select:hover .cm-status-bar-select-content { - display: block; -} - /* Change the background color of the dropup button when the dropup content is shown */ .cm-status-bar-select:hover .cm-status-bar-select-btn { background-color: #f1f1f1; } + +/* The search field */ +.cm-status-bar-filter-input { + box-sizing: border-box; + font-size: 12px; + padding-left: 10px !important; + border: none; +} + +.cm-status-bar-filter-search { + border-top: 1px solid #ddd; +} + +/* Show the dropup menu */ +.cm-status-bar-select .show { + display: block; +} + +.cm-status-bar-select-scroll { + overflow-y: auto; + max-height: 300px; +} + +.chr-enc-value { + max-width: 150px; + display: inline-block; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + vertical-align: middle; +} \ No newline at end of file diff --git a/src/web/utils/htmlWidget.mjs b/src/web/utils/htmlWidget.mjs index 5e5c41c1..34800933 100644 --- a/src/web/utils/htmlWidget.mjs +++ b/src/web/utils/htmlWidget.mjs @@ -65,9 +65,11 @@ class HTMLWidget extends WidgetType { */ replaceControlChars(textNode) { const val = escapeControlChars(textNode.nodeValue, true, this.view.state.lineBreak); - const node = document.createElement("null"); - node.innerHTML = val; - textNode.parentNode.replaceChild(node, textNode); + if (val.length !== textNode.nodeValue.length) { + const node = document.createElement("span"); + node.innerHTML = val; + textNode.parentNode.replaceChild(node, textNode); + } } } @@ -119,8 +121,7 @@ export function htmlPlugin(htmlOutput) { } } }, { - decorations: v => v.decorations, - + decorations: v => v.decorations } ); diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 431d8a3d..f9be5006 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -5,6 +5,7 @@ */ import {showPanel} from "@codemirror/view"; +import {CHR_ENC_SIMPLE_LOOKUP, CHR_ENC_SIMPLE_REVERSE_LOOKUP} from "../../core/lib/ChrEnc.mjs"; /** * A Status bar extension for CodeMirror @@ -19,6 +20,10 @@ class StatusBarPanel { this.label = opts.label; this.bakeStats = opts.bakeStats ? opts.bakeStats : null; this.eolHandler = opts.eolHandler; + this.chrEncHandler = opts.chrEncHandler; + + this.eolVal = null; + this.chrEncVal = null; this.dom = this.buildDOM(); } @@ -40,19 +45,42 @@ class StatusBarPanel { dom.appendChild(rhs); // Event listeners - dom.addEventListener("click", this.eolSelectClick.bind(this), false); + dom.querySelectorAll(".cm-status-bar-select-btn").forEach( + el => el.addEventListener("click", this.showDropUp.bind(this), false) + ); + dom.querySelector(".eol-select").addEventListener("click", this.eolSelectClick.bind(this), false); + dom.querySelector(".chr-enc-select").addEventListener("click", this.chrEncSelectClick.bind(this), false); + dom.querySelector(".cm-status-bar-filter-input").addEventListener("keyup", this.chrEncFilter.bind(this), false); return dom; } + /** + * Handler for dropup clicks + * Shows/Hides the dropup + * @param {Event} e + */ + showDropUp(e) { + const el = e.target + .closest(".cm-status-bar-select") + .querySelector(".cm-status-bar-select-content"); + + el.classList.add("show"); + + // Focus the filter input if present + const filter = el.querySelector(".cm-status-bar-filter-input"); + if (filter) filter.focus(); + + // Set up a listener to close the menu if the user clicks outside of it + hideOnClickOutside(el, e); + } + /** * Handler for EOL Select clicks * Sets the line separator * @param {Event} e */ eolSelectClick(e) { - e.preventDefault(); - const eolLookup = { "LF": "\u000a", "VT": "\u000b", @@ -65,8 +93,46 @@ class StatusBarPanel { }; const eolval = eolLookup[e.target.getAttribute("data-val")]; + if (eolval === undefined) return; + // Call relevant EOL change handler this.eolHandler(eolval); + hideElement(e.target.closest(".cm-status-bar-select-content")); + } + + /** + * Handler for Chr Enc Select clicks + * Sets the character encoding + * @param {Event} e + */ + chrEncSelectClick(e) { + const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); + + if (isNaN(chrEncVal)) return; + + this.chrEncHandler(chrEncVal); + this.updateCharEnc(chrEncVal); + hideElement(e.target.closest(".cm-status-bar-select-content")); + } + + /** + * Handler for Chr Enc keyup events + * Filters the list of selectable character encodings + * @param {Event} e + */ + chrEncFilter(e) { + const input = e.target; + const filter = input.value.toLowerCase(); + const div = input.closest(".cm-status-bar-select-content"); + const a = div.getElementsByTagName("a"); + for (let i = 0; i < a.length; i++) { + const txtValue = a[i].textContent || a[i].innerText; + if (txtValue.toLowerCase().includes(filter)) { + a[i].style.display = "block"; + } else { + a[i].style.display = "none"; + } + } } /** @@ -121,33 +187,48 @@ class StatusBarPanel { } /** - * Gets the current character encoding of the document - * @param {EditorState} state - */ - updateCharEnc(state) { - // const charenc = this.dom.querySelector("#char-enc-value"); - // TODO - // charenc.textContent = "TODO"; - } - - /** - * Returns what the current EOL separator is set to + * Sets the current EOL separator in the status bar * @param {EditorState} state */ updateEOL(state) { + if (state.lineBreak === this.eolVal) return; + const eolLookup = { - "\u000a": "LF", - "\u000b": "VT", - "\u000c": "FF", - "\u000d": "CR", - "\u000d\u000a": "CRLF", - "\u0085": "NEL", - "\u2028": "LS", - "\u2029": "PS" + "\u000a": ["LF", "Line Feed"], + "\u000b": ["VT", "Vertical Tab"], + "\u000c": ["FF", "Form Feed"], + "\u000d": ["CR", "Carriage Return"], + "\u000d\u000a": ["CRLF", "Carriage Return + Line Feed"], + "\u0085": ["NEL", "Next Line"], + "\u2028": ["LS", "Line Separator"], + "\u2029": ["PS", "Paragraph Separator"] }; const val = this.dom.querySelector(".eol-value"); - val.textContent = eolLookup[state.lineBreak]; + const button = val.closest(".cm-status-bar-select-btn"); + const eolName = eolLookup[state.lineBreak]; + val.textContent = eolName[0]; + button.setAttribute("title", `End of line sequence: ${eolName[1]}`); + button.setAttribute("data-original-title", `End of line sequence: ${eolName[1]}`); + this.eolVal = state.lineBreak; + } + + + /** + * Gets the current character encoding of the document + * @param {number} chrEncVal + */ + updateCharEnc(chrEncVal) { + if (chrEncVal === this.chrEncVal) return; + + const name = CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] ? CHR_ENC_SIMPLE_REVERSE_LOOKUP[chrEncVal] : "Raw Bytes"; + + const val = this.dom.querySelector(".chr-enc-value"); + const button = val.closest(".cm-status-bar-select-btn"); + val.textContent = name; + button.setAttribute("title", `${this.label} character encoding: ${name}`); + button.setAttribute("data-original-title", `${this.label} character encoding: ${name}`); + this.chrEncVal = chrEncVal; } /** @@ -168,6 +249,19 @@ class StatusBarPanel { } } + /** + * Updates the sizing of elements that need to fit correctly + * @param {EditorView} view + */ + updateSizing(view) { + const viewHeight = view.contentDOM.clientHeight; + this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( + el => { + el.style.maxHeight = (viewHeight - 50) + "px"; + } + ); + } + /** * Builds the Left-hand-side widgets * @returns {string} @@ -197,39 +291,98 @@ class StatusBarPanel { /** * Builds the Right-hand-side widgets * Event listener set up in Manager + * * @returns {string} */ constructRHS() { + const chrEncOptions = Object.keys(CHR_ENC_SIMPLE_LOOKUP).map(name => + `${name}` + ).join(""); + return ` - - language - UTF-16 - +
    + + text_fields Raw Bytes + +
    +
    + Raw Bytes + ${chrEncOptions} +
    + +
    +
    keyboard_return - `; } } +const elementsWithListeners = {}; + +/** + * Hides the provided element when a click is made outside of it + * @param {Element} element + * @param {Event} instantiatingEvent + */ +function hideOnClickOutside(element, instantiatingEvent) { + /** + * Handler for document click events + * Closes element if click is outside it. + * @param {Event} event + */ + const outsideClickListener = event => { + // Don't trigger if we're clicking inside the element, or if the element + // is not visible, or if this is the same click event that opened it. + if (!element.contains(event.target) && + event.timeStamp !== instantiatingEvent.timeStamp) { + hideElement(element); + } + }; + + if (!Object.keys(elementsWithListeners).includes(element)) { + document.addEventListener("click", outsideClickListener); + elementsWithListeners[element] = outsideClickListener; + } +} + +/** + * Hides the specified element and removes the click listener for it + * @param {Element} element + */ +function hideElement(element) { + element.classList.remove("show"); + document.removeEventListener("click", elementsWithListeners[element]); + delete elementsWithListeners[element]; +} + + /** * A panel constructor factory building a panel that re-counts the stats every time the document changes. * @param {Object} opts @@ -240,7 +393,7 @@ function makePanel(opts) { return (view) => { sbPanel.updateEOL(view.state); - sbPanel.updateCharEnc(view.state); + sbPanel.updateCharEnc(opts.initialChrEncVal); sbPanel.updateBakeStats(); sbPanel.updateStats(view.state.doc); sbPanel.updateSelection(view.state, false); @@ -250,8 +403,10 @@ function makePanel(opts) { update(update) { sbPanel.updateEOL(update.state); sbPanel.updateSelection(update.state, update.selectionSet); - sbPanel.updateCharEnc(update.state); sbPanel.updateBakeStats(); + if (update.geometryChanged) { + sbPanel.updateSizing(update.view); + } if (update.docChanged) { sbPanel.updateStats(update.state.doc); } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index ed8f174b..caa1a098 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -10,6 +10,7 @@ import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker import Utils, {debounce} from "../../core/Utils.mjs"; import {toBase64} from "../../core/lib/Base64.mjs"; import {isImage} from "../../core/lib/FileType.mjs"; +import cptable from "codepage"; import { EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, dropCursor @@ -39,6 +40,7 @@ class InputWaiter { this.manager = manager; this.inputTextEl = document.getElementById("input-text"); + this.inputChrEnc = 0; this.initEditor(); this.inputWorker = null; @@ -84,7 +86,9 @@ class InputWaiter { // Custom extensions statusBar({ label: "Input", - eolHandler: this.eolChange.bind(this) + eolHandler: this.eolChange.bind(this), + chrEncHandler: this.chrEncChange.bind(this), + initialChrEncVal: this.inputChrEnc }), // Mutable state @@ -122,19 +126,30 @@ class InputWaiter { /** * Handler for EOL change events * Sets the line separator + * @param {string} eolVal */ - eolChange(eolval) { + eolChange(eolVal) { const oldInputVal = this.getInput(); // Update the EOL value this.inputEditorView.dispatch({ - effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval)) + effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal)) }); // Reset the input so that lines are recalculated, preserving the old EOL values this.setInput(oldInputVal); } + /** + * Handler for Chr Enc change events + * Sets the input character encoding + * @param {number} chrEncVal + */ + chrEncChange(chrEncVal) { + this.inputChrEnc = chrEncVal; + this.inputChange(); + } + /** * Sets word wrap on the input editor * @param {boolean} wrap @@ -380,7 +395,7 @@ class InputWaiter { this.showLoadingInfo(r.data, true); break; case "setInput": - this.set(r.data.inputObj, r.data.silent); + this.set(r.data.inputNum, r.data.inputObj, r.data.silent); break; case "inputAdded": this.inputAdded(r.data.changeTab, r.data.inputNum); @@ -403,9 +418,6 @@ class InputWaiter { case "setUrl": this.setUrl(r.data); break; - case "inputSwitch": - this.manager.output.inputSwitch(r.data); - break; case "getInput": case "getInputNums": this.callbacks[r.data.id](r.data); @@ -435,22 +447,36 @@ class InputWaiter { /** * Sets the input in the input area * - * @param {object} inputData - Object containing the input and its metadata - * @param {number} inputData.inputNum - The unique inputNum for the selected input - * @param {string | object} inputData.input - The actual input data - * @param {string} inputData.name - The name of the input file - * @param {number} inputData.size - The size in bytes of the input file - * @param {string} inputData.type - The MIME type of the input file - * @param {number} inputData.progress - The load progress of the input file + * @param {number} inputNum + * @param {Object} inputData - Object containing the input and its metadata + * @param {string} type + * @param {ArrayBuffer} buffer + * @param {string} stringSample + * @param {Object} file + * @param {string} file.name + * @param {number} file.size + * @param {string} file.type + * @param {string} status + * @param {number} progress * @param {boolean} [silent=false] - If false, fires the manager statechange event */ - async set(inputData, silent=false) { + async set(inputNum, inputData, silent=false) { return new Promise(function(resolve, reject) { const activeTab = this.manager.tabs.getActiveInputTab(); - if (inputData.inputNum !== activeTab) return; + if (inputNum !== activeTab) return; - if (typeof inputData.input === "string") { - this.setInput(inputData.input); + if (inputData.file) { + this.setFile(inputNum, inputData, silent); + } else { + // TODO Per-tab encodings? + let inputVal; + if (this.inputChrEnc > 0) { + inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); + } else { + inputVal = Utils.arrayBufferToStr(inputData.buffer); + } + + this.setInput(inputVal); const fileOverlay = document.getElementById("input-file"), fileName = document.getElementById("input-file-name"), fileSize = document.getElementById("input-file-size"), @@ -466,8 +492,8 @@ class InputWaiter { this.inputTextEl.classList.remove("blur"); // Set URL to current input - const inputStr = toBase64(inputData.input, "A-Za-z0-9+/"); - if (inputStr.length >= 0 && inputStr.length <= 68267) { + if (inputVal.length >= 0 && inputVal.length <= 51200) { + const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); this.setUrl({ includeInput: true, input: inputStr @@ -475,8 +501,6 @@ class InputWaiter { } if (!silent) window.dispatchEvent(this.manager.statechange); - } else { - this.setFile(inputData, silent); } }.bind(this)); @@ -485,18 +509,22 @@ class InputWaiter { /** * Displays file details * - * @param {object} inputData - Object containing the input and its metadata - * @param {number} inputData.inputNum - The unique inputNum for the selected input - * @param {string | object} inputData.input - The actual input data - * @param {string} inputData.name - The name of the input file - * @param {number} inputData.size - The size in bytes of the input file - * @param {string} inputData.type - The MIME type of the input file - * @param {number} inputData.progress - The load progress of the input file + * @param {number} inputNum + * @param {Object} inputData - Object containing the input and its metadata + * @param {string} type + * @param {ArrayBuffer} buffer + * @param {string} stringSample + * @param {Object} file + * @param {string} file.name + * @param {number} file.size + * @param {string} file.type + * @param {string} status + * @param {number} progress * @param {boolean} [silent=true] - If false, fires the manager statechange event */ - setFile(inputData, silent=true) { + setFile(inputNum, inputData, silent=true) { const activeTab = this.manager.tabs.getActiveInputTab(); - if (inputData.inputNum !== activeTab) return; + if (inputNum !== activeTab) return; const fileOverlay = document.getElementById("input-file"), fileName = document.getElementById("input-file-name"), @@ -505,9 +533,9 @@ class InputWaiter { fileLoaded = document.getElementById("input-file-loaded"); fileOverlay.style.display = "block"; - fileName.textContent = inputData.name; - fileSize.textContent = inputData.size + " bytes"; - fileType.textContent = inputData.type; + fileName.textContent = inputData.file.name; + fileSize.textContent = inputData.file.size + " bytes"; + fileType.textContent = inputData.file.type; if (inputData.status === "error") { fileLoaded.textContent = "Error"; fileLoaded.style.color = "#FF0000"; @@ -516,7 +544,7 @@ class InputWaiter { fileLoaded.textContent = inputData.progress + "%"; } - this.displayFilePreview(inputData); + this.displayFilePreview(inputNum, inputData); if (!silent) window.dispatchEvent(this.manager.statechange); } @@ -583,19 +611,18 @@ class InputWaiter { /** * Shows a chunk of the file in the input behind the file overlay * + * @param {number} inputNum - The inputNum of the file being displayed * @param {Object} inputData - Object containing the input data - * @param {number} inputData.inputNum - The inputNum of the file being displayed - * @param {ArrayBuffer} inputData.input - The actual input to display + * @param {string} inputData.stringSample - The first 4096 bytes of input as a string */ - displayFilePreview(inputData) { + displayFilePreview(inputNum, inputData) { const activeTab = this.manager.tabs.getActiveInputTab(), - input = inputData.input; - if (inputData.inputNum !== activeTab) return; + input = inputData.buffer; + if (inputNum !== activeTab) return; this.inputTextEl.classList.add("blur"); - this.setInput(Utils.arrayBufferToStr(input.slice(0, 4096))); + this.setInput(input.stringSample); this.renderFileThumb(); - } /** @@ -623,46 +650,40 @@ class InputWaiter { * * @param {number} inputNum * @param {string | ArrayBuffer} value - * @param {boolean} [force=false] - If true, forces the value to be updated even if the type is different to the currently stored type */ updateInputValue(inputNum, value, force=false) { - let includeInput = false; - const recipeStr = toBase64(value, "A-Za-z0-9+/"); // B64 alphabet with no padding - if (recipeStr.length > 0 && recipeStr.length <= 68267) { - includeInput = true; + // Prepare the value as a buffer (full value) and a string sample (up to 4096 bytes) + let buffer; + let stringSample = ""; + + // If value is a string, interpret it using the specified character encoding + if (typeof value === "string") { + stringSample = value.slice(0, 4096); + if (this.inputChrEnc > 0) { + buffer = cptable.utils.encode(this.inputChrEnc, value); + buffer = new Uint8Array(buffer).buffer; + } else { + buffer = Utils.strToArrayBuffer(value); + } + } else { + buffer = value; + stringSample = Utils.arrayBufferToStr(value.slice(0, 4096)); } + + + const recipeStr = buffer.byteLength < 51200 ? toBase64(buffer, "A-Za-z0-9+/") : ""; // B64 alphabet with no padding this.setUrl({ - includeInput: includeInput, + includeInput: recipeStr.length > 0 && buffer.byteLength < 51200, input: recipeStr }); - // Value is either a string set by the input or an ArrayBuffer from a LoaderWorker, - // so is safe to use typeof === "string" - const transferable = (typeof value !== "string") ? [value] : undefined; + const transferable = [buffer]; this.inputWorker.postMessage({ action: "updateInputValue", data: { inputNum: inputNum, - value: value, - force: force - } - }, transferable); - } - - /** - * Updates the .data property for the input of the specified inputNum. - * Used for switching the output into the input - * - * @param {number} inputNum - The inputNum of the input we're changing - * @param {object} inputData - The new data object - */ - updateInputObj(inputNum, inputData) { - const transferable = (typeof inputData !== "string") ? [inputData.fileBuffer] : undefined; - this.inputWorker.postMessage({ - action: "updateInputObj", - data: { - inputNum: inputNum, - data: inputData + buffer: buffer, + stringSample: stringSample } }, transferable); } @@ -1052,9 +1073,8 @@ class InputWaiter { this.updateInputValue(inputNum, "", true); - this.set({ - inputNum: inputNum, - input: "" + this.set(inputNum, { + buffer: new ArrayBuffer() }); this.manager.tabs.updateInputTabHeader(inputNum, ""); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index deaeaed3..f0b03d72 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -9,6 +9,7 @@ import Utils, {debounce} from "../../core/Utils.mjs"; import Dish from "../../core/Dish.mjs"; import FileSaver from "file-saver"; import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; +import cptable from "codepage"; import { EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor @@ -48,6 +49,7 @@ class OutputWaiter { html: "", changed: false }; + this.outputChrEnc = 0; this.initEditor(); this.outputs = {}; @@ -86,7 +88,9 @@ class OutputWaiter { statusBar({ label: "Output", bakeStats: this.bakeStats, - eolHandler: this.eolChange.bind(this) + eolHandler: this.eolChange.bind(this), + chrEncHandler: this.chrEncChange.bind(this), + initialChrEncVal: this.outputChrEnc }), htmlPlugin(this.htmlOutput), copyOverride(), @@ -119,19 +123,29 @@ class OutputWaiter { /** * Handler for EOL change events * Sets the line separator + * @param {string} eolVal */ - eolChange(eolval) { + eolChange(eolVal) { const oldOutputVal = this.getOutput(); // Update the EOL value this.outputEditorView.dispatch({ - effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval)) + effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolVal)) }); // Reset the output so that lines are recalculated, preserving the old EOL values this.setOutput(oldOutputVal); } + /** + * Handler for Chr Enc change events + * Sets the output character encoding + * @param {number} chrEncVal + */ + chrEncChange(chrEncVal) { + this.outputChrEnc = chrEncVal; + } + /** * Sets word wrap on the output editor * @param {boolean} wrap @@ -193,7 +207,8 @@ class OutputWaiter { }); // Execute script sections - const scriptElements = document.getElementById("output-html").querySelectorAll("script"); + const outputHTML = document.getElementById("output-html"); + const scriptElements = outputHTML ? outputHTML.querySelectorAll("script") : []; for (let i = 0; i < scriptElements.length; i++) { try { eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval @@ -405,8 +420,6 @@ class OutputWaiter { removeAllOutputs() { this.outputs = {}; - this.resetSwitch(); - const tabsList = document.getElementById("output-tabs"); const tabsListChildren = tabsList.children; @@ -418,19 +431,18 @@ class OutputWaiter { } /** - * Sets the output in the output textarea. + * Sets the output in the output pane. * * @param {number} inputNum */ async set(inputNum) { + inputNum = parseInt(inputNum, 10); if (inputNum !== this.manager.tabs.getActiveOutputTab() || !this.outputExists(inputNum)) return; this.toggleLoader(true); return new Promise(async function(resolve, reject) { - const output = this.outputs[inputNum], - activeTab = this.manager.tabs.getActiveOutputTab(); - if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10); + const output = this.outputs[inputNum]; const outputFile = document.getElementById("output-file"); @@ -491,17 +503,33 @@ class OutputWaiter { switch (output.data.type) { case "html": outputFile.style.display = "none"; + // TODO what if the HTML content needs to be in a certain character encoding? + // Grey out chr enc selection? Set back to Raw Bytes? this.setHTMLOutput(output.data.result); break; - case "ArrayBuffer": + case "ArrayBuffer": { this.outputTextEl.style.display = "block"; + outputFile.style.display = "none"; this.clearHTMLOutput(); - this.setOutput(""); - this.setFile(await this.getDishBuffer(output.data.dish), activeTab); + let outputVal = ""; + if (this.outputChrEnc === 0) { + outputVal = Utils.arrayBufferToStr(output.data.result); + } else { + try { + outputVal = cptable.utils.decode(this.outputChrEnc, new Uint8Array(output.data.result)); + } catch (err) { + outputVal = err; + } + } + + this.setOutput(outputVal); + + // this.setFile(await this.getDishBuffer(output.data.dish), activeTab); break; + } case "string": default: this.outputTextEl.style.display = "block"; @@ -1333,7 +1361,6 @@ class OutputWaiter { */ async switchClick() { const activeTab = this.manager.tabs.getActiveOutputTab(); - const transferable = []; const switchButton = document.getElementById("switch"); switchButton.classList.add("spin"); @@ -1341,82 +1368,15 @@ class OutputWaiter { switchButton.firstElementChild.innerHTML = "autorenew"; $(switchButton).tooltip("hide"); - let active = await this.getDishBuffer(this.getOutputDish(activeTab)); + const activeData = await this.getDishBuffer(this.getOutputDish(activeTab)); - if (!this.outputExists(activeTab)) { - this.resetSwitchButton(); - return; - } - - if (this.outputs[activeTab].data.type === "string" && - active.byteLength <= this.app.options.ioDisplayThreshold * 1024) { - const dishString = await this.getDishStr(this.getOutputDish(activeTab)); - active = dishString; - } else { - transferable.push(active); - } - - this.manager.input.inputWorker.postMessage({ - action: "inputSwitch", - data: { + if (this.outputExists(activeTab)) { + this.manager.input.set({ inputNum: activeTab, - outputData: active - } - }, transferable); - } - - /** - * Handler for when the inputWorker has switched the inputs. - * Stores the old input - * - * @param {object} switchData - * @param {number} switchData.inputNum - * @param {string | object} switchData.data - * @param {ArrayBuffer} switchData.data.fileBuffer - * @param {number} switchData.data.size - * @param {string} switchData.data.type - * @param {string} switchData.data.name - */ - inputSwitch(switchData) { - this.switchOrigData = switchData; - document.getElementById("undo-switch").disabled = false; - - this.resetSwitchButton(); - - } - - /** - * Handler for undo switch click events. - * Removes the output from the input and replaces the input that was removed. - */ - undoSwitchClick() { - this.manager.input.updateInputObj(this.switchOrigData.inputNum, this.switchOrigData.data); - - this.manager.input.fileLoaded(this.switchOrigData.inputNum); - - this.resetSwitch(); - } - - /** - * Removes the switch data and resets the switch buttons - */ - resetSwitch() { - if (this.switchOrigData !== undefined) { - delete this.switchOrigData; + input: activeData + }); } - const undoSwitch = document.getElementById("undo-switch"); - undoSwitch.disabled = true; - $(undoSwitch).tooltip("hide"); - - this.resetSwitchButton(); - } - - /** - * Resets the switch button to its usual state - */ - resetSwitchButton() { - const switchButton = document.getElementById("switch"); switchButton.classList.remove("spin"); switchButton.disabled = false; switchButton.firstElementChild.innerHTML = "open_in_browser"; diff --git a/src/web/workers/InputWorker.mjs b/src/web/workers/InputWorker.mjs index 9912995b..e1c75de9 100644 --- a/src/web/workers/InputWorker.mjs +++ b/src/web/workers/InputWorker.mjs @@ -3,12 +3,12 @@ * Handles storage, modification and retrieval of the inputs. * * @author j433866 [j433866@gmail.com] + * @author n1474335 [n1474335@gmail.com] * @copyright Crown Copyright 2019 * @license Apache-2.0 */ import Utils from "../../core/Utils.mjs"; -import {detectFileType} from "../../core/lib/FileType.mjs"; // Default max values // These will be correctly calculated automatically @@ -16,6 +16,21 @@ self.maxWorkers = 4; self.maxTabs = 1; self.pendingFiles = []; + +/** + * Dictionary of inputs keyed on the inputNum + * Each entry is an object with the following type: + * @typedef {Object} Input + * @property {string} type + * @property {ArrayBuffer} buffer + * @property {string} stringSample + * @property {Object} file + * @property {string} file.name + * @property {number} file.size + * @property {string} file.type + * @property {string} status + * @property {number} progress + */ self.inputs = {}; self.loaderWorkers = []; self.currentInputNum = 1; @@ -53,9 +68,6 @@ self.addEventListener("message", function(e) { case "updateInputValue": self.updateInputValue(r.data); break; - case "updateInputObj": - self.updateInputObj(r.data); - break; case "updateInputProgress": self.updateInputProgress(r.data); break; @@ -75,7 +87,7 @@ self.addEventListener("message", function(e) { log.setLevel(r.data, false); break; case "addInput": - self.addInput(r.data, "string"); + self.addInput(r.data, "userinput"); break; case "refreshTabs": self.refreshTabs(r.data.inputNum, r.data.direction); @@ -98,9 +110,6 @@ self.addEventListener("message", function(e) { case "loaderWorkerMessage": self.handleLoaderMessage(r.data); break; - case "inputSwitch": - self.inputSwitch(r.data); - break; case "updateTabHeader": self.updateTabHeader(r.data); break; @@ -213,13 +222,10 @@ self.bakeInput = function(inputNum, bakeId) { return; } - let inputData = inputObj.data; - if (typeof inputData !== "string") inputData = inputData.fileBuffer; - self.postMessage({ action: "queueInput", data: { - input: inputData, + input: inputObj.buffer, inputNum: inputNum, bakeId: bakeId } @@ -236,23 +242,6 @@ self.getInputObj = function(inputNum) { return self.inputs[inputNum]; }; -/** - * Gets the stored value for a specific inputNum. - * - * @param {number} inputNum - The input we want to get the value of - * @returns {string | ArrayBuffer} - */ -self.getInputValue = function(inputNum) { - if (self.inputs[inputNum]) { - if (typeof self.inputs[inputNum].data === "string") { - return self.inputs[inputNum].data; - } else { - return self.inputs[inputNum].data.fileBuffer; - } - } - return ""; -}; - /** * Gets the stored value or object for a specific inputNum and sends it to the inputWaiter. * @@ -263,7 +252,7 @@ self.getInputValue = function(inputNum) { */ self.getInput = function(inputData) { const inputNum = inputData.inputNum, - data = (inputData.getObj) ? self.getInputObj(inputNum) : self.getInputValue(inputNum); + data = (inputData.getObj) ? self.getInputObj(inputNum) : self.inputs[inputNum].buffer; self.postMessage({ action: "getInput", data: { @@ -421,17 +410,15 @@ self.getNearbyNums = function(inputNum, direction) { self.updateTabHeader = function(inputNum) { const input = self.getInputObj(inputNum); if (input === null || input === undefined) return; - let inputData = input.data; - if (typeof inputData !== "string") { - inputData = input.data.name; - } - inputData = inputData.replace(/[\n\r]/g, ""); + + let header = input.type === "file" ? input.file.name : input.stringSample; + header = header.slice(0, 100).replace(/[\n\r]/g, ""); self.postMessage({ action: "updateTabHeader", data: { inputNum: inputNum, - input: inputData.slice(0, 100) + input: header } }); }; @@ -450,37 +437,15 @@ self.setInput = function(inputData) { const input = self.getInputObj(inputNum); if (input === undefined || input === null) return; - let inputVal = input.data; - const inputObj = { - inputNum: inputNum, - input: inputVal - }; - if (typeof inputVal !== "string") { - inputObj.name = inputVal.name; - inputObj.size = inputVal.size; - inputObj.type = inputVal.type; - inputObj.progress = input.progress; - inputObj.status = input.status; - inputVal = inputVal.fileBuffer; - const fileSlice = inputVal.slice(0, 512001); - inputObj.input = fileSlice; + self.postMessage({ + action: "setInput", + data: { + inputNum: inputNum, + inputObj: input, + silent: silent + } + }); - self.postMessage({ - action: "setInput", - data: { - inputObj: inputObj, - silent: silent - } - }, [fileSlice]); - } else { - self.postMessage({ - action: "setInput", - data: { - inputObj: inputObj, - silent: silent - } - }); - } self.updateTabHeader(inputNum); }; @@ -546,54 +511,23 @@ self.updateInputProgress = function(inputData) { * * @param {object} inputData * @param {number} inputData.inputNum - The input that's having its value updated - * @param {string | ArrayBuffer} inputData.value - The new value of the input - * @param {boolean} inputData.force - If true, still updates the input value if the input type is different to the stored value + * @param {ArrayBuffer} inputData.buffer - The new value of the input as a buffer + * @param {string} [inputData.stringSample] - A sample of the value as a string (truncated to 4096 chars) */ self.updateInputValue = function(inputData) { - const inputNum = inputData.inputNum; + const inputNum = parseInt(inputData.inputNum, 10); if (inputNum < 1) return; - if (Object.prototype.hasOwnProperty.call(self.inputs[inputNum].data, "fileBuffer") && - typeof inputData.value === "string" && !inputData.force) return; - const value = inputData.value; - if (self.inputs[inputNum] !== undefined) { - if (typeof value === "string") { - self.inputs[inputNum].data = value; - } else { - self.inputs[inputNum].data.fileBuffer = value; - } - self.inputs[inputNum].status = "loaded"; - self.inputs[inputNum].progress = 100; - return; + + if (!Object.prototype.hasOwnProperty.call(self.inputs, inputNum)) + throw new Error(`No input with ID ${inputNum} exists`); + + self.inputs[inputNum].buffer = inputData.buffer; + if (!("stringSample" in inputData)) { + inputData.stringSample = Utils.arrayBufferToStr(inputData.buffer.slice(0, 4096)); } - - // If we get to here, an input for inputNum could not be found, - // so create a new one. Only do this if the value is a string, as - // loadFiles will create the input object for files - if (typeof value === "string") { - self.inputs.push({ - inputNum: inputNum, - data: value, - status: "loaded", - progress: 100 - }); - } -}; - -/** - * Update the stored data object for an input. - * Used if we need to change a string to an ArrayBuffer - * - * @param {object} inputData - * @param {number} inputData.inputNum - The number of the input we're updating - * @param {object} inputData.data - The new data object for the input - */ -self.updateInputObj = function(inputData) { - const inputNum = inputData.inputNum; - const data = inputData.data; - - if (self.getInputObj(inputNum) === undefined) return; - - self.inputs[inputNum].data = data; + self.inputs[inputNum].stringSample = inputData.stringSample; + self.inputs[inputNum].status = "loaded"; + self.inputs[inputNum].progress = 100; }; /** @@ -632,8 +566,7 @@ self.loaderWorkerReady = function(workerData) { /** * Handler for messages sent by loaderWorkers. - * (Messages are sent between the inputWorker and - * loaderWorkers via the main thread) + * (Messages are sent between the inputWorker and loaderWorkers via the main thread) * * @param {object} r - The data sent by the loaderWorker * @param {number} r.inputNum - The inputNum which the message corresponds to @@ -667,7 +600,7 @@ self.handleLoaderMessage = function(r) { self.updateInputValue({ inputNum: inputNum, - value: r.fileBuffer + buffer: r.fileBuffer }); self.postMessage({ @@ -757,7 +690,8 @@ self.loadFiles = function(filesData) { let lastInputNum = -1; const inputNums = []; for (let i = 0; i < files.length; i++) { - if (i === 0 && self.getInputValue(activeTab) === "") { + // If the first input is empty, replace it rather than adding a new one + if (i === 0 && (!self.inputs[activeTab].buffer || self.inputs[activeTab].buffer.byteLength === 0)) { self.removeInput({ inputNum: activeTab, refreshTabs: false, @@ -798,7 +732,7 @@ self.loadFiles = function(filesData) { * Adds an input to the input dictionary * * @param {boolean} [changetab=false] - Whether or not to change to the new input - * @param {string} type - Either "string" or "file" + * @param {string} type - Either "userinput" or "file" * @param {Object} fileData - Contains information about the file to be added to the input (only used when type is "file") * @param {string} fileData.name - The filename of the input being added * @param {number} fileData.size - The file size (in bytes) of the input being added @@ -810,25 +744,30 @@ self.addInput = function( type, fileData = { name: "unknown", - size: "unknown", + size: 0, type: "unknown" }, inputNum = self.currentInputNum++ ) { self.numInputs++; const newInputObj = { - inputNum: inputNum + type: null, + buffer: new ArrayBuffer(), + stringSample: "", + file: null, + status: "pending", + progress: 0 }; switch (type) { - case "string": - newInputObj.data = ""; + case "userinput": + newInputObj.type = "userinput"; newInputObj.status = "loaded"; newInputObj.progress = 100; break; case "file": - newInputObj.data = { - fileBuffer: new ArrayBuffer(), + newInputObj.type = "file"; + newInputObj.file = { name: fileData.name, size: fileData.size, type: fileData.type @@ -837,7 +776,7 @@ self.addInput = function( newInputObj.progress = 0; break; default: - log.error(`Invalid type '${type}'.`); + log.error(`Invalid input type '${type}'.`); return -1; } self.inputs[inputNum] = newInputObj; @@ -976,18 +915,18 @@ self.filterTabs = function(searchData) { self.inputs[iNum].status === "loading" && showLoading || self.inputs[iNum].status === "loaded" && showLoaded) { try { - if (typeof self.inputs[iNum].data === "string") { + if (self.inputs[iNum].type === "userinput") { if (filterType.toLowerCase() === "content" && - filterExp.test(self.inputs[iNum].data.slice(0, 4096))) { - textDisplay = self.inputs[iNum].data.slice(0, 4096); + filterExp.test(self.inputs[iNum].stringSample)) { + textDisplay = self.inputs[iNum].stringSample; addInput = true; } } else { if ((filterType.toLowerCase() === "filename" && - filterExp.test(self.inputs[iNum].data.name)) || - filterType.toLowerCase() === "content" && - filterExp.test(Utils.arrayBufferToStr(self.inputs[iNum].data.fileBuffer.slice(0, 4096)))) { - textDisplay = self.inputs[iNum].data.name; + filterExp.test(self.inputs[iNum].file.name)) || + (filterType.toLowerCase() === "content" && + filterExp.test(self.inputs[iNum].stringSample))) { + textDisplay = self.inputs[iNum].file.name; addInput = true; } } @@ -1021,61 +960,3 @@ self.filterTabs = function(searchData) { data: inputs }); }; - -/** - * Swaps the input and outputs, and sends the old input back to the main thread. - * - * @param {object} switchData - * @param {number} switchData.inputNum - The inputNum of the input to be switched to - * @param {string | ArrayBuffer} switchData.outputData - The data to switch to - */ -self.inputSwitch = function(switchData) { - const currentInput = self.getInputObj(switchData.inputNum); - const currentData = currentInput.data; - if (currentInput === undefined || currentInput === null) return; - - if (typeof switchData.outputData !== "string") { - const output = new Uint8Array(switchData.outputData), - types = detectFileType(output); - let type = "unknown", - ext = "dat"; - if (types.length) { - type = types[0].mime; - ext = types[0].extension.split(",", 1)[0]; - } - - // ArrayBuffer - self.updateInputObj({ - inputNum: switchData.inputNum, - data: { - fileBuffer: switchData.outputData, - name: `output.${ext}`, - size: switchData.outputData.byteLength.toLocaleString(), - type: type - } - }); - } else { - // String - self.updateInputValue({ - inputNum: switchData.inputNum, - value: switchData.outputData, - force: true - }); - } - - self.postMessage({ - action: "inputSwitch", - data: { - data: currentData, - inputNum: switchData.inputNum - } - }); - - self.postMessage({ - action: "fileLoaded", - data: { - inputNum: switchData.inputNum - } - }); - -}; From 16b79e32f6ea4e4a00984f2d5d8a854f8d4275a4 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 2 Sep 2022 14:33:41 +0100 Subject: [PATCH 028/228] File details are now displayed in a side panel and the input is still editable --- src/web/Manager.mjs | 2 - src/web/html/index.html | 15 -- src/web/stylesheets/layout/_io.css | 48 +++++- src/web/utils/fileDetails.mjs | 134 +++++++++++++++ src/web/utils/sidePanel.mjs | 254 +++++++++++++++++++++++++++++ src/web/waiters/InputWaiter.mjs | 213 ++++++++---------------- 6 files changed, 500 insertions(+), 166 deletions(-) create mode 100644 src/web/utils/fileDetails.mjs create mode 100644 src/web/utils/sidePanel.mjs diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 793b61de..730d6e2e 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -152,7 +152,6 @@ class Manager { this.addListeners("#input-wrapper", "dragover", this.input.inputDragover, this.input); this.addListeners("#input-wrapper", "dragleave", this.input.inputDragleave, this.input); this.addListeners("#input-wrapper", "drop", this.input.inputDrop, this.input); - document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); document.getElementById("btn-next-input-tab").addEventListener("mousedown", this.input.nextTabClick.bind(this.input)); @@ -218,7 +217,6 @@ class Manager { this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); document.getElementById("theme").addEventListener("change", this.options.themeChange.bind(this.options)); document.getElementById("logLevel").addEventListener("change", this.options.logLevelChange.bind(this.options)); - document.getElementById("imagePreview").addEventListener("change", this.input.renderFileThumb.bind(this.input)); // Misc window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); diff --git a/src/web/html/index.html b/src/web/html/index.html index 68d69a78..6e2c60a3 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -265,21 +265,6 @@
    -
    -
    -
    -
    - -
    - - Name:
    - Size:
    - Type:
    - Loaded: -
    -
    -
    -
    diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 185b3bdb..9c64fe85 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -220,7 +220,6 @@ transition: all 0.5s ease; } -#input-file, #output-file { position: absolute; left: 0; @@ -450,9 +449,10 @@ font-size: 12px !important; } -.ͼ2 .cm-panels { +.ͼ2 .cm-panels, +.ͼ2 .cm-side-panels { background-color: var(--secondary-background-colour); - border-color: var(--secondary-border-colour); + border-color: var(--primary-border-colour); color: var(--primary-font-colour); } @@ -547,4 +547,44 @@ text-overflow: ellipsis; white-space: nowrap; vertical-align: middle; -} \ No newline at end of file +} + + +/* File details panel */ + +.cm-file-details { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + overflow-y: auto; + padding-bottom: 21px; + height: 100%; +} + +.file-details-heading { + font-weight: bold; + margin: 10px 0 10px 0; +} + +.file-details-data { + text-align: left; + margin: 10px 2px; +} + +.file-details-data td { + padding: 0 3px; + max-width: 130px; + min-width: 60px; + overflow: hidden; + vertical-align: top; + word-break: break-all; +} + +.file-details-error { + color: #f00; +} + +.file-details-thumbnail { + max-width: 180px; +} diff --git a/src/web/utils/fileDetails.mjs b/src/web/utils/fileDetails.mjs new file mode 100644 index 00000000..f8e3003b --- /dev/null +++ b/src/web/utils/fileDetails.mjs @@ -0,0 +1,134 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {showSidePanel} from "./sidePanel.mjs"; +import Utils from "../../core/Utils.mjs"; +import {isImage} from "../../core/lib/FileType.mjs"; + +/** + * A File Details extension for CodeMirror + */ +class FileDetailsPanel { + + /** + * FileDetailsPanel constructor + * @param {Object} opts + */ + constructor(opts) { + this.fileDetails = opts.fileDetails; + this.progress = opts.progress; + this.status = opts.status; + this.buffer = opts.buffer; + this.renderPreview = opts.renderPreview; + this.dom = this.buildDOM(); + this.renderFileThumb(); + } + + /** + * Builds the file details DOM tree + * @returns {DOMNode} + */ + buildDOM() { + const dom = document.createElement("div"); + + dom.className = "cm-file-details"; + const fileThumb = require("../static/images/file-128x128.png"); + dom.innerHTML = ` +

    File details

    + +
    EncodingValue
    ${enc}${value}
    + + + + + + + + + + + + + + + + +
    Name: + ${Utils.escapeHtml(this.fileDetails.name)} +
    Size: + ${Utils.escapeHtml(this.fileDetails.size)} bytes +
    Type: + ${Utils.escapeHtml(this.fileDetails.type)} +
    Loaded: + ${this.status === "error" ? "Error" : this.progress + "%"} +
    + `; + + return dom; + } + + /** + * Render the file thumbnail + */ + renderFileThumb() { + if (!this.renderPreview) { + this.resetFileThumb(); + return; + } + const fileThumb = this.dom.querySelector(".file-details-thumbnail"); + const fileType = this.dom.querySelector(".file-details-type"); + const fileBuffer = new Uint8Array(this.buffer); + const type = isImage(fileBuffer); + + if (type && type !== "image/tiff" && fileBuffer.byteLength <= 512000) { + // Most browsers don't support displaying TIFFs, so ignore them + // Don't render images over 512,000 bytes + const blob = new Blob([fileBuffer], {type: type}), + url = URL.createObjectURL(blob); + fileThumb.src = url; + } else { + this.resetFileThumb(); + } + fileType.textContent = type; + } + + /** + * Reset the file thumbnail to the default icon + */ + resetFileThumb() { + const fileThumb = this.dom.querySelector(".file-details-thumbnail"); + fileThumb.src = require("../static/images/file-128x128.png"); + } + +} + +/** + * A panel constructor factory building a panel that displays file details + * @param {Object} opts + * @returns {Function} + */ +function makePanel(opts) { + const fdPanel = new FileDetailsPanel(opts); + + return (view) => { + return { + dom: fdPanel.dom, + width: 200, + update(update) { + } + }; + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @param {Object} opts + * @returns {Extension} + */ +export function fileDetailsPanel(opts) { + const panelMaker = makePanel(opts); + return showSidePanel.of(panelMaker); +} diff --git a/src/web/utils/sidePanel.mjs b/src/web/utils/sidePanel.mjs new file mode 100644 index 00000000..a8de0931 --- /dev/null +++ b/src/web/utils/sidePanel.mjs @@ -0,0 +1,254 @@ +/** + * A modification of the CodeMirror Panel extension to enable panels to the + * left and right of the editor. + * Based on code here: https://github.com/codemirror/view/blob/main/src/panel.ts + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {EditorView, ViewPlugin} from "@codemirror/view"; +import {Facet} from "@codemirror/state"; + +const panelConfig = Facet.define({ + combine(configs) { + let leftContainer, rightContainer; + for (const c of configs) { + leftContainer = leftContainer || c.leftContainer; + rightContainer = rightContainer || c.rightContainer; + } + return {leftContainer, rightContainer}; + } +}); + +/** + * Configures the panel-managing extension. + * @param {PanelConfig} config + * @returns Extension + */ +export function panels(config) { + return config ? [panelConfig.of(config)] : []; +} + +/** + * Get the active panel created by the given constructor, if any. + * This can be useful when you need access to your panels' DOM + * structure. + * @param {EditorView} view + * @param {PanelConstructor} panel + * @returns {Panel} + */ +export function getPanel(view, panel) { + const plugin = view.plugin(panelPlugin); + const index = plugin ? plugin.specs.indexOf(panel) : -1; + return index > -1 ? plugin.panels[index] : null; +} + +const panelPlugin = ViewPlugin.fromClass(class { + + /** + * @param {EditorView} view + */ + constructor(view) { + this.input = view.state.facet(showSidePanel); + this.specs = this.input.filter(s => s); + this.panels = this.specs.map(spec => spec(view)); + const conf = view.state.facet(panelConfig); + this.left = new PanelGroup(view, true, conf.leftContainer); + this.right = new PanelGroup(view, false, conf.rightContainer); + this.left.sync(this.panels.filter(p => p.left)); + this.right.sync(this.panels.filter(p => !p.left)); + for (const p of this.panels) { + p.dom.classList.add("cm-panel"); + if (p.mount) p.mount(); + } + } + + /** + * @param {ViewUpdate} update + */ + update(update) { + const conf = update.state.facet(panelConfig); + if (this.left.container !== conf.leftContainer) { + this.left.sync([]); + this.left = new PanelGroup(update.view, true, conf.leftContainer); + } + if (this.right.container !== conf.rightContainer) { + this.right.sync([]); + this.right = new PanelGroup(update.view, false, conf.rightContainer); + } + this.left.syncClasses(); + this.right.syncClasses(); + const input = update.state.facet(showSidePanel); + if (input !== this.input) { + const specs = input.filter(x => x); + const panels = [], left = [], right = [], mount = []; + for (const spec of specs) { + const known = this.specs.indexOf(spec); + let panel; + if (known < 0) { + panel = spec(update.view); + mount.push(panel); + } else { + panel = this.panels[known]; + if (panel.update) panel.update(update); + } + panels.push(panel) + ;(panel.left ? left : right).push(panel); + } + this.specs = specs; + this.panels = panels; + this.left.sync(left); + this.right.sync(right); + for (const p of mount) { + p.dom.classList.add("cm-panel"); + if (p.mount) p.mount(); + } + } else { + for (const p of this.panels) if (p.update) p.update(update); + } + } + + /** + * Destroy panel + */ + destroy() { + this.left.sync([]); + this.right.sync([]); + } +}, { + // provide: PluginField.scrollMargins.from(value => ({left: value.left.scrollMargin(), right: value.right.scrollMargin()})) +}); + +/** + * PanelGroup + */ +class PanelGroup { + + /** + * @param {EditorView} view + * @param {boolean} left + * @param {HTMLElement} container + */ + constructor(view, left, container) { + this.view = view; + this.left = left; + this.container = container; + this.dom = undefined; + this.classes = ""; + this.panels = []; + this.bufferWidth = 0; + this.syncClasses(); + } + + /** + * @param {Panel[]} panels + */ + sync(panels) { + for (const p of this.panels) if (p.destroy && panels.indexOf(p) < 0) p.destroy(); + this.panels = panels; + this.syncDOM(); + } + + /** + * Synchronise the DOM + */ + syncDOM() { + if (this.panels.length === 0) { + if (this.dom) { + this.dom.remove(); + this.dom = undefined; + } + return; + } + + const parent = this.container || this.view.dom; + if (!this.dom) { + this.dom = document.createElement("div"); + this.dom.className = this.left ? "cm-side-panels cm-panels-left" : "cm-side-panels cm-panels-right"; + parent.insertBefore(this.dom, parent.firstChild); + } + + let curDOM = this.dom.firstChild; + for (const panel of this.panels) { + if (panel.dom.parentNode === this.dom) { + while (curDOM !== panel.dom) curDOM = rm(curDOM); + curDOM = curDOM.nextSibling; + } else { + this.dom.insertBefore(panel.dom, curDOM); + this.bufferWidth = panel.width; + panel.dom.style.width = panel.width + "px"; + this.dom.style.width = this.bufferWidth + "px"; + } + } + while (curDOM) curDOM = rm(curDOM); + + const margin = this.left ? "marginLeft" : "marginRight"; + parent.querySelector(".cm-scroller").style[margin] = this.bufferWidth + "px"; + } + + /** + * + */ + scrollMargin() { + return !this.dom || this.container ? 0 : + Math.max(0, this.left ? + this.dom.getBoundingClientRect().right - Math.max(0, this.view.scrollDOM.getBoundingClientRect().left) : + Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().right) - this.dom.getBoundingClientRect().left); + } + + /** + * + */ + syncClasses() { + if (!this.container || this.classes === this.view.themeClasses) return; + for (const cls of this.classes.split(" ")) if (cls) this.container.classList.remove(cls); + for (const cls of (this.classes = this.view.themeClasses).split(" ")) if (cls) this.container.classList.add(cls); + } +} + +/** + * @param {ChildNode} node + * @returns HTMLElement + */ +function rm(node) { + const next = node.nextSibling; + node.remove(); + return next; +} + +const baseTheme = EditorView.baseTheme({ + ".cm-side-panels": { + boxSizing: "border-box", + position: "absolute", + height: "100%", + top: 0, + bottom: 0 + }, + "&light .cm-side-panels": { + backgroundColor: "#f5f5f5", + color: "black" + }, + "&light .cm-panels-left": { + borderRight: "1px solid #ddd", + left: 0 + }, + "&light .cm-panels-right": { + borderLeft: "1px solid #ddd", + right: 0 + }, + "&dark .cm-side-panels": { + backgroundColor: "#333338", + color: "white" + } +}); + +/** + * Opening a panel is done by providing a constructor function for + * the panel through this facet. (The panel is closed again when its + * constructor is no longer provided.) Values of `null` are ignored. + */ +export const showSidePanel = Facet.define({ + enables: [panelPlugin, baseTheme] +}); diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index caa1a098..000940a4 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -9,7 +9,6 @@ import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWork import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs"; import Utils, {debounce} from "../../core/Utils.mjs"; import {toBase64} from "../../core/lib/Base64.mjs"; -import {isImage} from "../../core/lib/FileType.mjs"; import cptable from "codepage"; import { @@ -21,6 +20,7 @@ import {bracketMatching} from "@codemirror/language"; import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; import {statusBar} from "../utils/statusBar.mjs"; +import {fileDetailsPanel} from "../utils/fileDetails.mjs"; import {renderSpecialChar} from "../utils/editorUtils.mjs"; @@ -65,7 +65,8 @@ class InputWaiter { initEditor() { this.inputEditorConf = { eol: new Compartment, - lineWrapping: new Compartment + lineWrapping: new Compartment, + fileDetailsPanel: new Compartment }; const initialState = EditorState.create({ @@ -92,6 +93,7 @@ class InputWaiter { }), // Mutable state + this.inputEditorConf.fileDetailsPanel.of([]), this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), @@ -466,43 +468,32 @@ class InputWaiter { if (inputNum !== activeTab) return; if (inputData.file) { - this.setFile(inputNum, inputData, silent); + this.setFile(inputNum, inputData); } else { - // TODO Per-tab encodings? - let inputVal; - if (this.inputChrEnc > 0) { - inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); - } else { - inputVal = Utils.arrayBufferToStr(inputData.buffer); - } - - this.setInput(inputVal); - const fileOverlay = document.getElementById("input-file"), - fileName = document.getElementById("input-file-name"), - fileSize = document.getElementById("input-file-size"), - fileType = document.getElementById("input-file-type"), - fileLoaded = document.getElementById("input-file-loaded"); - - fileOverlay.style.display = "none"; - fileName.textContent = ""; - fileSize.textContent = ""; - fileType.textContent = ""; - fileLoaded.textContent = ""; - - this.inputTextEl.classList.remove("blur"); - - // Set URL to current input - if (inputVal.length >= 0 && inputVal.length <= 51200) { - const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); - this.setUrl({ - includeInput: true, - input: inputStr - }); - } - - if (!silent) window.dispatchEvent(this.manager.statechange); + this.clearFile(inputNum); } + // TODO Per-tab encodings? + let inputVal; + if (this.inputChrEnc > 0) { + inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); + } else { + inputVal = Utils.arrayBufferToStr(inputData.buffer); + } + + this.setInput(inputVal); + + // Set URL to current input + if (inputVal.length >= 0 && inputVal.length <= 51200) { + const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); + this.setUrl({ + includeInput: true, + input: inputStr + }); + } + + if (!silent) window.dispatchEvent(this.manager.statechange); + }.bind(this)); } @@ -520,33 +511,38 @@ class InputWaiter { * @param {string} file.type * @param {string} status * @param {number} progress - * @param {boolean} [silent=true] - If false, fires the manager statechange event */ - setFile(inputNum, inputData, silent=true) { + setFile(inputNum, inputData) { const activeTab = this.manager.tabs.getActiveInputTab(); if (inputNum !== activeTab) return; - const fileOverlay = document.getElementById("input-file"), - fileName = document.getElementById("input-file-name"), - fileSize = document.getElementById("input-file-size"), - fileType = document.getElementById("input-file-type"), - fileLoaded = document.getElementById("input-file-loaded"); + // Create file details panel + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure( + fileDetailsPanel({ + fileDetails: inputData.file, + progress: inputData.progress, + status: inputData.status, + buffer: inputData.buffer, + renderPreview: this.app.options.imagePreview + }) + ) + }); + } - fileOverlay.style.display = "block"; - fileName.textContent = inputData.file.name; - fileSize.textContent = inputData.file.size + " bytes"; - fileType.textContent = inputData.file.type; - if (inputData.status === "error") { - fileLoaded.textContent = "Error"; - fileLoaded.style.color = "#FF0000"; - } else { - fileLoaded.style.color = ""; - fileLoaded.textContent = inputData.progress + "%"; - } + /** + * Clears the file details panel + * + * @param {number} inputNum + */ + clearFile(inputNum) { + const activeTab = this.manager.tabs.getActiveInputTab(); + if (inputNum !== activeTab) return; - this.displayFilePreview(inputNum, inputData); - - if (!silent) window.dispatchEvent(this.manager.statechange); + // Clear file details panel + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure([]) + }); } /** @@ -571,60 +567,6 @@ class InputWaiter { this.updateFileProgress(inputNum, 100); } - /** - * Render the input thumbnail - */ - async renderFileThumb() { - const activeTab = this.manager.tabs.getActiveInputTab(), - input = await this.getInputValue(activeTab), - fileThumb = document.getElementById("input-file-thumbnail"); - - if (typeof input === "string" || - !this.app.options.imagePreview) { - this.resetFileThumb(); - return; - } - - const inputArr = new Uint8Array(input), - type = isImage(inputArr); - - if (type && type !== "image/tiff" && inputArr.byteLength <= 512000) { - // Most browsers don't support displaying TIFFs, so ignore them - // Don't render images over 512000 bytes - const blob = new Blob([inputArr], {type: type}), - url = URL.createObjectURL(blob); - fileThumb.src = url; - } else { - this.resetFileThumb(); - } - - } - - /** - * Reset the input thumbnail to the default icon - */ - resetFileThumb() { - const fileThumb = document.getElementById("input-file-thumbnail"); - fileThumb.src = require("../static/images/file-128x128.png").default; - } - - /** - * Shows a chunk of the file in the input behind the file overlay - * - * @param {number} inputNum - The inputNum of the file being displayed - * @param {Object} inputData - Object containing the input data - * @param {string} inputData.stringSample - The first 4096 bytes of input as a string - */ - displayFilePreview(inputNum, inputData) { - const activeTab = this.manager.tabs.getActiveInputTab(), - input = inputData.buffer; - if (inputNum !== activeTab) return; - this.inputTextEl.classList.add("blur"); - this.setInput(input.stringSample); - - this.renderFileThumb(); - } - /** * Updates the displayed load progress for a file * @@ -632,17 +574,19 @@ class InputWaiter { * @param {number | string} progress - Either a number or "error" */ updateFileProgress(inputNum, progress) { - const activeTab = this.manager.tabs.getActiveInputTab(); - if (inputNum !== activeTab) return; + // const activeTab = this.manager.tabs.getActiveInputTab(); + // if (inputNum !== activeTab) return; - const fileLoaded = document.getElementById("input-file-loaded"); - if (progress === "error") { - fileLoaded.textContent = "Error"; - fileLoaded.style.color = "#FF0000"; - } else { - fileLoaded.textContent = progress + "%"; - fileLoaded.style.color = ""; - } + // TODO + + // const fileLoaded = document.getElementById("input-file-loaded"); + // if (progress === "error") { + // fileLoaded.textContent = "Error"; + // fileLoaded.style.color = "#FF0000"; + // } else { + // fileLoaded.textContent = progress + "%"; + // fileLoaded.style.color = ""; + // } } /** @@ -778,10 +722,6 @@ class InputWaiter { */ inputChange(e) { debounce(function(e) { - // Ignore this function if the input is a file - const fileOverlay = document.getElementById("input-file"); - if (fileOverlay.style.display === "block") return; - const value = this.getInput(); const activeTab = this.manager.tabs.getActiveInputTab(); @@ -806,7 +746,7 @@ class InputWaiter { e.stopPropagation(); e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.add("dropping-file"); + e.target.closest("#input-text").classList.add("dropping-file"); } /** @@ -821,7 +761,7 @@ class InputWaiter { // Dragleave often fires when moving between lines in the editor. // If the target element is within the input-text element, we are still on target. if (!this.inputTextEl.contains(e.target)) - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); + e.target.closest("#input-text").classList.remove("dropping-file"); } /** @@ -837,7 +777,7 @@ class InputWaiter { e.stopPropagation(); e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); + e.target.closest("#input-text").classList.remove("dropping-file"); // Dropped text is handled by the editor itself if (e.dataTransfer.getData("Text")) return; @@ -1063,23 +1003,6 @@ class InputWaiter { window.dispatchEvent(this.manager.statechange); } - /** - * Handler for clear IO click event. - * Resets the input for the current tab - */ - clearIoClick() { - const inputNum = this.manager.tabs.getActiveInputTab(); - if (inputNum === -1) return; - - this.updateInputValue(inputNum, "", true); - - this.set(inputNum, { - buffer: new ArrayBuffer() - }); - - this.manager.tabs.updateInputTabHeader(inputNum, ""); - } - /** * Sets the console log level in the worker. * From 406da9fa2c8bc5b40e16b9dbb7251966f03a413c Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 2 Sep 2022 20:15:07 +0100 Subject: [PATCH 029/228] Efficiency improvements to reduce unnecessary casting --- src/core/Utils.mjs | 9 +++++- src/web/waiters/InputWaiter.mjs | 1 - src/web/waiters/OutputWaiter.mjs | 47 ++++++++++++++++---------------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 604b7b8c..fec3b9be 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -407,6 +407,7 @@ class Utils { */ static strToArrayBuffer(str) { log.debug("Converting string to array buffer"); + if (!str) return new ArrayBuffer; const arr = new Uint8Array(str.length); let i = str.length, b; while (i--) { @@ -434,6 +435,7 @@ class Utils { */ static strToUtf8ArrayBuffer(str) { log.debug("Converting string to UTF8 array buffer"); + if (!str) return new ArrayBuffer; const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -464,6 +466,7 @@ class Utils { */ static strToByteArray(str) { log.debug("Converting string to byte array"); + if (!str) return []; const byteArray = new Array(str.length); let i = str.length, b; while (i--) { @@ -491,6 +494,7 @@ class Utils { */ static strToUtf8ByteArray(str) { log.debug("Converting string to UTF8 byte array"); + if (!str) return []; const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -520,6 +524,7 @@ class Utils { */ static strToCharcode(str) { log.debug("Converting string to charcode"); + if (!str) return []; const charcode = []; for (let i = 0; i < str.length; i++) { @@ -555,6 +560,7 @@ class Utils { */ static byteArrayToUtf8(byteArray) { log.debug("Converting byte array to UTF8"); + if (!byteArray || !byteArray.length) return ""; const str = Utils.byteArrayToChars(byteArray); try { const utf8Str = utf8.decode(str); @@ -588,7 +594,7 @@ class Utils { */ static byteArrayToChars(byteArray) { log.debug("Converting byte array to chars"); - if (!byteArray) return ""; + if (!byteArray || !byteArray.length) return ""; let str = ""; // String concatenation appears to be faster than an array join for (let i = 0; i < byteArray.length;) { @@ -611,6 +617,7 @@ class Utils { */ static arrayBufferToStr(arrayBuffer, utf8=true) { log.debug("Converting array buffer to str"); + if (!arrayBuffer || !arrayBuffer.byteLength) return ""; const arr = new Uint8Array(arrayBuffer); return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr); } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 000940a4..86ad9873 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -997,7 +997,6 @@ class InputWaiter { this.setupInputWorker(); this.manager.worker.setupChefWorker(); this.addInput(true); - this.bakeAll(); // Fire the statechange event as the input has been modified window.dispatchEvent(this.manager.statechange); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index f0b03d72..a247375e 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -49,6 +49,8 @@ class OutputWaiter { html: "", changed: false }; + // Hold a copy of the currently displayed output so that we don't have to update it unnecessarily + this.currentOutputCache = null; this.outputChrEnc = 0; this.initEditor(); @@ -170,9 +172,26 @@ class OutputWaiter { /** * Sets the value of the current output - * @param {string} data + * @param {string|ArrayBuffer} data */ setOutput(data) { + // Don't do anything if the output hasn't changed + if (data === this.currentOutputCache) return; + this.currentOutputCache = data; + + // If data is an ArrayBuffer, convert to a string in the correct character encoding + if (data instanceof ArrayBuffer) { + if (this.outputChrEnc === 0) { + data = Utils.arrayBufferToStr(data); + } else { + try { + data = cptable.utils.decode(this.outputChrEnc, new Uint8Array(data)); + } catch (err) { + data = err; + } + } + } + // Turn drawSelection back on this.outputEditorView.dispatch({ effects: this.outputEditorConf.drawSelection.reconfigure( @@ -508,28 +527,7 @@ class OutputWaiter { this.setHTMLOutput(output.data.result); break; - case "ArrayBuffer": { - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - - this.clearHTMLOutput(); - - let outputVal = ""; - if (this.outputChrEnc === 0) { - outputVal = Utils.arrayBufferToStr(output.data.result); - } else { - try { - outputVal = cptable.utils.decode(this.outputChrEnc, new Uint8Array(output.data.result)); - } catch (err) { - outputVal = err; - } - } - - this.setOutput(outputVal); - - // this.setFile(await this.getDishBuffer(output.data.dish), activeTab); - break; - } + case "ArrayBuffer": case "string": default: this.outputTextEl.style.display = "block"; @@ -1136,7 +1134,8 @@ class OutputWaiter { * @param {number} inputNum */ async displayTabInfo(inputNum) { - if (!this.outputExists(inputNum)) return; + // Don't display anything if there are no, or only one, tabs + if (!this.outputExists(inputNum) || Object.keys(this.outputs).length <= 1) return; const dish = this.getOutputDish(inputNum); let tabStr = ""; From 3893c22275142774cd32d7f946a019ea130e35e0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Sep 2022 16:35:21 +0100 Subject: [PATCH 030/228] Changing the output encoding no longer triggers a full bake --- src/web/utils/statusBar.mjs | 12 +++++++++--- src/web/waiters/OutputWaiter.mjs | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index f9be5006..efabea81 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -81,6 +81,9 @@ class StatusBarPanel { * @param {Event} e */ eolSelectClick(e) { + // preventDefault is required to stop the URL being modified and popState being triggered + e.preventDefault(); + const eolLookup = { "LF": "\u000a", "VT": "\u000b", @@ -106,6 +109,9 @@ class StatusBarPanel { * @param {Event} e */ chrEncSelectClick(e) { + // preventDefault is required to stop the URL being modified and popState being triggered + e.preventDefault(); // TODO - this breaks the menus when you click the button itself + const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); if (isNaN(chrEncVal)) return; @@ -366,9 +372,9 @@ function hideOnClickOutside(element, instantiatingEvent) { } }; - if (!Object.keys(elementsWithListeners).includes(element)) { - document.addEventListener("click", outsideClickListener); + if (!Object.prototype.hasOwnProperty.call(elementsWithListeners, element)) { elementsWithListeners[element] = outsideClickListener; + document.addEventListener("click", elementsWithListeners[element], false); } } @@ -378,7 +384,7 @@ function hideOnClickOutside(element, instantiatingEvent) { */ function hideElement(element) { element.classList.remove("show"); - document.removeEventListener("click", elementsWithListeners[element]); + document.removeEventListener("click", elementsWithListeners[element], false); delete elementsWithListeners[element]; } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index a247375e..f1965c77 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -146,6 +146,8 @@ class OutputWaiter { */ chrEncChange(chrEncVal) { this.outputChrEnc = chrEncVal; + // Reset the output, forcing it to re-decode the data with the new character encoding + this.setOutput(this.currentOutputCache, true); } /** @@ -173,10 +175,11 @@ class OutputWaiter { /** * Sets the value of the current output * @param {string|ArrayBuffer} data + * @param {boolean} [force=false] */ - setOutput(data) { + setOutput(data, force=false) { // Don't do anything if the output hasn't changed - if (data === this.currentOutputCache) return; + if (!force && data === this.currentOutputCache) return; this.currentOutputCache = data; // If data is an ArrayBuffer, convert to a string in the correct character encoding From 08b91fd7ff0e91550eea3321ae9ece8909ea84a9 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 16 Sep 2022 16:00:03 +0100 Subject: [PATCH 031/228] Removed ioDisplayThreshold option --- src/web/html/index.html | 5 ----- src/web/index.js | 1 - 2 files changed, 6 deletions(-) diff --git a/src/web/html/index.html b/src/web/html/index.html index 6e2c60a3..9cf3e878 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -482,11 +482,6 @@
    -
    - - -
    -
    -
    to
    - -
    KiB
    -
    -
    -
    -
    -
    diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index f251fa27..54e67b3b 100755 --- a/src/web/stylesheets/components/_pane.css +++ b/src/web/stylesheets/components/_pane.css @@ -46,72 +46,6 @@ padding: 0; } -.io-card.card { - box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); - transition: 0.3s; - width: 400px; - height: 150px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-family: var(--primary-font-family); - color: var(--primary-font-colour); - line-height: 30px; - background-color: var(--primary-background-colour); - flex-direction: row; - padding-left: 10px; -} - -.io-card.card:hover { - box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); -} - -.io-card.card>img { - float: left; - width: auto; - height: auto; - max-width: 128px; - max-height: 128px; - margin-left: auto; - margin-top: auto; - margin-right: auto; - margin-bottom: auto; - padding: 0px; - -} - -.io-card.card .card-body .close { - position: absolute; - right: 10px; - top: 4px; -} - -.io-card.card .card-body { - float: left; - padding: 16px; - width: 250px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - user-select: text; -} - -.io-card.card .card-body>.btn { - margin-bottom: 5px; - margin-top: 5px; -} - -.io-card.card input[type=number] { - padding-right: 6px; - padding-left: 6px; - height: unset; -} - -.io-card.card .input-group { - padding-top: 5px; -} - #files .card-header .float-right a:hover { text-decoration: none; } diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 2d40fe4c..b6cc74bf 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -165,10 +165,6 @@ height: calc(100% - var(--tab-height) - var(--title-height)); } -#show-file-overlay { - height: 32px; -} - .input-wrapper.textarea-wrapper { width: 100%; box-sizing: border-box; @@ -221,30 +217,6 @@ transition: all 0.5s ease; } -#output-file { - position: absolute; - left: 0; - top: 50%; - width: 100%; - display: none; -} - -.file-overlay { - position: absolute; - opacity: 0.8; - background-color: var(--title-background-colour); - width: 100%; - height: 100%; -} - -#show-file-overlay { - position: absolute; - right: 15px; - top: calc(var(--title-height) + 10px); - cursor: pointer; - display: none; -} - .io-info { margin-right: 18px; margin-top: 1px; diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index f36841e1..843adcf0 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -512,8 +512,6 @@ class OutputWaiter { return new Promise(async function(resolve, reject) { const output = this.outputs[inputNum]; - const outputFile = document.getElementById("output-file"); - // Update the EOL value this.outputEditorView.dispatch({ effects: this.outputEditorConf.eol.reconfigure( @@ -539,18 +537,12 @@ class OutputWaiter { this.manager.recipe.updateBreakpointIndicator(false); } - document.getElementById("show-file-overlay").style.display = "none"; - if (output.status === "pending" || output.status === "baking") { // show the loader and the status message if it's being shown // otherwise don't do anything document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage; } else if (output.status === "error") { - // style the tab if it's being shown this.toggleLoader(false); - this.outputTextEl.style.display = "block"; - this.outputTextEl.classList.remove("blur"); - outputFile.style.display = "none"; this.clearHTMLOutput(); if (output.error) { @@ -560,15 +552,10 @@ class OutputWaiter { } } else if (output.status === "baked" || output.status === "inactive") { document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`; - this.closeFile(); if (output.data === null) { - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - this.clearHTMLOutput(); this.setOutput(""); - this.toggleLoader(false); return; } @@ -577,7 +564,6 @@ class OutputWaiter { switch (output.data.type) { case "html": - outputFile.style.display = "none"; // TODO what if the HTML content needs to be in a certain character encoding? // Grey out chr enc selection? Set back to Raw Bytes? @@ -586,9 +572,6 @@ class OutputWaiter { case "ArrayBuffer": case "string": default: - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - this.clearHTMLOutput(); this.setOutput(output.data.result); break; @@ -600,34 +583,6 @@ class OutputWaiter { }.bind(this)); } - /** - * Shows file details - * - * @param {ArrayBuffer} buf - * @param {number} activeTab - */ - setFile(buf, activeTab) { - if (activeTab !== this.manager.tabs.getActiveTab("output")) return; - // Display file overlay in output area with details - const fileOverlay = document.getElementById("output-file"), - fileSize = document.getElementById("output-file-size"), - fileSlice = buf.slice(0, 4096); - - fileOverlay.style.display = "block"; - fileSize.textContent = buf.byteLength.toLocaleString() + " bytes"; - - this.outputTextEl.classList.add("blur"); - this.setOutput(Utils.arrayBufferToStr(fileSlice)); - } - - /** - * Clears output file details - */ - closeFile() { - document.getElementById("output-file").style.display = "none"; - this.outputTextEl.classList.remove("blur"); - } - /** * Retrieves the dish as a string, returning the cached version if possible. * @@ -1297,80 +1252,6 @@ class OutputWaiter { magicButton.setAttribute("data-original-title", "Magic!"); } - - /** - * Handler for file slice display events. - */ - async displayFileSlice() { - document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice..."; - this.toggleLoader(true); - const outputFile = document.getElementById("output-file"), - showFileOverlay = document.getElementById("show-file-overlay"), - sliceFromEl = document.getElementById("output-file-slice-from"), - sliceToEl = document.getElementById("output-file-slice-to"), - sliceFrom = parseInt(sliceFromEl.value, 10) * 1024, - sliceTo = parseInt(sliceToEl.value, 10) * 1024, - output = this.outputs[this.manager.tabs.getActiveTab("output")].data; - - let str; - if (output.type === "ArrayBuffer") { - str = Utils.arrayBufferToStr(output.result.slice(sliceFrom, sliceTo)); - } else { - str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish).slice(sliceFrom, sliceTo)); - } - - this.outputTextEl.classList.remove("blur"); - showFileOverlay.style.display = "block"; - this.clearHTMLOutput(); - this.setOutput(str); - - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - - this.toggleLoader(false); - } - - /** - * Handler for showing an entire file at user's discretion (even if it's way too big) - */ - async showAllFile() { - document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash..."; - this.toggleLoader(true); - const outputFile = document.getElementById("output-file"), - showFileOverlay = document.getElementById("show-file-overlay"), - output = this.outputs[this.manager.tabs.getActiveTab("output")].data; - - let str; - if (output.type === "ArrayBuffer") { - str = Utils.arrayBufferToStr(output.result); - } else { - str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish)); - } - - this.outputTextEl.classList.remove("blur"); - showFileOverlay.style.display = "none"; - this.clearHTMLOutput(); - this.setOutput(str); - - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - - this.toggleLoader(false); - } - - /** - * Handler for show file overlay events - * - * @param {Event} e - */ - showFileOverlayClick(e) { - const showFileOverlay = e.target; - - this.outputTextEl.classList.add("blur"); - showFileOverlay.style.display = "none"; - this.set(this.manager.tabs.getActiveTab("output")); - } - /** * Handler for extract file events. * From ff45f61b68228d1ee4cf07a7a7dfdd52d29c4b30 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 20:46:01 +0000 Subject: [PATCH 049/228] Fixed the snackbar --- Gruntfile.js | 10 ++++++++++ package.json | 2 +- src/web/stylesheets/layout/_modals.css | 2 +- src/web/utils/statusBar.mjs | 2 +- src/web/waiters/OutputWaiter.mjs | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 78c26532..5cf9428f 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -398,6 +398,16 @@ module.exports = function (grunt) { `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'` ].join(" "), stdout: false + }, + fixSnackbarMarkup: { + command: [ + `[[ "$OSTYPE" == "darwin"* ]]`, + "&&", + `sed -i '' 's/
    /
    /g' ./node_modules/snackbarjs/src/snackbar.js`, + "||", + `sed -i 's/
    /
    /g' ./node_modules/snackbarjs/src/snackbar.js` + ].join(" "), + stdout: false } }, }); diff --git a/package.json b/package.json index 4ed5d5eb..54662c00 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "testui": "npx grunt testui", "testuidev": "npx nightwatch --env=dev", "lint": "npx grunt lint", - "postinstall": "npx grunt exec:fixCryptoApiImports", + "postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup", "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs", "minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs", "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'", diff --git a/src/web/stylesheets/layout/_modals.css b/src/web/stylesheets/layout/_modals.css index c1745eeb..affc372d 100755 --- a/src/web/stylesheets/layout/_modals.css +++ b/src/web/stylesheets/layout/_modals.css @@ -107,4 +107,4 @@ background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); -} \ No newline at end of file +} diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 43c5f89e..d58e8d68 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -111,7 +111,7 @@ class StatusBarPanel { */ chrEncSelectClick(e) { // preventDefault is required to stop the URL being modified and popState being triggered - e.preventDefault(); // TODO - this breaks the menus when you click the button itself + e.preventDefault(); const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 843adcf0..e88052d8 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -1274,7 +1274,7 @@ class OutputWaiter { * Handler for copy click events. * Copies the output to the clipboard */ - async copyClick() { // TODO - do we need this? + async copyClick() { const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); if (dish === null) { this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000); From 1b3d55f0512abe10aa79be50abd375e8fbd828d3 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 21:23:25 +0000 Subject: [PATCH 050/228] Status bar widgets are disabled for HTML output --- src/web/stylesheets/layout/_io.css | 5 +++++ src/web/utils/statusBar.mjs | 30 ++++++++++++++++++++++++++++++ src/web/waiters/OutputWaiter.mjs | 6 ++---- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index b6cc74bf..c29f855f 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -449,6 +449,11 @@ margin-left: 0; } +.cm-status-bar .disabled { + background-color: unset !important; + cursor: not-allowed; +} + /* Dropup Button */ .cm-status-bar-select-btn { border: none; diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index d58e8d68..8fe4e348 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -22,6 +22,7 @@ class StatusBarPanel { this.eolHandler = opts.eolHandler; this.chrEncHandler = opts.chrEncHandler; this.chrEncGetter = opts.chrEncGetter; + this.htmlOutput = opts.htmlOutput; this.eolVal = null; this.chrEncVal = null; @@ -65,6 +66,9 @@ class StatusBarPanel { const el = e.target .closest(".cm-status-bar-select") .querySelector(".cm-status-bar-select-content"); + const btn = e.target.closest(".cm-status-bar-select-btn"); + + if (btn.classList.contains("disabled")) return; el.classList.add("show"); @@ -269,6 +273,30 @@ class StatusBarPanel { ); } + /** + * Checks whether there is HTML output requiring some widgets to be disabled + */ + monitorHTMLOutput() { + if (!this.htmlOutput?.changed) return; + + if (this.htmlOutput?.html === "") { + // Enable all controls + this.dom.querySelectorAll(".disabled").forEach(el => { + el.classList.remove("disabled"); + }); + } else { + // Disable chrenc, length, selection etc. + this.dom.querySelectorAll(".cm-status-bar-select-btn").forEach(el => { + el.classList.add("disabled"); + }); + + this.dom.querySelector(".stats-length-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".stats-lines-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".sel-info").classList.add("disabled"); + this.dom.querySelector(".cur-offset-info").classList.add("disabled"); + } + } + /** * Builds the Left-hand-side widgets * @returns {string} @@ -404,6 +432,7 @@ function makePanel(opts) { sbPanel.updateBakeStats(); sbPanel.updateStats(view.state.doc); sbPanel.updateSelection(view.state, false); + sbPanel.monitorHTMLOutput(); return { "dom": sbPanel.dom, @@ -412,6 +441,7 @@ function makePanel(opts) { sbPanel.updateCharEnc(); sbPanel.updateSelection(update.state, update.selectionSet); sbPanel.updateBakeStats(); + sbPanel.monitorHTMLOutput(); if (update.geometryChanged) { sbPanel.updateSizing(update.view); } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index e88052d8..6f888c49 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -91,7 +91,8 @@ class OutputWaiter { bakeStats: this.bakeStats, eolHandler: this.eolChange.bind(this), chrEncHandler: this.chrEncChange.bind(this), - chrEncGetter: this.getChrEnc.bind(this) + chrEncGetter: this.getChrEnc.bind(this), + htmlOutput: this.htmlOutput }), htmlPlugin(this.htmlOutput), copyOverride(), @@ -564,9 +565,6 @@ class OutputWaiter { switch (output.data.type) { case "html": - // TODO what if the HTML content needs to be in a certain character encoding? - // Grey out chr enc selection? Set back to Raw Bytes? - this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": From 2c822314df25577d87cec60154057688caa3aebb Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 11 Jan 2023 05:16:37 +0900 Subject: [PATCH 051/228] add new operation: Levenshtein Distance --- src/core/config/Categories.json | 1 + src/core/operations/LevenshteinDistance.mjs | 98 +++++++++++ tests/operations/index.mjs | 1 + .../operations/tests/LevenshteinDistance.mjs | 165 ++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 src/core/operations/LevenshteinDistance.mjs create mode 100644 tests/operations/tests/LevenshteinDistance.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 307270d2..c918fbb8 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -269,6 +269,7 @@ "Fuzzy Match", "Offset checker", "Hamming Distance", + "Levenshtein Distance", "Convert distance", "Convert area", "Convert mass", diff --git a/src/core/operations/LevenshteinDistance.mjs b/src/core/operations/LevenshteinDistance.mjs new file mode 100644 index 00000000..b9d30aa0 --- /dev/null +++ b/src/core/operations/LevenshteinDistance.mjs @@ -0,0 +1,98 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Levenshtein Distance operation + */ +class LevenshteinDistance extends Operation { + + /** + * LevenshteinDistance constructor + */ + constructor() { + super(); + + this.name = "Levenshtein Distance"; + this.module = "Default"; + this.description = "Levenshtein Distance (also known as Edit Distance) is a string metric to measure a difference between two strings that counts operations (insertions, deletions, and substitutions) on single character that are required to change one string to another."; + this.infoURL = "https://wikipedia.org/wiki/Levenshtein_distance"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n" + }, + { + name: "Insertion cost", + type: "number", + value: 1 + }, + { + name: "Deletion cost", + type: "number", + value: 1 + }, + { + name: "Substitution cost", + type: "number", + value: 1 + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const [delim, insCost, delCost, subCost] = args; + const samples = input.split(delim); + if (samples.length !== 2) { + throw new OperationError("Incorrect number of samples. Check your input and/or delimiter."); + } + if (insCost < 0 || delCost < 0 || subCost < 0) { + throw new OperationError("Negative costs are not allowed."); + } + const src = samples[0], dest = samples[1]; + let currentCost = new Array(src.length + 1); + let nextCost = new Array(src.length + 1); + for (let i = 0; i < currentCost.length; i++) { + currentCost[i] = delCost * i; + } + for (let i = 0; i < dest.length; i++) { + const destc = dest.charAt(i); + nextCost[0] = currentCost[0] + insCost; + for (let j = 0; j < src.length; j++) { + let candidate; + // insertion + let optCost = currentCost[j + 1] + insCost; + // deletion + candidate = nextCost[j] + delCost; + if (candidate < optCost) optCost = candidate; + // substitution or matched character + candidate = currentCost[j]; + if (src.charAt(j) !== destc) candidate += subCost; + if (candidate < optCost) optCost = candidate; + // store calculated cost + nextCost[j + 1] = optCost; + } + const tempCost = nextCost; + nextCost = currentCost; + currentCost = tempCost; + } + + return currentCost[currentCost.length - 1]; + } + +} + +export default LevenshteinDistance; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 7a3361f2..2c27d868 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs"; import "./tests/CMAC.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/Rabbit.mjs"; +import "./tests/LevenshteinDistance.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/LevenshteinDistance.mjs b/tests/operations/tests/LevenshteinDistance.mjs new file mode 100644 index 00000000..e304165b --- /dev/null +++ b/tests/operations/tests/LevenshteinDistance.mjs @@ -0,0 +1,165 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Levenshtein Distance: Wikipedia example 1", + "input": "kitten\nsitting", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: Wikipedia example 2", + "input": "saturday\nsunday", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: Wikipedia example 1 with substitution cost 2", + "input": "kitten\nsitting", + "expectedOutput": "5", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 2, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: varied costs 1", + "input": "kitten\nsitting", + "expectedOutput": "230", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 10, 100, 1000, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: varied costs 2", + "input": "kitten\nsitting", + "expectedOutput": "1020", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1000, 100, 10, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: another delimiter", + "input": "kitten sitting", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + " ", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: too few samples", + "input": "kitten", + "expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: too many samples", + "input": "kitten\nsitting\nkitchen", + "expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative insertion cost", + "input": "kitten\nsitting", + "expectedOutput": "Negative costs are not allowed.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", -1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative deletion cost", + "input": "kitten\nsitting", + "expectedOutput": "Negative costs are not allowed.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, -1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative substitution cost", + "input": "kitten\nsitting", + "expectedOutput": "Negative costs are not allowed.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, -1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: cost zero", + "input": "kitten\nsitting", + "expectedOutput": "0", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 0, 0, 0, + ], + }, + ], + }, +]); From d5b72548fc81f5e5b60c094b4bdd6dddae8b2c2a Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 11 Jan 2023 05:48:05 +0900 Subject: [PATCH 052/228] add new operation: Swap case --- src/core/config/Categories.json | 1 + src/core/operations/SwapCase.mjs | 77 +++++++++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/SwapCase.mjs | 33 +++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 src/core/operations/SwapCase.mjs create mode 100644 tests/operations/tests/SwapCase.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 307270d2..80834d4d 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -245,6 +245,7 @@ "Remove null bytes", "To Upper case", "To Lower case", + "Swap case", "To Case Insensitive Regex", "From Case Insensitive Regex", "Add line numbers", diff --git a/src/core/operations/SwapCase.mjs b/src/core/operations/SwapCase.mjs new file mode 100644 index 00000000..e1fad730 --- /dev/null +++ b/src/core/operations/SwapCase.mjs @@ -0,0 +1,77 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Swap case operation + */ +class SwapCase extends Operation { + + /** + * SwapCase constructor + */ + constructor() { + super(); + + this.name = "Swap case"; + this.module = "Default"; + this.description = "Converts uppercase letters to lowercase ones, and lowercase ones to uppercase ones."; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = ""; + for (let i = 0; i < input.length; i++) { + const c = input.charAt(i); + const upper = c.toUpperCase(); + if (c === upper) { + result += c.toLowerCase(); + } else { + result += upper; + } + } + return result; + } + + /** + * Highlight Swap case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap case in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SwapCase; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 7a3361f2..43c6a5dd 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs"; import "./tests/CMAC.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/Rabbit.mjs"; +import "./tests/SwapCase.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/SwapCase.mjs b/tests/operations/tests/SwapCase.mjs new file mode 100644 index 00000000..2506fc44 --- /dev/null +++ b/tests/operations/tests/SwapCase.mjs @@ -0,0 +1,33 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Swap Case: basic example", + "input": "Hello, World!", + "expectedOutput": "hELLO, wORLD!", + "recipeConfig": [ + { + "op": "Swap case", + "args": [ + ], + }, + ], + }, + { + "name": "Swap Case: empty input", + "input": "", + "expectedOutput": "", + "recipeConfig": [ + { + "op": "Swap case", + "args": [ + ], + }, + ], + }, +]); From f2bd838596756fb232f5a85187405e01efb75661 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 13:12:01 +0000 Subject: [PATCH 053/228] Fixed CSS for theme highlighting and status bar dropup height --- src/web/stylesheets/themes/_dark.css | 2 +- src/web/stylesheets/themes/_solarizedDark.css | 4 ++-- src/web/stylesheets/themes/_solarizedLight.css | 4 ++-- src/web/utils/statusBar.mjs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/web/stylesheets/themes/_dark.css b/src/web/stylesheets/themes/_dark.css index 10340ea8..8ebfb132 100755 --- a/src/web/stylesheets/themes/_dark.css +++ b/src/web/stylesheets/themes/_dark.css @@ -110,7 +110,7 @@ --hl2: #675351; --hl3: #ffb6b6; --hl4: #fcf8e3; - --hl5: #8de768; + --hl5: #38811b; /* Scrollbar */ diff --git a/src/web/stylesheets/themes/_solarizedDark.css b/src/web/stylesheets/themes/_solarizedDark.css index 3b7d4338..5bb18d2e 100755 --- a/src/web/stylesheets/themes/_solarizedDark.css +++ b/src/web/stylesheets/themes/_solarizedDark.css @@ -125,9 +125,9 @@ /* Highlighter colours */ --hl1: var(--base01); --hl2: var(--sol-blue); - --hl3: var(--sol-magenta); + --hl3: var(--sol-green); --hl4: var(--sol-yellow); - --hl5: var(--sol-green); + --hl5: var(--sol-magenta); /* Scrollbar */ diff --git a/src/web/stylesheets/themes/_solarizedLight.css b/src/web/stylesheets/themes/_solarizedLight.css index 00b86091..f884c3e8 100755 --- a/src/web/stylesheets/themes/_solarizedLight.css +++ b/src/web/stylesheets/themes/_solarizedLight.css @@ -127,9 +127,9 @@ /* Highlighter colours */ --hl1: var(--base1); --hl2: var(--sol-blue); - --hl3: var(--sol-magenta); + --hl3: var(--sol-green); --hl4: var(--sol-yellow); - --hl5: var(--sol-green); + --hl5: var(--sol-magenta); /* Scrollbar */ diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 8fe4e348..4af09cf6 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -265,7 +265,7 @@ class StatusBarPanel { * @param {EditorView} view */ updateSizing(view) { - const viewHeight = view.contentDOM.clientHeight; + const viewHeight = view.contentDOM.parentNode.clientHeight; this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( el => { el.style.maxHeight = (viewHeight - 50) + "px"; From 17c349973db647cc7b44d6a14d6ec9959ad1f078 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 13:51:16 +0000 Subject: [PATCH 054/228] Fixed file loading bug where the wrong input is set --- src/web/waiters/InputWaiter.mjs | 5 ++++- src/web/workers/InputWorker.mjs | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 9d827396..3a63321d 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -485,7 +485,10 @@ class InputWaiter { async set(inputNum, inputData, silent=false) { return new Promise(function(resolve, reject) { const activeTab = this.manager.tabs.getActiveTab("input"); - if (inputNum !== activeTab) return; + if (inputNum !== activeTab) { + this.changeTab(inputNum, this.app.options.syncTabs); + return; + } // Update current character encoding this.inputChrEnc = inputData.encoding; diff --git a/src/web/workers/InputWorker.mjs b/src/web/workers/InputWorker.mjs index b3ac4e4a..a24c7cd8 100644 --- a/src/web/workers/InputWorker.mjs +++ b/src/web/workers/InputWorker.mjs @@ -434,8 +434,7 @@ self.updateTabHeader = function(inputNum) { * @param {boolean} inputData.silent - If false, the manager statechange event will be fired */ self.setInput = function(inputData) { - const inputNum = inputData.inputNum; - const silent = inputData.silent; + const {inputNum, silent} = inputData; const input = self.getInputObj(inputNum); if (input === undefined || input === null) return; @@ -695,8 +694,7 @@ self.terminateLoaderWorker = function(id) { * @param {number} filesData.activeTab - The active tab in the UI */ self.loadFiles = function(filesData) { - const files = filesData.files; - const activeTab = filesData.activeTab; + const {files, activeTab} = filesData; let lastInputNum = -1; const inputNums = []; for (let i = 0; i < files.length; i++) { @@ -735,7 +733,7 @@ self.loadFiles = function(filesData) { } self.getLoadProgress(); - self.setInput({inputNum: activeTab, silent: true}); + self.setInput({inputNum: lastInputNum, silent: true}); }; /** From c1394e299a66ed76c1308baa0c7168c2a05c84cb Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 14:14:57 +0000 Subject: [PATCH 055/228] Fixed replace input with output button --- src/web/waiters/OutputWaiter.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 6f888c49..608ad087 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -1305,9 +1305,9 @@ class OutputWaiter { const activeData = await this.getDishBuffer(this.getOutputDish(activeTab)); if (this.outputExists(activeTab)) { - this.manager.input.set({ - inputNum: activeTab, - input: activeData + this.manager.input.set(activeTab, { + type: "userinput", + buffer: activeData }); } From 4e512a9a7b814af6920a85c43ec844259752d3e2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 14:25:40 +0000 Subject: [PATCH 056/228] Updated dependencies --- package-lock.json | 2223 +++++++++++++++++++++++++---------- package.json | 56 +- src/web/utils/statusBar.mjs | 12 +- 3 files changed, 1625 insertions(+), 666 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2cd4b9b..502de66b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,15 @@ "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "arrive": "^2.4.1", - "avsc": "^5.7.4", + "avsc": "^5.7.7", "bcryptjs": "^2.4.3", - "bignumber.js": "^9.0.2", + "bignumber.js": "^9.1.1", "blakejs": "^1.2.1", - "bootstrap": "4.6.1", + "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", "bootstrap-material-design": "^4.1.3", "browserify-zlib": "^0.2.0", - "bson": "^4.6.4", + "bson": "^4.7.2", "buffer": "^6.0.3", "cbor": "8.1.0", "chi-squared": "^1.1.0", @@ -41,28 +41,28 @@ "file-saver": "^2.0.5", "flat": "^5.0.2", "geodesy": "1.1.3", - "highlight.js": "^11.5.1", - "jimp": "^0.16.1", - "jquery": "3.6.0", + "highlight.js": "^11.7.0", + "jimp": "^0.16.2", + "jquery": "3.6.3", "js-crc": "^0.2.0", "js-sha3": "^0.8.0", "jsesc": "^3.0.2", - "json5": "^2.2.1", + "json5": "^2.2.3", "jsonpath-plus": "^7.2.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "jsqr": "^1.4.0", - "jsrsasign": "^10.5.23", + "jsrsasign": "^10.6.1", "kbpgp": "2.1.15", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.21", - "loglevel": "^1.8.0", + "loglevel": "^1.8.1", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.4.4", "lz4js": "^0.2.0", "markdown-it": "^13.0.1", "moment": "^2.29.4", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "ngeohash": "^0.6.3", "node-forge": "^1.3.1", "node-md6": "^0.1.0", @@ -74,7 +74,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^6.11.3", + "protobufjs": "^7.1.2", "qr-image": "^3.2.0", "reflect-metadata": "^0.1.13", "scryptsy": "^2.1.0", @@ -83,8 +83,8 @@ "split.js": "^1.6.5", "ssdeep.js": "0.0.3", "stream-browserify": "^3.0.0", - "tesseract.js": "3.0.2", - "ua-parser-js": "^1.0.2", + "tesseract.js": "3.0.3", + "ua-parser-js": "^1.0.32", "unorm": "^1.6.0", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", @@ -94,28 +94,28 @@ "zlibjs": "^0.3.1" }, "devDependencies": { - "@babel/core": "^7.20.5", + "@babel/core": "^7.20.12", "@babel/eslint-parser": "^7.19.1", "@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", - "@babel/runtime": "^7.20.6", - "@codemirror/commands": "^6.1.2", - "@codemirror/language": "^6.3.1", + "@babel/runtime": "^7.20.7", + "@codemirror/commands": "^6.1.3", + "@codemirror/language": "^6.4.0", "@codemirror/search": "^6.2.3", - "@codemirror/state": "^6.1.4", - "@codemirror/view": "^6.7.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.3", "autoprefixer": "^10.4.13", - "babel-loader": "^9.1.0", + "babel-loader": "^9.1.2", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", - "chromedriver": "^108.0.0", + "chromedriver": "^109.0.0", "cli-progress": "^3.11.2", "colors": "^1.4.0", "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.26.1", - "css-loader": "6.7.2", - "eslint": "^8.29.0", + "core-js": "^3.27.1", + "css-loader": "6.7.3", + "eslint": "^8.31.0", "grunt": "^1.5.3", "grunt-chmod": "~1.1.1", "grunt-concurrent": "^3.0.0", @@ -131,8 +131,8 @@ "imports-loader": "^4.0.1", "mini-css-extract-plugin": "2.7.2", "modify-source-webpack-plugin": "^3.0.0", - "nightwatch": "^2.5.4", - "postcss": "^8.4.19", + "nightwatch": "^2.6.10", + "postcss": "^8.4.21", "postcss-css-variables": "^0.18.0", "postcss-import": "^15.1.0", "postcss-loader": "^7.0.2", @@ -197,25 +197,25 @@ } }, "node_modules/@babel/core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", - "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.5", - "@babel/parser": "^7.20.5", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" }, "engines": { @@ -245,11 +245,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", "dependencies": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -293,14 +293,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.0", + "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", "semver": "^6.3.0" }, "engines": { @@ -310,6 +311,21 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", @@ -432,9 +448,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", - "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", @@ -442,9 +458,9 @@ "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" @@ -581,14 +597,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", - "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" @@ -608,9 +624,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1733,9 +1749,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -1755,31 +1771,31 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", + "@babel/generator": "^7.20.7", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1788,9 +1804,9 @@ } }, "node_modules/@babel/types": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", - "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", "dependencies": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", @@ -1809,21 +1825,21 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", - "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz", + "integrity": "sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==", "dev": true, "dependencies": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", + "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0" } }, "node_modules/@codemirror/language": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.1.tgz", - "integrity": "sha512-MK+G1QKaGfSEUg9YEFaBkMBI6j1ge4VMBPZv9fDYotw7w695c42x5Ba1mmwBkesYnzYFBfte6Hh9TDcKa6xORQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", + "integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", "dev": true, "dependencies": { "@codemirror/state": "^6.0.0", @@ -1846,15 +1862,15 @@ } }, "node_modules/@codemirror/state": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.4.tgz", - "integrity": "sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", "dev": true }, "node_modules/@codemirror/view": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.0.tgz", - "integrity": "sha512-sI3CngHQlguxAquc2oZ4sMFDgTJiCnKkRcFmw5apqcnNLvQfQtEDIlYvCVl1adJ6UV7ZUV9wOdqkeJ8kz2+5gg==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz", + "integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==", "dev": true, "dependencies": { "@codemirror/state": "^6.1.4", @@ -1871,14 +1887,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1893,9 +1909,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dependencies": { "type-fest": "^0.20.2" }, @@ -1907,9 +1923,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -1937,11 +1953,12 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "node_modules/@jimp/bmp": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.2.tgz", + "integrity": "sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "bmp-js": "^0.1.0" }, "peerDependencies": { @@ -1949,11 +1966,12 @@ } }, "node_modules/@jimp/core": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.2.tgz", + "integrity": "sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", @@ -1967,6 +1985,8 @@ }, "node_modules/@jimp/core/node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -1981,7 +2001,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1989,7 +2008,8 @@ }, "node_modules/@jimp/core/node_modules/mkdirp": { "version": "0.5.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { "minimist": "^1.2.6" }, @@ -1998,19 +2018,21 @@ } }, "node_modules/@jimp/custom": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.2.tgz", + "integrity": "sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.1" + "@jimp/core": "^0.16.2" } }, "node_modules/@jimp/gif": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.2.tgz", + "integrity": "sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "gifwrap": "^0.9.2", "omggif": "^1.0.9" }, @@ -2032,44 +2054,48 @@ } }, "node_modules/@jimp/plugin-blit": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.2.tgz", + "integrity": "sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-blur": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.2.tgz", + "integrity": "sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-circle": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.2.tgz", + "integrity": "sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-color": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.2.tgz", + "integrity": "sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "tinycolor2": "^1.4.1" }, "peerDependencies": { @@ -2077,11 +2103,12 @@ } }, "node_modules/@jimp/plugin-contain": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.2.tgz", + "integrity": "sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2091,11 +2118,12 @@ } }, "node_modules/@jimp/plugin-cover": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.2.tgz", + "integrity": "sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2105,55 +2133,60 @@ } }, "node_modules/@jimp/plugin-crop": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.2.tgz", + "integrity": "sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-displace": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.2.tgz", + "integrity": "sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-dither": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.2.tgz", + "integrity": "sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-fisheye": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.2.tgz", + "integrity": "sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-flip": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.2.tgz", + "integrity": "sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2161,55 +2194,60 @@ } }, "node_modules/@jimp/plugin-gaussian": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.2.tgz", + "integrity": "sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-invert": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.2.tgz", + "integrity": "sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-mask": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.2.tgz", + "integrity": "sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-normalize": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.2.tgz", + "integrity": "sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-print": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.2.tgz", + "integrity": "sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "load-bmfont": "^1.4.0" }, "peerDependencies": { @@ -2218,22 +2256,24 @@ } }, "node_modules/@jimp/plugin-resize": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.2.tgz", + "integrity": "sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-rotate": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.2.tgz", + "integrity": "sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2243,11 +2283,12 @@ } }, "node_modules/@jimp/plugin-scale": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.2.tgz", + "integrity": "sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2255,11 +2296,12 @@ } }, "node_modules/@jimp/plugin-shadow": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.2.tgz", + "integrity": "sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2268,11 +2310,12 @@ } }, "node_modules/@jimp/plugin-threshold": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.2.tgz", + "integrity": "sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2281,31 +2324,32 @@ } }, "node_modules/@jimp/plugins": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.2.tgz", + "integrity": "sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.1", - "@jimp/plugin-blur": "^0.16.1", - "@jimp/plugin-circle": "^0.16.1", - "@jimp/plugin-color": "^0.16.1", - "@jimp/plugin-contain": "^0.16.1", - "@jimp/plugin-cover": "^0.16.1", - "@jimp/plugin-crop": "^0.16.1", - "@jimp/plugin-displace": "^0.16.1", - "@jimp/plugin-dither": "^0.16.1", - "@jimp/plugin-fisheye": "^0.16.1", - "@jimp/plugin-flip": "^0.16.1", - "@jimp/plugin-gaussian": "^0.16.1", - "@jimp/plugin-invert": "^0.16.1", - "@jimp/plugin-mask": "^0.16.1", - "@jimp/plugin-normalize": "^0.16.1", - "@jimp/plugin-print": "^0.16.1", - "@jimp/plugin-resize": "^0.16.1", - "@jimp/plugin-rotate": "^0.16.1", - "@jimp/plugin-scale": "^0.16.1", - "@jimp/plugin-shadow": "^0.16.1", - "@jimp/plugin-threshold": "^0.16.1", + "@jimp/plugin-blit": "^0.16.2", + "@jimp/plugin-blur": "^0.16.2", + "@jimp/plugin-circle": "^0.16.2", + "@jimp/plugin-color": "^0.16.2", + "@jimp/plugin-contain": "^0.16.2", + "@jimp/plugin-cover": "^0.16.2", + "@jimp/plugin-crop": "^0.16.2", + "@jimp/plugin-displace": "^0.16.2", + "@jimp/plugin-dither": "^0.16.2", + "@jimp/plugin-fisheye": "^0.16.2", + "@jimp/plugin-flip": "^0.16.2", + "@jimp/plugin-gaussian": "^0.16.2", + "@jimp/plugin-invert": "^0.16.2", + "@jimp/plugin-mask": "^0.16.2", + "@jimp/plugin-normalize": "^0.16.2", + "@jimp/plugin-print": "^0.16.2", + "@jimp/plugin-resize": "^0.16.2", + "@jimp/plugin-rotate": "^0.16.2", + "@jimp/plugin-scale": "^0.16.2", + "@jimp/plugin-shadow": "^0.16.2", + "@jimp/plugin-threshold": "^0.16.2", "timm": "^1.6.1" }, "peerDependencies": { @@ -2313,11 +2357,12 @@ } }, "node_modules/@jimp/png": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.2.tgz", + "integrity": "sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "pngjs": "^3.3.3" }, "peerDependencies": { @@ -2325,8 +2370,9 @@ } }, "node_modules/@jimp/tiff": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.2.tgz", + "integrity": "sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==", "dependencies": { "@babel/runtime": "^7.7.2", "utif": "^2.0.1" @@ -2336,15 +2382,16 @@ } }, "node_modules/@jimp/types": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.2.tgz", + "integrity": "sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.1", - "@jimp/gif": "^0.16.1", - "@jimp/jpeg": "^0.16.1", - "@jimp/png": "^0.16.1", - "@jimp/tiff": "^0.16.1", + "@jimp/bmp": "^0.16.2", + "@jimp/gif": "^0.16.2", + "@jimp/jpeg": "^0.16.2", + "@jimp/png": "^0.16.2", + "@jimp/tiff": "^0.16.2", "timm": "^1.6.1" }, "peerDependencies": { @@ -2462,6 +2509,12 @@ "node": ">=12" } }, + "node_modules/@nightwatch/html-reporter-template": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.1.4.tgz", + "integrity": "sha512-fVylXypRuNJbyFAwY/5H2QM1A1XVoZWis0zhiMwA5LQN0cxHzpG2aUheb+qP1EfkxhFxwSUHOcrvphFLbPA8ow==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -2546,6 +2599,15 @@ "integrity": "sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==", "dev": true }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "dev": true, @@ -2642,10 +2704,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/long": { - "version": "4.0.1", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "1.3.2", "dev": true, @@ -2871,6 +2929,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "dev": true, @@ -2899,6 +2963,37 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals/node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-import-assertions": { "version": "1.8.0", "dev": true, @@ -3055,7 +3150,8 @@ }, "node_modules/any-base": { "version": "1.1.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" }, "node_modules/anymatch": { "version": "3.1.2", @@ -3191,16 +3287,17 @@ } }, "node_modules/avsc": { - "version": "5.7.4", - "license": "MIT", + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.7.tgz", + "integrity": "sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==", "engines": { "node": ">=0.11" } }, "node_modules/axe-core": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", - "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", "dev": true, "engines": { "node": ">=4" @@ -3308,9 +3405,9 @@ } }, "node_modules/babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "dependencies": { "find-cache-dir": "^3.3.2", @@ -3535,8 +3632,9 @@ } }, "node_modules/bignumber.js": { - "version": "9.0.2", - "license": "MIT", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "engines": { "node": "*" } @@ -3730,12 +3828,19 @@ "license": "ISC" }, "node_modules/bootstrap": { - "version": "4.6.1", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - }, + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" @@ -3862,6 +3967,12 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "node_modules/browser-stdout": { "version": "1.3.1", "dev": true, @@ -3975,8 +4086,9 @@ } }, "node_modules/bson": { - "version": "4.6.4", - "license": "Apache-2.0", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "dependencies": { "buffer": "^5.6.0" }, @@ -4038,7 +4150,8 @@ }, "node_modules/buffer-equal": { "version": "0.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", "engines": { "node": ">=0.4.0" } @@ -4213,14 +4326,14 @@ } }, "node_modules/chromedriver": { - "version": "108.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-108.0.0.tgz", - "integrity": "sha512-/kb0rb0dlC4RfXh2BOT7RV87K6d+It3VV5YXebLzO5a8t2knNffiTE23XPJQCH+l1xmgoW8/sOX/NB9irskvOQ==", + "version": "109.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-109.0.0.tgz", + "integrity": "sha512-jdmBq11IUwfThLFiygGTZ89qbROSQI4bICQjvOVQy2Bqr1LwC+MFkhwyZp3YG99eehQbZuTlQmmfCZBfpewTNA==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.1.3", + "axios": "^1.2.1", "compare-versions": "^5.0.1", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", @@ -4591,9 +4704,9 @@ } }, "node_modules/core-js": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", - "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", + "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", "dev": true, "hasInstallScript": true, "funding": { @@ -4725,13 +4838,13 @@ "license": "MIT" }, "node_modules/css-loader": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", - "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.19", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", @@ -4802,6 +4915,30 @@ "node": ">=4" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, "node_modules/ctph.js": { "version": "0.0.5" }, @@ -5156,6 +5293,54 @@ "node": ">=12" } }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dateformat": { "version": "3.0.3", "dev": true, @@ -5179,6 +5364,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "node_modules/deep-eql": { "version": "4.0.1", "dev": true, @@ -5392,7 +5583,9 @@ } }, "node_modules/dom-walk": { - "version": "0.1.2" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "node_modules/domelementtype": { "version": "2.2.0", @@ -5405,6 +5598,27 @@ ], "license": "BSD-2-Clause" }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/domhandler": { "version": "4.3.1", "dev": true, @@ -5699,12 +5913,12 @@ } }, "node_modules/eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -5723,7 +5937,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -5866,8 +6080,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.15.0", - "license": "MIT", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dependencies": { "type-fest": "^0.20.2" }, @@ -6346,7 +6561,8 @@ }, "node_modules/file-type": { "version": "9.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==", "engines": { "node": ">=6" } @@ -6717,7 +6933,8 @@ }, "node_modules/gifwrap": { "version": "0.9.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" @@ -6759,7 +6976,8 @@ }, "node_modules/global": { "version": "4.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" @@ -7524,8 +7742,9 @@ } }, "node_modules/highlight.js": { - "version": "11.5.1", - "license": "BSD-3-Clause", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==", "engines": { "node": ">=12.0.0" } @@ -7590,6 +7809,18 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-entities": { "version": "2.3.3", "dev": true, @@ -7713,6 +7944,20 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-proxy-middleware": { "version": "2.0.4", "dev": true, @@ -7837,14 +8082,16 @@ }, "node_modules/image-q": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", "dependencies": { "@types/node": "16.9.1" } }, "node_modules/image-q/node_modules/@types/node": { "version": "16.9.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" }, "node_modules/immediate": { "version": "3.0.6", @@ -8062,7 +8309,8 @@ }, "node_modules/is-function": { "version": "1.0.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" }, "node_modules/is-glob": { "version": "4.0.3", @@ -8117,6 +8365,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-regex": { "version": "1.1.4", "license": "MIT", @@ -8347,13 +8601,14 @@ } }, "node_modules/jimp": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz", + "integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.1", - "@jimp/plugins": "^0.16.1", - "@jimp/types": "^0.16.1", + "@jimp/custom": "^0.16.2", + "@jimp/plugins": "^0.16.2", + "@jimp/types": "^0.16.2", "regenerator-runtime": "^0.13.3" } }, @@ -8363,8 +8618,9 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" }, "node_modules/jquery": { - "version": "3.6.0", - "license": "MIT" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", + "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" }, "node_modules/js-crc": { "version": "0.2.0", @@ -8397,6 +8653,86 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsesc": { "version": "3.0.2", "license": "MIT", @@ -8421,8 +8757,9 @@ "license": "MIT" }, "node_modules/json5": { - "version": "2.2.1", - "license": "MIT", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -8450,30 +8787,32 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "license": "MIT", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dependencies": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "license": "ISC", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/jsqr": { @@ -8644,7 +8983,8 @@ }, "node_modules/load-bmfont": { "version": "1.4.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", "dependencies": { "buffer-equal": "0.0.1", "mime": "^1.3.4", @@ -8779,10 +9119,6 @@ "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", "dev": true }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "license": "MIT" - }, "node_modules/lodash.isarguments": { "version": "3.1.0", "dev": true, @@ -8793,29 +9129,14 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "license": "MIT" - }, "node_modules/lodash.isfinite": { "version": "3.3.2", "dev": true, "license": "MIT" }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "license": "MIT" - }, "node_modules/lodash.isplainobject": { "version": "4.0.6", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", + "dev": true, "license": "MIT" }, "node_modules/lodash.keys": { @@ -8832,9 +9153,11 @@ "version": "4.6.2", "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "license": "MIT" + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -8916,8 +9239,9 @@ } }, "node_modules/loglevel": { - "version": "1.8.0", - "license": "MIT", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", "engines": { "node": ">= 0.6.0" }, @@ -8935,8 +9259,9 @@ } }, "node_modules/long": { - "version": "4.0.0", - "license": "Apache-2.0" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -8967,7 +9292,6 @@ }, "node_modules/lru-cache": { "version": "6.0.0", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -9169,6 +9493,8 @@ }, "node_modules/min-document": { "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", "dependencies": { "dom-walk": "^0.1.0" } @@ -9418,9 +9744,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.5.39", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.39.tgz", - "integrity": "sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w==", + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", + "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", "dependencies": { "moment": ">= 2.9.0" }, @@ -9527,12 +9853,13 @@ } }, "node_modules/nightwatch": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.5.4.tgz", - "integrity": "sha512-mlPyVDP5hmRuTl3W2HnHfM7jv23V4Pcl15QNkz5+EJUUtxmBqwYG742jvYRkN6f2XBg5L1gwco2+rDmnRuL58Q==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.10.tgz", + "integrity": "sha512-nn5HVEtETLQ8qgu7APAZKg/yXTBkMflwdmzhfywP8mZUfKk0/dRQbeDqY2RawHr/sYsFmZV6eMirlJaaQoQ7Yw==", "dev": true, "dependencies": { "@nightwatch/chai": "5.0.2", + "@nightwatch/html-reporter-template": "0.1.4", "ansi-to-html": "0.7.2", "assertion-error": "1.1.0", "boxen": "5.1.2", @@ -9545,15 +9872,17 @@ "envinfo": "7.8.1", "fs-extra": "^10.1.0", "glob": "^7.2.3", + "jsdom": "19.0.0", "lodash.clone": "3.0.3", "lodash.defaultsdeep": "4.6.1", "lodash.escape": "4.0.1", "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", "mkpath": "1.0.0", "mocha": "9.2.2", - "nightwatch-axe-verbose": "2.0.3", + "nightwatch-axe-verbose": "^2.1.0", "open": "8.4.0", "ora": "5.4.1", "selenium-webdriver": "4.6.1", @@ -9587,12 +9916,12 @@ } }, "node_modules/nightwatch-axe-verbose": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.0.3.tgz", - "integrity": "sha512-VxwYTXmdbWZ4GRxgAc0/6uZ1nDQ5/xnXUipLrxoUsLxrh9OjNmAwqlMsIlQN8o33XwbjGm+o9ikan5erYGEOFQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.1.0.tgz", + "integrity": "sha512-j31VB0wdv/HXoQWWAJsvNc9UenXzXf1u/QsvExCUDuFOMR4GRg3963wlPIxd2ME47egXsnkXPd1dl8Ozdk7XHA==", "dev": true, "dependencies": { - "axe-core": "^4.4.3" + "axe-core": "^4.6.1" } }, "node_modules/nightwatch/node_modules/glob": { @@ -9797,6 +10126,12 @@ "version": "1.4.4", "license": "MIT" }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "dev": true, @@ -9887,7 +10222,8 @@ }, "node_modules/omggif": { "version": "1.0.10", - "license": "MIT" + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" }, "node_modules/on-finished": { "version": "2.3.0", @@ -10244,15 +10580,18 @@ }, "node_modules/parse-bmfont-ascii": { "version": "1.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" }, "node_modules/parse-bmfont-binary": { "version": "1.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" }, "node_modules/parse-bmfont-xml": { "version": "1.1.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.4.5" @@ -10273,7 +10612,8 @@ }, "node_modules/parse-headers": { "version": "2.0.5", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" }, "node_modules/parse-json": { "version": "5.2.0", @@ -10300,6 +10640,12 @@ "node": ">=0.10.0" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "dev": true, @@ -10418,7 +10764,8 @@ }, "node_modules/phin": { "version": "2.9.3", - "license": "MIT" + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, "node_modules/picocolors": { "version": "1.0.0", @@ -10446,7 +10793,8 @@ }, "node_modules/pixelmatch": { "version": "4.0.2", - "license": "ISC", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", "dependencies": { "pngjs": "^3.0.0" }, @@ -10515,7 +10863,8 @@ }, "node_modules/pngjs": { "version": "3.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", "engines": { "node": ">=4.0.0" } @@ -10550,9 +10899,9 @@ } }, "node_modules/postcss": { - "version": "8.4.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", - "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "funding": [ { @@ -10773,9 +11122,10 @@ } }, "node_modules/protobufjs": { - "version": "6.11.3", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "hasInstallScript": true, - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -10787,13 +11137,11 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "engines": { + "node": ">=12.0.0" } }, "node_modules/proxy-addr": { @@ -10821,6 +11169,12 @@ "dev": true, "license": "MIT" }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "node_modules/public-encrypt": { "version": "4.0.3", "license": "MIT", @@ -10897,6 +11251,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11287,6 +11647,18 @@ "version": "1.2.4", "license": "ISC" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -11891,6 +12263,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "dev": true, @@ -12006,9 +12384,10 @@ "license": "MIT" }, "node_modules/tesseract.js": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-3.0.3.tgz", + "integrity": "sha512-eZ1+OGWvF5IMExAzIwnDf3S3kf2FeC+i4qrMTRvBSlZeHc3ONy0vCmaKmBQz6scjB6C1W2w2x0r4lCEh95qBnw==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "babel-eslint": "^10.1.0", "bmp-js": "^0.1.0", @@ -12020,14 +12399,15 @@ "opencollective-postinstall": "^2.0.2", "regenerator-runtime": "^0.13.3", "resolve-url": "^0.2.1", - "tesseract.js-core": "^3.0.1", + "tesseract.js-core": "^3.0.2", "wasm-feature-detect": "^1.2.11", "zlibjs": "^0.3.1" } }, "node_modules/tesseract.js-core": { - "version": "3.0.1", - "license": "Apache License 2.0" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-3.0.2.tgz", + "integrity": "sha512-2fD76ka9nO/C616R0fq+M9Zu91DA3vEfyozp0jlxaJOBmpfeprtgRP3cqVweZh2darE1kK/DazoxZ65g7WU99Q==" }, "node_modules/tesseract.js/node_modules/file-type": { "version": "12.4.2", @@ -12094,7 +12474,8 @@ }, "node_modules/timm": { "version": "1.7.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" }, "node_modules/tiny-lr": { "version": "1.1.1", @@ -12118,11 +12499,9 @@ } }, "node_modules/tinycolor2": { - "version": "1.4.2", - "license": "MIT", - "engines": { - "node": "*" - } + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", + "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==" }, "node_modules/tmp": { "version": "0.2.1", @@ -12170,6 +12549,30 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "license": "MIT" @@ -12235,7 +12638,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.2", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", + "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", "funding": [ { "type": "opencollective", @@ -12246,7 +12651,6 @@ "url": "https://paypal.me/faisalman" } ], - "license": "MIT", "engines": { "node": "*" } @@ -12409,6 +12813,16 @@ "querystring": "0.2.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "dev": true, @@ -12420,7 +12834,8 @@ }, "node_modules/utif": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", "dependencies": { "pako": "^1.0.5" } @@ -12485,12 +12900,34 @@ "version": "0.99.3", "license": "MIT" }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, "node_modules/w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", "dev": true }, + "node_modules/w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/wasm-feature-detect": { "version": "1.2.11", "license": "Apache-2.0" @@ -12866,6 +13303,27 @@ "ultron": "~1.1.0" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "license": "MIT", @@ -13048,7 +13506,8 @@ }, "node_modules/xhr": { "version": "2.6.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "dependencies": { "global": "~4.4.0", "is-function": "^1.0.1", @@ -13056,13 +13515,24 @@ "xtend": "^4.0.0" } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/xml-parse-from-string": { "version": "1.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" }, "node_modules/xml2js": { "version": "0.4.23", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -13073,11 +13543,18 @@ }, "node_modules/xmlbuilder": { "version": "11.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "engines": { "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "node_modules/xmldom": { "version": "0.6.0", "license": "MIT", @@ -13117,7 +13594,6 @@ }, "node_modules/yallist": { "version": "4.0.0", - "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -13250,25 +13726,25 @@ "dev": true }, "@babel/core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", - "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.5", - "@babel/parser": "^7.20.5", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" } }, @@ -13284,11 +13760,11 @@ } }, "@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", "requires": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -13318,15 +13794,33 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.0", + "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "@babel/helper-create-class-features-plugin": { @@ -13418,9 +13912,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", - "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", @@ -13428,9 +13922,9 @@ "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" } }, "@babel/helper-optimise-call-expression": { @@ -13528,14 +14022,14 @@ } }, "@babel/helpers": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", - "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" } }, "@babel/highlight": { @@ -13549,9 +14043,9 @@ } }, "@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -14298,9 +14792,9 @@ } }, "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -14313,36 +14807,36 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "requires": { "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" } }, "@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", "requires": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", + "@babel/generator": "^7.20.7", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", - "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", "requires": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", @@ -14355,21 +14849,21 @@ "integrity": "sha512-2ckRSsYewLAgq/s8tUW3o5gurtCNYga1f9l0egV4QlT8hgVEilQHRt18s+behmPL2M/BPBxUINaOz67u++r0wA==" }, "@codemirror/commands": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", - "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz", + "integrity": "sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==", "dev": true, "requires": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", + "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0" } }, "@codemirror/language": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.1.tgz", - "integrity": "sha512-MK+G1QKaGfSEUg9YEFaBkMBI6j1ge4VMBPZv9fDYotw7w695c42x5Ba1mmwBkesYnzYFBfte6Hh9TDcKa6xORQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", + "integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", "dev": true, "requires": { "@codemirror/state": "^6.0.0", @@ -14392,15 +14886,15 @@ } }, "@codemirror/state": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.4.tgz", - "integrity": "sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", "dev": true }, "@codemirror/view": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.0.tgz", - "integrity": "sha512-sI3CngHQlguxAquc2oZ4sMFDgTJiCnKkRcFmw5apqcnNLvQfQtEDIlYvCVl1adJ6UV7ZUV9wOdqkeJ8kz2+5gg==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz", + "integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==", "dev": true, "requires": { "@codemirror/state": "^6.1.4", @@ -14413,14 +14907,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -14429,9 +14923,9 @@ }, "dependencies": { "globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "requires": { "type-fest": "^0.20.2" } @@ -14439,9 +14933,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -14459,18 +14953,22 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "@jimp/bmp": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.2.tgz", + "integrity": "sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "bmp-js": "^0.1.0" } }, "@jimp/core": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.2.tgz", + "integrity": "sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", @@ -14484,6 +14982,8 @@ "dependencies": { "buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -14491,6 +14991,8 @@ }, "mkdirp": { "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "requires": { "minimist": "^1.2.6" } @@ -14498,17 +15000,21 @@ } }, "@jimp/custom": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.2.tgz", + "integrity": "sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.1" + "@jimp/core": "^0.16.2" } }, "@jimp/gif": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.2.tgz", + "integrity": "sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "gifwrap": "^0.9.2", "omggif": "^1.0.9" } @@ -14524,206 +15030,256 @@ } }, "@jimp/plugin-blit": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.2.tgz", + "integrity": "sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-blur": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.2.tgz", + "integrity": "sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-circle": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.2.tgz", + "integrity": "sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-color": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.2.tgz", + "integrity": "sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "tinycolor2": "^1.4.1" } }, "@jimp/plugin-contain": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.2.tgz", + "integrity": "sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-cover": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.2.tgz", + "integrity": "sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-crop": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.2.tgz", + "integrity": "sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-displace": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.2.tgz", + "integrity": "sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-dither": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.2.tgz", + "integrity": "sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-fisheye": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.2.tgz", + "integrity": "sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-flip": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.2.tgz", + "integrity": "sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-gaussian": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.2.tgz", + "integrity": "sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-invert": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.2.tgz", + "integrity": "sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-mask": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.2.tgz", + "integrity": "sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-normalize": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.2.tgz", + "integrity": "sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-print": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.2.tgz", + "integrity": "sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "load-bmfont": "^1.4.0" } }, "@jimp/plugin-resize": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.2.tgz", + "integrity": "sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-rotate": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.2.tgz", + "integrity": "sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-scale": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.2.tgz", + "integrity": "sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-shadow": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.2.tgz", + "integrity": "sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-threshold": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.2.tgz", + "integrity": "sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugins": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.2.tgz", + "integrity": "sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.1", - "@jimp/plugin-blur": "^0.16.1", - "@jimp/plugin-circle": "^0.16.1", - "@jimp/plugin-color": "^0.16.1", - "@jimp/plugin-contain": "^0.16.1", - "@jimp/plugin-cover": "^0.16.1", - "@jimp/plugin-crop": "^0.16.1", - "@jimp/plugin-displace": "^0.16.1", - "@jimp/plugin-dither": "^0.16.1", - "@jimp/plugin-fisheye": "^0.16.1", - "@jimp/plugin-flip": "^0.16.1", - "@jimp/plugin-gaussian": "^0.16.1", - "@jimp/plugin-invert": "^0.16.1", - "@jimp/plugin-mask": "^0.16.1", - "@jimp/plugin-normalize": "^0.16.1", - "@jimp/plugin-print": "^0.16.1", - "@jimp/plugin-resize": "^0.16.1", - "@jimp/plugin-rotate": "^0.16.1", - "@jimp/plugin-scale": "^0.16.1", - "@jimp/plugin-shadow": "^0.16.1", - "@jimp/plugin-threshold": "^0.16.1", + "@jimp/plugin-blit": "^0.16.2", + "@jimp/plugin-blur": "^0.16.2", + "@jimp/plugin-circle": "^0.16.2", + "@jimp/plugin-color": "^0.16.2", + "@jimp/plugin-contain": "^0.16.2", + "@jimp/plugin-cover": "^0.16.2", + "@jimp/plugin-crop": "^0.16.2", + "@jimp/plugin-displace": "^0.16.2", + "@jimp/plugin-dither": "^0.16.2", + "@jimp/plugin-fisheye": "^0.16.2", + "@jimp/plugin-flip": "^0.16.2", + "@jimp/plugin-gaussian": "^0.16.2", + "@jimp/plugin-invert": "^0.16.2", + "@jimp/plugin-mask": "^0.16.2", + "@jimp/plugin-normalize": "^0.16.2", + "@jimp/plugin-print": "^0.16.2", + "@jimp/plugin-resize": "^0.16.2", + "@jimp/plugin-rotate": "^0.16.2", + "@jimp/plugin-scale": "^0.16.2", + "@jimp/plugin-shadow": "^0.16.2", + "@jimp/plugin-threshold": "^0.16.2", "timm": "^1.6.1" } }, "@jimp/png": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.2.tgz", + "integrity": "sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "pngjs": "^3.3.3" } }, "@jimp/tiff": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.2.tgz", + "integrity": "sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==", "requires": { "@babel/runtime": "^7.7.2", "utif": "^2.0.1" } }, "@jimp/types": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.2.tgz", + "integrity": "sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.1", - "@jimp/gif": "^0.16.1", - "@jimp/jpeg": "^0.16.1", - "@jimp/png": "^0.16.1", - "@jimp/tiff": "^0.16.1", + "@jimp/bmp": "^0.16.2", + "@jimp/gif": "^0.16.2", + "@jimp/jpeg": "^0.16.2", + "@jimp/png": "^0.16.2", + "@jimp/tiff": "^0.16.2", "timm": "^1.6.1" } }, @@ -14819,6 +15375,12 @@ "type-detect": "4.0.8" } }, + "@nightwatch/html-reporter-template": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.1.4.tgz", + "integrity": "sha512-fVylXypRuNJbyFAwY/5H2QM1A1XVoZWis0zhiMwA5LQN0cxHzpG2aUheb+qP1EfkxhFxwSUHOcrvphFLbPA8ow==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "requires": { @@ -14880,6 +15442,12 @@ "integrity": "sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==", "dev": true }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, "@types/body-parser": { "version": "1.19.2", "dev": true, @@ -14964,9 +15532,6 @@ "version": "7.0.11", "dev": true }, - "@types/long": { - "version": "4.0.1" - }, "@types/mime": { "version": "1.3.2", "dev": true @@ -15162,6 +15727,12 @@ "version": "4.2.2", "dev": true }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "abbrev": { "version": "1.1.1", "dev": true @@ -15179,6 +15750,30 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + } + } + }, "acorn-import-assertions": { "version": "1.8.0", "dev": true, @@ -15276,7 +15871,9 @@ } }, "any-base": { - "version": "1.1.0" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" }, "anymatch": { "version": "3.1.2", @@ -15366,12 +15963,14 @@ } }, "avsc": { - "version": "5.7.4" + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.7.tgz", + "integrity": "sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==" }, "axe-core": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", - "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", "dev": true }, "axios": { @@ -15447,9 +16046,9 @@ } }, "babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "requires": { "find-cache-dir": "^3.3.2", @@ -15614,7 +16213,9 @@ "dev": true }, "bignumber.js": { - "version": "9.0.2" + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" }, "binary-extensions": { "version": "2.2.0", @@ -15749,7 +16350,9 @@ "dev": true }, "bootstrap": { - "version": "4.6.1", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", "requires": {} }, "bootstrap-colorpicker": { @@ -15833,6 +16436,12 @@ "brorand": { "version": "1.1.0" }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "browser-stdout": { "version": "1.3.1", "dev": true @@ -15910,7 +16519,9 @@ } }, "bson": { - "version": "4.6.4", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "requires": { "buffer": "^5.6.0" }, @@ -15936,7 +16547,9 @@ "dev": true }, "buffer-equal": { - "version": "0.0.1" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==" }, "buffer-equal-constant-time": { "version": "1.0.1" @@ -16044,13 +16657,13 @@ "dev": true }, "chromedriver": { - "version": "108.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-108.0.0.tgz", - "integrity": "sha512-/kb0rb0dlC4RfXh2BOT7RV87K6d+It3VV5YXebLzO5a8t2knNffiTE23XPJQCH+l1xmgoW8/sOX/NB9irskvOQ==", + "version": "109.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-109.0.0.tgz", + "integrity": "sha512-jdmBq11IUwfThLFiygGTZ89qbROSQI4bICQjvOVQy2Bqr1LwC+MFkhwyZp3YG99eehQbZuTlQmmfCZBfpewTNA==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.1.3", + "axios": "^1.2.1", "compare-versions": "^5.0.1", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", @@ -16297,9 +16910,9 @@ } }, "core-js": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", - "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", + "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", "dev": true }, "core-js-compat": { @@ -16399,13 +17012,13 @@ "version": "4.1.1" }, "css-loader": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", - "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.19", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", @@ -16444,6 +17057,29 @@ "version": "3.0.0", "dev": true }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, "ctph.js": { "version": "0.0.5" }, @@ -16657,6 +17293,44 @@ "d3-transition": "2 - 3" } }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "dateformat": { "version": "3.0.3", "dev": true @@ -16667,6 +17341,12 @@ "ms": "2.1.2" } }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "deep-eql": { "version": "4.0.1", "dev": true, @@ -16815,12 +17495,31 @@ } }, "dom-walk": { - "version": "0.1.2" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "domelementtype": { "version": "2.2.0", "dev": true }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + } + } + }, "domhandler": { "version": "4.3.1", "dev": true, @@ -17034,12 +17733,12 @@ } }, "eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -17058,7 +17757,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -17115,7 +17814,9 @@ "version": "3.3.0" }, "globals": { - "version": "13.15.0", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "requires": { "type-fest": "^0.20.2" } @@ -17461,7 +18162,9 @@ "dev": true }, "file-type": { - "version": "9.0.0" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" }, "filelist": { "version": "1.0.4", @@ -17695,6 +18398,8 @@ }, "gifwrap": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", "requires": { "image-q": "^4.0.0", "omggif": "^1.0.10" @@ -17725,6 +18430,8 @@ }, "global": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "requires": { "min-document": "^2.19.0", "process": "^0.11.10" @@ -18220,7 +18927,9 @@ "dev": true }, "highlight.js": { - "version": "11.5.1" + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==" }, "hmac-drbg": { "version": "1.0.1", @@ -18273,6 +18982,15 @@ } } }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, "html-entities": { "version": "2.3.3", "dev": true @@ -18354,6 +19072,17 @@ "requires-port": "^1.0.0" } }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, "http-proxy-middleware": { "version": "2.0.4", "dev": true, @@ -18423,12 +19152,16 @@ }, "image-q": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", "requires": { "@types/node": "16.9.1" }, "dependencies": { "@types/node": { - "version": "16.9.1" + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" } } }, @@ -18557,7 +19290,9 @@ "dev": true }, "is-function": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" }, "is-glob": { "version": "4.0.3", @@ -18592,6 +19327,12 @@ "isobject": "^3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-regex": { "version": "1.1.4", "requires": { @@ -18728,12 +19469,14 @@ } }, "jimp": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz", + "integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.1", - "@jimp/plugins": "^0.16.1", - "@jimp/types": "^0.16.1", + "@jimp/custom": "^0.16.2", + "@jimp/plugins": "^0.16.2", + "@jimp/types": "^0.16.2", "regenerator-runtime": "^0.13.3" } }, @@ -18743,7 +19486,9 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" }, "jquery": { - "version": "3.6.0" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", + "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" }, "js-crc": { "version": "0.2.0" @@ -18765,6 +19510,68 @@ "argparse": "^2.0.1" } }, + "jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "jsesc": { "version": "3.0.2" }, @@ -18779,7 +19586,9 @@ "version": "1.0.1" }, "json5": { - "version": "2.2.1" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { "version": "6.1.0", @@ -18795,22 +19604,23 @@ "version": "7.2.0" }, "jsonwebtoken": { - "version": "8.5.1", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "semver": { - "version": "5.7.1" + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -18952,6 +19762,8 @@ }, "load-bmfont": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", "requires": { "buffer-equal": "0.0.1", "mime": "^1.3.4", @@ -19060,9 +19872,6 @@ "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", "dev": true }, - "lodash.includes": { - "version": "4.3.0" - }, "lodash.isarguments": { "version": "3.1.0", "dev": true @@ -19071,24 +19880,13 @@ "version": "3.0.4", "dev": true }, - "lodash.isboolean": { - "version": "3.0.3" - }, "lodash.isfinite": { "version": "3.3.2", "dev": true }, - "lodash.isinteger": { - "version": "4.0.4" - }, - "lodash.isnumber": { - "version": "3.0.3" - }, "lodash.isplainobject": { - "version": "4.0.6" - }, - "lodash.isstring": { - "version": "4.0.1" + "version": "4.0.6", + "dev": true }, "lodash.keys": { "version": "3.1.2", @@ -19102,8 +19900,11 @@ "lodash.merge": { "version": "4.6.2" }, - "lodash.once": { - "version": "4.1.1" + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true }, "log-symbols": { "version": "4.1.0", @@ -19153,7 +19954,9 @@ } }, "loglevel": { - "version": "1.8.0" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==" }, "loglevel-message-prefix": { "version": "3.0.0", @@ -19163,7 +19966,9 @@ } }, "long": { - "version": "4.0.0" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, "loose-envify": { "version": "1.4.0", @@ -19188,7 +19993,6 @@ }, "lru-cache": { "version": "6.0.0", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -19312,6 +20116,8 @@ }, "min-document": { "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", "requires": { "dom-walk": "^0.1.0" } @@ -19469,9 +20275,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.39", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.39.tgz", - "integrity": "sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w==", + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", + "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", "requires": { "moment": ">= 2.9.0" } @@ -19544,12 +20350,13 @@ "version": "0.6.3" }, "nightwatch": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.5.4.tgz", - "integrity": "sha512-mlPyVDP5hmRuTl3W2HnHfM7jv23V4Pcl15QNkz5+EJUUtxmBqwYG742jvYRkN6f2XBg5L1gwco2+rDmnRuL58Q==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.10.tgz", + "integrity": "sha512-nn5HVEtETLQ8qgu7APAZKg/yXTBkMflwdmzhfywP8mZUfKk0/dRQbeDqY2RawHr/sYsFmZV6eMirlJaaQoQ7Yw==", "dev": true, "requires": { "@nightwatch/chai": "5.0.2", + "@nightwatch/html-reporter-template": "0.1.4", "ansi-to-html": "0.7.2", "assertion-error": "1.1.0", "boxen": "5.1.2", @@ -19562,15 +20369,17 @@ "envinfo": "7.8.1", "fs-extra": "^10.1.0", "glob": "^7.2.3", + "jsdom": "19.0.0", "lodash.clone": "3.0.3", "lodash.defaultsdeep": "4.6.1", "lodash.escape": "4.0.1", "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", "mkpath": "1.0.0", "mocha": "9.2.2", - "nightwatch-axe-verbose": "2.0.3", + "nightwatch-axe-verbose": "^2.1.0", "open": "8.4.0", "ora": "5.4.1", "selenium-webdriver": "4.6.1", @@ -19603,12 +20412,12 @@ } }, "nightwatch-axe-verbose": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.0.3.tgz", - "integrity": "sha512-VxwYTXmdbWZ4GRxgAc0/6uZ1nDQ5/xnXUipLrxoUsLxrh9OjNmAwqlMsIlQN8o33XwbjGm+o9ikan5erYGEOFQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.1.0.tgz", + "integrity": "sha512-j31VB0wdv/HXoQWWAJsvNc9UenXzXf1u/QsvExCUDuFOMR4GRg3963wlPIxd2ME47egXsnkXPd1dl8Ozdk7XHA==", "dev": true, "requires": { - "axe-core": "^4.4.3" + "axe-core": "^4.6.1" } }, "no-case": { @@ -19727,6 +20536,12 @@ "nwmatcher": { "version": "1.4.4" }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, "object-assign": { "version": "4.1.1", "dev": true @@ -19781,7 +20596,9 @@ "dev": true }, "omggif": { - "version": "1.0.10" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" }, "on-finished": { "version": "2.3.0", @@ -20011,13 +20828,19 @@ } }, "parse-bmfont-ascii": { - "version": "1.0.6" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" }, "parse-bmfont-binary": { - "version": "1.0.6" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" }, "parse-bmfont-xml": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", "requires": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.4.5" @@ -20033,7 +20856,9 @@ } }, "parse-headers": { - "version": "2.0.5" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" }, "parse-json": { "version": "5.2.0", @@ -20049,6 +20874,12 @@ "version": "1.0.0", "dev": true }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "parseurl": { "version": "1.3.3", "dev": true @@ -20125,7 +20956,9 @@ } }, "phin": { - "version": "2.9.3" + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, "picocolors": { "version": "1.0.0", @@ -20141,6 +20974,8 @@ }, "pixelmatch": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", "requires": { "pngjs": "^3.0.0" } @@ -20184,7 +21019,9 @@ } }, "pngjs": { - "version": "3.4.0" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" }, "popper.js": { "version": "1.16.1" @@ -20207,9 +21044,9 @@ } }, "postcss": { - "version": "8.4.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", - "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -20338,7 +21175,9 @@ } }, "protobufjs": { - "version": "6.11.3", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -20350,9 +21189,8 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" } }, "proxy-addr": { @@ -20373,6 +21211,12 @@ "version": "1.1.0", "dev": true }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "requires": { @@ -20433,6 +21277,12 @@ "version": "0.2.0", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -20688,6 +21538,15 @@ "sax": { "version": "1.2.4" }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -21128,6 +21987,12 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0" }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "tapable": { "version": "2.2.1", "dev": true @@ -21194,7 +22059,9 @@ } }, "tesseract.js": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-3.0.3.tgz", + "integrity": "sha512-eZ1+OGWvF5IMExAzIwnDf3S3kf2FeC+i4qrMTRvBSlZeHc3ONy0vCmaKmBQz6scjB6C1W2w2x0r4lCEh95qBnw==", "requires": { "babel-eslint": "^10.1.0", "bmp-js": "^0.1.0", @@ -21206,7 +22073,7 @@ "opencollective-postinstall": "^2.0.2", "regenerator-runtime": "^0.13.3", "resolve-url": "^0.2.1", - "tesseract.js-core": "^3.0.1", + "tesseract.js-core": "^3.0.2", "wasm-feature-detect": "^1.2.11", "zlibjs": "^0.3.1" }, @@ -21217,7 +22084,9 @@ } }, "tesseract.js-core": { - "version": "3.0.1" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-3.0.2.tgz", + "integrity": "sha512-2fD76ka9nO/C616R0fq+M9Zu91DA3vEfyozp0jlxaJOBmpfeprtgRP3cqVweZh2darE1kK/DazoxZ65g7WU99Q==" }, "text-table": { "version": "0.2.0" @@ -21267,7 +22136,9 @@ } }, "timm": { - "version": "1.7.1" + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" }, "tiny-lr": { "version": "1.1.1", @@ -21291,7 +22162,9 @@ } }, "tinycolor2": { - "version": "1.4.2" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", + "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==" }, "tmp": { "version": "0.2.1", @@ -21320,6 +22193,26 @@ "version": "1.1.0", "dev": true }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } + } + }, "tr46": { "version": "0.0.3" }, @@ -21363,7 +22256,9 @@ } }, "ua-parser-js": { - "version": "1.0.2" + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", + "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==" }, "uc.micro": { "version": "1.0.6" @@ -21467,11 +22362,23 @@ } } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "utf8": { "version": "3.0.0" }, "utif": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", "requires": { "pako": "^1.0.5" } @@ -21518,12 +22425,30 @@ "vkbeautify": { "version": "0.99.3" }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, "w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", "dev": true }, + "w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, "wasm-feature-detect": { "version": "1.2.11" }, @@ -21774,6 +22699,21 @@ } } }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, "whatwg-url": { "version": "5.0.0", "requires": { @@ -21883,6 +22823,8 @@ }, "xhr": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "requires": { "global": "~4.4.0", "is-function": "^1.0.1", @@ -21890,18 +22832,36 @@ "xtend": "^4.0.0" } }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, "xml-parse-from-string": { - "version": "1.0.1" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" }, "xml2js": { "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "xmlbuilder": { - "version": "11.0.1" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true }, "xmldom": { "version": "0.6.0" @@ -21925,8 +22885,7 @@ "dev": true }, "yallist": { - "version": "4.0.0", - "dev": true + "version": "4.0.0" }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 54662c00..93f93dce 100644 --- a/package.json +++ b/package.json @@ -39,28 +39,28 @@ "node >= 16" ], "devDependencies": { - "@babel/core": "^7.20.5", + "@babel/core": "^7.20.12", "@babel/eslint-parser": "^7.19.1", "@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", - "@babel/runtime": "^7.20.6", - "@codemirror/commands": "^6.1.2", - "@codemirror/language": "^6.3.1", + "@babel/runtime": "^7.20.7", + "@codemirror/commands": "^6.1.3", + "@codemirror/language": "^6.4.0", "@codemirror/search": "^6.2.3", - "@codemirror/state": "^6.1.4", - "@codemirror/view": "^6.7.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.3", "autoprefixer": "^10.4.13", - "babel-loader": "^9.1.0", + "babel-loader": "^9.1.2", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", - "chromedriver": "^108.0.0", + "chromedriver": "^109.0.0", "cli-progress": "^3.11.2", "colors": "^1.4.0", "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.26.1", - "css-loader": "6.7.2", - "eslint": "^8.29.0", + "core-js": "^3.27.1", + "css-loader": "6.7.3", + "eslint": "^8.31.0", "grunt": "^1.5.3", "grunt-chmod": "~1.1.1", "grunt-concurrent": "^3.0.0", @@ -76,8 +76,8 @@ "imports-loader": "^4.0.1", "mini-css-extract-plugin": "2.7.2", "modify-source-webpack-plugin": "^3.0.0", - "nightwatch": "^2.5.4", - "postcss": "^8.4.19", + "nightwatch": "^2.6.10", + "postcss": "^8.4.21", "postcss-css-variables": "^0.18.0", "postcss-import": "^15.1.0", "postcss-loader": "^7.0.2", @@ -95,15 +95,15 @@ "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "arrive": "^2.4.1", - "avsc": "^5.7.4", + "avsc": "^5.7.7", "bcryptjs": "^2.4.3", - "bignumber.js": "^9.0.2", + "bignumber.js": "^9.1.1", "blakejs": "^1.2.1", - "bootstrap": "4.6.1", + "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", "bootstrap-material-design": "^4.1.3", "browserify-zlib": "^0.2.0", - "bson": "^4.6.4", + "bson": "^4.7.2", "buffer": "^6.0.3", "cbor": "8.1.0", "chi-squared": "^1.1.0", @@ -122,28 +122,28 @@ "file-saver": "^2.0.5", "flat": "^5.0.2", "geodesy": "1.1.3", - "highlight.js": "^11.5.1", - "jimp": "^0.16.1", - "jquery": "3.6.0", + "highlight.js": "^11.7.0", + "jimp": "^0.16.2", + "jquery": "3.6.3", "js-crc": "^0.2.0", "js-sha3": "^0.8.0", "jsesc": "^3.0.2", - "json5": "^2.2.1", + "json5": "^2.2.3", "jsonpath-plus": "^7.2.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "jsqr": "^1.4.0", - "jsrsasign": "^10.5.23", + "jsrsasign": "^10.6.1", "kbpgp": "2.1.15", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.21", - "loglevel": "^1.8.0", + "loglevel": "^1.8.1", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.4.4", "lz4js": "^0.2.0", "markdown-it": "^13.0.1", "moment": "^2.29.4", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "ngeohash": "^0.6.3", "node-forge": "^1.3.1", "node-md6": "^0.1.0", @@ -155,7 +155,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^6.11.3", + "protobufjs": "^7.1.2", "qr-image": "^3.2.0", "reflect-metadata": "^0.1.13", "scryptsy": "^2.1.0", @@ -164,8 +164,8 @@ "split.js": "^1.6.5", "ssdeep.js": "0.0.3", "stream-browserify": "^3.0.0", - "tesseract.js": "3.0.2", - "ua-parser-js": "^1.0.2", + "tesseract.js": "3.0.3", + "ua-parser-js": "^1.0.32", "unorm": "^1.6.0", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 4af09cf6..2110c60d 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -219,8 +219,8 @@ class StatusBarPanel { const button = val.closest(".cm-status-bar-select-btn"); const eolName = eolLookup[state.lineBreak]; val.textContent = eolName[0]; - button.setAttribute("title", `End of line sequence: ${eolName[1]}`); - button.setAttribute("data-original-title", `End of line sequence: ${eolName[1]}`); + button.setAttribute("title", `End of line sequence:
    ${eolName[1]}`); + button.setAttribute("data-original-title", `End of line sequence:
    ${eolName[1]}`); this.eolVal = state.lineBreak; } @@ -237,8 +237,8 @@ class StatusBarPanel { const val = this.dom.querySelector(".chr-enc-value"); const button = val.closest(".cm-status-bar-select-btn"); val.textContent = name; - button.setAttribute("title", `${this.label} character encoding: ${name}`); - button.setAttribute("data-original-title", `${this.label} character encoding: ${name}`); + button.setAttribute("title", `${this.label} character encoding:
    ${name}`); + button.setAttribute("data-original-title", `${this.label} character encoding:
    ${name}`); this.chrEncVal = chrEncVal; } @@ -341,7 +341,7 @@ class StatusBarPanel {
    - + text_fields Raw Bytes
    @@ -361,7 +361,7 @@ class StatusBarPanel {
    - + keyboard_return
    From e9d7a8363cf7b4079b672aacf6dfc176dcd0f660 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 14:38:50 +0000 Subject: [PATCH 057/228] Removed treatAsUTF8 option --- src/core/Chef.mjs | 5 ++--- src/core/Dish.mjs | 30 ++++++++++++---------------- src/core/dishTypes/DishBigNumber.mjs | 5 ++--- src/core/dishTypes/DishJSON.mjs | 5 ++--- src/core/dishTypes/DishNumber.mjs | 5 ++--- src/core/dishTypes/DishString.mjs | 5 ++--- src/core/dishTypes/DishType.mjs | 3 +-- src/web/html/index.html | 7 ------- src/web/index.js | 1 - 9 files changed, 24 insertions(+), 42 deletions(-) diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs index 140774bc..dbde3e3f 100755 --- a/src/core/Chef.mjs +++ b/src/core/Chef.mjs @@ -41,8 +41,7 @@ class Chef { log.debug("Chef baking"); const startTime = Date.now(), recipe = new Recipe(recipeConfig), - containsFc = recipe.containsFlowControl(), - notUTF8 = options && "treatAsUtf8" in options && !options.treatAsUtf8; + containsFc = recipe.containsFlowControl(); let error = false, progress = 0; @@ -75,7 +74,7 @@ class Chef { return { dish: rawDish, - result: await this.dish.get(returnType, notUTF8), + result: await this.dish.get(returnType), type: Dish.enumLookup(this.dish.type), progress: progress, duration: Date.now() - startTime, diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs index 1afdef01..11b1ff9f 100755 --- a/src/core/Dish.mjs +++ b/src/core/Dish.mjs @@ -128,10 +128,9 @@ class Dish { * If running in a browser, get is asynchronous. * * @param {number} type - The data type of value, see Dish enums. - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. * @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type */ - get(type, notUTF8=false) { + get(type) { if (typeof type === "string") { type = Dish.typeEnum(type); } @@ -140,13 +139,13 @@ class Dish { // Node environment => _translate is sync if (isNodeEnvironment()) { - this._translate(type, notUTF8); + this._translate(type); return this.value; // Browser environment => _translate is async } else { return new Promise((resolve, reject) => { - this._translate(type, notUTF8) + this._translate(type) .then(() => { resolve(this.value); }) @@ -190,12 +189,11 @@ class Dish { * @Node * * @param {number} type - The data type of value, see Dish enums. - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. * @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type */ - presentAs(type, notUTF8=false) { + presentAs(type) { const clone = this.clone(); - return clone.get(type, notUTF8); + return clone.get(type); } @@ -414,17 +412,16 @@ class Dish { * If running in the browser, _translate is asynchronous. * * @param {number} toType - The data type of value, see Dish enums. - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. * @returns {Promise || undefined} */ - _translate(toType, notUTF8=false) { + _translate(toType) { log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); // Node environment => translate is sync if (isNodeEnvironment()) { this._toArrayBuffer(); this.type = Dish.ARRAY_BUFFER; - this._fromArrayBuffer(toType, notUTF8); + this._fromArrayBuffer(toType); // Browser environment => translate is async } else { @@ -486,18 +483,17 @@ class Dish { * Convert this.value to the given type from ArrayBuffer * * @param {number} toType - the Dish enum to convert to - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. */ - _fromArrayBuffer(toType, notUTF8) { + _fromArrayBuffer(toType) { // Using 'bind' here to allow this.value to be mutated within translation functions const toTypeFunctions = { - [Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(notUTF8), - [Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(notUTF8), - [Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(notUTF8), + [Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(), + [Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(), + [Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(), [Dish.ARRAY_BUFFER]: () => {}, - [Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(notUTF8), - [Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(notUTF8), + [Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(), + [Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(), [Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(), [Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(), [Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(), diff --git a/src/core/dishTypes/DishBigNumber.mjs b/src/core/dishTypes/DishBigNumber.mjs index d6f67698..3bb8122c 100644 --- a/src/core/dishTypes/DishBigNumber.mjs +++ b/src/core/dishTypes/DishBigNumber.mjs @@ -24,12 +24,11 @@ class DishBigNumber extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishBigNumber.checkForValue(this.value); try { - this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8)); + this.value = new BigNumber(Utils.arrayBufferToStr(this.value)); } catch (err) { this.value = new BigNumber(NaN); } diff --git a/src/core/dishTypes/DishJSON.mjs b/src/core/dishTypes/DishJSON.mjs index 703b0980..e1b32d64 100644 --- a/src/core/dishTypes/DishJSON.mjs +++ b/src/core/dishTypes/DishJSON.mjs @@ -22,11 +22,10 @@ class DishJSON extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishJSON.checkForValue(this.value); - this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8)); + this.value = JSON.parse(Utils.arrayBufferToStr(this.value)); } } diff --git a/src/core/dishTypes/DishNumber.mjs b/src/core/dishTypes/DishNumber.mjs index 8769a69a..e3ea31b8 100644 --- a/src/core/dishTypes/DishNumber.mjs +++ b/src/core/dishTypes/DishNumber.mjs @@ -23,11 +23,10 @@ class DishNumber extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishNumber.checkForValue(this.value); - this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0; + this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value)) : 0; } } diff --git a/src/core/dishTypes/DishString.mjs b/src/core/dishTypes/DishString.mjs index d7768859..7de8810d 100644 --- a/src/core/dishTypes/DishString.mjs +++ b/src/core/dishTypes/DishString.mjs @@ -23,11 +23,10 @@ class DishString extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishString.checkForValue(this.value); - this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : ""; + this.value = this.value ? Utils.arrayBufferToStr(this.value) : ""; } } diff --git a/src/core/dishTypes/DishType.mjs b/src/core/dishTypes/DishType.mjs index 849b5756..d89e3c0b 100644 --- a/src/core/dishTypes/DishType.mjs +++ b/src/core/dishTypes/DishType.mjs @@ -29,9 +29,8 @@ class DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8=undefined) { + static fromArrayBuffer() { throw new Error("fromArrayBuffer has not been implemented"); } } diff --git a/src/web/html/index.html b/src/web/html/index.html index a5897840..a762ea96 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -483,13 +483,6 @@
    -
    - -
    -
    -
    - -
    + +
    -
    -
    -
    -
    - -
    -
    +
    +
    +
    +
    +
    @@ -443,20 +442,6 @@
    -
    - - -
    - -
    - - -
    -
    - Operation error reporting (recommended) + Show errors from operations (recommended)
    +
    + + +
    +