From 1f09c03d4897206e7ed4a3d90cac6c577c486aed Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Fri, 15 Feb 2019 14:23:16 +0000 Subject: [PATCH 001/455] 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 8235ab10b..238c7282e 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 000000000..647d3c7f1 --- /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 fb68ed9ca..316e934ce 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 000000000..b68a843f7 --- /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/455] 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 b68a843f7..48e8c4ffc 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/455] 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 647d3c7f1..af788585a 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/455] 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 55ad63035..18da5b7a3 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 cb59db38b..35901453f 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 8235ab10b..2db5af517 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 000000000..76d4fd16a --- /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 000000000..ac8c64cb0 --- /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 000000000..0632fca95 --- /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/455] 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 76d4fd16a..d68593d85 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 ac8c64cb0..2f98449fd 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 0632fca95..ee9ba2f18 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/455] 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 94f7fd309..18fc19ffc 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/455] 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 000000000..5badaae75 --- /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/455] 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 11c80ca0a..930dfc40f 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 e9c33484b..1283f5450 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 94f7fd309..939aa22e7 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 000000000..4fe5990e0 --- /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 000000000..b9aef6382 --- /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 14c7408ef..b77f16a98 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 000000000..3977834c5 --- /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/455] 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 5badaae75..7390c1a9c 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 ce6d38860dcc1b76fc85ecbecde04dbceeb57416 Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 19:39:01 +0100 Subject: [PATCH 010/455] Add operation Added 'Take nth bytes' operation. --- src/core/config/Categories.json | 3 +- src/core/operations/TakeNthBytes.mjs | 78 ++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/TakeNthBytes.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index db2ab3a63..9fb63d360 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -238,7 +238,8 @@ "Escape string", "Unescape string", "Pseudo-Random Number Generator", - "Sleep" + "Sleep", + "Take nth bytes" ] }, { diff --git a/src/core/operations/TakeNthBytes.mjs b/src/core/operations/TakeNthBytes.mjs new file mode 100644 index 000000000..7dcf9c6ee --- /dev/null +++ b/src/core/operations/TakeNthBytes.mjs @@ -0,0 +1,78 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Take nth bytes operation + */ +class TakeNthBytes extends Operation { + + /** + * TakeNthBytes constructor + */ + constructor() { + super(); + + this.name = "Take nth bytes"; + this.module = "Default"; + this.description = "Takes every nth byte starting with a given byte."; + this.infoURL = ""; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Take every", + type: "number", + value: 4 + }, + { + name: "Starting at", + type: "number", + value: 0 + }, + { + name: "Apply to each line", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + let n = args[0]; + let start = args[1]; + let eachLine = args[2]; + + if (parseInt(n) !== n || n <= 0) { + throw new OperationError("'Take every' must be a positive integer.") + } + if (parseInt(start) !== start || start < 0) { + throw new OperationError("'Starting at' must be a positive or zero integer.") + } + + let offset = 0; + let output = []; + for (let i = 0; i < input.length; i++) { + if (eachLine && input[i] == 0x0a) { + offset = i + 1; + } else if (i - offset >= start && (i - (start + offset)) % n == 0) { + output.push(input[i]); + } + } + + return output; + } + +} + +export default TakeNthBytes; From 7c7d1823ca4e31139a804c3ca067806c200c530c Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 19:53:57 +0100 Subject: [PATCH 011/455] Fix formatting issue Fixed issue where new lines were truncated. --- src/core/operations/TakeNthBytes.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/operations/TakeNthBytes.mjs b/src/core/operations/TakeNthBytes.mjs index 7dcf9c6ee..ffc42ff9e 100644 --- a/src/core/operations/TakeNthBytes.mjs +++ b/src/core/operations/TakeNthBytes.mjs @@ -64,6 +64,7 @@ class TakeNthBytes extends Operation { let output = []; for (let i = 0; i < input.length; i++) { if (eachLine && input[i] == 0x0a) { + output.push(0x0a); offset = i + 1; } else if (i - offset >= start && (i - (start + offset)) % n == 0) { output.push(input[i]); From 502f126986cc10c2eb4df2aca46bcbe98cadeb3a Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 20:15:45 +0100 Subject: [PATCH 012/455] Fix linting Fixed linting. --- src/core/operations/TakeNthBytes.mjs | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/core/operations/TakeNthBytes.mjs b/src/core/operations/TakeNthBytes.mjs index ffc42ff9e..05de88697 100644 --- a/src/core/operations/TakeNthBytes.mjs +++ b/src/core/operations/TakeNthBytes.mjs @@ -49,24 +49,24 @@ class TakeNthBytes extends Operation { * @returns {byteArray} */ run(input, args) { - let n = args[0]; - let start = args[1]; - let eachLine = args[2]; + const n = args[0]; + const start = args[1]; + const eachLine = args[2]; - if (parseInt(n) !== n || n <= 0) { - throw new OperationError("'Take every' must be a positive integer.") + if (parseInt(n, 10) !== n || n <= 0) { + throw new OperationError("'Take every' must be a positive integer."); } - if (parseInt(start) !== start || start < 0) { - throw new OperationError("'Starting at' must be a positive or zero integer.") + if (parseInt(start, 10) !== start || start < 0) { + throw new OperationError("'Starting at' must be a positive or zero integer."); } - + let offset = 0; - let output = []; + const output = []; for (let i = 0; i < input.length; i++) { - if (eachLine && input[i] == 0x0a) { + if (eachLine && input[i] === 0x0a) { output.push(0x0a); offset = i + 1; - } else if (i - offset >= start && (i - (start + offset)) % n == 0) { + } else if (i - offset >= start && (i - (start + offset)) % n === 0) { output.push(input[i]); } } From 02f65379736b3a9739342f335fa84476600ea39e Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 20:16:14 +0100 Subject: [PATCH 013/455] Add tests Added tests for the 'Take nth byte' operation. --- tests/operations/tests/TakeNthBytes.mjs | 123 ++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 tests/operations/tests/TakeNthBytes.mjs diff --git a/tests/operations/tests/TakeNthBytes.mjs b/tests/operations/tests/TakeNthBytes.mjs new file mode 100644 index 000000000..22181c3d2 --- /dev/null +++ b/tests/operations/tests/TakeNthBytes.mjs @@ -0,0 +1,123 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +/** + * Take nth bytes tests + */ +TestRegister.addTests([ + { + name: "Take nth bytes: Nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 0, false], + }, + ], + }, + { + name: "Take nth bytes: Nothing (apply to each line)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 0, true], + }, + ], + }, + { + name: "Take nth bytes: Basic single line", + input: "0123456789", + expectedOutput: "048", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 0, false], + }, + ], + }, + { + name: "Take nth bytes: Basic single line (apply to each line)", + input: "0123456789", + expectedOutput: "048", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 0, true], + }, + ], + }, + { + name: "Take nth bytes: Complex single line", + input: "0123456789", + expectedOutput: "59", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 5, false], + }, + ], + }, + { + name: "Take nth bytes: Complex single line (apply to each line)", + input: "0123456789", + expectedOutput: "59", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 5, true], + }, + ], + }, + { + name: "Take nth bytes: Basic multi line", + input: "01234\n56789", + expectedOutput: "047", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 0, false], + }, + ], + }, + { + name: "Take nth bytes: Basic multi line (apply to each line)", + input: "01234\n56789", + expectedOutput: "04\n59", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 0, true], + }, + ], + }, + { + name: "Take nth bytes: Complex multi line", + input: "01234\n56789", + expectedOutput: "\n8", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 5, false], + }, + ], + }, + { + name: "Take nth bytes: Complex multi line (apply to each line)", + input: "012345\n6789ab", + expectedOutput: "5\nb", + recipeConfig: [ + { + op: "Take nth bytes", + args: [4, 5, true], + }, + ], + } +]); From 30349dbcb90156b98365462dcd93b6dabb540b9d Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 20:44:57 +0100 Subject: [PATCH 014/455] Add operation Added 'Drop nth bytes' operation. --- src/core/config/Categories.json | 3 +- src/core/operations/DropNthBytes.mjs | 79 ++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 src/core/operations/DropNthBytes.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index db2ab3a63..9cfeb7fe0 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -238,7 +238,8 @@ "Escape string", "Unescape string", "Pseudo-Random Number Generator", - "Sleep" + "Sleep", + "Drop nth bytes" ] }, { diff --git a/src/core/operations/DropNthBytes.mjs b/src/core/operations/DropNthBytes.mjs new file mode 100644 index 000000000..e6bac1cd2 --- /dev/null +++ b/src/core/operations/DropNthBytes.mjs @@ -0,0 +1,79 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Drop nth bytes operation + */ +class DropNthBytes extends Operation { + + /** + * DropNthBytes constructor + */ + constructor() { + super(); + + this.name = "Drop nth bytes"; + this.module = "Default"; + this.description = "Drops every nth byte starting with a given byte."; + this.infoURL = ""; + this.inputType = "byteArray"; + this.outputType = "byteArray"; + this.args = [ + { + name: "Drop every", + type: "number", + value: 4 + }, + { + name: "Starting at", + type: "number", + value: 0 + }, + { + name: "Apply to each line", + type: "boolean", + value: false + } + ]; + } + + /** + * @param {byteArray} input + * @param {Object[]} args + * @returns {byteArray} + */ + run(input, args) { + const n = args[0]; + const start = args[1]; + const eachLine = args[2]; + + if (parseInt(n, 10) !== n || n <= 0) { + throw new OperationError("'Drop every' must be a positive integer."); + } + if (parseInt(start, 10) !== start || start < 0) { + throw new OperationError("'Starting at' must be a positive or zero integer."); + } + + let offset = 0; + const output = []; + for (let i = 0; i < input.length; i++) { + if (eachLine && input[i] === 0x0a) { + output.push(0x0a); + offset = i + 1; + } else if (i - offset < start || (i - (start + offset)) % n !== 0) { + output.push(input[i]); + } + } + + return output; + } + +} + +export default DropNthBytes; From b125f82784274b3d68eb0a1e2777c63bbf68f205 Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 20:59:04 +0100 Subject: [PATCH 015/455] Add tests Added tests for 'Drop nth bytes' operation. --- tests/operations/index.mjs | 1 + tests/operations/tests/DropNthBytes.mjs | 123 ++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 tests/operations/tests/DropNthBytes.mjs diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index d64a77377..046a0b79d 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -91,6 +91,7 @@ import "./tests/Protobuf.mjs"; import "./tests/ParseSSHHostKey.mjs"; import "./tests/DefangIP.mjs"; import "./tests/ParseUDP.mjs"; +import "./tests/DropNthBytes.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/DropNthBytes.mjs b/tests/operations/tests/DropNthBytes.mjs new file mode 100644 index 000000000..00d4e0ab7 --- /dev/null +++ b/tests/operations/tests/DropNthBytes.mjs @@ -0,0 +1,123 @@ +/** + * @author Oshawk [oshawk@protonmail.com] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import TestRegister from "../../lib/TestRegister.mjs"; + +/** + * Drop nth bytes tests + */ +TestRegister.addTests([ + { + name: "Drop nth bytes: Nothing", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 0, false], + }, + ], + }, + { + name: "Drop nth bytes: Nothing (apply to each line)", + input: "", + expectedOutput: "", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 0, true], + }, + ], + }, + { + name: "Drop nth bytes: Basic single line", + input: "0123456789", + expectedOutput: "1235679", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 0, false], + }, + ], + }, + { + name: "Drop nth bytes: Basic single line (apply to each line)", + input: "0123456789", + expectedOutput: "1235679", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 0, true], + }, + ], + }, + { + name: "Drop nth bytes: Complex single line", + input: "0123456789", + expectedOutput: "01234678", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 5, false], + }, + ], + }, + { + name: "Drop nth bytes: Complex single line (apply to each line)", + input: "0123456789", + expectedOutput: "01234678", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 5, true], + }, + ], + }, + { + name: "Drop nth bytes: Basic multi line", + input: "01234\n56789", + expectedOutput: "123\n5689", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 0, false], + }, + ], + }, + { + name: "Drop nth bytes: Basic multi line (apply to each line)", + input: "01234\n56789", + expectedOutput: "123\n678", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 0, true], + }, + ], + }, + { + name: "Drop nth bytes: Complex multi line", + input: "01234\n56789", + expectedOutput: "012345679", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 5, false], + }, + ], + }, + { + name: "Drop nth bytes: Complex multi line (apply to each line)", + input: "012345\n6789ab", + expectedOutput: "01234\n6789a", + recipeConfig: [ + { + op: "Drop nth bytes", + args: [4, 5, true], + }, + ], + } +]); From 518b33643198ed6ee474b9296fa6f7ffdd6f9dfb Mon Sep 17 00:00:00 2001 From: Oshawk Date: Mon, 21 Oct 2019 21:01:33 +0100 Subject: [PATCH 016/455] Add tests to index Added tests to index. --- tests/operations/index.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index d64a77377..348e84605 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -91,6 +91,7 @@ import "./tests/Protobuf.mjs"; import "./tests/ParseSSHHostKey.mjs"; import "./tests/DefangIP.mjs"; import "./tests/ParseUDP.mjs"; +import "./tests/TakeNthBytes.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; From c689cf7f134df8e8309302c88e8b9bf1a22e94f8 Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Thu, 9 Jan 2020 15:14:33 +0000 Subject: [PATCH 017/455] 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 07b6a0ff8..832363279 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 e7e558cd9..ebf5e5c2b 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 018/455] 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 b56f9cf85..a6b9e2aca 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 019/455] 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 832363279..a80fdb2bf 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 ebf5e5c2b..7d550d466 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 6b76b7004a9d832acd4f19803c9990b18288b846 Mon Sep 17 00:00:00 2001 From: thezero Date: Sun, 14 Apr 2019 15:08:10 +0200 Subject: [PATCH 020/455] add button to hide recipe's options --- src/web/HTMLOperation.mjs | 1 + src/web/Manager.mjs | 1 + src/web/waiters/RecipeWaiter.mjs | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+) diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs index fe075c486..f46b3ba82 100755 --- a/src/web/HTMLOperation.mjs +++ b/src/web/HTMLOperation.mjs @@ -83,6 +83,7 @@ class HTMLOperation {
pause not_interested + keyboard_arrow_up
 
`; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index e1e07dfda..64dc3a352 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -135,6 +135,7 @@ class Manager { // Recipe this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); + this.addDynamicListener(".hide-options", "click", this.recipe.hideOptClick, this.recipe); this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs index ba0e7b11c..afa3e72b6 100755 --- a/src/web/waiters/RecipeWaiter.mjs +++ b/src/web/waiters/RecipeWaiter.mjs @@ -214,6 +214,30 @@ class RecipeWaiter { window.dispatchEvent(this.manager.statechange); } + /** + * Handler for hide-opt click events. + * Updates the icon status. + * + * @fires Manager#statechange + * @param {event} e + */ + hideOptClick(e) { + const icon = e.target; + + if (icon.getAttribute("hide-opt") === "false") { + icon.setAttribute("hide-opt", "true"); + icon.innerText = "keyboard_arrow_down"; + icon.classList.add("hide-options-selected"); + icon.parentNode.previousElementSibling.style.display = "none"; + } else { + icon.setAttribute("hide-opt", "false"); + icon.innerText = "keyboard_arrow_up"; + icon.classList.remove("hide-options-selected"); + icon.parentNode.previousElementSibling.style.display = "grid"; + } + + window.dispatchEvent(this.manager.statechange); + } /** * Handler for disable click events. From 3bb6a40f82e98fbc4cf45c82f75c033725862282 Mon Sep 17 00:00:00 2001 From: thezero Date: Mon, 22 Apr 2019 00:18:52 +0200 Subject: [PATCH 021/455] add button to hide all recipe options --- src/web/Manager.mjs | 1 + src/web/html/index.html | 3 +++ src/web/waiters/ControlsWaiter.mjs | 30 ++++++++++++++++++++++++++++++ 3 files changed, 34 insertions(+) diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 64dc3a352..493d3a192 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -120,6 +120,7 @@ class Manager { document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); + document.getElementById("hide-icon").addEventListener("click", this.controls.hideRecipeOptClick.bind(this.recipe)); document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); diff --git a/src/web/html/index.html b/src/web/html/index.html index 121f07806..ad9400400 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -177,6 +177,9 @@
Recipe + diff --git a/src/web/waiters/ControlsWaiter.mjs b/src/web/waiters/ControlsWaiter.mjs index 2f2705aa4..b051e3ce0 100755 --- a/src/web/waiters/ControlsWaiter.mjs +++ b/src/web/waiters/ControlsWaiter.mjs @@ -333,6 +333,36 @@ class ControlsWaiter { } + /** + * Hides the options for all the operations in the current recipe. + */ + hideRecipeOptClick() { + const icon = document.getElementById("hide-icon"); + + if (icon.getAttribute("hide-opt") === "false") { + icon.setAttribute("hide-opt", "true"); + icon.setAttribute("data-original-title", "Show options"); + icon.children[0].innerText = "keyboard_arrow_down"; + Array.from(document.getElementsByClassName("hide-options")).forEach(function(item){ + item.setAttribute("hide-opt", "true"); + item.innerText = "keyboard_arrow_down"; + item.classList.add("hide-options-selected"); + item.parentNode.previousElementSibling.style.display = "none"; + }); + } else { + icon.setAttribute("hide-opt", "false"); + icon.setAttribute("data-original-title", "Hide options"); + icon.children[0].innerText = "keyboard_arrow_up"; + Array.from(document.getElementsByClassName("hide-options")).forEach(function(item){ + item.setAttribute("hide-opt", "false"); + item.innerText = "keyboard_arrow_up"; + item.classList.remove("hide-options-selected"); + item.parentNode.previousElementSibling.style.display = "grid"; + }); + } + } + + /** * Populates the bug report information box with useful technical info. * From ed7baf57f0dbc04e07b1479b1e045b7c307d60c1 Mon Sep 17 00:00:00 2001 From: thezero Date: Wed, 21 Oct 2020 00:17:06 +0200 Subject: [PATCH 022/455] replace "options" with "arguments", invert global hide-icon if needed --- src/web/HTMLOperation.mjs | 2 +- src/web/Manager.mjs | 4 ++-- src/web/html/index.html | 2 +- src/web/waiters/ControlsWaiter.mjs | 26 ++++++++++++------------- src/web/waiters/RecipeWaiter.mjs | 31 +++++++++++++++++++++++------- 5 files changed, 41 insertions(+), 24 deletions(-) diff --git a/src/web/HTMLOperation.mjs b/src/web/HTMLOperation.mjs index f46b3ba82..285fe10ec 100755 --- a/src/web/HTMLOperation.mjs +++ b/src/web/HTMLOperation.mjs @@ -83,7 +83,7 @@ class HTMLOperation {
pause not_interested - keyboard_arrow_up + keyboard_arrow_up
 
`; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 493d3a192..f7e08aa60 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -120,7 +120,7 @@ class Manager { document.getElementById("load-delete-button").addEventListener("click", this.controls.loadDeleteClick.bind(this.controls)); document.getElementById("load-name").addEventListener("change", this.controls.loadNameChange.bind(this.controls)); document.getElementById("load-button").addEventListener("click", this.controls.loadButtonClick.bind(this.controls)); - document.getElementById("hide-icon").addEventListener("click", this.controls.hideRecipeOptClick.bind(this.recipe)); + document.getElementById("hide-icon").addEventListener("click", this.controls.hideRecipeArgsClick.bind(this.recipe)); document.getElementById("support").addEventListener("click", this.controls.supportButtonClick.bind(this.controls)); this.addMultiEventListeners("#save-texts textarea", "keyup paste", this.controls.saveTextChange, this.controls); @@ -136,7 +136,7 @@ class Manager { // Recipe this.addDynamicListener(".arg:not(select)", "input", this.recipe.ingChange, this.recipe); this.addDynamicListener(".arg[type=checkbox], .arg[type=radio], select.arg", "change", this.recipe.ingChange, this.recipe); - this.addDynamicListener(".hide-options", "click", this.recipe.hideOptClick, this.recipe); + this.addDynamicListener(".hide-args-icon", "click", this.recipe.hideArgsClick, this.recipe); this.addDynamicListener(".disable-icon", "click", this.recipe.disableClick, this.recipe); this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); diff --git a/src/web/html/index.html b/src/web/html/index.html index ad9400400..b5cff9f0c 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -177,7 +177,7 @@
Recipe - @@ -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 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 diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 7811144a5..625b81f74 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
diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index f251fa27a..54e67b3bd 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 2d40fe4c4..b6cc74bf0 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 f36841e1b..843adcf0e 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 058/455] 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 78c265323..5cf9428f9 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 4ed5d5ebd..54662c009 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 c1745eeb7..affc372d1 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 43c5f89e2..d58e8d68b 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 843adcf0e..e88052d8c 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 059/455] 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 b6cc74bf0..c29f855fd 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 d58e8d68b..8fe4e3482 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 e88052d8c..6f888c490 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 060/455] 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 307270d26..c918fbb89 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 000000000..b9d30aa03 --- /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 7a3361f24..2c27d8682 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 000000000..e304165b6 --- /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 061/455] 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 307270d26..80834d4db 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 000000000..e1fad7309 --- /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 7a3361f24..43c6a5dde 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 000000000..2506fc44b --- /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 062/455] 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 10340ea86..8ebfb1326 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 3b7d4338d..5bb18d2e0 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 00b860915..f884c3e88 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 8fe4e3482..4af09cf61 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 063/455] 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 9d8273961..3a63321da 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 b3ac4e4a9..a24c7cd8d 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 064/455] 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 6f888c490..608ad087c 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 065/455] 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 d2cd4b9b3..502de66bf 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 54662c009..93f93dce9 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 4af09cf61..2110c60dd 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 066/455] 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 140774bc2..dbde3e3f3 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 1afdef010..11b1ff9f6 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 d6f67698e..3bb8122cd 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 703b09800..e1b32d640 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 8769a69aa..e3ea31b80 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 d7768859f..7de8810da 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 849b57567..d89e3c0b2 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 a5897840f..a762ea96d 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)
+
+ + +
+
- 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 c06d3b8cc..fa2168365 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 5a9533f55..426107bb9 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 664daef8e..9f83b55c0 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 b421d8d87..e8e71b12f 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 5ef517d4a..52b81ab49 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 0eb6baeca..8996edb0c 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 41aff9b29..ba6f5204e 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 bb18dc5d8..d0933bb67 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 029/455] 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 9d4813e07..2d45d1f19 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 c410704b9..1edc41b52 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 bd70d10fe..339da0745 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 426107bb9..2879089ab 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 030/455] 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 66a98c364..5f36cae94 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 08a35d756..2477bb60f 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 8a837a514..000000000 --- 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 3d237bddc..3eb150e53 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 625b81f74..ba670f3d5 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 000000000..fe6b83d45 --- /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 000000000..fbce9b491 --- /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 000000000..431d8a3db --- /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 9f83b55c0..d1340165d 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 e8e71b12f..0dc44dbe5 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 52b81ab49..7d9a3e2da 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 8996edb0c..496b0ac5d 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 ba6f5204e..e63a80365 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 d0933bb67..64f8e0368 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 031/455] 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 f4a17f63c..d46a705dd 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 71893105f..092155a90 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 2477bb60f..a46379e95 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 d1340165d..8b4375fe5 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 0dc44dbe5..ff512f696 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 496b0ac5d..d1fd2532c 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 384b1ab76..f5b0efd46 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 7fcaa5099..a63bfc1f0 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 032/455] 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 ba670f3d5..cb196709b 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 3b3bd5550..971c1c574 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 8b4375fe5..189d37779 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 ff512f696..69417b92f 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 033/455] 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 045d8f052..31e575a1b 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 2d45d1f19..4ead8bc4f 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 a46379e95..9d03c728d 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 69417b92f..6a1b57df2 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 d1fd2532c..3f031ac7c 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 034/455] 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 9d03c728d..820b1a8d4 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 6a1b57df2..ed8f174b3 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 035/455] 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 8ac60048a..26e569055 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 000000000..e8264c4db --- /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 036/455] 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 e8264c4db..4fa0706dc 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 037/455] 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 4fa0706dc..1a0a43687 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 000000000..4787f086c --- /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 038/455] 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 5f36cae94..b72a60282 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 d5357d957..69cad1db1 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 aefe2ab71..7468ee110 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 5f346e006..fa1e90dce 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 18eb071ea..ef8b7f807 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 c657adeb9..a52b04511 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 9b548df80..8c0977314 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 3eb150e53..a7931de5a 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 cb196709b..ea15b6ac0 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 fa2168365..920aab890 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 000000000..51b2386bb --- /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 fe6b83d45..cb0ebed16 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 fbce9b491..5e5c41c1c 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 7d9a3e2da..36beef7e7 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 3f031ac7c..deaeaed3f 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 f4107e66b..d907a67c0 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 039/455] 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 36998cecf..140774bc2 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 d46a705dd..8989875ae 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 b72a60282..604b7b8c9 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 c5cb56059..8934d137f 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 9b01b79f1..0fc9d2b53 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 8fc61fcee..8cc1450f0 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 ef8b7f807..ae96fd0a2 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 820b1a8d4..793b61def 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 a7931de5a..68d69a781 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 ea15b6ac0..185b3bdba 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 5e5c41c1c..348009333 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 431d8a3db..f9be50066 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 ed8f174b3..caa1a0981 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 deaeaed3f..f0b03d721 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 9912995bf..e1c75de90 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 040/455] 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 793b61def..730d6e2e3 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 68d69a781..6e2c60a30 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 185b3bdba..9c64fe85f 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 000000000..f8e3003b1 --- /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 000000000..a8de0931a --- /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 caa1a0981..000940a4a 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 041/455] 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 604b7b8c9..fec3b9bee 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 000940a4a..86ad9873d 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 f0b03d721..a247375eb 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 042/455] 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 f9be50066..efabea81c 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 a247375eb..f1965c77b 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 043/455] 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 6e2c60a30..9cf3e8784 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -482,11 +482,6 @@
    -
    - - -
    -
    -
    to
    - -
    KiB
    -
    -
    -
    -
    -