From 1f09c03d4897206e7ed4a3d90cac6c577c486aed Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Fri, 15 Feb 2019 14:23:16 +0000 Subject: [PATCH 001/188] Add De Bruijn Operation --- src/core/config/Categories.json | 1 + .../operations/GenerateDeBruijnSequence.mjs | 87 +++++++++++++++++++ tests/operations/index.mjs | 1 + .../tests/GenerateDeBruijnSequence.mjs | 33 +++++++ 4 files changed, 122 insertions(+) create mode 100644 src/core/operations/GenerateDeBruijnSequence.mjs create mode 100644 tests/operations/tests/GenerateDeBruijnSequence.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 8235ab10..238c7282 100755 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -370,6 +370,7 @@ "Chi Square", "Disassemble x86", "Pseudo-Random Number Generator", + "Generate De Bruijn Sequence", "Generate UUID", "Generate TOTP", "Generate HOTP", diff --git a/src/core/operations/GenerateDeBruijnSequence.mjs b/src/core/operations/GenerateDeBruijnSequence.mjs new file mode 100644 index 00000000..647d3c7f --- /dev/null +++ b/src/core/operations/GenerateDeBruijnSequence.mjs @@ -0,0 +1,87 @@ +/** + * @author gchq77703 [gchq77703@gchq.gov.uk] + * @copyright Crown Copyright 2019 + * @license Apache-2.0 + */ + +import Operation from "../Operation"; +import OperationError from "../errors/OperationError"; + +/** + * Generate De Bruijn Sequence operation + */ +class GenerateDeBruijnSequence extends Operation { + + /** + * GenerateDeBruijnSequence constructor + */ + constructor() { + super(); + + this.name = "Generate De Bruijn Sequence"; + this.module = "Default"; + this.description = "Generates rolling keycode combinations given a certain alphabet size and key length."; + this.infoURL = "https://wikipedia.org/wiki/De_Bruijn_sequence"; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + { + name: "Alphabet size (k)", + type: "number", + value: 2 + }, + { + name: "Key length (n)", + type: "number", + value: 3 + } + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + const [k, n] = args; + + if (k < 2 || k > 9) { + throw new OperationError("Invalid alphabet size, required to be between 2 and 9 (inclusive)."); + } + + if (n < 2) { + throw new OperationError("Invalid key length, required to be at least 2."); + } + + if (Math.pow(k, n) > 50000) { + throw new OperationError("Too many permutations, please reduce k^n to under 50,000."); + } + + const a = []; + for (let i = 0; i < k * n; i++) a.push(0); + + const sequence = []; + + (function db(t = 1, p = 1) { + if (t > n) { + if (n % p !== 0) return; + for (let j = 1; j <= p; j++) { + sequence.push(a[j]); + } + return; + } + + a[t] = a[t - p]; + db(t + 1, p); + for (let j = a[t - p] + 1; j < k; j++) { + a[t] = j; + db(t + 1, t); + } + })(); + + return sequence.join(""); + } +} + +export default GenerateDeBruijnSequence; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index fb68ed9c..316e934c 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -45,6 +45,7 @@ import "./tests/DateTime"; import "./tests/ExtractEmailAddresses"; import "./tests/Fork"; import "./tests/FromDecimal"; +import "./tests/GenerateDeBruijnSequence"; import "./tests/Hash"; import "./tests/HaversineDistance"; import "./tests/Hexdump"; diff --git a/tests/operations/tests/GenerateDeBruijnSequence.mjs b/tests/operations/tests/GenerateDeBruijnSequence.mjs new file mode 100644 index 00000000..b68a843f --- /dev/null +++ b/tests/operations/tests/GenerateDeBruijnSequence.mjs @@ -0,0 +1,33 @@ +/** + * De Brujin Sequence tests. + * + * @author gchq77703 [gchq77703@gchq.gov.uk] + * @copyright Crown Copyright 2017 + * @license Apache-2.0 + */ +import TestRegister from "../TestRegister"; + +TestRegister.addTests([ + { + name: "Small Sequence", + input: "", + expectedOutput: "00010111", + recipeConfig: [ + { + "op": "Generate De Bruijn Sequence", + "args": [2, 3] + } + ] + }, + { + name: "Long Sequence", + input: "", + expectedOutput: "0000010000200003000110001200013000210002200023000310003200033001010010200103001110011200113001210012200123001310013200133002010020200203002110021200213002210022200223002310023200233003010030200303003110031200313003210032200323003310033200333010110101201013010210102201023010310103201033011020110301111011120111301121011220112301131011320113301202012030121101212012130122101222012230123101232012330130201303013110131201313013210132201323013310133201333020210202202023020310203202033021030211102112021130212102122021230213102132021330220302211022120221302221022220222302231022320223302303023110231202313023210232202323023310233202333030310303203033031110311203113031210312203123031310313203133032110321203213032210322203223032310323203233033110331203313033210332203323033310333203333111112111131112211123111321113311212112131122211223112321123311312113131132211323113321133312122121231213212133122131222212223122321223312313123221232312332123331313213133132221322313232132331332213323133321333322222322233223232233323233233333", + recipeConfig: [ + { + "op": "Generate De Bruijn Sequence", + "args": [4, 5] + } + ] + } +]) \ No newline at end of file From 44a164ed2825ddd799b656459b89b1a4ee5a9f0a Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Tue, 19 Feb 2019 09:56:38 +0000 Subject: [PATCH 002/188] Fix test script linter --- tests/operations/tests/GenerateDeBruijnSequence.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/operations/tests/GenerateDeBruijnSequence.mjs b/tests/operations/tests/GenerateDeBruijnSequence.mjs index b68a843f..48e8c4ff 100644 --- a/tests/operations/tests/GenerateDeBruijnSequence.mjs +++ b/tests/operations/tests/GenerateDeBruijnSequence.mjs @@ -30,4 +30,4 @@ TestRegister.addTests([ } ] } -]) \ No newline at end of file +]); From 822a4fab86572817fcd2e6218d8c736d1e22bbf4 Mon Sep 17 00:00:00 2001 From: GCHQ 77703 Date: Tue, 19 Feb 2019 10:16:51 +0000 Subject: [PATCH 003/188] Fix operation linting --- src/core/operations/GenerateDeBruijnSequence.mjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/operations/GenerateDeBruijnSequence.mjs b/src/core/operations/GenerateDeBruijnSequence.mjs index 647d3c7f..af788585 100644 --- a/src/core/operations/GenerateDeBruijnSequence.mjs +++ b/src/core/operations/GenerateDeBruijnSequence.mjs @@ -71,7 +71,7 @@ class GenerateDeBruijnSequence extends Operation { } return; } - + a[t] = a[t - p]; db(t + 1, p); for (let j = a[t - p] + 1; j < k; j++) { From 85ffe487432a21dd56c6133e16db4c828d718009 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 29 Jun 2022 18:02:49 +0100 Subject: [PATCH 004/188] Input now uses CodeMirror editor --- package-lock.json | 197 +++++++++++ package.json | 5 + src/core/operations/ParseColourCode.mjs | 2 +- src/web/Manager.mjs | 4 +- src/web/extensions/statusBar.mjs | 190 +++++++++++ src/web/html/index.html | 4 +- .../static/fonts/MaterialIcons-Regular.ttf | Bin 0 -> 354228 bytes .../static/fonts/MaterialIcons-Regular.woff2 | Bin 44300 -> 0 bytes src/web/stylesheets/layout/_io.css | 101 +++++- src/web/stylesheets/utils/_overrides.css | 2 +- src/web/waiters/ControlsWaiter.mjs | 2 +- src/web/waiters/HighlighterWaiter.mjs | 15 +- src/web/waiters/InputWaiter.mjs | 307 +++++++++--------- src/web/waiters/OptionsWaiter.mjs | 6 +- src/web/waiters/OutputWaiter.mjs | 5 +- tests/browser/nightwatch.js | 2 +- tests/browser/ops.js | 6 +- 17 files changed, 666 insertions(+), 182 deletions(-) create mode 100644 src/web/extensions/statusBar.mjs create mode 100644 src/web/static/fonts/MaterialIcons-Regular.ttf delete mode 100644 src/web/static/fonts/MaterialIcons-Regular.woff2 diff --git a/package-lock.json b/package-lock.json index ffde1368..18759956 100644 --- a/package-lock.json +++ b/package-lock.json @@ -95,6 +95,11 @@ "@babel/plugin-transform-runtime": "^7.18.2", "@babel/preset-env": "^7.18.2", "@babel/runtime": "^7.18.3", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.1.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.1", + "@codemirror/view": "^6.0.2", "autoprefixer": "^10.4.7", "babel-loader": "^8.2.5", "babel-plugin-dynamic-import-node": "^2.3.3", @@ -1782,6 +1787,60 @@ "node": ">=6.9.0" } }, + "node_modules/@codemirror/commands": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.0.0.tgz", + "integrity": "sha512-nVJDPiCQXWXj5AZxqNVXyIM3nOYauF4Dko9NGPSwgVdK+lXWJQhI5LGhS/AvdG5b7u7/pTQBkrQmzkLWRBF62A==", + "dev": true, + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.1.0.tgz", + "integrity": "sha512-CeqY80nvUFrJcXcBW115aNi06D0PS8NSW6nuJRSwbrYFkE0SfJnPfyLGrcM90AV95lqg5+4xUi99BCmzNaPGJg==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/search": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.0.0.tgz", + "integrity": "sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "node_modules/@codemirror/state": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.0.1.tgz", + "integrity": "sha512-6vYgaXc4KjSY0BUfSVDJooGcoswg/RJZpq/ZGjsUYmY0KN1lmB8u03nv+jiG1ncUV5qoggyxFT5AGD5Ak+5Zrw==", + "dev": true + }, + "node_modules/@codemirror/view": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz", + "integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==", + "dev": true, + "dependencies": { + "@codemirror/state": "^6.0.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -2370,6 +2429,30 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "node_modules/@lezer/common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.0.tgz", + "integrity": "sha512-ohydQe+Hb+w4oMDvXzs8uuJd2NoA3D8YDcLiuDsLqH+yflDTPEpgCsWI3/6rH5C3BAedtH1/R51dxENldQceEA==", + "dev": true + }, + "node_modules/@lezer/highlight": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.0.0.tgz", + "integrity": "sha512-nsCnNtim90UKsB5YxoX65v3GEIw3iCHw9RM2DtdgkiqAbKh9pCdvi8AWNwkYf10Lu6fxNhXPpkpHbW6mihhvJA==", + "dev": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.0.0.tgz", + "integrity": "sha512-k6DEqBh4HxqO/cVGedb6Ern6LS7K6IOzfydJ5WaqCR26v6UR9sIFyb6PS+5rPUs/mXgnBR/QQCW7RkyjSCMoQA==", + "dev": true, + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, "node_modules/@nightwatch/chai": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz", @@ -5059,6 +5142,12 @@ "sha.js": "^2.4.8" } }, + "node_modules/crelt": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", + "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==", + "dev": true + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -14244,6 +14333,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/style-mod": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", + "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "dev": true + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -14982,6 +15077,12 @@ "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==" }, + "node_modules/w3c-keyname": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", + "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==", + "dev": true + }, "node_modules/watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", @@ -17001,6 +17102,60 @@ "to-fast-properties": "^2.0.0" } }, + "@codemirror/commands": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.0.0.tgz", + "integrity": "sha512-nVJDPiCQXWXj5AZxqNVXyIM3nOYauF4Dko9NGPSwgVdK+lXWJQhI5LGhS/AvdG5b7u7/pTQBkrQmzkLWRBF62A==", + "dev": true, + "requires": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0" + } + }, + "@codemirror/language": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.1.0.tgz", + "integrity": "sha512-CeqY80nvUFrJcXcBW115aNi06D0PS8NSW6nuJRSwbrYFkE0SfJnPfyLGrcM90AV95lqg5+4xUi99BCmzNaPGJg==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "@codemirror/search": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/@codemirror/search/-/search-6.0.0.tgz", + "integrity": "sha512-rL0rd3AhI0TAsaJPUaEwC63KHLO7KL0Z/dYozXj6E7L3wNHRyx7RfE0/j5HsIf912EE5n2PCb4Vg0rGYmDv4UQ==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "crelt": "^1.0.5" + } + }, + "@codemirror/state": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.0.1.tgz", + "integrity": "sha512-6vYgaXc4KjSY0BUfSVDJooGcoswg/RJZpq/ZGjsUYmY0KN1lmB8u03nv+jiG1ncUV5qoggyxFT5AGD5Ak+5Zrw==", + "dev": true + }, + "@codemirror/view": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.0.2.tgz", + "integrity": "sha512-mnVT/q1JvKPjpmjXJNeCi/xHyaJ3abGJsumIVpdQ1nE1MXAyHf7GHWt8QpWMUvDiqF0j+inkhVR2OviTdFFX7Q==", + "dev": true, + "requires": { + "@codemirror/state": "^6.0.0", + "style-mod": "^4.0.0", + "w3c-keyname": "^2.2.4" + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -17452,6 +17607,30 @@ "integrity": "sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==", "dev": true }, + "@lezer/common": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.0.tgz", + "integrity": "sha512-ohydQe+Hb+w4oMDvXzs8uuJd2NoA3D8YDcLiuDsLqH+yflDTPEpgCsWI3/6rH5C3BAedtH1/R51dxENldQceEA==", + "dev": true + }, + "@lezer/highlight": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.0.0.tgz", + "integrity": "sha512-nsCnNtim90UKsB5YxoX65v3GEIw3iCHw9RM2DtdgkiqAbKh9pCdvi8AWNwkYf10Lu6fxNhXPpkpHbW6mihhvJA==", + "dev": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, + "@lezer/lr": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.0.0.tgz", + "integrity": "sha512-k6DEqBh4HxqO/cVGedb6Ern6LS7K6IOzfydJ5WaqCR26v6UR9sIFyb6PS+5rPUs/mXgnBR/QQCW7RkyjSCMoQA==", + "dev": true, + "requires": { + "@lezer/common": "^1.0.0" + } + }, "@nightwatch/chai": { "version": "5.0.2", "resolved": "https://registry.npmjs.org/@nightwatch/chai/-/chai-5.0.2.tgz", @@ -19640,6 +19819,12 @@ "sha.js": "^2.4.8" } }, + "crelt": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", + "integrity": "sha512-+BO9wPPi+DWTDcNYhr/W90myha8ptzftZT+LwcmUbbok0rcP/fequmFYCw8NMoH7pkAZQzU78b3kYrlua5a9eA==", + "dev": true + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -26720,6 +26905,12 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "style-mod": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.0.tgz", + "integrity": "sha512-OPhtyEjyyN9x3nhPsu76f52yUGXiZcgvsrFVtvTkyGRQJ0XK+GPc6ov1z+lRpbeabka+MYEQxOYRnt5nF30aMw==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -27303,6 +27494,12 @@ "resolved": "https://registry.npmjs.org/vkbeautify/-/vkbeautify-0.99.3.tgz", "integrity": "sha512-2ozZEFfmVvQcHWoHLNuiKlUfDKlhh4KGsy54U0UrlLMR1SO+XKAIDqBxtBwHgNrekurlJwE8A9K6L49T78ZQ9Q==" }, + "w3c-keyname": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.4.tgz", + "integrity": "sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==", + "dev": true + }, "watchpack": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.3.1.tgz", diff --git a/package.json b/package.json index cea3fb19..8e89248a 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,11 @@ "@babel/plugin-transform-runtime": "^7.18.2", "@babel/preset-env": "^7.18.2", "@babel/runtime": "^7.18.3", + "@codemirror/commands": "^6.0.0", + "@codemirror/language": "^6.1.0", + "@codemirror/search": "^6.0.0", + "@codemirror/state": "^6.0.1", + "@codemirror/view": "^6.0.2", "autoprefixer": "^10.4.7", "babel-loader": "^8.2.5", "babel-plugin-dynamic-import-node": "^2.3.3", diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs index 9cf40ba7..045d8f05 100644 --- a/src/core/operations/ParseColourCode.mjs +++ b/src/core/operations/ParseColourCode.mjs @@ -112,7 +112,7 @@ CMYK: ${cmyk} useAlpha: true }).on('colorpickerChange', function(e) { var color = e.color.string('rgba'); - document.getElementById('input-text').value = color; + window.app.manager.input.setInput(color); window.app.manager.input.debounceInputChange(new Event("keyup")); }); `; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index e1e07dfd..08a35d75 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -146,8 +146,7 @@ class Manager { this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); // Input - this.addMultiEventListener("#input-text", "keyup", this.input.debounceInputChange, this.input); - this.addMultiEventListener("#input-text", "paste", this.input.inputPaste, this.input); + document.getElementById("input-text").addEventListener("keyup", this.input.debounceInputChange.bind(this.input)); document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); @@ -179,6 +178,7 @@ class Manager { this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); + this.addDynamicListener(".eol-select a", "click", this.input.eolSelectClick, this.input); // Output diff --git a/src/web/extensions/statusBar.mjs b/src/web/extensions/statusBar.mjs new file mode 100644 index 00000000..8a837a51 --- /dev/null +++ b/src/web/extensions/statusBar.mjs @@ -0,0 +1,190 @@ +/** + * A Status bar extension for CodeMirror + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {showPanel} from "@codemirror/view"; + +/** + * Counts the stats of a document + * @param {element} el + * @param {Text} doc + */ +function updateStats(el, doc) { + const length = el.querySelector("#stats-length-value"), + lines = el.querySelector("#stats-lines-value"); + length.textContent = doc.length; + lines.textContent = doc.lines; +} + +/** + * Gets the current selection info + * @param {element} el + * @param {EditorState} state + * @param {boolean} selectionSet + */ +function updateSelection(el, state, selectionSet) { + const selLen = state.selection && state.selection.main ? + state.selection.main.to - state.selection.main.from : + 0; + + const selInfo = el.querySelector("#sel-info"), + curOffsetInfo = el.querySelector("#cur-offset-info"); + + if (!selectionSet) { + selInfo.style.display = "none"; + curOffsetInfo.style.display = "none"; + return; + } + + if (selLen > 0) { // Range + const start = el.querySelector("#sel-start-value"), + end = el.querySelector("#sel-end-value"), + length = el.querySelector("#sel-length-value"); + + selInfo.style.display = "inline-block"; + curOffsetInfo.style.display = "none"; + + start.textContent = state.selection.main.from; + end.textContent = state.selection.main.to; + length.textContent = state.selection.main.to - state.selection.main.from; + } else { // Position + const offset = el.querySelector("#cur-offset-value"); + + selInfo.style.display = "none"; + curOffsetInfo.style.display = "inline-block"; + + offset.textContent = state.selection.main.from; + } +} + +/** + * Gets the current character encoding of the document + * @param {element} el + * @param {EditorState} state + */ +function updateCharEnc(el, state) { + // const charenc = el.querySelector("#char-enc-value"); + // TODO + // charenc.textContent = "TODO"; +} + +/** + * Returns what the current EOL separator is set to + * @param {element} el + * @param {EditorState} state + */ +function updateEOL(el, state) { + const eolLookup = { + "\u000a": "LF", + "\u000b": "VT", + "\u000c": "FF", + "\u000d": "CR", + "\u000d\u000a": "CRLF", + "\u0085": "NEL", + "\u2028": "LS", + "\u2029": "PS" + }; + + const val = el.querySelector("#eol-value"); + val.textContent = eolLookup[state.lineBreak]; +} + +/** + * Builds the Left-hand-side widgets + * @returns {string} + */ +function constructLHS() { + return ` + abc + + + + sort + + + + + highlight_alt + \u279E + ( selected) + + + location_on + + `; +} + +/** + * Builds the Right-hand-side widgets + * Event listener set up in Manager + * @returns {string} + */ +function constructRHS() { + return ` + language + UTF-16 + + + `; +} + +/** + * A panel constructor building a panel that re-counts the stats every time the document changes. + * @param {EditorView} view + * @returns {Panel} + */ +function wordCountPanel(view) { + const dom = document.createElement("div"); + const lhs = document.createElement("div"); + const rhs = document.createElement("div"); + + dom.className = "cm-status-bar"; + lhs.innerHTML = constructLHS(); + rhs.innerHTML = constructRHS(); + + dom.appendChild(lhs); + dom.appendChild(rhs); + + updateEOL(rhs, view.state); + updateCharEnc(rhs, view.state); + updateStats(lhs, view.state.doc); + updateSelection(lhs, view.state, false); + + return { + dom, + update(update) { + updateEOL(rhs, update.state); + updateSelection(lhs, update.state, update.selectionSet); + updateCharEnc(rhs, update.state); + if (update.docChanged) { + updateStats(lhs, update.state.doc); + } + } + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @returns {Extension} + */ +export function statusBar() { + return showPanel.of(wordCountPanel); +} diff --git a/src/web/html/index.html b/src/web/html/index.html index d222fee1..3d237bdd 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -219,8 +219,6 @@
-
-
@@ -267,7 +265,7 @@
- +
diff --git a/src/web/static/fonts/MaterialIcons-Regular.ttf b/src/web/static/fonts/MaterialIcons-Regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..54938737932514a89614069b081163cb34826768 GIT binary patch literal 354228 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 diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 7811144a..625b81f7 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -6,7 +6,24 @@ * @license Apache-2.0 */ -#input-text, +#input-text { + position: relative; + width: 100%; + height: 100%; + margin: 0; + background-color: transparent; +} + +.cm-editor { + height: 100%; +} + +.cm-editor .cm-content { + font-family: var(--fixed-width-font-family); + font-size: var(--fixed-width-font-size); + color: var(--fixed-width-font-colour); +} + #output-text, #output-html { position: relative; @@ -163,14 +180,14 @@ #input-wrapper, #output-wrapper, -#input-wrapper > * , +#input-wrapper > :not(#input-text), #output-wrapper > .textarea-wrapper > div, #output-wrapper > .textarea-wrapper > textarea { height: calc(100% - var(--title-height)); } #input-wrapper.show-tabs, -#input-wrapper.show-tabs > *, +#input-wrapper.show-tabs > :not(#input-text), #output-wrapper.show-tabs, #output-wrapper.show-tabs > .textarea-wrapper > div, #output-wrapper.show-tabs > .textarea-wrapper > textarea { @@ -193,7 +210,9 @@ } .textarea-wrapper textarea, -.textarea-wrapper>div { +.textarea-wrapper #output-text, +.textarea-wrapper #output-html, +.textarea-wrapper #output-highlighter { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); color: var(--fixed-width-font-colour); @@ -292,10 +311,6 @@ align-items: center; } -#input-info { - line-height: 15px; -} - .dropping-file { border: 5px dashed var(--drop-file-border-colour) !important; } @@ -458,3 +473,73 @@ cursor: pointer; filter: brightness(98%); } + + +/* Status bar */ + +.cm-status-bar { + font-family: var(--fixed-width-font-family); + font-weight: normal; + font-size: 8pt; + margin: 0 5px; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; +} + +.cm-status-bar i { + font-size: 12pt; + vertical-align: middle; + margin-left: 8px; +} +.cm-status-bar>div>span:first-child i { + margin-left: 0; +} + +/* Dropup Button */ +.cm-status-bar-select-btn { + border: none; + cursor: pointer; +} + +/* The container
diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index f251fa27..54e67b3b 100755 --- a/src/web/stylesheets/components/_pane.css +++ b/src/web/stylesheets/components/_pane.css @@ -46,72 +46,6 @@ padding: 0; } -.io-card.card { - box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); - transition: 0.3s; - width: 400px; - height: 150px; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - font-family: var(--primary-font-family); - color: var(--primary-font-colour); - line-height: 30px; - background-color: var(--primary-background-colour); - flex-direction: row; - padding-left: 10px; -} - -.io-card.card:hover { - box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); -} - -.io-card.card>img { - float: left; - width: auto; - height: auto; - max-width: 128px; - max-height: 128px; - margin-left: auto; - margin-top: auto; - margin-right: auto; - margin-bottom: auto; - padding: 0px; - -} - -.io-card.card .card-body .close { - position: absolute; - right: 10px; - top: 4px; -} - -.io-card.card .card-body { - float: left; - padding: 16px; - width: 250px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - user-select: text; -} - -.io-card.card .card-body>.btn { - margin-bottom: 5px; - margin-top: 5px; -} - -.io-card.card input[type=number] { - padding-right: 6px; - padding-left: 6px; - height: unset; -} - -.io-card.card .input-group { - padding-top: 5px; -} - #files .card-header .float-right a:hover { text-decoration: none; } diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 2d40fe4c..b6cc74bf 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -165,10 +165,6 @@ height: calc(100% - var(--tab-height) - var(--title-height)); } -#show-file-overlay { - height: 32px; -} - .input-wrapper.textarea-wrapper { width: 100%; box-sizing: border-box; @@ -221,30 +217,6 @@ transition: all 0.5s ease; } -#output-file { - position: absolute; - left: 0; - top: 50%; - width: 100%; - display: none; -} - -.file-overlay { - position: absolute; - opacity: 0.8; - background-color: var(--title-background-colour); - width: 100%; - height: 100%; -} - -#show-file-overlay { - position: absolute; - right: 15px; - top: calc(var(--title-height) + 10px); - cursor: pointer; - display: none; -} - .io-info { margin-right: 18px; margin-top: 1px; diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index f36841e1..843adcf0 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -512,8 +512,6 @@ class OutputWaiter { return new Promise(async function(resolve, reject) { const output = this.outputs[inputNum]; - const outputFile = document.getElementById("output-file"); - // Update the EOL value this.outputEditorView.dispatch({ effects: this.outputEditorConf.eol.reconfigure( @@ -539,18 +537,12 @@ class OutputWaiter { this.manager.recipe.updateBreakpointIndicator(false); } - document.getElementById("show-file-overlay").style.display = "none"; - if (output.status === "pending" || output.status === "baking") { // show the loader and the status message if it's being shown // otherwise don't do anything document.querySelector("#output-loader .loading-msg").textContent = output.statusMessage; } else if (output.status === "error") { - // style the tab if it's being shown this.toggleLoader(false); - this.outputTextEl.style.display = "block"; - this.outputTextEl.classList.remove("blur"); - outputFile.style.display = "none"; this.clearHTMLOutput(); if (output.error) { @@ -560,15 +552,10 @@ class OutputWaiter { } } else if (output.status === "baked" || output.status === "inactive") { document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`; - this.closeFile(); if (output.data === null) { - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - this.clearHTMLOutput(); this.setOutput(""); - this.toggleLoader(false); return; } @@ -577,7 +564,6 @@ class OutputWaiter { switch (output.data.type) { case "html": - outputFile.style.display = "none"; // TODO what if the HTML content needs to be in a certain character encoding? // Grey out chr enc selection? Set back to Raw Bytes? @@ -586,9 +572,6 @@ class OutputWaiter { case "ArrayBuffer": case "string": default: - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - this.clearHTMLOutput(); this.setOutput(output.data.result); break; @@ -600,34 +583,6 @@ class OutputWaiter { }.bind(this)); } - /** - * Shows file details - * - * @param {ArrayBuffer} buf - * @param {number} activeTab - */ - setFile(buf, activeTab) { - if (activeTab !== this.manager.tabs.getActiveTab("output")) return; - // Display file overlay in output area with details - const fileOverlay = document.getElementById("output-file"), - fileSize = document.getElementById("output-file-size"), - fileSlice = buf.slice(0, 4096); - - fileOverlay.style.display = "block"; - fileSize.textContent = buf.byteLength.toLocaleString() + " bytes"; - - this.outputTextEl.classList.add("blur"); - this.setOutput(Utils.arrayBufferToStr(fileSlice)); - } - - /** - * Clears output file details - */ - closeFile() { - document.getElementById("output-file").style.display = "none"; - this.outputTextEl.classList.remove("blur"); - } - /** * Retrieves the dish as a string, returning the cached version if possible. * @@ -1297,80 +1252,6 @@ class OutputWaiter { magicButton.setAttribute("data-original-title", "Magic!"); } - - /** - * Handler for file slice display events. - */ - async displayFileSlice() { - document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice..."; - this.toggleLoader(true); - const outputFile = document.getElementById("output-file"), - showFileOverlay = document.getElementById("show-file-overlay"), - sliceFromEl = document.getElementById("output-file-slice-from"), - sliceToEl = document.getElementById("output-file-slice-to"), - sliceFrom = parseInt(sliceFromEl.value, 10) * 1024, - sliceTo = parseInt(sliceToEl.value, 10) * 1024, - output = this.outputs[this.manager.tabs.getActiveTab("output")].data; - - let str; - if (output.type === "ArrayBuffer") { - str = Utils.arrayBufferToStr(output.result.slice(sliceFrom, sliceTo)); - } else { - str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish).slice(sliceFrom, sliceTo)); - } - - this.outputTextEl.classList.remove("blur"); - showFileOverlay.style.display = "block"; - this.clearHTMLOutput(); - this.setOutput(str); - - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - - this.toggleLoader(false); - } - - /** - * Handler for showing an entire file at user's discretion (even if it's way too big) - */ - async showAllFile() { - document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash..."; - this.toggleLoader(true); - const outputFile = document.getElementById("output-file"), - showFileOverlay = document.getElementById("show-file-overlay"), - output = this.outputs[this.manager.tabs.getActiveTab("output")].data; - - let str; - if (output.type === "ArrayBuffer") { - str = Utils.arrayBufferToStr(output.result); - } else { - str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish)); - } - - this.outputTextEl.classList.remove("blur"); - showFileOverlay.style.display = "none"; - this.clearHTMLOutput(); - this.setOutput(str); - - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - - this.toggleLoader(false); - } - - /** - * Handler for show file overlay events - * - * @param {Event} e - */ - showFileOverlayClick(e) { - const showFileOverlay = e.target; - - this.outputTextEl.classList.add("blur"); - showFileOverlay.style.display = "none"; - this.set(this.manager.tabs.getActiveTab("output")); - } - /** * Handler for extract file events. * From ff45f61b68228d1ee4cf07a7a7dfdd52d29c4b30 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 20:46:01 +0000 Subject: [PATCH 109/188] Fixed the snackbar --- Gruntfile.js | 10 ++++++++++ package.json | 2 +- src/web/stylesheets/layout/_modals.css | 2 +- src/web/utils/statusBar.mjs | 2 +- src/web/waiters/OutputWaiter.mjs | 2 +- 5 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Gruntfile.js b/Gruntfile.js index 78c26532..5cf9428f 100755 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -398,6 +398,16 @@ module.exports = function (grunt) { `find ./node_modules/crypto-api/src/ \\( -type d -name .git -prune \\) -o -type f -print0 | xargs -0 sed -i -e '/\\.mjs/!s/\\(from "\\.[^"]*\\)";/\\1.mjs";/g'` ].join(" "), stdout: false + }, + fixSnackbarMarkup: { + command: [ + `[[ "$OSTYPE" == "darwin"* ]]`, + "&&", + `sed -i '' 's/
/
/g' ./node_modules/snackbarjs/src/snackbar.js`, + "||", + `sed -i 's/
/
/g' ./node_modules/snackbarjs/src/snackbar.js` + ].join(" "), + stdout: false } }, }); diff --git a/package.json b/package.json index 4ed5d5eb..54662c00 100644 --- a/package.json +++ b/package.json @@ -184,7 +184,7 @@ "testui": "npx grunt testui", "testuidev": "npx nightwatch --env=dev", "lint": "npx grunt lint", - "postinstall": "npx grunt exec:fixCryptoApiImports", + "postinstall": "npx grunt exec:fixCryptoApiImports && npx grunt exec:fixSnackbarMarkup", "newop": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newOperation.mjs", "minor": "node --experimental-modules --experimental-json-modules src/core/config/scripts/newMinorVersion.mjs", "getheapsize": "node -e 'console.log(`node heap limit = ${require(\"v8\").getHeapStatistics().heap_size_limit / (1024 * 1024)} Mb`)'", diff --git a/src/web/stylesheets/layout/_modals.css b/src/web/stylesheets/layout/_modals.css index c1745eeb..affc372d 100755 --- a/src/web/stylesheets/layout/_modals.css +++ b/src/web/stylesheets/layout/_modals.css @@ -107,4 +107,4 @@ background-image: linear-gradient(to top, var(--input-highlight-colour) 2px, rgba(0, 0, 0, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px); -} \ No newline at end of file +} diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 43c5f89e..d58e8d68 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -111,7 +111,7 @@ class StatusBarPanel { */ chrEncSelectClick(e) { // preventDefault is required to stop the URL being modified and popState being triggered - e.preventDefault(); // TODO - this breaks the menus when you click the button itself + e.preventDefault(); const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 843adcf0..e88052d8 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -1274,7 +1274,7 @@ class OutputWaiter { * Handler for copy click events. * Copies the output to the clipboard */ - async copyClick() { // TODO - do we need this? + async copyClick() { const dish = this.getOutputDish(this.manager.tabs.getActiveTab("output")); if (dish === null) { this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000); From 1b3d55f0512abe10aa79be50abd375e8fbd828d3 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Dec 2022 21:23:25 +0000 Subject: [PATCH 110/188] Status bar widgets are disabled for HTML output --- src/web/stylesheets/layout/_io.css | 5 +++++ src/web/utils/statusBar.mjs | 30 ++++++++++++++++++++++++++++++ src/web/waiters/OutputWaiter.mjs | 6 ++---- 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index b6cc74bf..c29f855f 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -449,6 +449,11 @@ margin-left: 0; } +.cm-status-bar .disabled { + background-color: unset !important; + cursor: not-allowed; +} + /* Dropup Button */ .cm-status-bar-select-btn { border: none; diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index d58e8d68..8fe4e348 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -22,6 +22,7 @@ class StatusBarPanel { this.eolHandler = opts.eolHandler; this.chrEncHandler = opts.chrEncHandler; this.chrEncGetter = opts.chrEncGetter; + this.htmlOutput = opts.htmlOutput; this.eolVal = null; this.chrEncVal = null; @@ -65,6 +66,9 @@ class StatusBarPanel { const el = e.target .closest(".cm-status-bar-select") .querySelector(".cm-status-bar-select-content"); + const btn = e.target.closest(".cm-status-bar-select-btn"); + + if (btn.classList.contains("disabled")) return; el.classList.add("show"); @@ -269,6 +273,30 @@ class StatusBarPanel { ); } + /** + * Checks whether there is HTML output requiring some widgets to be disabled + */ + monitorHTMLOutput() { + if (!this.htmlOutput?.changed) return; + + if (this.htmlOutput?.html === "") { + // Enable all controls + this.dom.querySelectorAll(".disabled").forEach(el => { + el.classList.remove("disabled"); + }); + } else { + // Disable chrenc, length, selection etc. + this.dom.querySelectorAll(".cm-status-bar-select-btn").forEach(el => { + el.classList.add("disabled"); + }); + + this.dom.querySelector(".stats-length-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".stats-lines-value").parentNode.classList.add("disabled"); + this.dom.querySelector(".sel-info").classList.add("disabled"); + this.dom.querySelector(".cur-offset-info").classList.add("disabled"); + } + } + /** * Builds the Left-hand-side widgets * @returns {string} @@ -404,6 +432,7 @@ function makePanel(opts) { sbPanel.updateBakeStats(); sbPanel.updateStats(view.state.doc); sbPanel.updateSelection(view.state, false); + sbPanel.monitorHTMLOutput(); return { "dom": sbPanel.dom, @@ -412,6 +441,7 @@ function makePanel(opts) { sbPanel.updateCharEnc(); sbPanel.updateSelection(update.state, update.selectionSet); sbPanel.updateBakeStats(); + sbPanel.monitorHTMLOutput(); if (update.geometryChanged) { sbPanel.updateSizing(update.view); } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index e88052d8..6f888c49 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -91,7 +91,8 @@ class OutputWaiter { bakeStats: this.bakeStats, eolHandler: this.eolChange.bind(this), chrEncHandler: this.chrEncChange.bind(this), - chrEncGetter: this.getChrEnc.bind(this) + chrEncGetter: this.getChrEnc.bind(this), + htmlOutput: this.htmlOutput }), htmlPlugin(this.htmlOutput), copyOverride(), @@ -564,9 +565,6 @@ class OutputWaiter { switch (output.data.type) { case "html": - // TODO what if the HTML content needs to be in a certain character encoding? - // Grey out chr enc selection? Set back to Raw Bytes? - this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": From 2c822314df25577d87cec60154057688caa3aebb Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 11 Jan 2023 05:16:37 +0900 Subject: [PATCH 111/188] add new operation: Levenshtein Distance --- src/core/config/Categories.json | 1 + src/core/operations/LevenshteinDistance.mjs | 98 +++++++++++ tests/operations/index.mjs | 1 + .../operations/tests/LevenshteinDistance.mjs | 165 ++++++++++++++++++ 4 files changed, 265 insertions(+) create mode 100644 src/core/operations/LevenshteinDistance.mjs create mode 100644 tests/operations/tests/LevenshteinDistance.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 307270d2..c918fbb8 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -269,6 +269,7 @@ "Fuzzy Match", "Offset checker", "Hamming Distance", + "Levenshtein Distance", "Convert distance", "Convert area", "Convert mass", diff --git a/src/core/operations/LevenshteinDistance.mjs b/src/core/operations/LevenshteinDistance.mjs new file mode 100644 index 00000000..b9d30aa0 --- /dev/null +++ b/src/core/operations/LevenshteinDistance.mjs @@ -0,0 +1,98 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; +import OperationError from "../errors/OperationError.mjs"; + +/** + * Levenshtein Distance operation + */ +class LevenshteinDistance extends Operation { + + /** + * LevenshteinDistance constructor + */ + constructor() { + super(); + + this.name = "Levenshtein Distance"; + this.module = "Default"; + this.description = "Levenshtein Distance (also known as Edit Distance) is a string metric to measure a difference between two strings that counts operations (insertions, deletions, and substitutions) on single character that are required to change one string to another."; + this.infoURL = "https://wikipedia.org/wiki/Levenshtein_distance"; + this.inputType = "string"; + this.outputType = "number"; + this.args = [ + { + name: "Sample delimiter", + type: "binaryString", + value: "\\n" + }, + { + name: "Insertion cost", + type: "number", + value: 1 + }, + { + name: "Deletion cost", + type: "number", + value: 1 + }, + { + name: "Substitution cost", + type: "number", + value: 1 + }, + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {number} + */ + run(input, args) { + const [delim, insCost, delCost, subCost] = args; + const samples = input.split(delim); + if (samples.length !== 2) { + throw new OperationError("Incorrect number of samples. Check your input and/or delimiter."); + } + if (insCost < 0 || delCost < 0 || subCost < 0) { + throw new OperationError("Negative costs are not allowed."); + } + const src = samples[0], dest = samples[1]; + let currentCost = new Array(src.length + 1); + let nextCost = new Array(src.length + 1); + for (let i = 0; i < currentCost.length; i++) { + currentCost[i] = delCost * i; + } + for (let i = 0; i < dest.length; i++) { + const destc = dest.charAt(i); + nextCost[0] = currentCost[0] + insCost; + for (let j = 0; j < src.length; j++) { + let candidate; + // insertion + let optCost = currentCost[j + 1] + insCost; + // deletion + candidate = nextCost[j] + delCost; + if (candidate < optCost) optCost = candidate; + // substitution or matched character + candidate = currentCost[j]; + if (src.charAt(j) !== destc) candidate += subCost; + if (candidate < optCost) optCost = candidate; + // store calculated cost + nextCost[j + 1] = optCost; + } + const tempCost = nextCost; + nextCost = currentCost; + currentCost = tempCost; + } + + return currentCost[currentCost.length - 1]; + } + +} + +export default LevenshteinDistance; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 7a3361f2..2c27d868 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs"; import "./tests/CMAC.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/Rabbit.mjs"; +import "./tests/LevenshteinDistance.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/LevenshteinDistance.mjs b/tests/operations/tests/LevenshteinDistance.mjs new file mode 100644 index 00000000..e304165b --- /dev/null +++ b/tests/operations/tests/LevenshteinDistance.mjs @@ -0,0 +1,165 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Levenshtein Distance: Wikipedia example 1", + "input": "kitten\nsitting", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: Wikipedia example 2", + "input": "saturday\nsunday", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: Wikipedia example 1 with substitution cost 2", + "input": "kitten\nsitting", + "expectedOutput": "5", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 2, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: varied costs 1", + "input": "kitten\nsitting", + "expectedOutput": "230", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 10, 100, 1000, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: varied costs 2", + "input": "kitten\nsitting", + "expectedOutput": "1020", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1000, 100, 10, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: another delimiter", + "input": "kitten sitting", + "expectedOutput": "3", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + " ", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: too few samples", + "input": "kitten", + "expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: too many samples", + "input": "kitten\nsitting\nkitchen", + "expectedOutput": "Incorrect number of samples. Check your input and/or delimiter.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative insertion cost", + "input": "kitten\nsitting", + "expectedOutput": "Negative costs are not allowed.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", -1, 1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative deletion cost", + "input": "kitten\nsitting", + "expectedOutput": "Negative costs are not allowed.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, -1, 1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: negative substitution cost", + "input": "kitten\nsitting", + "expectedOutput": "Negative costs are not allowed.", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 1, 1, -1, + ], + }, + ], + }, + { + "name": "Levenshtein Distance: cost zero", + "input": "kitten\nsitting", + "expectedOutput": "0", + "recipeConfig": [ + { + "op": "Levenshtein Distance", + "args": [ + "\\n", 0, 0, 0, + ], + }, + ], + }, +]); From d5b72548fc81f5e5b60c094b4bdd6dddae8b2c2a Mon Sep 17 00:00:00 2001 From: MikeCAT Date: Wed, 11 Jan 2023 05:48:05 +0900 Subject: [PATCH 112/188] add new operation: Swap case --- src/core/config/Categories.json | 1 + src/core/operations/SwapCase.mjs | 77 +++++++++++++++++++++++++++++ tests/operations/index.mjs | 1 + tests/operations/tests/SwapCase.mjs | 33 +++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 src/core/operations/SwapCase.mjs create mode 100644 tests/operations/tests/SwapCase.mjs diff --git a/src/core/config/Categories.json b/src/core/config/Categories.json index 307270d2..80834d4d 100644 --- a/src/core/config/Categories.json +++ b/src/core/config/Categories.json @@ -245,6 +245,7 @@ "Remove null bytes", "To Upper case", "To Lower case", + "Swap case", "To Case Insensitive Regex", "From Case Insensitive Regex", "Add line numbers", diff --git a/src/core/operations/SwapCase.mjs b/src/core/operations/SwapCase.mjs new file mode 100644 index 00000000..e1fad730 --- /dev/null +++ b/src/core/operations/SwapCase.mjs @@ -0,0 +1,77 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ + +import Operation from "../Operation.mjs"; + +/** + * Swap case operation + */ +class SwapCase extends Operation { + + /** + * SwapCase constructor + */ + constructor() { + super(); + + this.name = "Swap case"; + this.module = "Default"; + this.description = "Converts uppercase letters to lowercase ones, and lowercase ones to uppercase ones."; + this.infoURL = ""; + this.inputType = "string"; + this.outputType = "string"; + this.args = [ + ]; + } + + /** + * @param {string} input + * @param {Object[]} args + * @returns {string} + */ + run(input, args) { + let result = ""; + for (let i = 0; i < input.length; i++) { + const c = input.charAt(i); + const upper = c.toUpperCase(); + if (c === upper) { + result += c.toLowerCase(); + } else { + result += upper; + } + } + return result; + } + + /** + * Highlight Swap case + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlight(pos, args) { + return pos; + } + + /** + * Highlight Swap case in reverse + * + * @param {Object[]} pos + * @param {number} pos[].start + * @param {number} pos[].end + * @param {Object[]} args + * @returns {Object[]} pos + */ + highlightReverse(pos, args) { + return pos; + } + +} + +export default SwapCase; diff --git a/tests/operations/index.mjs b/tests/operations/index.mjs index 7a3361f2..43c6a5dd 100644 --- a/tests/operations/index.mjs +++ b/tests/operations/index.mjs @@ -130,6 +130,7 @@ import "./tests/FletcherChecksum.mjs"; import "./tests/CMAC.mjs"; import "./tests/AESKeyWrap.mjs"; import "./tests/Rabbit.mjs"; +import "./tests/SwapCase.mjs"; // Cannot test operations that use the File type yet // import "./tests/SplitColourChannels.mjs"; diff --git a/tests/operations/tests/SwapCase.mjs b/tests/operations/tests/SwapCase.mjs new file mode 100644 index 00000000..2506fc44 --- /dev/null +++ b/tests/operations/tests/SwapCase.mjs @@ -0,0 +1,33 @@ +/** + * @author mikecat + * @copyright Crown Copyright 2023 + * @license Apache-2.0 + */ +import TestRegister from "../../lib/TestRegister.mjs"; + +TestRegister.addTests([ + { + "name": "Swap Case: basic example", + "input": "Hello, World!", + "expectedOutput": "hELLO, wORLD!", + "recipeConfig": [ + { + "op": "Swap case", + "args": [ + ], + }, + ], + }, + { + "name": "Swap Case: empty input", + "input": "", + "expectedOutput": "", + "recipeConfig": [ + { + "op": "Swap case", + "args": [ + ], + }, + ], + }, +]); From f2bd838596756fb232f5a85187405e01efb75661 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 13:12:01 +0000 Subject: [PATCH 113/188] Fixed CSS for theme highlighting and status bar dropup height --- src/web/stylesheets/themes/_dark.css | 2 +- src/web/stylesheets/themes/_solarizedDark.css | 4 ++-- src/web/stylesheets/themes/_solarizedLight.css | 4 ++-- src/web/utils/statusBar.mjs | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/web/stylesheets/themes/_dark.css b/src/web/stylesheets/themes/_dark.css index 10340ea8..8ebfb132 100755 --- a/src/web/stylesheets/themes/_dark.css +++ b/src/web/stylesheets/themes/_dark.css @@ -110,7 +110,7 @@ --hl2: #675351; --hl3: #ffb6b6; --hl4: #fcf8e3; - --hl5: #8de768; + --hl5: #38811b; /* Scrollbar */ diff --git a/src/web/stylesheets/themes/_solarizedDark.css b/src/web/stylesheets/themes/_solarizedDark.css index 3b7d4338..5bb18d2e 100755 --- a/src/web/stylesheets/themes/_solarizedDark.css +++ b/src/web/stylesheets/themes/_solarizedDark.css @@ -125,9 +125,9 @@ /* Highlighter colours */ --hl1: var(--base01); --hl2: var(--sol-blue); - --hl3: var(--sol-magenta); + --hl3: var(--sol-green); --hl4: var(--sol-yellow); - --hl5: var(--sol-green); + --hl5: var(--sol-magenta); /* Scrollbar */ diff --git a/src/web/stylesheets/themes/_solarizedLight.css b/src/web/stylesheets/themes/_solarizedLight.css index 00b86091..f884c3e8 100755 --- a/src/web/stylesheets/themes/_solarizedLight.css +++ b/src/web/stylesheets/themes/_solarizedLight.css @@ -127,9 +127,9 @@ /* Highlighter colours */ --hl1: var(--base1); --hl2: var(--sol-blue); - --hl3: var(--sol-magenta); + --hl3: var(--sol-green); --hl4: var(--sol-yellow); - --hl5: var(--sol-green); + --hl5: var(--sol-magenta); /* Scrollbar */ diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 8fe4e348..4af09cf6 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -265,7 +265,7 @@ class StatusBarPanel { * @param {EditorView} view */ updateSizing(view) { - const viewHeight = view.contentDOM.clientHeight; + const viewHeight = view.contentDOM.parentNode.clientHeight; this.dom.querySelectorAll(".cm-status-bar-select-scroll").forEach( el => { el.style.maxHeight = (viewHeight - 50) + "px"; From 17c349973db647cc7b44d6a14d6ec9959ad1f078 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 13:51:16 +0000 Subject: [PATCH 114/188] Fixed file loading bug where the wrong input is set --- src/web/waiters/InputWaiter.mjs | 5 ++++- src/web/workers/InputWorker.mjs | 8 +++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 9d827396..3a63321d 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -485,7 +485,10 @@ class InputWaiter { async set(inputNum, inputData, silent=false) { return new Promise(function(resolve, reject) { const activeTab = this.manager.tabs.getActiveTab("input"); - if (inputNum !== activeTab) return; + if (inputNum !== activeTab) { + this.changeTab(inputNum, this.app.options.syncTabs); + return; + } // Update current character encoding this.inputChrEnc = inputData.encoding; diff --git a/src/web/workers/InputWorker.mjs b/src/web/workers/InputWorker.mjs index b3ac4e4a..a24c7cd8 100644 --- a/src/web/workers/InputWorker.mjs +++ b/src/web/workers/InputWorker.mjs @@ -434,8 +434,7 @@ self.updateTabHeader = function(inputNum) { * @param {boolean} inputData.silent - If false, the manager statechange event will be fired */ self.setInput = function(inputData) { - const inputNum = inputData.inputNum; - const silent = inputData.silent; + const {inputNum, silent} = inputData; const input = self.getInputObj(inputNum); if (input === undefined || input === null) return; @@ -695,8 +694,7 @@ self.terminateLoaderWorker = function(id) { * @param {number} filesData.activeTab - The active tab in the UI */ self.loadFiles = function(filesData) { - const files = filesData.files; - const activeTab = filesData.activeTab; + const {files, activeTab} = filesData; let lastInputNum = -1; const inputNums = []; for (let i = 0; i < files.length; i++) { @@ -735,7 +733,7 @@ self.loadFiles = function(filesData) { } self.getLoadProgress(); - self.setInput({inputNum: activeTab, silent: true}); + self.setInput({inputNum: lastInputNum, silent: true}); }; /** From c1394e299a66ed76c1308baa0c7168c2a05c84cb Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 14:14:57 +0000 Subject: [PATCH 115/188] Fixed replace input with output button --- src/web/waiters/OutputWaiter.mjs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 6f888c49..608ad087 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -1305,9 +1305,9 @@ class OutputWaiter { const activeData = await this.getDishBuffer(this.getOutputDish(activeTab)); if (this.outputExists(activeTab)) { - this.manager.input.set({ - inputNum: activeTab, - input: activeData + this.manager.input.set(activeTab, { + type: "userinput", + buffer: activeData }); } From 4e512a9a7b814af6920a85c43ec844259752d3e2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 14:25:40 +0000 Subject: [PATCH 116/188] Updated dependencies --- package-lock.json | 2223 +++++++++++++++++++++++++---------- package.json | 56 +- src/web/utils/statusBar.mjs | 12 +- 3 files changed, 1625 insertions(+), 666 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2cd4b9b..502de66b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,15 +14,15 @@ "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "arrive": "^2.4.1", - "avsc": "^5.7.4", + "avsc": "^5.7.7", "bcryptjs": "^2.4.3", - "bignumber.js": "^9.0.2", + "bignumber.js": "^9.1.1", "blakejs": "^1.2.1", - "bootstrap": "4.6.1", + "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", "bootstrap-material-design": "^4.1.3", "browserify-zlib": "^0.2.0", - "bson": "^4.6.4", + "bson": "^4.7.2", "buffer": "^6.0.3", "cbor": "8.1.0", "chi-squared": "^1.1.0", @@ -41,28 +41,28 @@ "file-saver": "^2.0.5", "flat": "^5.0.2", "geodesy": "1.1.3", - "highlight.js": "^11.5.1", - "jimp": "^0.16.1", - "jquery": "3.6.0", + "highlight.js": "^11.7.0", + "jimp": "^0.16.2", + "jquery": "3.6.3", "js-crc": "^0.2.0", "js-sha3": "^0.8.0", "jsesc": "^3.0.2", - "json5": "^2.2.1", + "json5": "^2.2.3", "jsonpath-plus": "^7.2.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "jsqr": "^1.4.0", - "jsrsasign": "^10.5.23", + "jsrsasign": "^10.6.1", "kbpgp": "2.1.15", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.21", - "loglevel": "^1.8.0", + "loglevel": "^1.8.1", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.4.4", "lz4js": "^0.2.0", "markdown-it": "^13.0.1", "moment": "^2.29.4", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "ngeohash": "^0.6.3", "node-forge": "^1.3.1", "node-md6": "^0.1.0", @@ -74,7 +74,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^6.11.3", + "protobufjs": "^7.1.2", "qr-image": "^3.2.0", "reflect-metadata": "^0.1.13", "scryptsy": "^2.1.0", @@ -83,8 +83,8 @@ "split.js": "^1.6.5", "ssdeep.js": "0.0.3", "stream-browserify": "^3.0.0", - "tesseract.js": "3.0.2", - "ua-parser-js": "^1.0.2", + "tesseract.js": "3.0.3", + "ua-parser-js": "^1.0.32", "unorm": "^1.6.0", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", @@ -94,28 +94,28 @@ "zlibjs": "^0.3.1" }, "devDependencies": { - "@babel/core": "^7.20.5", + "@babel/core": "^7.20.12", "@babel/eslint-parser": "^7.19.1", "@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", - "@babel/runtime": "^7.20.6", - "@codemirror/commands": "^6.1.2", - "@codemirror/language": "^6.3.1", + "@babel/runtime": "^7.20.7", + "@codemirror/commands": "^6.1.3", + "@codemirror/language": "^6.4.0", "@codemirror/search": "^6.2.3", - "@codemirror/state": "^6.1.4", - "@codemirror/view": "^6.7.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.3", "autoprefixer": "^10.4.13", - "babel-loader": "^9.1.0", + "babel-loader": "^9.1.2", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", - "chromedriver": "^108.0.0", + "chromedriver": "^109.0.0", "cli-progress": "^3.11.2", "colors": "^1.4.0", "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.26.1", - "css-loader": "6.7.2", - "eslint": "^8.29.0", + "core-js": "^3.27.1", + "css-loader": "6.7.3", + "eslint": "^8.31.0", "grunt": "^1.5.3", "grunt-chmod": "~1.1.1", "grunt-concurrent": "^3.0.0", @@ -131,8 +131,8 @@ "imports-loader": "^4.0.1", "mini-css-extract-plugin": "2.7.2", "modify-source-webpack-plugin": "^3.0.0", - "nightwatch": "^2.5.4", - "postcss": "^8.4.19", + "nightwatch": "^2.6.10", + "postcss": "^8.4.21", "postcss-css-variables": "^0.18.0", "postcss-import": "^15.1.0", "postcss-loader": "^7.0.2", @@ -197,25 +197,25 @@ } }, "node_modules/@babel/core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", - "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, "dependencies": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.5", - "@babel/parser": "^7.20.5", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" }, "engines": { @@ -245,11 +245,11 @@ } }, "node_modules/@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", "dependencies": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -293,14 +293,15 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", "dev": true, "dependencies": { - "@babel/compat-data": "^7.20.0", + "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", "semver": "^6.3.0" }, "engines": { @@ -310,6 +311,21 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/@babel/helper-create-class-features-plugin": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz", @@ -432,9 +448,9 @@ } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", - "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", "dev": true, "dependencies": { "@babel/helper-environment-visitor": "^7.18.9", @@ -442,9 +458,9 @@ "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" @@ -581,14 +597,14 @@ } }, "node_modules/@babel/helpers": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", - "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", "dev": true, "dependencies": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" @@ -608,9 +624,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==", "bin": { "parser": "bin/babel-parser.js" }, @@ -1733,9 +1749,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "dependencies": { "regenerator-runtime": "^0.13.11" }, @@ -1755,31 +1771,31 @@ } }, "node_modules/@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", "dependencies": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", + "@babel/generator": "^7.20.7", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "debug": "^4.1.0", "globals": "^11.1.0" }, @@ -1788,9 +1804,9 @@ } }, "node_modules/@babel/types": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", - "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", "dependencies": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", @@ -1809,21 +1825,21 @@ } }, "node_modules/@codemirror/commands": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", - "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz", + "integrity": "sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==", "dev": true, "dependencies": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", + "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0" } }, "node_modules/@codemirror/language": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.1.tgz", - "integrity": "sha512-MK+G1QKaGfSEUg9YEFaBkMBI6j1ge4VMBPZv9fDYotw7w695c42x5Ba1mmwBkesYnzYFBfte6Hh9TDcKa6xORQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", + "integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", "dev": true, "dependencies": { "@codemirror/state": "^6.0.0", @@ -1846,15 +1862,15 @@ } }, "node_modules/@codemirror/state": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.4.tgz", - "integrity": "sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", "dev": true }, "node_modules/@codemirror/view": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.0.tgz", - "integrity": "sha512-sI3CngHQlguxAquc2oZ4sMFDgTJiCnKkRcFmw5apqcnNLvQfQtEDIlYvCVl1adJ6UV7ZUV9wOdqkeJ8kz2+5gg==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz", + "integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==", "dev": true, "dependencies": { "@codemirror/state": "^6.1.4", @@ -1871,14 +1887,14 @@ } }, "node_modules/@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -1893,9 +1909,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dependencies": { "type-fest": "^0.20.2" }, @@ -1907,9 +1923,9 @@ } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "dependencies": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -1937,11 +1953,12 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "node_modules/@jimp/bmp": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.2.tgz", + "integrity": "sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "bmp-js": "^0.1.0" }, "peerDependencies": { @@ -1949,11 +1966,12 @@ } }, "node_modules/@jimp/core": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.2.tgz", + "integrity": "sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", @@ -1967,6 +1985,8 @@ }, "node_modules/@jimp/core/node_modules/buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "funding": [ { "type": "github", @@ -1981,7 +2001,6 @@ "url": "https://feross.org/support" } ], - "license": "MIT", "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1989,7 +2008,8 @@ }, "node_modules/@jimp/core/node_modules/mkdirp": { "version": "0.5.6", - "license": "MIT", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dependencies": { "minimist": "^1.2.6" }, @@ -1998,19 +2018,21 @@ } }, "node_modules/@jimp/custom": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.2.tgz", + "integrity": "sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.1" + "@jimp/core": "^0.16.2" } }, "node_modules/@jimp/gif": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.2.tgz", + "integrity": "sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "gifwrap": "^0.9.2", "omggif": "^1.0.9" }, @@ -2032,44 +2054,48 @@ } }, "node_modules/@jimp/plugin-blit": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.2.tgz", + "integrity": "sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-blur": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.2.tgz", + "integrity": "sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-circle": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.2.tgz", + "integrity": "sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-color": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.2.tgz", + "integrity": "sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "tinycolor2": "^1.4.1" }, "peerDependencies": { @@ -2077,11 +2103,12 @@ } }, "node_modules/@jimp/plugin-contain": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.2.tgz", + "integrity": "sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2091,11 +2118,12 @@ } }, "node_modules/@jimp/plugin-cover": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.2.tgz", + "integrity": "sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2105,55 +2133,60 @@ } }, "node_modules/@jimp/plugin-crop": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.2.tgz", + "integrity": "sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-displace": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.2.tgz", + "integrity": "sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-dither": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.2.tgz", + "integrity": "sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-fisheye": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.2.tgz", + "integrity": "sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-flip": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.2.tgz", + "integrity": "sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2161,55 +2194,60 @@ } }, "node_modules/@jimp/plugin-gaussian": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.2.tgz", + "integrity": "sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-invert": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.2.tgz", + "integrity": "sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-mask": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.2.tgz", + "integrity": "sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-normalize": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.2.tgz", + "integrity": "sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-print": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.2.tgz", + "integrity": "sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "load-bmfont": "^1.4.0" }, "peerDependencies": { @@ -2218,22 +2256,24 @@ } }, "node_modules/@jimp/plugin-resize": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.2.tgz", + "integrity": "sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5" } }, "node_modules/@jimp/plugin-rotate": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.2.tgz", + "integrity": "sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2243,11 +2283,12 @@ } }, "node_modules/@jimp/plugin-scale": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.2.tgz", + "integrity": "sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2255,11 +2296,12 @@ } }, "node_modules/@jimp/plugin-shadow": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.2.tgz", + "integrity": "sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2268,11 +2310,12 @@ } }, "node_modules/@jimp/plugin-threshold": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.2.tgz", + "integrity": "sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" }, "peerDependencies": { "@jimp/custom": ">=0.3.5", @@ -2281,31 +2324,32 @@ } }, "node_modules/@jimp/plugins": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.2.tgz", + "integrity": "sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.1", - "@jimp/plugin-blur": "^0.16.1", - "@jimp/plugin-circle": "^0.16.1", - "@jimp/plugin-color": "^0.16.1", - "@jimp/plugin-contain": "^0.16.1", - "@jimp/plugin-cover": "^0.16.1", - "@jimp/plugin-crop": "^0.16.1", - "@jimp/plugin-displace": "^0.16.1", - "@jimp/plugin-dither": "^0.16.1", - "@jimp/plugin-fisheye": "^0.16.1", - "@jimp/plugin-flip": "^0.16.1", - "@jimp/plugin-gaussian": "^0.16.1", - "@jimp/plugin-invert": "^0.16.1", - "@jimp/plugin-mask": "^0.16.1", - "@jimp/plugin-normalize": "^0.16.1", - "@jimp/plugin-print": "^0.16.1", - "@jimp/plugin-resize": "^0.16.1", - "@jimp/plugin-rotate": "^0.16.1", - "@jimp/plugin-scale": "^0.16.1", - "@jimp/plugin-shadow": "^0.16.1", - "@jimp/plugin-threshold": "^0.16.1", + "@jimp/plugin-blit": "^0.16.2", + "@jimp/plugin-blur": "^0.16.2", + "@jimp/plugin-circle": "^0.16.2", + "@jimp/plugin-color": "^0.16.2", + "@jimp/plugin-contain": "^0.16.2", + "@jimp/plugin-cover": "^0.16.2", + "@jimp/plugin-crop": "^0.16.2", + "@jimp/plugin-displace": "^0.16.2", + "@jimp/plugin-dither": "^0.16.2", + "@jimp/plugin-fisheye": "^0.16.2", + "@jimp/plugin-flip": "^0.16.2", + "@jimp/plugin-gaussian": "^0.16.2", + "@jimp/plugin-invert": "^0.16.2", + "@jimp/plugin-mask": "^0.16.2", + "@jimp/plugin-normalize": "^0.16.2", + "@jimp/plugin-print": "^0.16.2", + "@jimp/plugin-resize": "^0.16.2", + "@jimp/plugin-rotate": "^0.16.2", + "@jimp/plugin-scale": "^0.16.2", + "@jimp/plugin-shadow": "^0.16.2", + "@jimp/plugin-threshold": "^0.16.2", "timm": "^1.6.1" }, "peerDependencies": { @@ -2313,11 +2357,12 @@ } }, "node_modules/@jimp/png": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.2.tgz", + "integrity": "sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "pngjs": "^3.3.3" }, "peerDependencies": { @@ -2325,8 +2370,9 @@ } }, "node_modules/@jimp/tiff": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.2.tgz", + "integrity": "sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==", "dependencies": { "@babel/runtime": "^7.7.2", "utif": "^2.0.1" @@ -2336,15 +2382,16 @@ } }, "node_modules/@jimp/types": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.2.tgz", + "integrity": "sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.1", - "@jimp/gif": "^0.16.1", - "@jimp/jpeg": "^0.16.1", - "@jimp/png": "^0.16.1", - "@jimp/tiff": "^0.16.1", + "@jimp/bmp": "^0.16.2", + "@jimp/gif": "^0.16.2", + "@jimp/jpeg": "^0.16.2", + "@jimp/png": "^0.16.2", + "@jimp/tiff": "^0.16.2", "timm": "^1.6.1" }, "peerDependencies": { @@ -2462,6 +2509,12 @@ "node": ">=12" } }, + "node_modules/@nightwatch/html-reporter-template": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.1.4.tgz", + "integrity": "sha512-fVylXypRuNJbyFAwY/5H2QM1A1XVoZWis0zhiMwA5LQN0cxHzpG2aUheb+qP1EfkxhFxwSUHOcrvphFLbPA8ow==", + "dev": true + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "license": "MIT", @@ -2546,6 +2599,15 @@ "integrity": "sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==", "dev": true }, + "node_modules/@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@types/body-parser": { "version": "1.19.2", "dev": true, @@ -2642,10 +2704,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/long": { - "version": "4.0.1", - "license": "MIT" - }, "node_modules/@types/mime": { "version": "1.3.2", "dev": true, @@ -2871,6 +2929,12 @@ "dev": true, "license": "Apache-2.0" }, + "node_modules/abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "node_modules/abbrev": { "version": "1.1.1", "dev": true, @@ -2899,6 +2963,37 @@ "node": ">=0.4.0" } }, + "node_modules/acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals/node_modules/acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/acorn-import-assertions": { "version": "1.8.0", "dev": true, @@ -3055,7 +3150,8 @@ }, "node_modules/any-base": { "version": "1.1.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" }, "node_modules/anymatch": { "version": "3.1.2", @@ -3191,16 +3287,17 @@ } }, "node_modules/avsc": { - "version": "5.7.4", - "license": "MIT", + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.7.tgz", + "integrity": "sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==", "engines": { "node": ">=0.11" } }, "node_modules/axe-core": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", - "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", "dev": true, "engines": { "node": ">=4" @@ -3308,9 +3405,9 @@ } }, "node_modules/babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "dependencies": { "find-cache-dir": "^3.3.2", @@ -3535,8 +3632,9 @@ } }, "node_modules/bignumber.js": { - "version": "9.0.2", - "license": "MIT", + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==", "engines": { "node": "*" } @@ -3730,12 +3828,19 @@ "license": "ISC" }, "node_modules/bootstrap": { - "version": "4.6.1", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - }, + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], "peerDependencies": { "jquery": "1.9.1 - 3", "popper.js": "^1.16.1" @@ -3862,6 +3967,12 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "node_modules/browser-stdout": { "version": "1.3.1", "dev": true, @@ -3975,8 +4086,9 @@ } }, "node_modules/bson": { - "version": "4.6.4", - "license": "Apache-2.0", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "dependencies": { "buffer": "^5.6.0" }, @@ -4038,7 +4150,8 @@ }, "node_modules/buffer-equal": { "version": "0.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", "engines": { "node": ">=0.4.0" } @@ -4213,14 +4326,14 @@ } }, "node_modules/chromedriver": { - "version": "108.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-108.0.0.tgz", - "integrity": "sha512-/kb0rb0dlC4RfXh2BOT7RV87K6d+It3VV5YXebLzO5a8t2knNffiTE23XPJQCH+l1xmgoW8/sOX/NB9irskvOQ==", + "version": "109.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-109.0.0.tgz", + "integrity": "sha512-jdmBq11IUwfThLFiygGTZ89qbROSQI4bICQjvOVQy2Bqr1LwC+MFkhwyZp3YG99eehQbZuTlQmmfCZBfpewTNA==", "dev": true, "hasInstallScript": true, "dependencies": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.1.3", + "axios": "^1.2.1", "compare-versions": "^5.0.1", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", @@ -4591,9 +4704,9 @@ } }, "node_modules/core-js": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", - "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", + "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", "dev": true, "hasInstallScript": true, "funding": { @@ -4725,13 +4838,13 @@ "license": "MIT" }, "node_modules/css-loader": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", - "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", "dev": true, "dependencies": { "icss-utils": "^5.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.19", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", @@ -4802,6 +4915,30 @@ "node": ">=4" } }, + "node_modules/cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "node_modules/cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "dependencies": { + "cssom": "~0.3.6" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cssstyle/node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + }, "node_modules/ctph.js": { "version": "0.0.5" }, @@ -5156,6 +5293,54 @@ "node": ">=12" } }, + "node_modules/data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "dependencies": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/dateformat": { "version": "3.0.3", "dev": true, @@ -5179,6 +5364,12 @@ } } }, + "node_modules/decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "node_modules/deep-eql": { "version": "4.0.1", "dev": true, @@ -5392,7 +5583,9 @@ } }, "node_modules/dom-walk": { - "version": "0.1.2" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "node_modules/domelementtype": { "version": "2.2.0", @@ -5405,6 +5598,27 @@ ], "license": "BSD-2-Clause" }, + "node_modules/domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "dependencies": { + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/domexception/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/domhandler": { "version": "4.3.1", "dev": true, @@ -5699,12 +5913,12 @@ } }, "node_modules/eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", "dependencies": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -5723,7 +5937,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -5866,8 +6080,9 @@ } }, "node_modules/eslint/node_modules/globals": { - "version": "13.15.0", - "license": "MIT", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "dependencies": { "type-fest": "^0.20.2" }, @@ -6346,7 +6561,8 @@ }, "node_modules/file-type": { "version": "9.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==", "engines": { "node": ">=6" } @@ -6717,7 +6933,8 @@ }, "node_modules/gifwrap": { "version": "0.9.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", "dependencies": { "image-q": "^4.0.0", "omggif": "^1.0.10" @@ -6759,7 +6976,8 @@ }, "node_modules/global": { "version": "4.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "dependencies": { "min-document": "^2.19.0", "process": "^0.11.10" @@ -7524,8 +7742,9 @@ } }, "node_modules/highlight.js": { - "version": "11.5.1", - "license": "BSD-3-Clause", + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==", "engines": { "node": ">=12.0.0" } @@ -7590,6 +7809,18 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "dependencies": { + "whatwg-encoding": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/html-entities": { "version": "2.3.3", "dev": true, @@ -7713,6 +7944,20 @@ "node": ">=8.0.0" } }, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "dependencies": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/http-proxy-middleware": { "version": "2.0.4", "dev": true, @@ -7837,14 +8082,16 @@ }, "node_modules/image-q": { "version": "4.0.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", "dependencies": { "@types/node": "16.9.1" } }, "node_modules/image-q/node_modules/@types/node": { "version": "16.9.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" }, "node_modules/immediate": { "version": "3.0.6", @@ -8062,7 +8309,8 @@ }, "node_modules/is-function": { "version": "1.0.2", - "license": "MIT" + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" }, "node_modules/is-glob": { "version": "4.0.3", @@ -8117,6 +8365,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-regex": { "version": "1.1.4", "license": "MIT", @@ -8347,13 +8601,14 @@ } }, "node_modules/jimp": { - "version": "0.16.1", - "license": "MIT", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz", + "integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==", "dependencies": { "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.1", - "@jimp/plugins": "^0.16.1", - "@jimp/types": "^0.16.1", + "@jimp/custom": "^0.16.2", + "@jimp/plugins": "^0.16.2", + "@jimp/types": "^0.16.2", "regenerator-runtime": "^0.13.3" } }, @@ -8363,8 +8618,9 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" }, "node_modules/jquery": { - "version": "3.6.0", - "license": "MIT" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", + "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" }, "node_modules/js-crc": { "version": "0.2.0", @@ -8397,6 +8653,86 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dev": true, + "dependencies": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "canvas": "^2.5.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/jsdom/node_modules/tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "dependencies": { + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/jsdom/node_modules/whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dev": true, + "dependencies": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/jsesc": { "version": "3.0.2", "license": "MIT", @@ -8421,8 +8757,9 @@ "license": "MIT" }, "node_modules/json5": { - "version": "2.2.1", - "license": "MIT", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -8450,30 +8787,32 @@ } }, "node_modules/jsonwebtoken": { - "version": "8.5.1", - "license": "MIT", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "dependencies": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "engines": { - "node": ">=4", - "npm": ">=1.4.28" + "node": ">=12", + "npm": ">=6" } }, "node_modules/jsonwebtoken/node_modules/semver": { - "version": "5.7.1", - "license": "ISC", + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dependencies": { + "lru-cache": "^6.0.0" + }, "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/jsqr": { @@ -8644,7 +8983,8 @@ }, "node_modules/load-bmfont": { "version": "1.4.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", "dependencies": { "buffer-equal": "0.0.1", "mime": "^1.3.4", @@ -8779,10 +9119,6 @@ "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", "dev": true }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "license": "MIT" - }, "node_modules/lodash.isarguments": { "version": "3.1.0", "dev": true, @@ -8793,29 +9129,14 @@ "dev": true, "license": "MIT" }, - "node_modules/lodash.isboolean": { - "version": "3.0.3", - "license": "MIT" - }, "node_modules/lodash.isfinite": { "version": "3.3.2", "dev": true, "license": "MIT" }, - "node_modules/lodash.isinteger": { - "version": "4.0.4", - "license": "MIT" - }, - "node_modules/lodash.isnumber": { - "version": "3.0.3", - "license": "MIT" - }, "node_modules/lodash.isplainobject": { "version": "4.0.6", - "license": "MIT" - }, - "node_modules/lodash.isstring": { - "version": "4.0.1", + "dev": true, "license": "MIT" }, "node_modules/lodash.keys": { @@ -8832,9 +9153,11 @@ "version": "4.6.2", "license": "MIT" }, - "node_modules/lodash.once": { - "version": "4.1.1", - "license": "MIT" + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true }, "node_modules/log-symbols": { "version": "4.1.0", @@ -8916,8 +9239,9 @@ } }, "node_modules/loglevel": { - "version": "1.8.0", - "license": "MIT", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==", "engines": { "node": ">= 0.6.0" }, @@ -8935,8 +9259,9 @@ } }, "node_modules/long": { - "version": "4.0.0", - "license": "Apache-2.0" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, "node_modules/loose-envify": { "version": "1.4.0", @@ -8967,7 +9292,6 @@ }, "node_modules/lru-cache": { "version": "6.0.0", - "dev": true, "license": "ISC", "dependencies": { "yallist": "^4.0.0" @@ -9169,6 +9493,8 @@ }, "node_modules/min-document": { "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", "dependencies": { "dom-walk": "^0.1.0" } @@ -9418,9 +9744,9 @@ } }, "node_modules/moment-timezone": { - "version": "0.5.39", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.39.tgz", - "integrity": "sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w==", + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", + "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", "dependencies": { "moment": ">= 2.9.0" }, @@ -9527,12 +9853,13 @@ } }, "node_modules/nightwatch": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.5.4.tgz", - "integrity": "sha512-mlPyVDP5hmRuTl3W2HnHfM7jv23V4Pcl15QNkz5+EJUUtxmBqwYG742jvYRkN6f2XBg5L1gwco2+rDmnRuL58Q==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.10.tgz", + "integrity": "sha512-nn5HVEtETLQ8qgu7APAZKg/yXTBkMflwdmzhfywP8mZUfKk0/dRQbeDqY2RawHr/sYsFmZV6eMirlJaaQoQ7Yw==", "dev": true, "dependencies": { "@nightwatch/chai": "5.0.2", + "@nightwatch/html-reporter-template": "0.1.4", "ansi-to-html": "0.7.2", "assertion-error": "1.1.0", "boxen": "5.1.2", @@ -9545,15 +9872,17 @@ "envinfo": "7.8.1", "fs-extra": "^10.1.0", "glob": "^7.2.3", + "jsdom": "19.0.0", "lodash.clone": "3.0.3", "lodash.defaultsdeep": "4.6.1", "lodash.escape": "4.0.1", "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", "mkpath": "1.0.0", "mocha": "9.2.2", - "nightwatch-axe-verbose": "2.0.3", + "nightwatch-axe-verbose": "^2.1.0", "open": "8.4.0", "ora": "5.4.1", "selenium-webdriver": "4.6.1", @@ -9587,12 +9916,12 @@ } }, "node_modules/nightwatch-axe-verbose": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.0.3.tgz", - "integrity": "sha512-VxwYTXmdbWZ4GRxgAc0/6uZ1nDQ5/xnXUipLrxoUsLxrh9OjNmAwqlMsIlQN8o33XwbjGm+o9ikan5erYGEOFQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.1.0.tgz", + "integrity": "sha512-j31VB0wdv/HXoQWWAJsvNc9UenXzXf1u/QsvExCUDuFOMR4GRg3963wlPIxd2ME47egXsnkXPd1dl8Ozdk7XHA==", "dev": true, "dependencies": { - "axe-core": "^4.4.3" + "axe-core": "^4.6.1" } }, "node_modules/nightwatch/node_modules/glob": { @@ -9797,6 +10126,12 @@ "version": "1.4.4", "license": "MIT" }, + "node_modules/nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, "node_modules/object-assign": { "version": "4.1.1", "dev": true, @@ -9887,7 +10222,8 @@ }, "node_modules/omggif": { "version": "1.0.10", - "license": "MIT" + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" }, "node_modules/on-finished": { "version": "2.3.0", @@ -10244,15 +10580,18 @@ }, "node_modules/parse-bmfont-ascii": { "version": "1.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" }, "node_modules/parse-bmfont-binary": { "version": "1.0.6", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" }, "node_modules/parse-bmfont-xml": { "version": "1.1.4", - "license": "MIT", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", "dependencies": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.4.5" @@ -10273,7 +10612,8 @@ }, "node_modules/parse-headers": { "version": "2.0.5", - "license": "MIT" + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" }, "node_modules/parse-json": { "version": "5.2.0", @@ -10300,6 +10640,12 @@ "node": ">=0.10.0" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "node_modules/parseurl": { "version": "1.3.3", "dev": true, @@ -10418,7 +10764,8 @@ }, "node_modules/phin": { "version": "2.9.3", - "license": "MIT" + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, "node_modules/picocolors": { "version": "1.0.0", @@ -10446,7 +10793,8 @@ }, "node_modules/pixelmatch": { "version": "4.0.2", - "license": "ISC", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", "dependencies": { "pngjs": "^3.0.0" }, @@ -10515,7 +10863,8 @@ }, "node_modules/pngjs": { "version": "3.4.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", "engines": { "node": ">=4.0.0" } @@ -10550,9 +10899,9 @@ } }, "node_modules/postcss": { - "version": "8.4.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", - "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "funding": [ { @@ -10773,9 +11122,10 @@ } }, "node_modules/protobufjs": { - "version": "6.11.3", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "hasInstallScript": true, - "license": "BSD-3-Clause", "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -10787,13 +11137,11 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" }, - "bin": { - "pbjs": "bin/pbjs", - "pbts": "bin/pbts" + "engines": { + "node": ">=12.0.0" } }, "node_modules/proxy-addr": { @@ -10821,6 +11169,12 @@ "dev": true, "license": "MIT" }, + "node_modules/psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "node_modules/public-encrypt": { "version": "4.0.3", "license": "MIT", @@ -10897,6 +11251,12 @@ "node": ">=0.4.x" } }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -11287,6 +11647,18 @@ "version": "1.2.4", "license": "ISC" }, + "node_modules/saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -11891,6 +12263,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "node_modules/tapable": { "version": "2.2.1", "dev": true, @@ -12006,9 +12384,10 @@ "license": "MIT" }, "node_modules/tesseract.js": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-3.0.3.tgz", + "integrity": "sha512-eZ1+OGWvF5IMExAzIwnDf3S3kf2FeC+i4qrMTRvBSlZeHc3ONy0vCmaKmBQz6scjB6C1W2w2x0r4lCEh95qBnw==", "hasInstallScript": true, - "license": "Apache-2.0", "dependencies": { "babel-eslint": "^10.1.0", "bmp-js": "^0.1.0", @@ -12020,14 +12399,15 @@ "opencollective-postinstall": "^2.0.2", "regenerator-runtime": "^0.13.3", "resolve-url": "^0.2.1", - "tesseract.js-core": "^3.0.1", + "tesseract.js-core": "^3.0.2", "wasm-feature-detect": "^1.2.11", "zlibjs": "^0.3.1" } }, "node_modules/tesseract.js-core": { - "version": "3.0.1", - "license": "Apache License 2.0" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-3.0.2.tgz", + "integrity": "sha512-2fD76ka9nO/C616R0fq+M9Zu91DA3vEfyozp0jlxaJOBmpfeprtgRP3cqVweZh2darE1kK/DazoxZ65g7WU99Q==" }, "node_modules/tesseract.js/node_modules/file-type": { "version": "12.4.2", @@ -12094,7 +12474,8 @@ }, "node_modules/timm": { "version": "1.7.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" }, "node_modules/tiny-lr": { "version": "1.1.1", @@ -12118,11 +12499,9 @@ } }, "node_modules/tinycolor2": { - "version": "1.4.2", - "license": "MIT", - "engines": { - "node": "*" - } + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", + "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==" }, "node_modules/tmp": { "version": "0.2.1", @@ -12170,6 +12549,30 @@ "node": ">=6" } }, + "node_modules/tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "dependencies": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tough-cookie/node_modules/universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true, + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/tr46": { "version": "0.0.3", "license": "MIT" @@ -12235,7 +12638,9 @@ } }, "node_modules/ua-parser-js": { - "version": "1.0.2", + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", + "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==", "funding": [ { "type": "opencollective", @@ -12246,7 +12651,6 @@ "url": "https://paypal.me/faisalman" } ], - "license": "MIT", "engines": { "node": "*" } @@ -12409,6 +12813,16 @@ "querystring": "0.2.0" } }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "node_modules/url/node_modules/punycode": { "version": "1.3.2", "dev": true, @@ -12420,7 +12834,8 @@ }, "node_modules/utif": { "version": "2.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", "dependencies": { "pako": "^1.0.5" } @@ -12485,12 +12900,34 @@ "version": "0.99.3", "license": "MIT" }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "deprecated": "Use your platform's native performance.now() and performance.timeOrigin.", + "dev": true, + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, "node_modules/w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", "dev": true }, + "node_modules/w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "dependencies": { + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/wasm-feature-detect": { "version": "1.2.11", "license": "Apache-2.0" @@ -12866,6 +13303,27 @@ "ultron": "~1.1.0" } }, + "node_modules/whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/whatwg-url": { "version": "5.0.0", "license": "MIT", @@ -13048,7 +13506,8 @@ }, "node_modules/xhr": { "version": "2.6.0", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "dependencies": { "global": "~4.4.0", "is-function": "^1.0.1", @@ -13056,13 +13515,24 @@ "xtend": "^4.0.0" } }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, "node_modules/xml-parse-from-string": { "version": "1.0.1", - "license": "MIT" + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" }, "node_modules/xml2js": { "version": "0.4.23", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "dependencies": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" @@ -13073,11 +13543,18 @@ }, "node_modules/xmlbuilder": { "version": "11.0.1", - "license": "MIT", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", "engines": { "node": ">=4.0" } }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true + }, "node_modules/xmldom": { "version": "0.6.0", "license": "MIT", @@ -13117,7 +13594,6 @@ }, "node_modules/yallist": { "version": "4.0.0", - "dev": true, "license": "ISC" }, "node_modules/yaml": { @@ -13250,25 +13726,25 @@ "dev": true }, "@babel/core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz", - "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.12.tgz", + "integrity": "sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg==", "dev": true, "requires": { "@ampproject/remapping": "^2.1.0", "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", - "@babel/helper-compilation-targets": "^7.20.0", - "@babel/helper-module-transforms": "^7.20.2", - "@babel/helpers": "^7.20.5", - "@babel/parser": "^7.20.5", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/generator": "^7.20.7", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.20.11", + "@babel/helpers": "^7.20.7", + "@babel/parser": "^7.20.7", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.12", + "@babel/types": "^7.20.7", "convert-source-map": "^1.7.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", - "json5": "^2.2.1", + "json5": "^2.2.2", "semver": "^6.3.0" } }, @@ -13284,11 +13760,11 @@ } }, "@babel/generator": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz", - "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.7.tgz", + "integrity": "sha512-7wqMOJq8doJMZmP4ApXTzLxSr7+oO2jroJURrVEp6XShrQUObV8Tq/D0NCcoYg2uHqUrjzO0zwBjoYzelxK+sw==", "requires": { - "@babel/types": "^7.20.5", + "@babel/types": "^7.20.7", "@jridgewell/gen-mapping": "^0.3.2", "jsesc": "^2.5.1" }, @@ -13318,15 +13794,33 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.20.0", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz", - "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", "dev": true, "requires": { - "@babel/compat-data": "^7.20.0", + "@babel/compat-data": "^7.20.5", "@babel/helper-validator-option": "^7.18.6", "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", "semver": "^6.3.0" + }, + "dependencies": { + "lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "requires": { + "yallist": "^3.0.2" + } + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } } }, "@babel/helper-create-class-features-plugin": { @@ -13418,9 +13912,9 @@ } }, "@babel/helper-module-transforms": { - "version": "7.20.2", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz", - "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==", + "version": "7.20.11", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz", + "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==", "dev": true, "requires": { "@babel/helper-environment-visitor": "^7.18.9", @@ -13428,9 +13922,9 @@ "@babel/helper-simple-access": "^7.20.2", "@babel/helper-split-export-declaration": "^7.18.6", "@babel/helper-validator-identifier": "^7.19.1", - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.1", - "@babel/types": "^7.20.2" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.10", + "@babel/types": "^7.20.7" } }, "@babel/helper-optimise-call-expression": { @@ -13528,14 +14022,14 @@ } }, "@babel/helpers": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz", - "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.7.tgz", + "integrity": "sha512-PBPjs5BppzsGaxHQCDKnZ6Gd9s6xl8bBCluz3vEInLGRJmnZan4F6BYCeqtyXqkk4W5IlPmjK4JlOuZkpJ3xZA==", "dev": true, "requires": { - "@babel/template": "^7.18.10", - "@babel/traverse": "^7.20.5", - "@babel/types": "^7.20.5" + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" } }, "@babel/highlight": { @@ -13549,9 +14043,9 @@ } }, "@babel/parser": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz", - "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==" + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.7.tgz", + "integrity": "sha512-T3Z9oHybU+0vZlY9CiDSJQTD5ZapcW18ZctFMi0MOAl/4BjFF4ul7NVSARLdbGO5vDqy9eQiGTV0LtKfvCYvcg==" }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { "version": "7.18.6", @@ -14298,9 +14792,9 @@ } }, "@babel/runtime": { - "version": "7.20.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz", - "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz", + "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==", "requires": { "regenerator-runtime": "^0.13.11" } @@ -14313,36 +14807,36 @@ } }, "@babel/template": { - "version": "7.18.10", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz", - "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", "requires": { "@babel/code-frame": "^7.18.6", - "@babel/parser": "^7.18.10", - "@babel/types": "^7.18.10" + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" } }, "@babel/traverse": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz", - "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==", + "version": "7.20.12", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.12.tgz", + "integrity": "sha512-MsIbFN0u+raeja38qboyF8TIT7K0BFzz/Yd/77ta4MsUsmP2RAnidIlwq7d5HFQrH/OZJecGV6B71C4zAgpoSQ==", "requires": { "@babel/code-frame": "^7.18.6", - "@babel/generator": "^7.20.5", + "@babel/generator": "^7.20.7", "@babel/helper-environment-visitor": "^7.18.9", "@babel/helper-function-name": "^7.19.0", "@babel/helper-hoist-variables": "^7.18.6", "@babel/helper-split-export-declaration": "^7.18.6", - "@babel/parser": "^7.20.5", - "@babel/types": "^7.20.5", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", "debug": "^4.1.0", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz", - "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==", + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz", + "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==", "requires": { "@babel/helper-string-parser": "^7.19.4", "@babel/helper-validator-identifier": "^7.19.1", @@ -14355,21 +14849,21 @@ "integrity": "sha512-2ckRSsYewLAgq/s8tUW3o5gurtCNYga1f9l0egV4QlT8hgVEilQHRt18s+behmPL2M/BPBxUINaOz67u++r0wA==" }, "@codemirror/commands": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.2.tgz", - "integrity": "sha512-sO3jdX1s0pam6lIdeSJLMN3DQ6mPEbM4yLvyKkdqtmd/UDwhXA5+AwFJ89rRXm6vTeOXBsE5cAmlos/t7MJdgg==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.1.3.tgz", + "integrity": "sha512-wUw1+vb34Ultv0Q9m/OVB7yizGXgtoDbkI5f5ErM8bebwLyUYjicdhJTKhTvPTpgkv8dq/BK0lQ3K5pRf2DAJw==", "dev": true, "requires": { "@codemirror/language": "^6.0.0", - "@codemirror/state": "^6.0.0", + "@codemirror/state": "^6.2.0", "@codemirror/view": "^6.0.0", "@lezer/common": "^1.0.0" } }, "@codemirror/language": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.3.1.tgz", - "integrity": "sha512-MK+G1QKaGfSEUg9YEFaBkMBI6j1ge4VMBPZv9fDYotw7w695c42x5Ba1mmwBkesYnzYFBfte6Hh9TDcKa6xORQ==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.4.0.tgz", + "integrity": "sha512-Wzb7GnNj8vnEtbPWiOy9H0m1fBtE28kepQNGLXekU2EEZv43BF865VKITUn+NoV8OpW6gRtvm29YEhqm46927Q==", "dev": true, "requires": { "@codemirror/state": "^6.0.0", @@ -14392,15 +14886,15 @@ } }, "@codemirror/state": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.1.4.tgz", - "integrity": "sha512-g+3OJuRylV5qsXuuhrc6Cvs1NQluNioepYMM2fhnpYkNk7NgX+j0AFuevKSVKzTDmDyt9+Puju+zPdHNECzCNQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.2.0.tgz", + "integrity": "sha512-69QXtcrsc3RYtOtd+GsvczJ319udtBf1PTrr2KbLWM/e2CXUPnh0Nz9AUo8WfhSQ7GeL8dPVNUmhQVgpmuaNGA==", "dev": true }, "@codemirror/view": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.0.tgz", - "integrity": "sha512-sI3CngHQlguxAquc2oZ4sMFDgTJiCnKkRcFmw5apqcnNLvQfQtEDIlYvCVl1adJ6UV7ZUV9wOdqkeJ8kz2+5gg==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.7.3.tgz", + "integrity": "sha512-Lt+4POnhXrZFfHOdPzXEHxrzwdy7cjqYlMkOWvoFGi6/bAsjzlFfr0NY3B15B/PGx+cDFgM1hlc12wvYeZbGLw==", "dev": true, "requires": { "@codemirror/state": "^6.1.4", @@ -14413,14 +14907,14 @@ "dev": true }, "@eslint/eslintrc": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", - "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.4.1.tgz", + "integrity": "sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", "espree": "^9.4.0", - "globals": "^13.15.0", + "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", "js-yaml": "^4.1.0", @@ -14429,9 +14923,9 @@ }, "dependencies": { "globals": { - "version": "13.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz", - "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "requires": { "type-fest": "^0.20.2" } @@ -14439,9 +14933,9 @@ } }, "@humanwhocodes/config-array": { - "version": "0.11.7", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz", - "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==", + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", "requires": { "@humanwhocodes/object-schema": "^1.2.1", "debug": "^4.1.1", @@ -14459,18 +14953,22 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" }, "@jimp/bmp": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.16.2.tgz", + "integrity": "sha512-4g9vW45QfMoGhLVvaFj26h4e7cC+McHUQwyFQmNTLW4FfC1OonN9oUr2m/FEDGkTYKR7aqdXR5XUqqIkHWLaFw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "bmp-js": "^0.1.0" } }, "@jimp/core": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.16.2.tgz", + "integrity": "sha512-dp7HcyUMzjXphXYodI6PaXue+I9PXAavbb+AN+1XqFbotN22Z12DosNPEyy+UhLY/hZiQQqUkEaJHkvV31rs+w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "any-base": "^1.1.0", "buffer": "^5.2.0", "exif-parser": "^0.1.12", @@ -14484,6 +14982,8 @@ "dependencies": { "buffer": { "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -14491,6 +14991,8 @@ }, "mkdirp": { "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "requires": { "minimist": "^1.2.6" } @@ -14498,17 +15000,21 @@ } }, "@jimp/custom": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.16.2.tgz", + "integrity": "sha512-GtNwOs4hcVS2GIbqRUf42rUuX07oLB92cj7cqxZb0ZGWwcwhnmSW0TFLAkNafXmqn9ug4VTpNvcJSUdiuECVKg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/core": "^0.16.1" + "@jimp/core": "^0.16.2" } }, "@jimp/gif": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.16.2.tgz", + "integrity": "sha512-TMdyT9Q0paIKNtT7c5KzQD29CNCsI/t8ka28jMrBjEK7j5RRTvBfuoOnHv7pDJRCjCIqeUoaUSJ7QcciKic6CA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "gifwrap": "^0.9.2", "omggif": "^1.0.9" } @@ -14524,206 +15030,256 @@ } }, "@jimp/plugin-blit": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.16.2.tgz", + "integrity": "sha512-Z31rRfV80gC/r+B/bOPSVVpJEWXUV248j7MdnMOFLu4vr8DMqXVo9jYqvwU/s4LSTMAMXqm4Jg6E/jQfadPKAg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-blur": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.16.2.tgz", + "integrity": "sha512-ShkJCAzRI+1fAKPuLLgEkixpSpVmKTYaKEFROUcgmrv9AansDXGNCupchqVMTdxf8zPyW8rR1ilvG3OJobufLQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-circle": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.16.2.tgz", + "integrity": "sha512-6T4z/48F4Z5+YwAVCLOvXQcyGmo0E3WztxCz6XGQf66r4JJK78+zcCDYZFLMx0BGM0091FogNK4QniP8JaOkrA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-color": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.16.2.tgz", + "integrity": "sha512-6oBV0g0J17/7E+aTquvUsgSc85nUbUi+64tIK5eFIDzvjhlqhjGNJYlc46KJMCWIs61qRJayQoZdL/iT/iQuGQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "tinycolor2": "^1.4.1" } }, "@jimp/plugin-contain": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.16.2.tgz", + "integrity": "sha512-pLcxO3hVN3LCEhMNvpZ9B7xILHVlS433Vv16zFFJxLRqZdYvPLsc+ZzJhjAiHHuEjVblQrktHE3LGeQwGJPo0w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-cover": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.16.2.tgz", + "integrity": "sha512-gzWM7VvYeI8msyiwbUZxH+sGQEgO6Vd6adGxZ0CeKX00uQOe5lDzxb1Wjx7sHcJGz8a/5fmAuwz7rdDtpDUbkw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-crop": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.16.2.tgz", + "integrity": "sha512-qCd3hfMEE+Z2EuuyXewgXRTtKJGIerWzc1zLEJztsUkPz5i73IGgkOL+mrNutZwGaXZbm+8SwUaGb46sxAO6Tw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-displace": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.16.2.tgz", + "integrity": "sha512-6nXdvNNjCdD95v2o3/jPeur903dz08lG4Y8gmr5oL2yVv9LSSbMonoXYrR/ASesdyXqGdXJLU4NL+yZs4zUqbQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-dither": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.16.2.tgz", + "integrity": "sha512-DERpIzy21ZanMkVsD0Tdy8HQLbD1E41OuvIzaMRoW4183PA6AgGNlrQoFTyXmzjy6FTy1SxaQgTEdouInAWZ9Q==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-fisheye": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.16.2.tgz", + "integrity": "sha512-Df7PsGIwiIpQu3EygYCnaJyTfOwvwtYV3cmYJS7yFLtdiFUuod+hlSo5GkwEPLAy+QBxhUbDuUqnsWo4NQtbiQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-flip": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.16.2.tgz", + "integrity": "sha512-+2uC8ioVQUr06mnjSWraskz2L33nJHze35LkQ8ZNsIpoZLkgvfiWatqAs5bj+1jGI/9kxoCFAaT1Is0f+a4/rw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-gaussian": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.16.2.tgz", + "integrity": "sha512-2mnuDSg4ZEH8zcJig7DZZf4st/cYmQ5UYJKP76iGhZ+6JDACk6uejwAgT5xHecNhkVAaXMdCybA2eknH/9OE1w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-invert": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.16.2.tgz", + "integrity": "sha512-xFvHbVepTY/nus+6yXiYN1iq+UBRkT0MdnObbiQPstUrAsz0Imn6MWISsnAyMvcNxHGrxaxjuU777JT/esM0gg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-mask": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.16.2.tgz", + "integrity": "sha512-AbdO85xxhfgEDdxYKpUotEI9ixiCMaIpfYHD5a5O/VWeimz2kuwhcrzlHGiyq1kKAgRcl0WEneTCZAHVSyvPKA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-normalize": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.16.2.tgz", + "integrity": "sha512-+ItBWFwmB0Od7OfOtTYT1gm543PpHUgU8/DN55z83l1JqS0OomDJAe7BmCppo2405TN6YtVm/csXo7p4iWd/SQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-print": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.16.2.tgz", + "integrity": "sha512-ifTGEeJ5UZTCiqC70HMeU3iXk/vsOmhWiwVGOXSFXhFeE8ZpDWvlmBsrMYnRrJGuaaogHOIrrQPI+kCdDBSBIQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "load-bmfont": "^1.4.0" } }, "@jimp/plugin-resize": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.16.2.tgz", + "integrity": "sha512-gE4N9l6xuwzacFZ2EPCGZCJ/xR+aX2V7GdMndIl/6kYIw5/eib1SFuF9AZLvIPSFuE1FnGo8+vT0pr++SSbhYg==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-rotate": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.16.2.tgz", + "integrity": "sha512-/CTEYkR1HrgmnE0VqPhhbBARbDAfFX590LWGIpxcYIYsUUGQCadl+8Qo4UX13FH0Nt8UHEtPA+O2x08uPYg9UA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-scale": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.16.2.tgz", + "integrity": "sha512-3inuxfrlquyLaqFdiiiQNJUurR0WbvN5wAf1qcYX2LubG1AG8grayYD6H7XVoxfUGTZXh1kpmeirEYlqA2zxcw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-shadow": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.16.2.tgz", + "integrity": "sha512-Q0aIs2/L6fWMcEh9Ms73u34bT1hyUMw/oxaVoIzOLo6/E8YzCs2Bi63H0/qaPS0MQpEppI++kvosPbblABY79w==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugin-threshold": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.16.2.tgz", + "integrity": "sha512-gyOwmBgjtMPvcuyOhkP6dOGWbQdaTfhcBRN22mYeI/k/Wh/Zh1OI21F6eKLApsVRmg15MoFnkrCz64RROC34sw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1" + "@jimp/utils": "^0.16.2" } }, "@jimp/plugins": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.16.2.tgz", + "integrity": "sha512-zCvYtCgctmC0tkYEu+y+kSwSIZBsNznqJ3/3vkpzxdyjd6wCfNY5Qc/68MPrLc1lmdeGo4cOOTYHG7Vc6myzRw==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/plugin-blit": "^0.16.1", - "@jimp/plugin-blur": "^0.16.1", - "@jimp/plugin-circle": "^0.16.1", - "@jimp/plugin-color": "^0.16.1", - "@jimp/plugin-contain": "^0.16.1", - "@jimp/plugin-cover": "^0.16.1", - "@jimp/plugin-crop": "^0.16.1", - "@jimp/plugin-displace": "^0.16.1", - "@jimp/plugin-dither": "^0.16.1", - "@jimp/plugin-fisheye": "^0.16.1", - "@jimp/plugin-flip": "^0.16.1", - "@jimp/plugin-gaussian": "^0.16.1", - "@jimp/plugin-invert": "^0.16.1", - "@jimp/plugin-mask": "^0.16.1", - "@jimp/plugin-normalize": "^0.16.1", - "@jimp/plugin-print": "^0.16.1", - "@jimp/plugin-resize": "^0.16.1", - "@jimp/plugin-rotate": "^0.16.1", - "@jimp/plugin-scale": "^0.16.1", - "@jimp/plugin-shadow": "^0.16.1", - "@jimp/plugin-threshold": "^0.16.1", + "@jimp/plugin-blit": "^0.16.2", + "@jimp/plugin-blur": "^0.16.2", + "@jimp/plugin-circle": "^0.16.2", + "@jimp/plugin-color": "^0.16.2", + "@jimp/plugin-contain": "^0.16.2", + "@jimp/plugin-cover": "^0.16.2", + "@jimp/plugin-crop": "^0.16.2", + "@jimp/plugin-displace": "^0.16.2", + "@jimp/plugin-dither": "^0.16.2", + "@jimp/plugin-fisheye": "^0.16.2", + "@jimp/plugin-flip": "^0.16.2", + "@jimp/plugin-gaussian": "^0.16.2", + "@jimp/plugin-invert": "^0.16.2", + "@jimp/plugin-mask": "^0.16.2", + "@jimp/plugin-normalize": "^0.16.2", + "@jimp/plugin-print": "^0.16.2", + "@jimp/plugin-resize": "^0.16.2", + "@jimp/plugin-rotate": "^0.16.2", + "@jimp/plugin-scale": "^0.16.2", + "@jimp/plugin-shadow": "^0.16.2", + "@jimp/plugin-threshold": "^0.16.2", "timm": "^1.6.1" } }, "@jimp/png": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.16.2.tgz", + "integrity": "sha512-sFOtOSz/tzDwXEChFQ/Nxe+0+vG3Tj0eUxnZVDUG/StXE9dI8Bqmwj3MIa0EgK5s+QG3YlnDOmlPUa4JqmeYeQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/utils": "^0.16.1", + "@jimp/utils": "^0.16.2", "pngjs": "^3.3.3" } }, "@jimp/tiff": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.16.2.tgz", + "integrity": "sha512-ADcdqmtZF+U2YoaaHTzFX8D6NFpmN4WZUT0BPMerEuY7Cq8QoLYU22z2h034FrVW+Rbi1b3y04sB9iDiQAlf2w==", "requires": { "@babel/runtime": "^7.7.2", "utif": "^2.0.1" } }, "@jimp/types": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.16.2.tgz", + "integrity": "sha512-0Ue5Sq0XnDF6TirisWv5E+8uOnRcd8vRLuwocJOhF76NIlcQrz+5r2k2XWKcr3d+11n28dHLXW5TKSqrUopxhA==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/bmp": "^0.16.1", - "@jimp/gif": "^0.16.1", - "@jimp/jpeg": "^0.16.1", - "@jimp/png": "^0.16.1", - "@jimp/tiff": "^0.16.1", + "@jimp/bmp": "^0.16.2", + "@jimp/gif": "^0.16.2", + "@jimp/jpeg": "^0.16.2", + "@jimp/png": "^0.16.2", + "@jimp/tiff": "^0.16.2", "timm": "^1.6.1" } }, @@ -14819,6 +15375,12 @@ "type-detect": "4.0.8" } }, + "@nightwatch/html-reporter-template": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@nightwatch/html-reporter-template/-/html-reporter-template-0.1.4.tgz", + "integrity": "sha512-fVylXypRuNJbyFAwY/5H2QM1A1XVoZWis0zhiMwA5LQN0cxHzpG2aUheb+qP1EfkxhFxwSUHOcrvphFLbPA8ow==", + "dev": true + }, "@nodelib/fs.scandir": { "version": "2.1.5", "requires": { @@ -14880,6 +15442,12 @@ "integrity": "sha512-g697J3WxV/Zytemz8aTuKjTGYtta9+02kva3C1xc7KXB8GdbfE1akGJIsZLyY/FSh2QrnE+fiB7vmWU3XNcb6A==", "dev": true }, + "@tootallnate/once": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "dev": true + }, "@types/body-parser": { "version": "1.19.2", "dev": true, @@ -14964,9 +15532,6 @@ "version": "7.0.11", "dev": true }, - "@types/long": { - "version": "4.0.1" - }, "@types/mime": { "version": "1.3.2", "dev": true @@ -15162,6 +15727,12 @@ "version": "4.2.2", "dev": true }, + "abab": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.6.tgz", + "integrity": "sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==", + "dev": true + }, "abbrev": { "version": "1.1.1", "dev": true @@ -15179,6 +15750,30 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" }, + "acorn-globals": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", + "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "acorn-walk": "^7.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + }, + "acorn-walk": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", + "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", + "dev": true + } + } + }, "acorn-import-assertions": { "version": "1.8.0", "dev": true, @@ -15276,7 +15871,9 @@ } }, "any-base": { - "version": "1.1.0" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" }, "anymatch": { "version": "3.1.2", @@ -15366,12 +15963,14 @@ } }, "avsc": { - "version": "5.7.4" + "version": "5.7.7", + "resolved": "https://registry.npmjs.org/avsc/-/avsc-5.7.7.tgz", + "integrity": "sha512-9cYNccliXZDByFsFliVwk5GvTq058Fj513CiR4E60ndDwmuXzTJEp/Bp8FyuRmGyYupLjHLs+JA9/CBoVS4/NQ==" }, "axe-core": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.5.2.tgz", - "integrity": "sha512-u2MVsXfew5HBvjsczCv+xlwdNnB1oQR9HlAcsejZttNjKKSkeDNVwB1vMThIUIFI9GoT57Vtk8iQLwqOfAkboA==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.6.2.tgz", + "integrity": "sha512-b1WlTV8+XKLj9gZy2DZXgQiyDp9xkkoe2a6U6UbYccScq2wgH/YwCeI2/Jq2mgo0HzQxqJOjWZBLeA/mqsk5Mg==", "dev": true }, "axios": { @@ -15447,9 +16046,9 @@ } }, "babel-loader": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.0.tgz", - "integrity": "sha512-Antt61KJPinUMwHwIIz9T5zfMgevnfZkEVWYDWlG888fgdvRRGD0JTuf/fFozQnfT+uq64sk1bmdHDy/mOEWnA==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", "dev": true, "requires": { "find-cache-dir": "^3.3.2", @@ -15614,7 +16213,9 @@ "dev": true }, "bignumber.js": { - "version": "9.0.2" + "version": "9.1.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.1.1.tgz", + "integrity": "sha512-pHm4LsMJ6lzgNGVfZHjMoO8sdoRhOzOH4MLmY65Jg70bpxCKu5iOHNJyfF6OyvYw7t8Fpf35RuzUyqnQsj8Vig==" }, "binary-extensions": { "version": "2.2.0", @@ -15749,7 +16350,9 @@ "dev": true }, "bootstrap": { - "version": "4.6.1", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.6.2.tgz", + "integrity": "sha512-51Bbp/Uxr9aTuy6ca/8FbFloBUJZLHwnhTcnjIeRn2suQWsWzcuJhGjKDB5eppVte/8oCdOL3VuwxvZDUggwGQ==", "requires": {} }, "bootstrap-colorpicker": { @@ -15833,6 +16436,12 @@ "brorand": { "version": "1.1.0" }, + "browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", + "dev": true + }, "browser-stdout": { "version": "1.3.1", "dev": true @@ -15910,7 +16519,9 @@ } }, "bson": { - "version": "4.6.4", + "version": "4.7.2", + "resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz", + "integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==", "requires": { "buffer": "^5.6.0" }, @@ -15936,7 +16547,9 @@ "dev": true }, "buffer-equal": { - "version": "0.0.1" + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==" }, "buffer-equal-constant-time": { "version": "1.0.1" @@ -16044,13 +16657,13 @@ "dev": true }, "chromedriver": { - "version": "108.0.0", - "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-108.0.0.tgz", - "integrity": "sha512-/kb0rb0dlC4RfXh2BOT7RV87K6d+It3VV5YXebLzO5a8t2knNffiTE23XPJQCH+l1xmgoW8/sOX/NB9irskvOQ==", + "version": "109.0.0", + "resolved": "https://registry.npmjs.org/chromedriver/-/chromedriver-109.0.0.tgz", + "integrity": "sha512-jdmBq11IUwfThLFiygGTZ89qbROSQI4bICQjvOVQy2Bqr1LwC+MFkhwyZp3YG99eehQbZuTlQmmfCZBfpewTNA==", "dev": true, "requires": { "@testim/chrome-version": "^1.1.3", - "axios": "^1.1.3", + "axios": "^1.2.1", "compare-versions": "^5.0.1", "extract-zip": "^2.0.1", "https-proxy-agent": "^5.0.1", @@ -16297,9 +16910,9 @@ } }, "core-js": { - "version": "3.26.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.26.1.tgz", - "integrity": "sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==", + "version": "3.27.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.27.1.tgz", + "integrity": "sha512-GutwJLBChfGCpwwhbYoqfv03LAfmiz7e7D/BNxzeMxwQf10GRSzqiOjx7AmtEk+heiD/JWmBuyBPgFtx0Sg1ww==", "dev": true }, "core-js-compat": { @@ -16399,13 +17012,13 @@ "version": "4.1.1" }, "css-loader": { - "version": "6.7.2", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.2.tgz", - "integrity": "sha512-oqGbbVcBJkm8QwmnNzrFrWTnudnRZC+1eXikLJl0n4ljcfotgRifpg2a1lKy8jTrc4/d9A/ap1GFq1jDKG7J+Q==", + "version": "6.7.3", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.7.3.tgz", + "integrity": "sha512-qhOH1KlBMnZP8FzRO6YCH9UHXQhVMcEGLyNdb7Hv2cpcmJbW0YrddO+tG1ab5nT41KpHIYGsbeHqxB9xPu1pKQ==", "dev": true, "requires": { "icss-utils": "^5.1.0", - "postcss": "^8.4.18", + "postcss": "^8.4.19", "postcss-modules-extract-imports": "^3.0.0", "postcss-modules-local-by-default": "^4.0.0", "postcss-modules-scope": "^3.0.0", @@ -16444,6 +17057,29 @@ "version": "3.0.0", "dev": true }, + "cssom": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.5.0.tgz", + "integrity": "sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==", + "dev": true + }, + "cssstyle": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", + "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", + "dev": true, + "requires": { + "cssom": "~0.3.6" + }, + "dependencies": { + "cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", + "dev": true + } + } + }, "ctph.js": { "version": "0.0.5" }, @@ -16657,6 +17293,44 @@ "d3-transition": "2 - 3" } }, + "data-urls": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-3.0.2.tgz", + "integrity": "sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==", + "dev": true, + "requires": { + "abab": "^2.0.6", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^11.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-11.0.0.tgz", + "integrity": "sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "dateformat": { "version": "3.0.3", "dev": true @@ -16667,6 +17341,12 @@ "ms": "2.1.2" } }, + "decimal.js": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.4.3.tgz", + "integrity": "sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==", + "dev": true + }, "deep-eql": { "version": "4.0.1", "dev": true, @@ -16815,12 +17495,31 @@ } }, "dom-walk": { - "version": "0.1.2" + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" }, "domelementtype": { "version": "2.2.0", "dev": true }, + "domexception": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-4.0.0.tgz", + "integrity": "sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==", + "dev": true, + "requires": { + "webidl-conversions": "^7.0.0" + }, + "dependencies": { + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + } + } + }, "domhandler": { "version": "4.3.1", "dev": true, @@ -17034,12 +17733,12 @@ } }, "eslint": { - "version": "8.29.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz", - "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==", + "version": "8.31.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.31.0.tgz", + "integrity": "sha512-0tQQEVdmPZ1UtUKXjX7EMm9BlgJ08G90IhWh0PKDCb3ZLsgAOHI8fYSIzYVZej92zsgq+ft0FGsxhJ3xo2tbuA==", "requires": { - "@eslint/eslintrc": "^1.3.3", - "@humanwhocodes/config-array": "^0.11.6", + "@eslint/eslintrc": "^1.4.1", + "@humanwhocodes/config-array": "^0.11.8", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", "ajv": "^6.10.0", @@ -17058,7 +17757,7 @@ "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.15.0", + "globals": "^13.19.0", "grapheme-splitter": "^1.0.4", "ignore": "^5.2.0", "import-fresh": "^3.0.0", @@ -17115,7 +17814,9 @@ "version": "3.3.0" }, "globals": { - "version": "13.15.0", + "version": "13.19.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.19.0.tgz", + "integrity": "sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==", "requires": { "type-fest": "^0.20.2" } @@ -17461,7 +18162,9 @@ "dev": true }, "file-type": { - "version": "9.0.0" + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" }, "filelist": { "version": "1.0.4", @@ -17695,6 +18398,8 @@ }, "gifwrap": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", "requires": { "image-q": "^4.0.0", "omggif": "^1.0.10" @@ -17725,6 +18430,8 @@ }, "global": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", "requires": { "min-document": "^2.19.0", "process": "^0.11.10" @@ -18220,7 +18927,9 @@ "dev": true }, "highlight.js": { - "version": "11.5.1" + "version": "11.7.0", + "resolved": "https://registry.npmjs.org/highlight.js/-/highlight.js-11.7.0.tgz", + "integrity": "sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==" }, "hmac-drbg": { "version": "1.0.1", @@ -18273,6 +18982,15 @@ } } }, + "html-encoding-sniffer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-3.0.0.tgz", + "integrity": "sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==", + "dev": true, + "requires": { + "whatwg-encoding": "^2.0.0" + } + }, "html-entities": { "version": "2.3.3", "dev": true @@ -18354,6 +19072,17 @@ "requires-port": "^1.0.0" } }, + "http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "dev": true, + "requires": { + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" + } + }, "http-proxy-middleware": { "version": "2.0.4", "dev": true, @@ -18423,12 +19152,16 @@ }, "image-q": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", "requires": { "@types/node": "16.9.1" }, "dependencies": { "@types/node": { - "version": "16.9.1" + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" } } }, @@ -18557,7 +19290,9 @@ "dev": true }, "is-function": { - "version": "1.0.2" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" }, "is-glob": { "version": "4.0.3", @@ -18592,6 +19327,12 @@ "isobject": "^3.0.1" } }, + "is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "is-regex": { "version": "1.1.4", "requires": { @@ -18728,12 +19469,14 @@ } }, "jimp": { - "version": "0.16.1", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.16.2.tgz", + "integrity": "sha512-UpItBk81a92f8oEyoGYbO3YK4QcM0hoIyuGHmShoF9Ov63P5Qo7Q/X2xsAgnODmSuDJFOtrPtJd5GSWW4LKdOQ==", "requires": { "@babel/runtime": "^7.7.2", - "@jimp/custom": "^0.16.1", - "@jimp/plugins": "^0.16.1", - "@jimp/types": "^0.16.1", + "@jimp/custom": "^0.16.2", + "@jimp/plugins": "^0.16.2", + "@jimp/types": "^0.16.2", "regenerator-runtime": "^0.13.3" } }, @@ -18743,7 +19486,9 @@ "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" }, "jquery": { - "version": "3.6.0" + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.3.tgz", + "integrity": "sha512-bZ5Sy3YzKo9Fyc8wH2iIQK4JImJ6R0GWI9kL1/k7Z91ZBNgkRXE6U0JfHIizZbort8ZunhSI3jw9I6253ahKfg==" }, "js-crc": { "version": "0.2.0" @@ -18765,6 +19510,68 @@ "argparse": "^2.0.1" } }, + "jsdom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-19.0.0.tgz", + "integrity": "sha512-RYAyjCbxy/vri/CfnjUWJQQtZ3LKlLnDqj+9XLNnJPgEGeirZs3hllKR20re8LUZ6o1b1X4Jat+Qd26zmP41+A==", + "dev": true, + "requires": { + "abab": "^2.0.5", + "acorn": "^8.5.0", + "acorn-globals": "^6.0.0", + "cssom": "^0.5.0", + "cssstyle": "^2.3.0", + "data-urls": "^3.0.1", + "decimal.js": "^10.3.1", + "domexception": "^4.0.0", + "escodegen": "^2.0.0", + "form-data": "^4.0.0", + "html-encoding-sniffer": "^3.0.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-potential-custom-element-name": "^1.0.1", + "nwsapi": "^2.2.0", + "parse5": "6.0.1", + "saxes": "^5.0.1", + "symbol-tree": "^3.2.4", + "tough-cookie": "^4.0.0", + "w3c-hr-time": "^1.0.2", + "w3c-xmlserializer": "^3.0.0", + "webidl-conversions": "^7.0.0", + "whatwg-encoding": "^2.0.0", + "whatwg-mimetype": "^3.0.0", + "whatwg-url": "^10.0.0", + "ws": "^8.2.3", + "xml-name-validator": "^4.0.0" + }, + "dependencies": { + "tr46": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-3.0.0.tgz", + "integrity": "sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==", + "dev": true, + "requires": { + "punycode": "^2.1.1" + } + }, + "webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "dev": true + }, + "whatwg-url": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-10.0.0.tgz", + "integrity": "sha512-CLxxCmdUby142H5FZzn4D8ikO1cmypvXVQktsgosNy4a4BHrDHeciBBGZhb0bNoR5/MltoCatso+vFjjGx8t0w==", + "dev": true, + "requires": { + "tr46": "^3.0.0", + "webidl-conversions": "^7.0.0" + } + } + } + }, "jsesc": { "version": "3.0.2" }, @@ -18779,7 +19586,9 @@ "version": "1.0.1" }, "json5": { - "version": "2.2.1" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "jsonfile": { "version": "6.1.0", @@ -18795,22 +19604,23 @@ "version": "7.2.0" }, "jsonwebtoken": { - "version": "8.5.1", + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.0.tgz", + "integrity": "sha512-tuGfYXxkQGDPnLJ7SibiQgVgeDgfbPq2k2ICcbgqW8WxWLBAxKQM/ZCu/IT8SOSwmaYl4dpTFCW5xZv7YbbWUw==", "requires": { "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", + "lodash": "^4.17.21", "ms": "^2.1.1", - "semver": "^5.6.0" + "semver": "^7.3.8" }, "dependencies": { "semver": { - "version": "5.7.1" + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "requires": { + "lru-cache": "^6.0.0" + } } } }, @@ -18952,6 +19762,8 @@ }, "load-bmfont": { "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", "requires": { "buffer-equal": "0.0.1", "mime": "^1.3.4", @@ -19060,9 +19872,6 @@ "integrity": "sha512-nXEOnb/jK9g0DYMr1/Xvq6l5xMD7GDG55+GSYIYmS0G4tBk/hURD4JR9WCavs04t33WmJx9kCyp9vJ+mr4BOUw==", "dev": true }, - "lodash.includes": { - "version": "4.3.0" - }, "lodash.isarguments": { "version": "3.1.0", "dev": true @@ -19071,24 +19880,13 @@ "version": "3.0.4", "dev": true }, - "lodash.isboolean": { - "version": "3.0.3" - }, "lodash.isfinite": { "version": "3.3.2", "dev": true }, - "lodash.isinteger": { - "version": "4.0.4" - }, - "lodash.isnumber": { - "version": "3.0.3" - }, "lodash.isplainobject": { - "version": "4.0.6" - }, - "lodash.isstring": { - "version": "4.0.1" + "version": "4.0.6", + "dev": true }, "lodash.keys": { "version": "3.1.2", @@ -19102,8 +19900,11 @@ "lodash.merge": { "version": "4.6.2" }, - "lodash.once": { - "version": "4.1.1" + "lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q==", + "dev": true }, "log-symbols": { "version": "4.1.0", @@ -19153,7 +19954,9 @@ } }, "loglevel": { - "version": "1.8.0" + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.8.1.tgz", + "integrity": "sha512-tCRIJM51SHjAayKwC+QAg8hT8vg6z7GSgLJKGvzuPb1Wc+hLzqtuVLxp6/HzSPOozuK+8ErAhy7U/sVzw8Dgfg==" }, "loglevel-message-prefix": { "version": "3.0.0", @@ -19163,7 +19966,9 @@ } }, "long": { - "version": "4.0.0" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.1.tgz", + "integrity": "sha512-GKSNGeNAtw8IryjjkhZxuKB3JzlcLTwjtiQCHKvqQet81I93kXslhDQruGI/QsddO83mcDToBVy7GqGS/zYf/A==" }, "loose-envify": { "version": "1.4.0", @@ -19188,7 +19993,6 @@ }, "lru-cache": { "version": "6.0.0", - "dev": true, "requires": { "yallist": "^4.0.0" } @@ -19312,6 +20116,8 @@ }, "min-document": { "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", "requires": { "dom-walk": "^0.1.0" } @@ -19469,9 +20275,9 @@ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, "moment-timezone": { - "version": "0.5.39", - "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.39.tgz", - "integrity": "sha512-hoB6suq4ISDj7BDgctiOy6zljBsdYT0++0ZzZm9rtxIvJhIbQ3nmbgSWe7dNFGurl6/7b1OUkHlmN9JWgXVz7w==", + "version": "0.5.40", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz", + "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==", "requires": { "moment": ">= 2.9.0" } @@ -19544,12 +20350,13 @@ "version": "0.6.3" }, "nightwatch": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.5.4.tgz", - "integrity": "sha512-mlPyVDP5hmRuTl3W2HnHfM7jv23V4Pcl15QNkz5+EJUUtxmBqwYG742jvYRkN6f2XBg5L1gwco2+rDmnRuL58Q==", + "version": "2.6.10", + "resolved": "https://registry.npmjs.org/nightwatch/-/nightwatch-2.6.10.tgz", + "integrity": "sha512-nn5HVEtETLQ8qgu7APAZKg/yXTBkMflwdmzhfywP8mZUfKk0/dRQbeDqY2RawHr/sYsFmZV6eMirlJaaQoQ7Yw==", "dev": true, "requires": { "@nightwatch/chai": "5.0.2", + "@nightwatch/html-reporter-template": "0.1.4", "ansi-to-html": "0.7.2", "assertion-error": "1.1.0", "boxen": "5.1.2", @@ -19562,15 +20369,17 @@ "envinfo": "7.8.1", "fs-extra": "^10.1.0", "glob": "^7.2.3", + "jsdom": "19.0.0", "lodash.clone": "3.0.3", "lodash.defaultsdeep": "4.6.1", "lodash.escape": "4.0.1", "lodash.merge": "4.6.2", + "lodash.pick": "4.4.0", "minimatch": "3.1.2", "minimist": "1.2.6", "mkpath": "1.0.0", "mocha": "9.2.2", - "nightwatch-axe-verbose": "2.0.3", + "nightwatch-axe-verbose": "^2.1.0", "open": "8.4.0", "ora": "5.4.1", "selenium-webdriver": "4.6.1", @@ -19603,12 +20412,12 @@ } }, "nightwatch-axe-verbose": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.0.3.tgz", - "integrity": "sha512-VxwYTXmdbWZ4GRxgAc0/6uZ1nDQ5/xnXUipLrxoUsLxrh9OjNmAwqlMsIlQN8o33XwbjGm+o9ikan5erYGEOFQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nightwatch-axe-verbose/-/nightwatch-axe-verbose-2.1.0.tgz", + "integrity": "sha512-j31VB0wdv/HXoQWWAJsvNc9UenXzXf1u/QsvExCUDuFOMR4GRg3963wlPIxd2ME47egXsnkXPd1dl8Ozdk7XHA==", "dev": true, "requires": { - "axe-core": "^4.4.3" + "axe-core": "^4.6.1" } }, "no-case": { @@ -19727,6 +20536,12 @@ "nwmatcher": { "version": "1.4.4" }, + "nwsapi": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.2.tgz", + "integrity": "sha512-90yv+6538zuvUMnN+zCr8LuV6bPFdq50304114vJYJ8RDyK8D5O9Phpbd6SZWgI7PwzmmfN1upeOJlvybDSgCw==", + "dev": true + }, "object-assign": { "version": "4.1.1", "dev": true @@ -19781,7 +20596,9 @@ "dev": true }, "omggif": { - "version": "1.0.10" + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" }, "on-finished": { "version": "2.3.0", @@ -20011,13 +20828,19 @@ } }, "parse-bmfont-ascii": { - "version": "1.0.6" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" }, "parse-bmfont-binary": { - "version": "1.0.6" + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" }, "parse-bmfont-xml": { "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", "requires": { "xml-parse-from-string": "^1.0.0", "xml2js": "^0.4.5" @@ -20033,7 +20856,9 @@ } }, "parse-headers": { - "version": "2.0.5" + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" }, "parse-json": { "version": "5.2.0", @@ -20049,6 +20874,12 @@ "version": "1.0.0", "dev": true }, + "parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, "parseurl": { "version": "1.3.3", "dev": true @@ -20125,7 +20956,9 @@ } }, "phin": { - "version": "2.9.3" + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" }, "picocolors": { "version": "1.0.0", @@ -20141,6 +20974,8 @@ }, "pixelmatch": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", "requires": { "pngjs": "^3.0.0" } @@ -20184,7 +21019,9 @@ } }, "pngjs": { - "version": "3.4.0" + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" }, "popper.js": { "version": "1.16.1" @@ -20207,9 +21044,9 @@ } }, "postcss": { - "version": "8.4.19", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.19.tgz", - "integrity": "sha512-h+pbPsyhlYj6N2ozBmHhHrs9DzGmbaarbLvWipMRO7RLS+v4onj26MPFXA5OBYFxyqYhUJK456SwDcY9H2/zsA==", + "version": "8.4.21", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.21.tgz", + "integrity": "sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==", "dev": true, "requires": { "nanoid": "^3.3.4", @@ -20338,7 +21175,9 @@ } }, "protobufjs": { - "version": "6.11.3", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", + "integrity": "sha512-4ZPTPkXCdel3+L81yw3dG6+Kq3umdWKh7Dc7GW/CpNk4SX3hK58iPCWeCyhVTDrbkNeKrYNZ7EojM5WDaEWTLQ==", "requires": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", @@ -20350,9 +21189,8 @@ "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", - "@types/long": "^4.0.1", "@types/node": ">=13.7.0", - "long": "^4.0.0" + "long": "^5.0.0" } }, "proxy-addr": { @@ -20373,6 +21211,12 @@ "version": "1.1.0", "dev": true }, + "psl": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", + "integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==", + "dev": true + }, "public-encrypt": { "version": "4.0.3", "requires": { @@ -20433,6 +21277,12 @@ "version": "0.2.0", "dev": true }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==", + "dev": true + }, "queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -20688,6 +21538,15 @@ "sax": { "version": "1.2.4" }, + "saxes": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", + "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", + "dev": true, + "requires": { + "xmlchars": "^2.2.0" + } + }, "schema-utils": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", @@ -21128,6 +21987,12 @@ "supports-preserve-symlinks-flag": { "version": "1.0.0" }, + "symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true + }, "tapable": { "version": "2.2.1", "dev": true @@ -21194,7 +22059,9 @@ } }, "tesseract.js": { - "version": "3.0.2", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-3.0.3.tgz", + "integrity": "sha512-eZ1+OGWvF5IMExAzIwnDf3S3kf2FeC+i4qrMTRvBSlZeHc3ONy0vCmaKmBQz6scjB6C1W2w2x0r4lCEh95qBnw==", "requires": { "babel-eslint": "^10.1.0", "bmp-js": "^0.1.0", @@ -21206,7 +22073,7 @@ "opencollective-postinstall": "^2.0.2", "regenerator-runtime": "^0.13.3", "resolve-url": "^0.2.1", - "tesseract.js-core": "^3.0.1", + "tesseract.js-core": "^3.0.2", "wasm-feature-detect": "^1.2.11", "zlibjs": "^0.3.1" }, @@ -21217,7 +22084,9 @@ } }, "tesseract.js-core": { - "version": "3.0.1" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-3.0.2.tgz", + "integrity": "sha512-2fD76ka9nO/C616R0fq+M9Zu91DA3vEfyozp0jlxaJOBmpfeprtgRP3cqVweZh2darE1kK/DazoxZ65g7WU99Q==" }, "text-table": { "version": "0.2.0" @@ -21267,7 +22136,9 @@ } }, "timm": { - "version": "1.7.1" + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" }, "tiny-lr": { "version": "1.1.1", @@ -21291,7 +22162,9 @@ } }, "tinycolor2": { - "version": "1.4.2" + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.5.2.tgz", + "integrity": "sha512-h80m9GPFGbcLzZByXlNSEhp1gf8Dy+VX/2JCGUZsWLo7lV1mnE/XlxGYgRBoMLJh1lIDXP0EMC4RPTjlRaV+Bg==" }, "tmp": { "version": "0.2.1", @@ -21320,6 +22193,26 @@ "version": "1.1.0", "dev": true }, + "tough-cookie": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.2.tgz", + "integrity": "sha512-G9fqXWoYFZgTc2z8Q5zaHy/vJMjm+WV0AkAeHxVCQiEB1b+dGvWzFW6QV07cY5jQ5gRkeid2qIkzkxUnmoQZUQ==", + "dev": true, + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.2.0", + "url-parse": "^1.5.3" + }, + "dependencies": { + "universalify": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz", + "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==", + "dev": true + } + } + }, "tr46": { "version": "0.0.3" }, @@ -21363,7 +22256,9 @@ } }, "ua-parser-js": { - "version": "1.0.2" + "version": "1.0.32", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.32.tgz", + "integrity": "sha512-dXVsz3M4j+5tTiovFVyVqssXBu5HM47//YSOeZ9fQkdDKkfzv2v3PP1jmH6FUyPW+yCSn7aBVK1fGGKNhowdDA==" }, "uc.micro": { "version": "1.0.6" @@ -21467,11 +22362,23 @@ } } }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dev": true, + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, "utf8": { "version": "3.0.0" }, "utif": { "version": "2.0.1", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", "requires": { "pako": "^1.0.5" } @@ -21518,12 +22425,30 @@ "vkbeautify": { "version": "0.99.3" }, + "w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dev": true, + "requires": { + "browser-process-hrtime": "^1.0.0" + } + }, "w3c-keyname": { "version": "2.2.6", "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.6.tgz", "integrity": "sha512-f+fciywl1SJEniZHD6H+kUO8gOnwIr7f4ijKA6+ZvJFjeGi1r4PDLl53Ayud9O/rk64RqgoQine0feoeOU0kXg==", "dev": true }, + "w3c-xmlserializer": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-3.0.0.tgz", + "integrity": "sha512-3WFqGEgSXIyGhOmAFtlicJNMjEps8b1MG31NCA0/vOF9+nKMUW1ckhi9cnNHmf88Rzw5V+dwIwsm2C7X8k9aQg==", + "dev": true, + "requires": { + "xml-name-validator": "^4.0.0" + } + }, "wasm-feature-detect": { "version": "1.2.11" }, @@ -21774,6 +22699,21 @@ } } }, + "whatwg-encoding": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-2.0.0.tgz", + "integrity": "sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==", + "dev": true, + "requires": { + "iconv-lite": "0.6.3" + } + }, + "whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "dev": true + }, "whatwg-url": { "version": "5.0.0", "requires": { @@ -21883,6 +22823,8 @@ }, "xhr": { "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", "requires": { "global": "~4.4.0", "is-function": "^1.0.1", @@ -21890,18 +22832,36 @@ "xtend": "^4.0.0" } }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, "xml-parse-from-string": { - "version": "1.0.1" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" }, "xml2js": { "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", "requires": { "sax": ">=0.6.0", "xmlbuilder": "~11.0.0" } }, "xmlbuilder": { - "version": "11.0.1" + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true }, "xmldom": { "version": "0.6.0" @@ -21925,8 +22885,7 @@ "dev": true }, "yallist": { - "version": "4.0.0", - "dev": true + "version": "4.0.0" }, "yaml": { "version": "1.10.2", diff --git a/package.json b/package.json index 54662c00..93f93dce 100644 --- a/package.json +++ b/package.json @@ -39,28 +39,28 @@ "node >= 16" ], "devDependencies": { - "@babel/core": "^7.20.5", + "@babel/core": "^7.20.12", "@babel/eslint-parser": "^7.19.1", "@babel/plugin-syntax-import-assertions": "^7.20.0", "@babel/plugin-transform-runtime": "^7.19.6", "@babel/preset-env": "^7.20.2", - "@babel/runtime": "^7.20.6", - "@codemirror/commands": "^6.1.2", - "@codemirror/language": "^6.3.1", + "@babel/runtime": "^7.20.7", + "@codemirror/commands": "^6.1.3", + "@codemirror/language": "^6.4.0", "@codemirror/search": "^6.2.3", - "@codemirror/state": "^6.1.4", - "@codemirror/view": "^6.7.0", + "@codemirror/state": "^6.2.0", + "@codemirror/view": "^6.7.3", "autoprefixer": "^10.4.13", - "babel-loader": "^9.1.0", + "babel-loader": "^9.1.2", "babel-plugin-dynamic-import-node": "^2.3.3", "babel-plugin-transform-builtin-extend": "1.1.2", - "chromedriver": "^108.0.0", + "chromedriver": "^109.0.0", "cli-progress": "^3.11.2", "colors": "^1.4.0", "copy-webpack-plugin": "^11.0.0", - "core-js": "^3.26.1", - "css-loader": "6.7.2", - "eslint": "^8.29.0", + "core-js": "^3.27.1", + "css-loader": "6.7.3", + "eslint": "^8.31.0", "grunt": "^1.5.3", "grunt-chmod": "~1.1.1", "grunt-concurrent": "^3.0.0", @@ -76,8 +76,8 @@ "imports-loader": "^4.0.1", "mini-css-extract-plugin": "2.7.2", "modify-source-webpack-plugin": "^3.0.0", - "nightwatch": "^2.5.4", - "postcss": "^8.4.19", + "nightwatch": "^2.6.10", + "postcss": "^8.4.21", "postcss-css-variables": "^0.18.0", "postcss-import": "^15.1.0", "postcss-loader": "^7.0.2", @@ -95,15 +95,15 @@ "@babel/polyfill": "^7.12.1", "@blu3r4y/lzma": "^2.3.3", "arrive": "^2.4.1", - "avsc": "^5.7.4", + "avsc": "^5.7.7", "bcryptjs": "^2.4.3", - "bignumber.js": "^9.0.2", + "bignumber.js": "^9.1.1", "blakejs": "^1.2.1", - "bootstrap": "4.6.1", + "bootstrap": "4.6.2", "bootstrap-colorpicker": "^3.4.0", "bootstrap-material-design": "^4.1.3", "browserify-zlib": "^0.2.0", - "bson": "^4.6.4", + "bson": "^4.7.2", "buffer": "^6.0.3", "cbor": "8.1.0", "chi-squared": "^1.1.0", @@ -122,28 +122,28 @@ "file-saver": "^2.0.5", "flat": "^5.0.2", "geodesy": "1.1.3", - "highlight.js": "^11.5.1", - "jimp": "^0.16.1", - "jquery": "3.6.0", + "highlight.js": "^11.7.0", + "jimp": "^0.16.2", + "jquery": "3.6.3", "js-crc": "^0.2.0", "js-sha3": "^0.8.0", "jsesc": "^3.0.2", - "json5": "^2.2.1", + "json5": "^2.2.3", "jsonpath-plus": "^7.2.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", "jsqr": "^1.4.0", - "jsrsasign": "^10.5.23", + "jsrsasign": "^10.6.1", "kbpgp": "2.1.15", "libbzip2-wasm": "0.0.4", "libyara-wasm": "^1.2.1", "lodash": "^4.17.21", - "loglevel": "^1.8.0", + "loglevel": "^1.8.1", "loglevel-message-prefix": "^3.0.0", "lz-string": "^1.4.4", "lz4js": "^0.2.0", "markdown-it": "^13.0.1", "moment": "^2.29.4", - "moment-timezone": "^0.5.39", + "moment-timezone": "^0.5.40", "ngeohash": "^0.6.3", "node-forge": "^1.3.1", "node-md6": "^0.1.0", @@ -155,7 +155,7 @@ "path": "^0.12.7", "popper.js": "^1.16.1", "process": "^0.11.10", - "protobufjs": "^6.11.3", + "protobufjs": "^7.1.2", "qr-image": "^3.2.0", "reflect-metadata": "^0.1.13", "scryptsy": "^2.1.0", @@ -164,8 +164,8 @@ "split.js": "^1.6.5", "ssdeep.js": "0.0.3", "stream-browserify": "^3.0.0", - "tesseract.js": "3.0.2", - "ua-parser-js": "^1.0.2", + "tesseract.js": "3.0.3", + "ua-parser-js": "^1.0.32", "unorm": "^1.6.0", "utf8": "^3.0.0", "vkbeautify": "^0.99.3", diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index 4af09cf6..2110c60d 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -219,8 +219,8 @@ class StatusBarPanel { const button = val.closest(".cm-status-bar-select-btn"); const eolName = eolLookup[state.lineBreak]; val.textContent = eolName[0]; - button.setAttribute("title", `End of line sequence: ${eolName[1]}`); - button.setAttribute("data-original-title", `End of line sequence: ${eolName[1]}`); + button.setAttribute("title", `End of line sequence:
${eolName[1]}`); + button.setAttribute("data-original-title", `End of line sequence:
${eolName[1]}`); this.eolVal = state.lineBreak; } @@ -237,8 +237,8 @@ class StatusBarPanel { const val = this.dom.querySelector(".chr-enc-value"); const button = val.closest(".cm-status-bar-select-btn"); val.textContent = name; - button.setAttribute("title", `${this.label} character encoding: ${name}`); - button.setAttribute("data-original-title", `${this.label} character encoding: ${name}`); + button.setAttribute("title", `${this.label} character encoding:
${name}`); + button.setAttribute("data-original-title", `${this.label} character encoding:
${name}`); this.chrEncVal = chrEncVal; } @@ -341,7 +341,7 @@ class StatusBarPanel {
- + text_fields Raw Bytes
@@ -361,7 +361,7 @@ class StatusBarPanel {
- + keyboard_return
From e9d7a8363cf7b4079b672aacf6dfc176dcd0f660 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 14:38:50 +0000 Subject: [PATCH 117/188] Removed treatAsUTF8 option --- src/core/Chef.mjs | 5 ++--- src/core/Dish.mjs | 30 ++++++++++++---------------- src/core/dishTypes/DishBigNumber.mjs | 5 ++--- src/core/dishTypes/DishJSON.mjs | 5 ++--- src/core/dishTypes/DishNumber.mjs | 5 ++--- src/core/dishTypes/DishString.mjs | 5 ++--- src/core/dishTypes/DishType.mjs | 3 +-- src/web/html/index.html | 7 ------- src/web/index.js | 1 - 9 files changed, 24 insertions(+), 42 deletions(-) diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs index 140774bc..dbde3e3f 100755 --- a/src/core/Chef.mjs +++ b/src/core/Chef.mjs @@ -41,8 +41,7 @@ class Chef { log.debug("Chef baking"); const startTime = Date.now(), recipe = new Recipe(recipeConfig), - containsFc = recipe.containsFlowControl(), - notUTF8 = options && "treatAsUtf8" in options && !options.treatAsUtf8; + containsFc = recipe.containsFlowControl(); let error = false, progress = 0; @@ -75,7 +74,7 @@ class Chef { return { dish: rawDish, - result: await this.dish.get(returnType, notUTF8), + result: await this.dish.get(returnType), type: Dish.enumLookup(this.dish.type), progress: progress, duration: Date.now() - startTime, diff --git a/src/core/Dish.mjs b/src/core/Dish.mjs index 1afdef01..11b1ff9f 100755 --- a/src/core/Dish.mjs +++ b/src/core/Dish.mjs @@ -128,10 +128,9 @@ class Dish { * If running in a browser, get is asynchronous. * * @param {number} type - The data type of value, see Dish enums. - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. * @returns {* | Promise} - (Browser) A promise | (Node) value of dish in given type */ - get(type, notUTF8=false) { + get(type) { if (typeof type === "string") { type = Dish.typeEnum(type); } @@ -140,13 +139,13 @@ class Dish { // Node environment => _translate is sync if (isNodeEnvironment()) { - this._translate(type, notUTF8); + this._translate(type); return this.value; // Browser environment => _translate is async } else { return new Promise((resolve, reject) => { - this._translate(type, notUTF8) + this._translate(type) .then(() => { resolve(this.value); }) @@ -190,12 +189,11 @@ class Dish { * @Node * * @param {number} type - The data type of value, see Dish enums. - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. * @returns {Dish | Promise} - (Browser) A promise | (Node) value of dish in given type */ - presentAs(type, notUTF8=false) { + presentAs(type) { const clone = this.clone(); - return clone.get(type, notUTF8); + return clone.get(type); } @@ -414,17 +412,16 @@ class Dish { * If running in the browser, _translate is asynchronous. * * @param {number} toType - The data type of value, see Dish enums. - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. * @returns {Promise || undefined} */ - _translate(toType, notUTF8=false) { + _translate(toType) { log.debug(`Translating Dish from ${Dish.enumLookup(this.type)} to ${Dish.enumLookup(toType)}`); // Node environment => translate is sync if (isNodeEnvironment()) { this._toArrayBuffer(); this.type = Dish.ARRAY_BUFFER; - this._fromArrayBuffer(toType, notUTF8); + this._fromArrayBuffer(toType); // Browser environment => translate is async } else { @@ -486,18 +483,17 @@ class Dish { * Convert this.value to the given type from ArrayBuffer * * @param {number} toType - the Dish enum to convert to - * @param {boolean} [notUTF8=false] - Do not treat strings as UTF8. */ - _fromArrayBuffer(toType, notUTF8) { + _fromArrayBuffer(toType) { // Using 'bind' here to allow this.value to be mutated within translation functions const toTypeFunctions = { - [Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(notUTF8), - [Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(notUTF8), - [Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(notUTF8), + [Dish.STRING]: () => DishString.fromArrayBuffer.bind(this)(), + [Dish.NUMBER]: () => DishNumber.fromArrayBuffer.bind(this)(), + [Dish.HTML]: () => DishHTML.fromArrayBuffer.bind(this)(), [Dish.ARRAY_BUFFER]: () => {}, - [Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(notUTF8), - [Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(notUTF8), + [Dish.BIG_NUMBER]: () => DishBigNumber.fromArrayBuffer.bind(this)(), + [Dish.JSON]: () => DishJSON.fromArrayBuffer.bind(this)(), [Dish.FILE]: () => DishFile.fromArrayBuffer.bind(this)(), [Dish.LIST_FILE]: () => DishListFile.fromArrayBuffer.bind(this)(), [Dish.BYTE_ARRAY]: () => DishByteArray.fromArrayBuffer.bind(this)(), diff --git a/src/core/dishTypes/DishBigNumber.mjs b/src/core/dishTypes/DishBigNumber.mjs index d6f67698..3bb8122c 100644 --- a/src/core/dishTypes/DishBigNumber.mjs +++ b/src/core/dishTypes/DishBigNumber.mjs @@ -24,12 +24,11 @@ class DishBigNumber extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishBigNumber.checkForValue(this.value); try { - this.value = new BigNumber(Utils.arrayBufferToStr(this.value, !notUTF8)); + this.value = new BigNumber(Utils.arrayBufferToStr(this.value)); } catch (err) { this.value = new BigNumber(NaN); } diff --git a/src/core/dishTypes/DishJSON.mjs b/src/core/dishTypes/DishJSON.mjs index 703b0980..e1b32d64 100644 --- a/src/core/dishTypes/DishJSON.mjs +++ b/src/core/dishTypes/DishJSON.mjs @@ -22,11 +22,10 @@ class DishJSON extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishJSON.checkForValue(this.value); - this.value = JSON.parse(Utils.arrayBufferToStr(this.value, !notUTF8)); + this.value = JSON.parse(Utils.arrayBufferToStr(this.value)); } } diff --git a/src/core/dishTypes/DishNumber.mjs b/src/core/dishTypes/DishNumber.mjs index 8769a69a..e3ea31b8 100644 --- a/src/core/dishTypes/DishNumber.mjs +++ b/src/core/dishTypes/DishNumber.mjs @@ -23,11 +23,10 @@ class DishNumber extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishNumber.checkForValue(this.value); - this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value, !notUTF8)) : 0; + this.value = this.value ? parseFloat(Utils.arrayBufferToStr(this.value)) : 0; } } diff --git a/src/core/dishTypes/DishString.mjs b/src/core/dishTypes/DishString.mjs index d7768859..7de8810d 100644 --- a/src/core/dishTypes/DishString.mjs +++ b/src/core/dishTypes/DishString.mjs @@ -23,11 +23,10 @@ class DishString extends DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8) { + static fromArrayBuffer() { DishString.checkForValue(this.value); - this.value = this.value ? Utils.arrayBufferToStr(this.value, !notUTF8) : ""; + this.value = this.value ? Utils.arrayBufferToStr(this.value) : ""; } } diff --git a/src/core/dishTypes/DishType.mjs b/src/core/dishTypes/DishType.mjs index 849b5756..d89e3c0b 100644 --- a/src/core/dishTypes/DishType.mjs +++ b/src/core/dishTypes/DishType.mjs @@ -29,9 +29,8 @@ class DishType { /** * convert the given value from a ArrayBuffer - * @param {boolean} notUTF8 */ - static fromArrayBuffer(notUTF8=undefined) { + static fromArrayBuffer() { throw new Error("fromArrayBuffer has not been implemented"); } } diff --git a/src/web/html/index.html b/src/web/html/index.html index a5897840..a762ea96 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -483,13 +483,6 @@
-
- -
-
-
- -
+ +
-
-
-
-
- -
-
+
+
+
+
+
@@ -443,20 +442,6 @@
-
- - -
- -
- - -
-
- Operation error reporting (recommended) + Show errors from operations (recommended)
+
+ + +
+
- needed to position the dropup content */ +.cm-status-bar-select { + position: relative; + display: inline-block; +} + +/* Dropup content (Hidden by Default) */ +.cm-status-bar-select-content { + display: none; + position: absolute; + bottom: 20px; + right: 0; + background-color: #f1f1f1; + min-width: 200px; + box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +/* Links inside the dropup */ +.cm-status-bar-select-content a { + color: black; + padding: 2px 5px; + text-decoration: none; + display: block; +} + +/* Change color of dropup links on hover */ +.cm-status-bar-select-content a:hover { + background-color: #ddd +} + +/* Show the dropup menu on hover */ +.cm-status-bar-select:hover .cm-status-bar-select-content { + display: block; +} + +/* Change the background color of the dropup button when the dropup content is shown */ +.cm-status-bar-select:hover .cm-status-bar-select-btn { + background-color: #f1f1f1; +} diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index c06d3b8c..fa216836 100755 --- a/src/web/stylesheets/utils/_overrides.css +++ b/src/web/stylesheets/utils/_overrides.css @@ -13,7 +13,7 @@ font-family: 'Material Icons'; font-style: normal; font-weight: 400; - src: url("../static/fonts/MaterialIcons-Regular.woff2") format('woff2'); + src: url("../static/fonts/MaterialIcons-Regular.ttf") format('truetype'); } .material-icons { diff --git a/src/web/waiters/ControlsWaiter.mjs b/src/web/waiters/ControlsWaiter.mjs index 5a9533f5..426107bb 100755 --- a/src/web/waiters/ControlsWaiter.mjs +++ b/src/web/waiters/ControlsWaiter.mjs @@ -140,7 +140,7 @@ class ControlsWaiter { const params = [ includeRecipe ? ["recipe", recipeStr] : undefined, - includeInput ? ["input", Utils.escapeHtml(input)] : undefined, + includeInput && input.length ? ["input", Utils.escapeHtml(input)] : undefined, ]; const hash = params diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 664daef8..9f83b55c 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -155,12 +155,11 @@ class HighlighterWaiter { this.mouseTarget = INPUT; this.removeHighlights(); - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightOutput([{start: start, end: end}]); } } @@ -248,12 +247,11 @@ class HighlighterWaiter { this.mouseTarget !== INPUT) return; - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("input-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightOutput([{start: start, end: end}]); } } @@ -328,7 +326,6 @@ class HighlighterWaiter { removeHighlights() { document.getElementById("input-highlighter").innerHTML = ""; document.getElementById("output-highlighter").innerHTML = ""; - document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index b421d8d8..e8e71b12 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -7,9 +7,19 @@ import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWorker.js"; import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs"; -import Utils, { debounce } from "../../core/Utils.mjs"; -import { toBase64 } from "../../core/lib/Base64.mjs"; -import { isImage } from "../../core/lib/FileType.mjs"; +import Utils, {debounce} from "../../core/Utils.mjs"; +import {toBase64} from "../../core/lib/Base64.mjs"; +import {isImage} from "../../core/lib/FileType.mjs"; + +import { + EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor +} from "@codemirror/view"; +import {EditorState, Compartment} from "@codemirror/state"; +import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@codemirror/commands"; +import {bracketMatching} from "@codemirror/language"; +import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; + +import {statusBar} from "../extensions/statusBar.mjs"; /** @@ -27,6 +37,9 @@ class InputWaiter { this.app = app; this.manager = manager; + this.inputTextEl = document.getElementById("input-text"); + this.initEditor(); + // Define keys that don't change the input so we don't have to autobake when they are pressed this.badKeys = [ 16, // Shift @@ -61,6 +74,135 @@ class InputWaiter { } } + /** + * Sets up the CodeMirror Editor and returns the view + */ + initEditor() { + this.inputEditorConf = { + eol: new Compartment, + lineWrapping: new Compartment + }; + + const initialState = EditorState.create({ + doc: null, + extensions: [ + history(), + highlightSpecialChars({render: this.renderSpecialChar}), + drawSelection(), + rectangularSelection(), + crosshairCursor(), + bracketMatching(), + highlightSelectionMatches(), + search({top: true}), + statusBar(this.inputEditorConf), + this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), + this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + EditorState.allowMultipleSelections.of(true), + keymap.of([ + // Explicitly insert a tab rather than indenting the line + { key: "Tab", run: insertTab }, + // Explicitly insert a new line (using the current EOL char) rather + // than messing around with indenting, which does not respect EOL chars + { key: "Enter", run: insertNewline }, + ...historyKeymap, + ...defaultKeymap, + ...searchKeymap + ]), + ] + }); + + this.inputEditorView = new EditorView({ + state: initialState, + parent: this.inputTextEl + }); + } + + /** + * Override for rendering special characters. + * Should mirror the toDOM function in + * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 + * But reverts the replacement of line feeds with newline control pictures. + * @param {number} code + * @param {string} desc + * @param {string} placeholder + * @returns {element} + */ + renderSpecialChar(code, desc, placeholder) { + const s = document.createElement("span"); + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. + s.textContent = code === 0x0a ? "\u240a" : placeholder; + s.title = desc; + s.setAttribute("aria-label", desc); + s.className = "cm-specialChar"; + return s; + } + + /** + * Handler for EOL Select clicks + * Sets the line separator + * @param {Event} e + */ + eolSelectClick(e) { + e.preventDefault(); + + const eolLookup = { + "LF": "\u000a", + "VT": "\u000b", + "FF": "\u000c", + "CR": "\u000d", + "CRLF": "\u000d\u000a", + "NEL": "\u0085", + "LS": "\u2028", + "PS": "\u2029" + }; + const eolval = eolLookup[e.target.getAttribute("data-val")]; + const oldInputVal = this.getInput(); + + // Update the EOL value + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval)) + }); + + // Reset the input so that lines are recalculated, preserving the old EOL values + this.setInput(oldInputVal); + } + + /** + * Sets word wrap on the input editor + * @param {boolean} wrap + */ + setWordWrap(wrap) { + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.lineWrapping.reconfigure( + wrap ? EditorView.lineWrapping : [] + ) + }); + } + + /** + * Gets the value of the current input + * @returns {string} + */ + getInput() { + const doc = this.inputEditorView.state.doc; + const eol = this.inputEditorView.state.lineBreak; + return doc.sliceString(0, doc.length, eol); + } + + /** + * Sets the value of the current input + * @param {string} data + */ + setInput(data) { + this.inputEditorView.dispatch({ + changes: { + from: 0, + to: this.inputEditorView.state.doc.length, + insert: data + } + }); + } + /** * Calculates the maximum number of tabs to display */ @@ -339,10 +481,8 @@ class InputWaiter { const activeTab = this.manager.tabs.getActiveInputTab(); if (inputData.inputNum !== activeTab) return; - const inputText = document.getElementById("input-text"); - if (typeof inputData.input === "string") { - inputText.value = inputData.input; + this.setInput(inputData.input); const fileOverlay = document.getElementById("input-file"), fileName = document.getElementById("input-file-name"), fileSize = document.getElementById("input-file-size"), @@ -355,17 +495,11 @@ class InputWaiter { fileType.textContent = ""; fileLoaded.textContent = ""; - inputText.style.overflow = "auto"; - inputText.classList.remove("blur"); - inputText.scroll(0, 0); - - const lines = inputData.input.length < (this.app.options.ioDisplayThreshold * 1024) ? - inputData.input.count("\n") + 1 : null; - this.setInputInfo(inputData.input.length, lines); + this.inputTextEl.classList.remove("blur"); // Set URL to current input const inputStr = toBase64(inputData.input, "A-Za-z0-9+/"); - if (inputStr.length > 0 && inputStr.length <= 68267) { + if (inputStr.length >= 0 && inputStr.length <= 68267) { this.setUrl({ includeInput: true, input: inputStr @@ -414,7 +548,6 @@ class InputWaiter { fileLoaded.textContent = inputData.progress + "%"; } - this.setInputInfo(inputData.size, null); this.displayFilePreview(inputData); if (!silent) window.dispatchEvent(this.manager.statechange); @@ -488,12 +621,10 @@ class InputWaiter { */ displayFilePreview(inputData) { const activeTab = this.manager.tabs.getActiveInputTab(), - input = inputData.input, - inputText = document.getElementById("input-text"); + input = inputData.input; if (inputData.inputNum !== activeTab) return; - inputText.style.overflow = "hidden"; - inputText.classList.add("blur"); - inputText.value = Utils.printable(Utils.arrayBufferToStr(input.slice(0, 4096))); + this.inputTextEl.classList.add("blur"); + this.setInput(Utils.arrayBufferToStr(input.slice(0, 4096))); this.renderFileThumb(); @@ -576,7 +707,7 @@ class InputWaiter { */ async getInputValue(inputNum) { return await new Promise(resolve => { - this.getInput(inputNum, false, r => { + this.getInputFromWorker(inputNum, false, r => { resolve(r.data); }); }); @@ -590,7 +721,7 @@ class InputWaiter { */ async getInputObj(inputNum) { return await new Promise(resolve => { - this.getInput(inputNum, true, r => { + this.getInputFromWorker(inputNum, true, r => { resolve(r.data); }); }); @@ -604,7 +735,7 @@ class InputWaiter { * @param {Function} callback - The callback to execute when the input is returned * @returns {ArrayBuffer | string | object} */ - getInput(inputNum, getObj, callback) { + getInputFromWorker(inputNum, getObj, callback) { const id = this.callbackID++; this.callbacks[id] = callback; @@ -647,29 +778,6 @@ class InputWaiter { }); } - - /** - * Displays information about the input. - * - * @param {number} length - The length of the current input string - * @param {number} lines - The number of the lines in the current input string - */ - setInputInfo(length, lines) { - let width = length.toString().length.toLocaleString(); - width = width < 2 ? 2 : width; - - const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); - let msg = "length: " + lengthStr; - - if (typeof lines === "number") { - const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); - msg += "
lines: " + linesStr; - } - - document.getElementById("input-info").innerHTML = msg; - - } - /** * Handler for input change events. * Debounces the input so we don't call autobake too often. @@ -696,17 +804,13 @@ class InputWaiter { // Remove highlighting from input and output panes as the offsets might be different now this.manager.highlighter.removeHighlights(); - const textArea = document.getElementById("input-text"); - const value = (textArea.value !== undefined) ? textArea.value : ""; + const value = this.getInput(); const activeTab = this.manager.tabs.getActiveInputTab(); this.app.progress = 0; - const lines = value.length < (this.app.options.ioDisplayThreshold * 1024) ? - (value.count("\n") + 1) : null; - this.setInputInfo(value.length, lines); this.updateInputValue(activeTab, value); - this.manager.tabs.updateInputTabHeader(activeTab, value.replace(/[\n\r]/g, "").slice(0, 100)); + this.manager.tabs.updateInputTabHeader(activeTab, value.slice(0, 100).replace(/[\n\r]/g, "")); if (e && this.badKeys.indexOf(e.keyCode) < 0) { // Fire the statechange event as the input has been modified @@ -714,62 +818,6 @@ class InputWaiter { } } - /** - * Handler for input paste events - * Checks that the size of the input is below the display limit, otherwise treats it as a file/blob - * - * @param {event} e - */ - async inputPaste(e) { - e.preventDefault(); - e.stopPropagation(); - - const self = this; - /** - * Triggers the input file/binary data overlay - * - * @param {string} pastedData - */ - function triggerOverlay(pastedData) { - const file = new File([pastedData], "PastedData", { - type: "text/plain", - lastModified: Date.now() - }); - - self.loadUIFiles([file]); - } - - const pastedData = e.clipboardData.getData("Text"); - const inputText = document.getElementById("input-text"); - const selStart = inputText.selectionStart; - const selEnd = inputText.selectionEnd; - const startVal = inputText.value.slice(0, selStart); - const endVal = inputText.value.slice(selEnd); - const val = startVal + pastedData + endVal; - - if (val.length >= (this.app.options.ioDisplayThreshold * 1024)) { - // Data too large to display, use overlay - triggerOverlay(val); - return false; - } else if (await this.preserveCarriageReturns(val)) { - // Data contains a carriage return and the user doesn't wish to edit it, use overlay - // We check this in a separate condition to make sure it is not run unless absolutely - // necessary. - triggerOverlay(val); - return false; - } else { - // Pasting normally fires the inputChange() event before - // changing the value, so instead change it here ourselves - // and manually fire inputChange() - inputText.value = val; - inputText.setSelectionRange(selStart + pastedData.length, selStart + pastedData.length); - // Don't debounce here otherwise the keyup event for the Ctrl key will cancel an autobake - // (at least for large inputs) - this.inputChange(e, true); - } - } - - /** * Handler for input dragover events. * Gives the user a visual cue to show that items can be dropped here. @@ -818,7 +866,7 @@ class InputWaiter { if (text) { // Append the text to the current input and fire inputChange() - document.getElementById("input-text").value += text; + this.setInput(this.getInput() + text); this.inputChange(e); return; } @@ -843,44 +891,6 @@ class InputWaiter { } } - /** - * Checks if an input contains carriage returns. - * If a CR is detected, checks if the preserve CR option has been set, - * and if not, asks the user for their preference. - * - * @param {string} input - The input to be checked - * @returns {boolean} - If true, the input contains a CR which should be - * preserved, so display an overlay so it can't be edited - */ - async preserveCarriageReturns(input) { - if (input.indexOf("\r") < 0) return false; - - const optionsStr = "This behaviour can be changed in the
Options pane"; - const preserveStr = `A carriage return (\\r, 0x0d) was detected in your input. To preserve it, editing has been disabled.
${optionsStr}`; - const dontPreserveStr = `A carriage return (\\r, 0x0d) was detected in your input. It has not been preserved.
${optionsStr}`; - - switch (this.app.options.preserveCR) { - case "always": - this.app.alert(preserveStr, 6000); - return true; - case "never": - this.app.alert(dontPreserveStr, 6000); - return false; - } - - // Only preserve for high-entropy inputs - const data = Utils.strToArrayBuffer(input); - const entropy = Utils.calculateShannonEntropy(data); - - if (entropy > 6) { - this.app.alert(preserveStr, 6000); - return true; - } - - this.app.alert(dontPreserveStr, 6000); - return false; - } - /** * Load files from the UI into the inputWorker * @@ -1080,6 +1090,9 @@ class InputWaiter { this.manager.worker.setupChefWorker(); this.addInput(true); this.bakeAll(); + + // Fire the statechange event as the input has been modified + window.dispatchEvent(this.manager.statechange); } /** diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 5ef517d4..52b81ab4 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -53,6 +53,9 @@ class OptionsWaiter { selects[i].selectedIndex = 0; } } + + // Initialise options + this.setWordWrap(); } @@ -136,14 +139,13 @@ class OptionsWaiter { * Sets or unsets word wrap on the input and output depending on the wordWrap option value. */ setWordWrap() { - document.getElementById("input-text").classList.remove("word-wrap"); + this.manager.input.setWordWrap(this.app.options.wordWrap); document.getElementById("output-text").classList.remove("word-wrap"); document.getElementById("output-html").classList.remove("word-wrap"); document.getElementById("input-highlighter").classList.remove("word-wrap"); document.getElementById("output-highlighter").classList.remove("word-wrap"); if (!this.app.options.wordWrap) { - document.getElementById("input-text").classList.add("word-wrap"); document.getElementById("output-text").classList.add("word-wrap"); document.getElementById("output-html").classList.add("word-wrap"); document.getElementById("input-highlighter").classList.add("word-wrap"); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 0eb6baec..8996edb0 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -1019,7 +1019,6 @@ class OutputWaiter { } document.getElementById("output-info").innerHTML = msg; - document.getElementById("input-selection-info").innerHTML = ""; document.getElementById("output-selection-info").innerHTML = ""; } @@ -1292,9 +1291,7 @@ class OutputWaiter { if (this.outputs[activeTab].data.type === "string" && active.byteLength <= this.app.options.ioDisplayThreshold * 1024) { const dishString = await this.getDishStr(this.getOutputDish(activeTab)); - if (!await this.manager.input.preserveCarriageReturns(dishString)) { - active = dishString; - } + active = dishString; } else { transferable.push(active); } diff --git a/tests/browser/nightwatch.js b/tests/browser/nightwatch.js index 41aff9b2..ba6f5204 100644 --- a/tests/browser/nightwatch.js +++ b/tests/browser/nightwatch.js @@ -82,7 +82,7 @@ module.exports = { // Enter input browser .useCss() - .setValue("#input-text", "Don't Panic.") + .setValue("#input-text", "Don't Panic.") // TODO .pause(1000) .click("#bake"); diff --git a/tests/browser/ops.js b/tests/browser/ops.js index bb18dc5d..d0933bb6 100644 --- a/tests/browser/ops.js +++ b/tests/browser/ops.js @@ -409,16 +409,16 @@ function bakeOp(browser, opName, input, args=[]) { .click("#clr-recipe") .click("#clr-io") .waitForElementNotPresent("#rec-list li.operation") - .expect.element("#input-text").to.have.property("value").that.equals(""); + .expect.element("#input-text").to.have.property("value").that.equals(""); // TODO browser .perform(function() { console.log(`Current test: ${opName}`); }) .urlHash("recipe=" + recipeConfig) - .setValue("#input-text", input) + .setValue("#input-text", input) // TODO .waitForElementPresent("#rec-list li.operation") - .expect.element("#input-text").to.have.property("value").that.equals(input); + .expect.element("#input-text").to.have.property("value").that.equals(input); // TODO browser .waitForElementVisible("#stale-indicator", 5000) From bc949b47d918fd77142c7fd22c086f5795d1a522 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 1 Jul 2022 12:01:48 +0100 Subject: [PATCH 005/188] Improved Controls CSS --- src/web/App.mjs | 1 + src/web/stylesheets/layout/_controls.css | 16 +++++----------- src/web/stylesheets/layout/_recipe.css | 1 - src/web/waiters/ControlsWaiter.mjs | 11 +++++++++++ 4 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/web/App.mjs b/src/web/App.mjs index 9d4813e0..2d45d1f1 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -589,6 +589,7 @@ class App { this.manager.recipe.adjustWidth(); this.manager.input.calcMaxTabs(); this.manager.output.calcMaxTabs(); + this.manager.controls.calcControlsHeight(); } diff --git a/src/web/stylesheets/layout/_controls.css b/src/web/stylesheets/layout/_controls.css index c410704b..1edc41b5 100755 --- a/src/web/stylesheets/layout/_controls.css +++ b/src/web/stylesheets/layout/_controls.css @@ -6,27 +6,20 @@ * @license Apache-2.0 */ -:root { - --controls-height: 75px; -} - #controls { position: absolute; width: 100%; - height: var(--controls-height); bottom: 0; - padding: 0; - padding-top: 12px; + padding: 10px 0; border-top: 1px solid var(--primary-border-colour); background-color: var(--secondary-background-colour); } #controls-content { position: relative; - left: 50%; - top: 50%; - transform: translate(-50%, -50%); - transform-origin: center left; + display: flex; + flex-flow: row nowrap; + align-items: center; } #auto-bake-label { @@ -56,6 +49,7 @@ #controls .btn { border-radius: 30px; + margin: 0; } .output-maximised .hide-on-maximised-output { diff --git a/src/web/stylesheets/layout/_recipe.css b/src/web/stylesheets/layout/_recipe.css index bd70d10f..339da074 100755 --- a/src/web/stylesheets/layout/_recipe.css +++ b/src/web/stylesheets/layout/_recipe.css @@ -7,7 +7,6 @@ */ #rec-list { - bottom: var(--controls-height); overflow: auto; } diff --git a/src/web/waiters/ControlsWaiter.mjs b/src/web/waiters/ControlsWaiter.mjs index 426107bb..2879089a 100755 --- a/src/web/waiters/ControlsWaiter.mjs +++ b/src/web/waiters/ControlsWaiter.mjs @@ -410,6 +410,17 @@ ${navigator.userAgent} } } + /** + * Calculates the height of the controls area and adjusts the recipe + * height accordingly. + */ + calcControlsHeight() { + const controls = document.getElementById("controls"), + recList = document.getElementById("rec-list"); + + recList.style.bottom = controls.clientHeight + "px"; + } + } export default ControlsWaiter; From 68733c74cc5dd5067d750c28e32708e9e7a280a0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Sat, 2 Jul 2022 19:23:03 +0100 Subject: [PATCH 006/188] Output now uses CodeMirror editor --- src/core/Utils.mjs | 2 +- src/web/Manager.mjs | 4 - src/web/extensions/statusBar.mjs | 190 ----------------- src/web/html/index.html | 7 +- src/web/stylesheets/layout/_io.css | 48 +---- src/web/utils/editorUtils.mjs | 28 +++ src/web/utils/htmlWidget.mjs | 87 ++++++++ src/web/utils/statusBar.mjs | 271 ++++++++++++++++++++++++ src/web/waiters/HighlighterWaiter.mjs | 173 ++++++---------- src/web/waiters/InputWaiter.mjs | 48 +---- src/web/waiters/OptionsWaiter.mjs | 5 +- src/web/waiters/OutputWaiter.mjs | 285 +++++++++++++++++--------- tests/browser/nightwatch.js | 4 +- tests/browser/ops.js | 8 +- 14 files changed, 665 insertions(+), 495 deletions(-) delete mode 100644 src/web/extensions/statusBar.mjs create mode 100644 src/web/utils/editorUtils.mjs create mode 100644 src/web/utils/htmlWidget.mjs create mode 100644 src/web/utils/statusBar.mjs diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 66a98c36..5f36cae9 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -424,7 +424,7 @@ class Utils { const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { - if (isWorkerEnvironment()) { + if (isWorkerEnvironment() && self && typeof self.setOption === "function") { self.setOption("attemptHighlight", false); } else if (isWebEnvironment()) { window.app.options.attemptHighlight = false; diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 08a35d75..2477bb60 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -178,7 +178,6 @@ class Manager { this.addDynamicListener(".input-filter-result", "click", this.input.filterItemClick, this.input); document.getElementById("btn-open-file").addEventListener("click", this.input.inputOpenClick.bind(this.input)); document.getElementById("btn-open-folder").addEventListener("click", this.input.folderOpenClick.bind(this.input)); - this.addDynamicListener(".eol-select a", "click", this.input.eolSelectClick, this.input); // Output @@ -192,10 +191,7 @@ class Manager { document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mouseup", this.highlighter.outputHtmlMouseup.bind(this.highlighter)); - document.getElementById("output-html").addEventListener("mousemove", this.highlighter.outputHtmlMousemove.bind(this.highlighter)); this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); - this.addMultiEventListener("#output-html", "mousedown dblclick select", this.highlighter.outputHtmlMousedown, this.highlighter); this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); this.addDynamicListener("#output-file-show-all", "click", this.output.showAllFile, this.output); this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output); diff --git a/src/web/extensions/statusBar.mjs b/src/web/extensions/statusBar.mjs deleted file mode 100644 index 8a837a51..00000000 --- a/src/web/extensions/statusBar.mjs +++ /dev/null @@ -1,190 +0,0 @@ -/** - * A Status bar extension for CodeMirror - * - * @author n1474335 [n1474335@gmail.com] - * @copyright Crown Copyright 2022 - * @license Apache-2.0 - */ - -import {showPanel} from "@codemirror/view"; - -/** - * Counts the stats of a document - * @param {element} el - * @param {Text} doc - */ -function updateStats(el, doc) { - const length = el.querySelector("#stats-length-value"), - lines = el.querySelector("#stats-lines-value"); - length.textContent = doc.length; - lines.textContent = doc.lines; -} - -/** - * Gets the current selection info - * @param {element} el - * @param {EditorState} state - * @param {boolean} selectionSet - */ -function updateSelection(el, state, selectionSet) { - const selLen = state.selection && state.selection.main ? - state.selection.main.to - state.selection.main.from : - 0; - - const selInfo = el.querySelector("#sel-info"), - curOffsetInfo = el.querySelector("#cur-offset-info"); - - if (!selectionSet) { - selInfo.style.display = "none"; - curOffsetInfo.style.display = "none"; - return; - } - - if (selLen > 0) { // Range - const start = el.querySelector("#sel-start-value"), - end = el.querySelector("#sel-end-value"), - length = el.querySelector("#sel-length-value"); - - selInfo.style.display = "inline-block"; - curOffsetInfo.style.display = "none"; - - start.textContent = state.selection.main.from; - end.textContent = state.selection.main.to; - length.textContent = state.selection.main.to - state.selection.main.from; - } else { // Position - const offset = el.querySelector("#cur-offset-value"); - - selInfo.style.display = "none"; - curOffsetInfo.style.display = "inline-block"; - - offset.textContent = state.selection.main.from; - } -} - -/** - * Gets the current character encoding of the document - * @param {element} el - * @param {EditorState} state - */ -function updateCharEnc(el, state) { - // const charenc = el.querySelector("#char-enc-value"); - // TODO - // charenc.textContent = "TODO"; -} - -/** - * Returns what the current EOL separator is set to - * @param {element} el - * @param {EditorState} state - */ -function updateEOL(el, state) { - const eolLookup = { - "\u000a": "LF", - "\u000b": "VT", - "\u000c": "FF", - "\u000d": "CR", - "\u000d\u000a": "CRLF", - "\u0085": "NEL", - "\u2028": "LS", - "\u2029": "PS" - }; - - const val = el.querySelector("#eol-value"); - val.textContent = eolLookup[state.lineBreak]; -} - -/** - * Builds the Left-hand-side widgets - * @returns {string} - */ -function constructLHS() { - return ` - abc - - - - sort - - - - - highlight_alt - \u279E - ( selected) - - - location_on - - `; -} - -/** - * Builds the Right-hand-side widgets - * Event listener set up in Manager - * @returns {string} - */ -function constructRHS() { - return ` - language - UTF-16 - - - `; -} - -/** - * A panel constructor building a panel that re-counts the stats every time the document changes. - * @param {EditorView} view - * @returns {Panel} - */ -function wordCountPanel(view) { - const dom = document.createElement("div"); - const lhs = document.createElement("div"); - const rhs = document.createElement("div"); - - dom.className = "cm-status-bar"; - lhs.innerHTML = constructLHS(); - rhs.innerHTML = constructRHS(); - - dom.appendChild(lhs); - dom.appendChild(rhs); - - updateEOL(rhs, view.state); - updateCharEnc(rhs, view.state); - updateStats(lhs, view.state.doc); - updateSelection(lhs, view.state, false); - - return { - dom, - update(update) { - updateEOL(rhs, update.state); - updateSelection(lhs, update.state, update.selectionSet); - updateCharEnc(rhs, update.state); - if (update.docChanged) { - updateStats(lhs, update.state.doc); - } - } - }; -} - -/** - * A function that build the extension that enables the panel in an editor. - * @returns {Extension} - */ -export function statusBar() { - return showPanel.of(wordCountPanel); -} diff --git a/src/web/html/index.html b/src/web/html/index.html index 3d237bdd..3eb150e5 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -191,7 +191,7 @@
    -
    +
    @@ -289,8 +289,6 @@
    -
    -
    @@ -344,8 +342,7 @@
    -
    - +
    diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index 625b81f7..ba670f3d 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -6,7 +6,8 @@ * @license Apache-2.0 */ -#input-text { +#input-text, +#output-text { position: relative; width: 100%; height: 100%; @@ -24,23 +25,6 @@ color: var(--fixed-width-font-colour); } -#output-text, -#output-html { - position: relative; - width: 100%; - height: 100%; - margin: 0; - padding: 3px; - -moz-padding-start: 3px; - -moz-padding-end: 3px; - border: none; - border-width: 0px; - resize: none; - background-color: transparent; - white-space: pre-wrap; - word-wrap: break-word; -} - #output-wrapper{ margin: 0; padding: 0; @@ -54,13 +38,6 @@ pointer-events: auto; } - -#output-html { - display: none; - overflow-y: auto; - -moz-padding-start: 1px; /* Fixes bug in Firefox */ -} - #input-tabs-wrapper #input-tabs, #output-tabs-wrapper #output-tabs { list-style: none; @@ -179,25 +156,15 @@ } #input-wrapper, -#output-wrapper, -#input-wrapper > :not(#input-text), -#output-wrapper > .textarea-wrapper > div, -#output-wrapper > .textarea-wrapper > textarea { +#output-wrapper { height: calc(100% - var(--title-height)); } #input-wrapper.show-tabs, -#input-wrapper.show-tabs > :not(#input-text), -#output-wrapper.show-tabs, -#output-wrapper.show-tabs > .textarea-wrapper > div, -#output-wrapper.show-tabs > .textarea-wrapper > textarea { +#output-wrapper.show-tabs { height: calc(100% - var(--tab-height) - var(--title-height)); } -#output-wrapper > .textarea-wrapper > #output-html { - height: 100%; -} - #show-file-overlay { height: 32px; } @@ -211,7 +178,6 @@ .textarea-wrapper textarea, .textarea-wrapper #output-text, -.textarea-wrapper #output-html, .textarea-wrapper #output-highlighter { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); @@ -477,6 +443,12 @@ /* Status bar */ +.ͼ2 .cm-panels { + background-color: var(--secondary-background-colour); + border-color: var(--secondary-border-colour); + color: var(--primary-font-colour); +} + .cm-status-bar { font-family: var(--fixed-width-font-family); font-weight: normal; diff --git a/src/web/utils/editorUtils.mjs b/src/web/utils/editorUtils.mjs new file mode 100644 index 00000000..fe6b83d4 --- /dev/null +++ b/src/web/utils/editorUtils.mjs @@ -0,0 +1,28 @@ +/** + * CodeMirror utilities that are relevant to both the input and output + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + + +/** + * Override for rendering special characters. + * Should mirror the toDOM function in + * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 + * But reverts the replacement of line feeds with newline control pictures. + * @param {number} code + * @param {string} desc + * @param {string} placeholder + * @returns {element} + */ +export function renderSpecialChar(code, desc, placeholder) { + const s = document.createElement("span"); + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. + s.textContent = code === 0x0a ? "\u240a" : placeholder; + s.title = desc; + s.setAttribute("aria-label", desc); + s.className = "cm-specialChar"; + return s; +} diff --git a/src/web/utils/htmlWidget.mjs b/src/web/utils/htmlWidget.mjs new file mode 100644 index 00000000..fbce9b49 --- /dev/null +++ b/src/web/utils/htmlWidget.mjs @@ -0,0 +1,87 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view"; + +/** + * Adds an HTML widget to the Code Mirror editor + */ +class HTMLWidget extends WidgetType { + + /** + * HTMLWidget consructor + */ + constructor(html) { + super(); + this.html = html; + } + + /** + * Builds the DOM node + * @returns {DOMNode} + */ + toDOM() { + const wrap = document.createElement("span"); + wrap.setAttribute("id", "output-html"); + wrap.innerHTML = this.html; + return wrap; + } + +} + +/** + * Decorator function to provide a set of widgets for the editor DOM + * @param {EditorView} view + * @param {string} html + * @returns {DecorationSet} + */ +function decorateHTML(view, html) { + const widgets = []; + if (html.length) { + const deco = Decoration.widget({ + widget: new HTMLWidget(html), + side: 1 + }); + widgets.push(deco.range(0)); + } + return Decoration.set(widgets); +} + + +/** + * An HTML Plugin builder + * @param {Object} htmlOutput + * @returns {ViewPlugin} + */ +export function htmlPlugin(htmlOutput) { + const plugin = ViewPlugin.fromClass( + class { + /** + * Plugin constructor + * @param {EditorView} view + */ + constructor(view) { + this.htmlOutput = htmlOutput; + this.decorations = decorateHTML(view, this.htmlOutput.html); + } + + /** + * Editor update listener + * @param {ViewUpdate} update + */ + update(update) { + if (this.htmlOutput.changed) { + this.decorations = decorateHTML(update.view, this.htmlOutput.html); + this.htmlOutput.changed = false; + } + } + }, { + decorations: v => v.decorations + } + ); + + return plugin; +} diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs new file mode 100644 index 00000000..431d8a3d --- /dev/null +++ b/src/web/utils/statusBar.mjs @@ -0,0 +1,271 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {showPanel} from "@codemirror/view"; + +/** + * A Status bar extension for CodeMirror + */ +class StatusBarPanel { + + /** + * StatusBarPanel constructor + * @param {Object} opts + */ + constructor(opts) { + this.label = opts.label; + this.bakeStats = opts.bakeStats ? opts.bakeStats : null; + this.eolHandler = opts.eolHandler; + + this.dom = this.buildDOM(); + } + + /** + * Builds the status bar DOM tree + * @returns {DOMNode} + */ + buildDOM() { + const dom = document.createElement("div"); + const lhs = document.createElement("div"); + const rhs = document.createElement("div"); + + dom.className = "cm-status-bar"; + lhs.innerHTML = this.constructLHS(); + rhs.innerHTML = this.constructRHS(); + + dom.appendChild(lhs); + dom.appendChild(rhs); + + // Event listeners + dom.addEventListener("click", this.eolSelectClick.bind(this), false); + + return dom; + } + + /** + * Handler for EOL Select clicks + * Sets the line separator + * @param {Event} e + */ + eolSelectClick(e) { + e.preventDefault(); + + const eolLookup = { + "LF": "\u000a", + "VT": "\u000b", + "FF": "\u000c", + "CR": "\u000d", + "CRLF": "\u000d\u000a", + "NEL": "\u0085", + "LS": "\u2028", + "PS": "\u2029" + }; + const eolval = eolLookup[e.target.getAttribute("data-val")]; + + // Call relevant EOL change handler + this.eolHandler(eolval); + } + + /** + * Counts the stats of a document + * @param {Text} doc + */ + updateStats(doc) { + const length = this.dom.querySelector(".stats-length-value"), + lines = this.dom.querySelector(".stats-lines-value"); + length.textContent = doc.length; + lines.textContent = doc.lines; + } + + /** + * Gets the current selection info + * @param {EditorState} state + * @param {boolean} selectionSet + */ + updateSelection(state, selectionSet) { + const selLen = state.selection && state.selection.main ? + state.selection.main.to - state.selection.main.from : + 0; + + const selInfo = this.dom.querySelector(".sel-info"), + curOffsetInfo = this.dom.querySelector(".cur-offset-info"); + + if (!selectionSet) { + selInfo.style.display = "none"; + curOffsetInfo.style.display = "none"; + return; + } + + if (selLen > 0) { // Range + const start = this.dom.querySelector(".sel-start-value"), + end = this.dom.querySelector(".sel-end-value"), + length = this.dom.querySelector(".sel-length-value"); + + selInfo.style.display = "inline-block"; + curOffsetInfo.style.display = "none"; + + start.textContent = state.selection.main.from; + end.textContent = state.selection.main.to; + length.textContent = state.selection.main.to - state.selection.main.from; + } else { // Position + const offset = this.dom.querySelector(".cur-offset-value"); + + selInfo.style.display = "none"; + curOffsetInfo.style.display = "inline-block"; + + offset.textContent = state.selection.main.from; + } + } + + /** + * Gets the current character encoding of the document + * @param {EditorState} state + */ + updateCharEnc(state) { + // const charenc = this.dom.querySelector("#char-enc-value"); + // TODO + // charenc.textContent = "TODO"; + } + + /** + * Returns what the current EOL separator is set to + * @param {EditorState} state + */ + updateEOL(state) { + const eolLookup = { + "\u000a": "LF", + "\u000b": "VT", + "\u000c": "FF", + "\u000d": "CR", + "\u000d\u000a": "CRLF", + "\u0085": "NEL", + "\u2028": "LS", + "\u2029": "PS" + }; + + const val = this.dom.querySelector(".eol-value"); + val.textContent = eolLookup[state.lineBreak]; + } + + /** + * Sets the latest bake duration + */ + updateBakeStats() { + const bakingTime = this.dom.querySelector(".baking-time-value"); + const bakingTimeInfo = this.dom.querySelector(".baking-time-info"); + + if (this.label === "Output" && + this.bakeStats && + typeof this.bakeStats.duration === "number" && + this.bakeStats.duration >= 0) { + bakingTimeInfo.style.display = "inline-block"; + bakingTime.textContent = this.bakeStats.duration; + } else { + bakingTimeInfo.style.display = "none"; + } + } + + /** + * Builds the Left-hand-side widgets + * @returns {string} + */ + constructLHS() { + return ` + + abc + + + + sort + + + + + highlight_alt + \u279E + ( selected) + + + location_on + + `; + } + + /** + * Builds the Right-hand-side widgets + * Event listener set up in Manager + * @returns {string} + */ + constructRHS() { + return ` + + + + language + UTF-16 + + + `; + } + +} + +/** + * A panel constructor factory building a panel that re-counts the stats every time the document changes. + * @param {Object} opts + * @returns {Function} + */ +function makePanel(opts) { + const sbPanel = new StatusBarPanel(opts); + + return (view) => { + sbPanel.updateEOL(view.state); + sbPanel.updateCharEnc(view.state); + sbPanel.updateBakeStats(); + sbPanel.updateStats(view.state.doc); + sbPanel.updateSelection(view.state, false); + + return { + "dom": sbPanel.dom, + update(update) { + sbPanel.updateEOL(update.state); + sbPanel.updateSelection(update.state, update.selectionSet); + sbPanel.updateCharEnc(update.state); + sbPanel.updateBakeStats(); + if (update.docChanged) { + sbPanel.updateStats(update.state.doc); + } + } + }; + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @param {Object} opts + * @returns {Extension} + */ +export function statusBar(opts) { + const panelMaker = makePanel(opts); + return showPanel.of(panelMaker); +} diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 9f83b55c..d1340165 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -176,34 +176,16 @@ class HighlighterWaiter { this.mouseTarget = OUTPUT; this.removeHighlights(); - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightInput([{start: start, end: end}]); } } - /** - * Handler for output HTML mousedown events. - * Calculates the current selection info. - * - * @param {event} e - */ - outputHtmlMousedown(e) { - this.mouseButtonDown = true; - this.mouseTarget = OUTPUT; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } - } - - /** * Handler for input mouseup events. * @@ -224,16 +206,6 @@ class HighlighterWaiter { } - /** - * Handler for output HTML mouseup events. - * - * @param {event} e - */ - outputHtmlMouseup(e) { - this.mouseButtonDown = false; - } - - /** * Handler for input mousemove events. * Calculates the current selection info, and highlights the corresponding data in the output. @@ -270,37 +242,16 @@ class HighlighterWaiter { this.mouseTarget !== OUTPUT) return; - const el = e.target; - const start = el.selectionStart; - const end = el.selectionEnd; + const sel = document.getSelection(); + const start = sel.baseOffset; + const end = sel.extentOffset; if (start !== 0 || end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(start, end); this.highlightInput([{start: start, end: end}]); } } - /** - * Handler for output HTML mousemove events. - * Calculates the current selection info. - * - * @param {event} e - */ - outputHtmlMousemove(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== OUTPUT) - return; - - const sel = this._getOutputHtmlSelectionOffsets(); - if (sel.start !== 0 || sel.end !== 0) { - document.getElementById("output-selection-info").innerHTML = this.selectionInfo(sel.start, sel.end); - } - } - - /** * Given start and end offsets, writes the HTML for the selection info element with the correct * padding. @@ -326,7 +277,6 @@ class HighlighterWaiter { removeHighlights() { document.getElementById("input-highlighter").innerHTML = ""; document.getElementById("output-highlighter").innerHTML = ""; - document.getElementById("output-selection-info").innerHTML = ""; } @@ -379,7 +329,8 @@ class HighlighterWaiter { const io = direction === "forward" ? "output" : "input"; - document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); + // TODO + // document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); this.highlight( document.getElementById(io + "-text"), document.getElementById(io + "-highlighter"), @@ -398,67 +349,67 @@ class HighlighterWaiter { * @param {number} pos.end - The end offset. */ async highlight(textarea, highlighter, pos) { - if (!this.app.options.showHighlighter) return false; - if (!this.app.options.attemptHighlight) return false; + // if (!this.app.options.showHighlighter) return false; + // if (!this.app.options.attemptHighlight) return false; - // Check if there is a carriage return in the output dish as this will not - // be displayed by the HTML textarea and will mess up highlighting offsets. - if (await this.manager.output.containsCR()) return false; + // // Check if there is a carriage return in the output dish as this will not + // // be displayed by the HTML textarea and will mess up highlighting offsets. + // if (await this.manager.output.containsCR()) return false; - const startPlaceholder = "[startHighlight]"; - const startPlaceholderRegex = /\[startHighlight\]/g; - const endPlaceholder = "[endHighlight]"; - const endPlaceholderRegex = /\[endHighlight\]/g; - let text = textarea.value; + // const startPlaceholder = "[startHighlight]"; + // const startPlaceholderRegex = /\[startHighlight\]/g; + // const endPlaceholder = "[endHighlight]"; + // const endPlaceholderRegex = /\[endHighlight\]/g; + // // let text = textarea.value; // TODO - // Put placeholders in position - // If there's only one value, select that - // If there are multiple, ignore the first one and select all others - if (pos.length === 1) { - if (pos[0].end < pos[0].start) return; - text = text.slice(0, pos[0].start) + - startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + - text.slice(pos[0].end, text.length); - } else { - // O(n^2) - Can anyone improve this without overwriting placeholders? - let result = "", - endPlaced = true; + // // Put placeholders in position + // // If there's only one value, select that + // // If there are multiple, ignore the first one and select all others + // if (pos.length === 1) { + // if (pos[0].end < pos[0].start) return; + // text = text.slice(0, pos[0].start) + + // startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + + // text.slice(pos[0].end, text.length); + // } else { + // // O(n^2) - Can anyone improve this without overwriting placeholders? + // let result = "", + // endPlaced = true; - for (let i = 0; i < text.length; i++) { - for (let j = 1; j < pos.length; j++) { - if (pos[j].end < pos[j].start) continue; - if (pos[j].start === i) { - result += startPlaceholder; - endPlaced = false; - } - if (pos[j].end === i) { - result += endPlaceholder; - endPlaced = true; - } - } - result += text[i]; - } - if (!endPlaced) result += endPlaceholder; - text = result; - } + // for (let i = 0; i < text.length; i++) { + // for (let j = 1; j < pos.length; j++) { + // if (pos[j].end < pos[j].start) continue; + // if (pos[j].start === i) { + // result += startPlaceholder; + // endPlaced = false; + // } + // if (pos[j].end === i) { + // result += endPlaceholder; + // endPlaced = true; + // } + // } + // result += text[i]; + // } + // if (!endPlaced) result += endPlaceholder; + // text = result; + // } - const cssClass = "hl1"; + // const cssClass = "hl1"; - // Remove HTML tags - text = text - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/\n/g, " ") - // Convert placeholders to tags - .replace(startPlaceholderRegex, "") - .replace(endPlaceholderRegex, "") + " "; + // // Remove HTML tags + // text = text + // .replace(/&/g, "&") + // .replace(//g, ">") + // .replace(/\n/g, " ") + // // Convert placeholders to tags + // .replace(startPlaceholderRegex, "") + // .replace(endPlaceholderRegex, "") + " "; - // Adjust width to allow for scrollbars - highlighter.style.width = textarea.clientWidth + "px"; - highlighter.innerHTML = text; - highlighter.scrollTop = textarea.scrollTop; - highlighter.scrollLeft = textarea.scrollLeft; + // // Adjust width to allow for scrollbars + // highlighter.style.width = textarea.clientWidth + "px"; + // highlighter.innerHTML = text; + // highlighter.scrollTop = textarea.scrollTop; + // highlighter.scrollLeft = textarea.scrollLeft; } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index e8e71b12..0dc44dbe 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -19,7 +19,8 @@ import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@ import {bracketMatching} from "@codemirror/language"; import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; -import {statusBar} from "../extensions/statusBar.mjs"; +import {statusBar} from "../utils/statusBar.mjs"; +import {renderSpecialChar} from "../utils/editorUtils.mjs"; /** @@ -87,14 +88,17 @@ class InputWaiter { doc: null, extensions: [ history(), - highlightSpecialChars({render: this.renderSpecialChar}), + highlightSpecialChars({render: renderSpecialChar}), drawSelection(), rectangularSelection(), crosshairCursor(), bracketMatching(), highlightSelectionMatches(), search({top: true}), - statusBar(this.inputEditorConf), + statusBar({ + label: "Input", + eolHandler: this.eolChange.bind(this) + }), this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), EditorState.allowMultipleSelections.of(true), @@ -118,44 +122,10 @@ class InputWaiter { } /** - * Override for rendering special characters. - * Should mirror the toDOM function in - * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 - * But reverts the replacement of line feeds with newline control pictures. - * @param {number} code - * @param {string} desc - * @param {string} placeholder - * @returns {element} - */ - renderSpecialChar(code, desc, placeholder) { - const s = document.createElement("span"); - // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. - s.textContent = code === 0x0a ? "\u240a" : placeholder; - s.title = desc; - s.setAttribute("aria-label", desc); - s.className = "cm-specialChar"; - return s; - } - - /** - * Handler for EOL Select clicks + * Handler for EOL change events * Sets the line separator - * @param {Event} e */ - eolSelectClick(e) { - e.preventDefault(); - - const eolLookup = { - "LF": "\u000a", - "VT": "\u000b", - "FF": "\u000c", - "CR": "\u000d", - "CRLF": "\u000d\u000a", - "NEL": "\u0085", - "LS": "\u2028", - "PS": "\u2029" - }; - const eolval = eolLookup[e.target.getAttribute("data-val")]; + eolChange(eolval) { const oldInputVal = this.getInput(); // Update the EOL value diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 52b81ab4..7d9a3e2d 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -140,14 +140,11 @@ class OptionsWaiter { */ setWordWrap() { this.manager.input.setWordWrap(this.app.options.wordWrap); - document.getElementById("output-text").classList.remove("word-wrap"); - document.getElementById("output-html").classList.remove("word-wrap"); + this.manager.output.setWordWrap(this.app.options.wordWrap); document.getElementById("input-highlighter").classList.remove("word-wrap"); document.getElementById("output-highlighter").classList.remove("word-wrap"); if (!this.app.options.wordWrap) { - document.getElementById("output-text").classList.add("word-wrap"); - document.getElementById("output-html").classList.add("word-wrap"); document.getElementById("input-highlighter").classList.add("word-wrap"); document.getElementById("output-highlighter").classList.add("word-wrap"); } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 8996edb0..496b0ac5 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -10,6 +10,18 @@ import Dish from "../../core/Dish.mjs"; import FileSaver from "file-saver"; import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; +import { + EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor +} from "@codemirror/view"; +import {EditorState, Compartment} from "@codemirror/state"; +import {defaultKeymap} from "@codemirror/commands"; +import {bracketMatching} from "@codemirror/language"; +import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; + +import {statusBar} from "../utils/statusBar.mjs"; +import {renderSpecialChar} from "../utils/editorUtils.mjs"; +import {htmlPlugin} from "../utils/htmlWidget.mjs"; + /** * Waiter to handle events related to the output */ @@ -25,12 +37,155 @@ class OutputWaiter { this.app = app; this.manager = manager; + this.outputTextEl = document.getElementById("output-text"); + // Object to contain bake statistics - used by statusBar extension + this.bakeStats = { + duration: 0 + }; + // Object to handle output HTML state - used by htmlWidget extension + this.htmlOutput = { + html: "", + changed: false + }; + this.initEditor(); + this.outputs = {}; this.zipWorker = null; this.maxTabs = this.manager.tabs.calcMaxTabs(); this.tabTimeout = null; } + /** + * Sets up the CodeMirror Editor and returns the view + */ + initEditor() { + this.outputEditorConf = { + eol: new Compartment, + lineWrapping: new Compartment + }; + + const initialState = EditorState.create({ + doc: null, + extensions: [ + EditorState.readOnly.of(true), + htmlPlugin(this.htmlOutput), + highlightSpecialChars({render: renderSpecialChar}), + drawSelection(), + rectangularSelection(), + crosshairCursor(), + bracketMatching(), + highlightSelectionMatches(), + search({top: true}), + statusBar({ + label: "Output", + bakeStats: this.bakeStats, + eolHandler: this.eolChange.bind(this) + }), + this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), + this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + EditorState.allowMultipleSelections.of(true), + keymap.of([ + ...defaultKeymap, + ...searchKeymap + ]), + ] + }); + + this.outputEditorView = new EditorView({ + state: initialState, + parent: this.outputTextEl + }); + } + + /** + * Handler for EOL change events + * Sets the line separator + */ + eolChange(eolval) { + const oldOutputVal = this.getOutput(); + + // Update the EOL value + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.eol.reconfigure(EditorState.lineSeparator.of(eolval)) + }); + + // Reset the output so that lines are recalculated, preserving the old EOL values + this.setOutput(oldOutputVal); + } + + /** + * Sets word wrap on the output editor + * @param {boolean} wrap + */ + setWordWrap(wrap) { + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.lineWrapping.reconfigure( + wrap ? EditorView.lineWrapping : [] + ) + }); + } + + /** + * Gets the value of the current output + * @returns {string} + */ + getOutput() { + const doc = this.outputEditorView.state.doc; + const eol = this.outputEditorView.state.lineBreak; + return doc.sliceString(0, doc.length, eol); + } + + /** + * Sets the value of the current output + * @param {string} data + */ + setOutput(data) { + this.outputEditorView.dispatch({ + changes: { + from: 0, + to: this.outputEditorView.state.doc.length, + insert: data + } + }); + } + + /** + * Sets the value of the current output to a rendered HTML value + * @param {string} html + */ + setHTMLOutput(html) { + this.htmlOutput.html = html; + this.htmlOutput.changed = true; + // This clears the text output, but also fires a View update which + // triggers the htmlWidget to render the HTML. + this.setOutput(""); + + // Execute script sections + const scriptElements = document.getElementById("output-html").querySelectorAll("script"); + for (let i = 0; i < scriptElements.length; i++) { + try { + eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval + } catch (err) { + log.error(err); + } + } + } + + /** + * Clears the HTML output + */ + clearHTMLOutput() { + this.htmlOutput.html = ""; + this.htmlOutput.changed = true; + // Fire a blank change to force the htmlWidget to update and remove any HTML + this.outputEditorView.dispatch({ + changes: { + from: 0, + insert: "" + } + }); + } + /** * Calculates the maximum number of tabs to display */ @@ -245,8 +400,6 @@ class OutputWaiter { activeTab = this.manager.tabs.getActiveOutputTab(); if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10); - const outputText = document.getElementById("output-text"); - const outputHtml = document.getElementById("output-html"); const outputFile = document.getElementById("output-file"); const outputHighlighter = document.getElementById("output-highlighter"); const inputHighlighter = document.getElementById("input-highlighter"); @@ -278,95 +431,68 @@ class OutputWaiter { } else if (output.status === "error") { // style the tab if it's being shown this.toggleLoader(false); - outputText.style.display = "block"; - outputText.classList.remove("blur"); - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; + this.outputTextEl.classList.remove("blur"); outputFile.style.display = "none"; outputHighlighter.display = "none"; inputHighlighter.display = "none"; + this.clearHTMLOutput(); if (output.error) { - outputText.value = output.error; + this.setOutput(output.error); } else { - outputText.value = output.data.result; + this.setOutput(output.data.result); } - outputHtml.innerHTML = ""; } else if (output.status === "baked" || output.status === "inactive") { document.querySelector("#output-loader .loading-msg").textContent = `Loading output ${inputNum}`; this.closeFile(); - let scriptElements, lines, length; if (output.data === null) { - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; - outputText.value = ""; - outputHtml.innerHTML = ""; + this.clearHTMLOutput(); + this.setOutput(""); this.toggleLoader(false); return; } + this.bakeStats.duration = output.data.duration; + switch (output.data.type) { case "html": - outputText.style.display = "none"; - outputHtml.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.style.display = "none"; inputHighlighter.style.display = "none"; - outputText.value = ""; - outputHtml.innerHTML = output.data.result; - - // Execute script sections - scriptElements = outputHtml.querySelectorAll("script"); - for (let i = 0; i < scriptElements.length; i++) { - try { - eval(scriptElements[i].innerHTML); // eslint-disable-line no-eval - } catch (err) { - log.error(err); - } - } + this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputHighlighter.display = "none"; inputHighlighter.display = "none"; - outputText.value = ""; - outputHtml.innerHTML = ""; + this.clearHTMLOutput(); + this.setOutput(""); - length = output.data.result.byteLength; this.setFile(await this.getDishBuffer(output.data.dish), activeTab); break; case "string": default: - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; - outputText.value = Utils.printable(output.data.result, true); - outputHtml.innerHTML = ""; - - lines = output.data.result.count("\n") + 1; - length = output.data.result.length; + this.clearHTMLOutput(); + this.setOutput(output.data.result); break; } this.toggleLoader(false); - if (output.data.type === "html") { - const dishStr = await this.getDishStr(output.data.dish); - length = dishStr.length; - lines = dishStr.count("\n") + 1; - } - - this.setOutputInfo(length, lines, output.data.duration); debounce(this.backgroundMagic, 50, "backgroundMagic", this, [])(); } }.bind(this)); @@ -383,14 +509,13 @@ class OutputWaiter { // Display file overlay in output area with details const fileOverlay = document.getElementById("output-file"), fileSize = document.getElementById("output-file-size"), - outputText = document.getElementById("output-text"), fileSlice = buf.slice(0, 4096); fileOverlay.style.display = "block"; fileSize.textContent = buf.byteLength.toLocaleString() + " bytes"; - outputText.classList.add("blur"); - outputText.value = Utils.printable(Utils.arrayBufferToStr(fileSlice)); + this.outputTextEl.classList.add("blur"); + this.setOutput(Utils.arrayBufferToStr(fileSlice)); } /** @@ -398,7 +523,7 @@ class OutputWaiter { */ closeFile() { document.getElementById("output-file").style.display = "none"; - document.getElementById("output-text").classList.remove("blur"); + this.outputTextEl.classList.remove("blur"); } /** @@ -466,7 +591,6 @@ class OutputWaiter { clearTimeout(this.outputLoaderTimeout); const outputLoader = document.getElementById("output-loader"), - outputElement = document.getElementById("output-text"), animation = document.getElementById("output-loader-animation"); if (value) { @@ -483,7 +607,6 @@ class OutputWaiter { // Show the loading screen this.outputLoaderTimeout = setTimeout(function() { - outputElement.disabled = true; outputLoader.style.visibility = "visible"; outputLoader.style.opacity = 1; }, 200); @@ -494,7 +617,6 @@ class OutputWaiter { animation.removeChild(this.bombeEl); } catch (err) {} }.bind(this), 500); - outputElement.disabled = false; outputLoader.style.opacity = 0; outputLoader.style.visibility = "hidden"; } @@ -717,8 +839,7 @@ class OutputWaiter { debounce(this.set, 50, "setOutput", this, [inputNum])(); - document.getElementById("output-html").scroll(0, 0); - document.getElementById("output-text").scroll(0, 0); + this.outputTextEl.scroll(0, 0); // TODO if (changeInput) { this.manager.input.changeTab(inputNum, false); @@ -996,32 +1117,6 @@ class OutputWaiter { } } - /** - * Displays information about the output. - * - * @param {number} length - The length of the current output string - * @param {number} lines - The number of the lines in the current output string - * @param {number} duration - The length of time (ms) it took to generate the output - */ - setOutputInfo(length, lines, duration) { - if (!length) return; - let width = length.toString().length; - width = width < 4 ? 4 : width; - - const lengthStr = length.toString().padStart(width, " ").replace(/ /g, " "); - const timeStr = (duration.toString() + "ms").padStart(width, " ").replace(/ /g, " "); - - let msg = "time: " + timeStr + "
    length: " + lengthStr; - - if (typeof lines === "number") { - const linesStr = lines.toString().padStart(width, " ").replace(/ /g, " "); - msg += "
    lines: " + linesStr; - } - - document.getElementById("output-info").innerHTML = msg; - document.getElementById("output-selection-info").innerHTML = ""; - } - /** * Triggers the BackgroundWorker to attempt Magic on the current output. */ @@ -1111,9 +1206,7 @@ class OutputWaiter { async displayFileSlice() { document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice..."; this.toggleLoader(true); - const outputText = document.getElementById("output-text"), - outputHtml = document.getElementById("output-html"), - outputFile = document.getElementById("output-file"), + const outputFile = document.getElementById("output-file"), outputHighlighter = document.getElementById("output-highlighter"), inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), @@ -1130,12 +1223,12 @@ class OutputWaiter { str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish).slice(sliceFrom, sliceTo)); } - outputText.classList.remove("blur"); + this.outputTextEl.classList.remove("blur"); showFileOverlay.style.display = "block"; - outputText.value = Utils.printable(str, true); + this.clearHTMLOutput(); + this.setOutput(str); - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; @@ -1149,9 +1242,7 @@ class OutputWaiter { async showAllFile() { document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash..."; this.toggleLoader(true); - const outputText = document.getElementById("output-text"), - outputHtml = document.getElementById("output-html"), - outputFile = document.getElementById("output-file"), + const outputFile = document.getElementById("output-file"), outputHighlighter = document.getElementById("output-highlighter"), inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), @@ -1164,12 +1255,12 @@ class OutputWaiter { str = Utils.arrayBufferToStr(await this.getDishBuffer(output.dish)); } - outputText.classList.remove("blur"); + this.outputTextEl.classList.remove("blur"); showFileOverlay.style.display = "none"; - outputText.value = Utils.printable(str, true); + this.clearHTMLOutput(); + this.setOutput(str); - outputText.style.display = "block"; - outputHtml.style.display = "none"; + this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; outputHighlighter.display = "block"; inputHighlighter.display = "block"; @@ -1185,7 +1276,7 @@ class OutputWaiter { showFileOverlayClick(e) { const showFileOverlay = e.target; - document.getElementById("output-text").classList.add("blur"); + this.outputTextEl.classList.add("blur"); showFileOverlay.style.display = "none"; this.set(this.manager.tabs.getActiveOutputTab()); } @@ -1212,7 +1303,7 @@ class OutputWaiter { * Handler for copy click events. * Copies the output to the clipboard */ - async copyClick() { + async copyClick() { // TODO - do we need this? const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab()); if (dish === null) { this.app.alert("Could not find data to copy. Has this output been baked yet?", 3000); diff --git a/tests/browser/nightwatch.js b/tests/browser/nightwatch.js index ba6f5204..e63a8036 100644 --- a/tests/browser/nightwatch.js +++ b/tests/browser/nightwatch.js @@ -90,7 +90,7 @@ module.exports = { browser .useCss() .waitForElementNotVisible("#stale-indicator", 1000) - .expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); + .expect.element("#output-text").to.have.property("value").that.equals("44 6f 6e 27 74 20 50 61 6e 69 63 2e"); // TODO // Clear recipe browser @@ -206,7 +206,7 @@ module.exports = { .useCss() .waitForElementVisible(".operation .op-title", 1000) .waitForElementNotVisible("#stale-indicator", 1000) - .expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/); + .expect.element("#output-text").to.have.property("value").which.matches(/[\da-f-]{36}/); // TODO browser.click("#clr-recipe"); }, diff --git a/tests/browser/ops.js b/tests/browser/ops.js index d0933bb6..64f8e036 100644 --- a/tests/browser/ops.js +++ b/tests/browser/ops.js @@ -443,9 +443,9 @@ function testOp(browser, opName, input, output, args=[]) { bakeOp(browser, opName, input, args); if (typeof output === "string") { - browser.expect.element("#output-text").to.have.property("value").that.equals(output); + browser.expect.element("#output-text").to.have.property("value").that.equals(output); // TODO } else if (output instanceof RegExp) { - browser.expect.element("#output-text").to.have.property("value").that.matches(output); + browser.expect.element("#output-text").to.have.property("value").that.matches(output); // TODO } } @@ -463,8 +463,8 @@ function testOpHtml(browser, opName, input, cssSelector, output, args=[]) { bakeOp(browser, opName, input, args); if (typeof output === "string") { - browser.expect.element("#output-html " + cssSelector).text.that.equals(output); + browser.expect.element("#output-html " + cssSelector).text.that.equals(output); // TODO } else if (output instanceof RegExp) { - browser.expect.element("#output-html " + cssSelector).text.that.matches(output); + browser.expect.element("#output-html " + cssSelector).text.that.matches(output); // TODO } } From 890f645eebd6665f9fffbebcfb200a518190f008 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Sun, 10 Jul 2022 22:01:22 +0100 Subject: [PATCH 007/188] Overhauled Highlighting to work with new editor and support multiple selections --- src/core/ChefWorker.js | 2 +- src/core/operations/ToHex.mjs | 6 +- src/web/Manager.mjs | 8 - src/web/waiters/HighlighterWaiter.mjs | 419 +++++--------------------- src/web/waiters/InputWaiter.mjs | 24 +- src/web/waiters/OutputWaiter.mjs | 33 +- src/web/waiters/TabWaiter.mjs | 3 - src/web/waiters/WorkerWaiter.mjs | 2 +- 8 files changed, 104 insertions(+), 393 deletions(-) diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index f4a17f63..d46a705d 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -186,7 +186,7 @@ async function getDishTitle(data) { * * @param {Object[]} recipeConfig * @param {string} direction - * @param {Object} pos - The position object for the highlight. + * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. */ diff --git a/src/core/operations/ToHex.mjs b/src/core/operations/ToHex.mjs index 71893105..092155a9 100644 --- a/src/core/operations/ToHex.mjs +++ b/src/core/operations/ToHex.mjs @@ -76,7 +76,7 @@ class ToHex extends Operation { } const lineSize = args[1], - len = (delim === "\r\n" ? 1 : delim.length) + commaLen; + len = delim.length + commaLen; const countLF = function(p) { // Count the number of LFs from 0 upto p @@ -105,7 +105,7 @@ class ToHex extends Operation { * @returns {Object[]} pos */ highlightReverse(pos, args) { - let delim, commaLen; + let delim, commaLen = 0; if (args[0] === "0x with comma") { delim = "0x"; commaLen = 1; @@ -114,7 +114,7 @@ class ToHex extends Operation { } const lineSize = args[1], - len = (delim === "\r\n" ? 1 : delim.length) + commaLen, + len = delim.length + commaLen, width = len + 2; const countLF = function(p) { diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 2477bb60..a46379e9 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -153,10 +153,6 @@ class Manager { this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); - document.getElementById("input-text").addEventListener("scroll", this.highlighter.inputScroll.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mouseup", this.highlighter.inputMouseup.bind(this.highlighter)); - document.getElementById("input-text").addEventListener("mousemove", this.highlighter.inputMousemove.bind(this.highlighter)); - this.addMultiEventListener("#input-text", "mousedown dblclick select", this.highlighter.inputMousedown, this.highlighter); document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); @@ -188,10 +184,6 @@ class Manager { document.getElementById("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output)); document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.bind(this.output)); document.getElementById("magic").addEventListener("click", this.output.magicClick.bind(this.output)); - document.getElementById("output-text").addEventListener("scroll", this.highlighter.outputScroll.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.bind(this.highlighter)); - document.getElementById("output-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter)); - this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, this.highlighter); this.addDynamicListener("#output-file-download", "click", this.output.downloadFile, this.output); this.addDynamicListener("#output-file-show-all", "click", this.output.showAllFile, this.output); this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output); diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index d1340165..8b4375fe 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -4,17 +4,7 @@ * @license Apache-2.0 */ -/** - * HighlighterWaiter data type enum for the input. - * @enum - */ -const INPUT = 0; - -/** - * HighlighterWaiter data type enum for the output. - * @enum - */ -const OUTPUT = 1; +import {EditorSelection} from "@codemirror/state"; /** @@ -32,309 +22,81 @@ class HighlighterWaiter { this.app = app; this.manager = manager; - this.mouseButtonDown = false; - this.mouseTarget = null; + this.currentSelectionRanges = []; } - /** - * Determines if the current text selection is running backwards or forwards. - * StackOverflow answer id: 12652116 + * Handler for selection change events in the input and output * - * @private - * @returns {boolean} - */ - _isSelectionBackwards() { - let backwards = false; - const sel = window.getSelection(); - - if (!sel.isCollapsed) { - const range = document.createRange(); - range.setStart(sel.anchorNode, sel.anchorOffset); - range.setEnd(sel.focusNode, sel.focusOffset); - backwards = range.collapsed; - range.detach(); - } - return backwards; - } - - - /** - * Calculates the text offset of a position in an HTML element, ignoring HTML tags. - * - * @private - * @param {element} node - The parent HTML node. - * @param {number} offset - The offset since the last HTML element. - * @returns {number} - */ - _getOutputHtmlOffset(node, offset) { - const sel = window.getSelection(); - const range = document.createRange(); - - range.selectNodeContents(document.getElementById("output-html")); - range.setEnd(node, offset); - sel.removeAllRanges(); - sel.addRange(range); - - return sel.toString().length; - } - - - /** - * Gets the current selection offsets in the output HTML, ignoring HTML tags. - * - * @private - * @returns {Object} pos - * @returns {number} pos.start - * @returns {number} pos.end - */ - _getOutputHtmlSelectionOffsets() { - const sel = window.getSelection(); - let range, - start = 0, - end = 0, - backwards = false; - - if (sel.rangeCount) { - range = sel.getRangeAt(sel.rangeCount - 1); - backwards = this._isSelectionBackwards(); - start = this._getOutputHtmlOffset(range.startContainer, range.startOffset); - end = this._getOutputHtmlOffset(range.endContainer, range.endOffset); - sel.removeAllRanges(); - sel.addRange(range); - - if (backwards) { - // If selecting backwards, reverse the start and end offsets for the selection to - // prevent deselecting as the drag continues. - sel.collapseToEnd(); - sel.extend(sel.anchorNode, range.startOffset); - } - } - - return { - start: start, - end: end - }; - } - - - /** - * Handler for input scroll events. - * Scrolls the highlighter pane to match the input textarea position. - * - * @param {event} e - */ - inputScroll(e) { - const el = e.target; - document.getElementById("input-highlighter").scrollTop = el.scrollTop; - document.getElementById("input-highlighter").scrollLeft = el.scrollLeft; - } - - - /** - * Handler for output scroll events. - * Scrolls the highlighter pane to match the output textarea position. - * - * @param {event} e - */ - outputScroll(e) { - const el = e.target; - document.getElementById("output-highlighter").scrollTop = el.scrollTop; - document.getElementById("output-highlighter").scrollLeft = el.scrollLeft; - } - - - /** - * Handler for input mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ - inputMousedown(e) { - this.mouseButtonDown = true; - this.mouseTarget = INPUT; - this.removeHighlights(); - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightOutput([{start: start, end: end}]); - } - } - - - /** - * Handler for output mousedown events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ - outputMousedown(e) { - this.mouseButtonDown = true; - this.mouseTarget = OUTPUT; - this.removeHighlights(); - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightInput([{start: start, end: end}]); - } - } - - - /** - * Handler for input mouseup events. - * - * @param {event} e - */ - inputMouseup(e) { - this.mouseButtonDown = false; - } - - - /** - * Handler for output mouseup events. - * - * @param {event} e - */ - outputMouseup(e) { - this.mouseButtonDown = false; - } - - - /** - * Handler for input mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the output. - * - * @param {event} e - */ - inputMousemove(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== INPUT) - return; - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightOutput([{start: start, end: end}]); - } - } - - - /** - * Handler for output mousemove events. - * Calculates the current selection info, and highlights the corresponding data in the input. - * - * @param {event} e - */ - outputMousemove(e) { - // Check that the left mouse button is pressed - if (!this.mouseButtonDown || - e.which !== 1 || - this.mouseTarget !== OUTPUT) - return; - - const sel = document.getSelection(); - const start = sel.baseOffset; - const end = sel.extentOffset; - - if (start !== 0 || end !== 0) { - this.highlightInput([{start: start, end: end}]); - } - } - - - /** - * Given start and end offsets, writes the HTML for the selection info element with the correct - * padding. - * - * @param {number} start - The start offset. - * @param {number} end - The end offset. - * @returns {string} - */ - selectionInfo(start, end) { - const len = end.toString().length; - const width = len < 2 ? 2 : len; - const startStr = start.toString().padStart(width, " ").replace(/ /g, " "); - const endStr = end.toString().padStart(width, " ").replace(/ /g, " "); - const lenStr = (end-start).toString().padStart(width, " ").replace(/ /g, " "); - - return "start: " + startStr + "
    end: " + endStr + "
    length: " + lenStr; - } - - - /** - * Removes highlighting and selection information. - */ - removeHighlights() { - document.getElementById("input-highlighter").innerHTML = ""; - document.getElementById("output-highlighter").innerHTML = ""; - } - - - /** - * Highlights the given offsets in the output. + * Highlights the given offsets in the input or output. * We will only highlight if: * - input hasn't changed since last bake * - last bake was a full bake * - all operations in the recipe support highlighting * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. + * @param {string} io + * @param {ViewUpdate} e */ - highlightOutput(pos) { + selectionChange(io, e) { + // Confirm we are not currently baking if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", pos); + + // Confirm this was a user-generated event to prevent looping + // from setting the selection in this class + if (!e.transactions[0].isUserEvent("select")) return false; + + const view = io === "input" ? + this.manager.output.outputEditorView : + this.manager.input.inputEditorView; + + this.currentSelectionRanges = []; + + // Confirm some non-empty ranges are set + const selectionRanges = e.state.selection.ranges.filter(r => !r.empty); + if (!selectionRanges.length) { + this.resetSelections(view); + return; + } + + // Loop through ranges and send request for output offsets for each one + const direction = io === "input" ? "forward" : "reverse"; + for (const range of selectionRanges) { + const pos = [{ + start: range.from, + end: range.to + }]; + this.manager.worker.highlight(this.app.getRecipeConfig(), direction, pos); + } } - /** - * Highlights the given offsets in the input. - * We will only highlight if: - * - input hasn't changed since last bake - * - last bake was a full bake - * - all operations in the recipe support highlighting - * - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. + * Resets the current set of selections in the given view + * @param {EditorView} view */ - highlightInput(pos) { - if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); + resetSelections(view) { + this.currentSelectionRanges = []; + + // Clear current selection in output or input + view.dispatch({ + selection: EditorSelection.create([EditorSelection.range(0, 0)]) + }); } /** * Displays highlight offsets sent back from the Chef. * - * @param {Object} pos - The position object for the highlight. + * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. * @param {string} direction */ displayHighlights(pos, direction) { if (!pos) return; - if (this.manager.tabs.getActiveInputTab() !== this.manager.tabs.getActiveOutputTab()) return; const io = direction === "forward" ? "output" : "input"; - - // TODO - // document.getElementById(io + "-selection-info").innerHTML = this.selectionInfo(pos[0].start, pos[0].end); - this.highlight( - document.getElementById(io + "-text"), - document.getElementById(io + "-highlighter"), - pos); + this.highlight(io, pos); } @@ -342,74 +104,35 @@ class HighlighterWaiter { * Adds the relevant HTML to the specified highlight element such that highlighting appears * underneath the correct offset. * - * @param {element} textarea - The input or output textarea. - * @param {element} highlighter - The input or output highlighter element. - * @param {Object} pos - The position object for the highlight. - * @param {number} pos.start - The start offset. - * @param {number} pos.end - The end offset. + * @param {string} io - The input or output + * @param {Object[]} ranges - An array of position objects to highlight + * @param {number} ranges.start - The start offset + * @param {number} ranges.end - The end offset */ - async highlight(textarea, highlighter, pos) { - // if (!this.app.options.showHighlighter) return false; - // if (!this.app.options.attemptHighlight) return false; + async highlight(io, ranges) { + if (!this.app.options.showHighlighter) return false; + if (!this.app.options.attemptHighlight) return false; + if (!ranges || !ranges.length) return false; - // // Check if there is a carriage return in the output dish as this will not - // // be displayed by the HTML textarea and will mess up highlighting offsets. - // if (await this.manager.output.containsCR()) return false; + const view = io === "input" ? + this.manager.input.inputEditorView : + this.manager.output.outputEditorView; - // const startPlaceholder = "[startHighlight]"; - // const startPlaceholderRegex = /\[startHighlight\]/g; - // const endPlaceholder = "[endHighlight]"; - // const endPlaceholderRegex = /\[endHighlight\]/g; - // // let text = textarea.value; // TODO + // Add new SelectionRanges to existing ones + for (const range of ranges) { + if (!range.start || !range.end) continue; + this.currentSelectionRanges.push( + EditorSelection.range(range.start, range.end) + ); + } - // // Put placeholders in position - // // If there's only one value, select that - // // If there are multiple, ignore the first one and select all others - // if (pos.length === 1) { - // if (pos[0].end < pos[0].start) return; - // text = text.slice(0, pos[0].start) + - // startPlaceholder + text.slice(pos[0].start, pos[0].end) + endPlaceholder + - // text.slice(pos[0].end, text.length); - // } else { - // // O(n^2) - Can anyone improve this without overwriting placeholders? - // let result = "", - // endPlaced = true; - - // for (let i = 0; i < text.length; i++) { - // for (let j = 1; j < pos.length; j++) { - // if (pos[j].end < pos[j].start) continue; - // if (pos[j].start === i) { - // result += startPlaceholder; - // endPlaced = false; - // } - // if (pos[j].end === i) { - // result += endPlaceholder; - // endPlaced = true; - // } - // } - // result += text[i]; - // } - // if (!endPlaced) result += endPlaceholder; - // text = result; - // } - - // const cssClass = "hl1"; - - // // Remove HTML tags - // text = text - // .replace(/&/g, "&") - // .replace(//g, ">") - // .replace(/\n/g, " ") - // // Convert placeholders to tags - // .replace(startPlaceholderRegex, "") - // .replace(endPlaceholderRegex, "") + " "; - - // // Adjust width to allow for scrollbars - // highlighter.style.width = textarea.clientWidth + "px"; - // highlighter.innerHTML = text; - // highlighter.scrollTop = textarea.scrollTop; - // highlighter.scrollLeft = textarea.scrollLeft; + // Set selection + if (this.currentSelectionRanges.length) { + view.dispatch({ + selection: EditorSelection.create(this.currentSelectionRanges), + scrollIntoView: true + }); + } } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 0dc44dbe..ff512f69 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -87,6 +87,7 @@ class InputWaiter { const initialState = EditorState.create({ doc: null, extensions: [ + // Editor extensions history(), highlightSpecialChars({render: renderSpecialChar}), drawSelection(), @@ -95,13 +96,19 @@ class InputWaiter { bracketMatching(), highlightSelectionMatches(), search({top: true}), + EditorState.allowMultipleSelections.of(true), + + // Custom extensions statusBar({ label: "Input", eolHandler: this.eolChange.bind(this) }), + + // Mutable state this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), - EditorState.allowMultipleSelections.of(true), + + // Keymap keymap.of([ // Explicitly insert a tab rather than indenting the line { key: "Tab", run: insertTab }, @@ -112,6 +119,12 @@ class InputWaiter { ...defaultKeymap, ...searchKeymap ]), + + // Event listeners + EditorView.updateListener.of(e => { + if (e.selectionSet) + this.manager.highlighter.selectionChange("input", e); + }) ] }); @@ -771,9 +784,6 @@ class InputWaiter { const fileOverlay = document.getElementById("input-file"); if (fileOverlay.style.display === "block") return; - // Remove highlighting from input and output panes as the offsets might be different now - this.manager.highlighter.removeHighlights(); - const value = this.getInput(); const activeTab = this.manager.tabs.getActiveInputTab(); @@ -1033,9 +1043,6 @@ class InputWaiter { this.manager.output.removeAllOutputs(); this.manager.output.terminateZipWorker(); - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - const tabsList = document.getElementById("input-tabs"); const tabsListChildren = tabsList.children; @@ -1073,9 +1080,6 @@ class InputWaiter { const inputNum = this.manager.tabs.getActiveInputTab(); if (inputNum === -1) return; - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - this.updateInputValue(inputNum, "", true); this.set({ diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 496b0ac5..d1fd2532 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -67,6 +67,7 @@ class OutputWaiter { const initialState = EditorState.create({ doc: null, extensions: [ + // Editor extensions EditorState.readOnly.of(true), htmlPlugin(this.htmlOutput), highlightSpecialChars({render: renderSpecialChar}), @@ -76,18 +77,30 @@ class OutputWaiter { bracketMatching(), highlightSelectionMatches(), search({top: true}), + EditorState.allowMultipleSelections.of(true), + + // Custom extensiosn statusBar({ label: "Output", bakeStats: this.bakeStats, eolHandler: this.eolChange.bind(this) }), + + // Mutable state this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), - EditorState.allowMultipleSelections.of(true), + + // Keymap keymap.of([ ...defaultKeymap, ...searchKeymap ]), + + // Event listeners + EditorView.updateListener.of(e => { + if (e.selectionSet) + this.manager.highlighter.selectionChange("output", e); + }) ] }); @@ -817,9 +830,6 @@ class OutputWaiter { this.hideMagicButton(); - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - if (!this.manager.tabs.changeOutputTab(inputNum)) { let direction = "right"; if (currentNum > inputNum) { @@ -1343,21 +1353,6 @@ class OutputWaiter { document.body.removeChild(textarea); } - /** - * Returns true if the output contains carriage returns - * - * @returns {boolean} - */ - async containsCR() { - const dish = this.getOutputDish(this.manager.tabs.getActiveOutputTab()); - if (dish === null) return; - - if (dish.type === Dish.STRING) { - const data = await dish.get(Dish.STRING); - return data.indexOf("\r") >= 0; - } - } - /** * Handler for switch click events. * Moves the current output into the input textarea. diff --git a/src/web/waiters/TabWaiter.mjs b/src/web/waiters/TabWaiter.mjs index 384b1ab7..f5b0efd4 100644 --- a/src/web/waiters/TabWaiter.mjs +++ b/src/web/waiters/TabWaiter.mjs @@ -305,9 +305,6 @@ class TabWaiter { changeTab(inputNum, io) { const tabsList = document.getElementById(`${io}-tabs`); - this.manager.highlighter.removeHighlights(); - getSelection().removeAllRanges(); - let found = false; for (let i = 0; i < tabsList.children.length; i++) { const tabNum = parseInt(tabsList.children.item(i).getAttribute("inputNum"), 10); diff --git a/src/web/waiters/WorkerWaiter.mjs b/src/web/waiters/WorkerWaiter.mjs index 7fcaa509..a63bfc1f 100644 --- a/src/web/waiters/WorkerWaiter.mjs +++ b/src/web/waiters/WorkerWaiter.mjs @@ -794,7 +794,7 @@ class WorkerWaiter { * * @param {Object[]} recipeConfig * @param {string} direction - * @param {Object} pos - The position object for the highlight. + * @param {Object[]} pos - The position object for the highlight. * @param {number} pos.start - The start offset. * @param {number} pos.end - The end offset. */ From 157dacb3a52fd082ffd203d5a88e328217260eb2 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 11 Jul 2022 11:43:48 +0100 Subject: [PATCH 008/188] Improved highlighting colours and selection ranges --- src/web/stylesheets/layout/_io.css | 22 +++++++++++ src/web/stylesheets/themes/_classic.css | 10 ++--- src/web/waiters/HighlighterWaiter.mjs | 51 ++++++++++--------------- src/web/waiters/InputWaiter.mjs | 3 +- 4 files changed, 50 insertions(+), 36 deletions(-) diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index ba670f3d..cb196709 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -440,6 +440,28 @@ filter: brightness(98%); } +/* Highlighting */ +.ͼ2.cm-focused .cm-selectionBackground { + background-color: var(--hl5); +} + +.ͼ2 .cm-selectionBackground { + background-color: var(--hl1); +} + +.ͼ1 .cm-selectionMatch { + background-color: var(--hl2); +} + +.ͼ1.cm-focused .cm-cursor.cm-cursor-primary { + border-color: var(--primary-font-colour); +} + +.ͼ1 .cm-cursor.cm-cursor-primary { + display: block; + border-color: var(--subtext-font-colour); +} + /* Status bar */ diff --git a/src/web/stylesheets/themes/_classic.css b/src/web/stylesheets/themes/_classic.css index 3b3bd555..971c1c57 100755 --- a/src/web/stylesheets/themes/_classic.css +++ b/src/web/stylesheets/themes/_classic.css @@ -110,11 +110,11 @@ /* Highlighter colours */ - --hl1: #fff000; - --hl2: #95dfff; - --hl3: #ffb6b6; - --hl4: #fcf8e3; - --hl5: #8de768; + --hl1: #ffee00aa; + --hl2: #95dfffaa; + --hl3: #ffb6b6aa; + --hl4: #fcf8e3aa; + --hl5: #8de768aa; /* Scrollbar */ diff --git a/src/web/waiters/HighlighterWaiter.mjs b/src/web/waiters/HighlighterWaiter.mjs index 8b4375fe..189d3777 100755 --- a/src/web/waiters/HighlighterWaiter.mjs +++ b/src/web/waiters/HighlighterWaiter.mjs @@ -45,18 +45,10 @@ class HighlighterWaiter { // from setting the selection in this class if (!e.transactions[0].isUserEvent("select")) return false; - const view = io === "input" ? - this.manager.output.outputEditorView : - this.manager.input.inputEditorView; - this.currentSelectionRanges = []; // Confirm some non-empty ranges are set - const selectionRanges = e.state.selection.ranges.filter(r => !r.empty); - if (!selectionRanges.length) { - this.resetSelections(view); - return; - } + const selectionRanges = e.state.selection.ranges; // Loop through ranges and send request for output offsets for each one const direction = io === "input" ? "forward" : "reverse"; @@ -69,19 +61,6 @@ class HighlighterWaiter { } } - /** - * Resets the current set of selections in the given view - * @param {EditorView} view - */ - resetSelections(view) { - this.currentSelectionRanges = []; - - // Clear current selection in output or input - view.dispatch({ - selection: EditorSelection.create([EditorSelection.range(0, 0)]) - }); - } - /** * Displays highlight offsets sent back from the Chef. @@ -120,18 +99,30 @@ class HighlighterWaiter { // Add new SelectionRanges to existing ones for (const range of ranges) { - if (!range.start || !range.end) continue; - this.currentSelectionRanges.push( - EditorSelection.range(range.start, range.end) - ); + if (typeof range.start !== "number" || + typeof range.end !== "number") + continue; + const selection = range.end <= range.start ? + EditorSelection.cursor(range.start) : + EditorSelection.range(range.start, range.end); + + this.currentSelectionRanges.push(selection); } // Set selection if (this.currentSelectionRanges.length) { - view.dispatch({ - selection: EditorSelection.create(this.currentSelectionRanges), - scrollIntoView: true - }); + try { + view.dispatch({ + selection: EditorSelection.create(this.currentSelectionRanges), + scrollIntoView: true + }); + } catch (err) { + // Ignore Range Errors + if (!err.toString().startsWith("RangeError")) { + console.error(err); + } + + } } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index ff512f69..69417b92 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -12,7 +12,7 @@ import {toBase64} from "../../core/lib/Base64.mjs"; import {isImage} from "../../core/lib/FileType.mjs"; import { - EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor + EditorView, keymap, highlightSpecialChars, drawSelection, rectangularSelection, crosshairCursor, dropCursor } from "@codemirror/view"; import {EditorState, Compartment} from "@codemirror/state"; import {defaultKeymap, insertTab, insertNewline, history, historyKeymap} from "@codemirror/commands"; @@ -93,6 +93,7 @@ class InputWaiter { drawSelection(), rectangularSelection(), crosshairCursor(), + dropCursor(), bracketMatching(), highlightSelectionMatches(), search({top: true}), From 5c8aac5572b687186d390d07c7206e068df25a19 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 11 Jul 2022 13:43:19 +0100 Subject: [PATCH 009/188] Improved input change update responsiveness --- src/core/operations/ParseColourCode.mjs | 2 +- src/web/App.mjs | 9 +++-- src/web/Manager.mjs | 1 - src/web/waiters/InputWaiter.mjs | 53 ++++++------------------- src/web/waiters/OutputWaiter.mjs | 4 +- 5 files changed, 20 insertions(+), 49 deletions(-) diff --git a/src/core/operations/ParseColourCode.mjs b/src/core/operations/ParseColourCode.mjs index 045d8f05..31e575a1 100644 --- a/src/core/operations/ParseColourCode.mjs +++ b/src/core/operations/ParseColourCode.mjs @@ -113,7 +113,7 @@ CMYK: ${cmyk} }).on('colorpickerChange', function(e) { var color = e.color.string('rgba'); window.app.manager.input.setInput(color); - window.app.manager.input.debounceInputChange(new Event("keyup")); + window.app.manager.input.inputChange(new Event("keyup")); }); `; } diff --git a/src/web/App.mjs b/src/web/App.mjs index 2d45d1f1..4ead8bc4 100755 --- a/src/web/App.mjs +++ b/src/web/App.mjs @@ -728,10 +728,11 @@ class App { * @param {event} e */ stateChange(e) { - this.progress = 0; - this.autoBake(); - - this.updateTitle(true, null, true); + debounce(function() { + this.progress = 0; + this.autoBake(); + this.updateTitle(true, null, true); + }, 20, "stateChange", this, [])(); } diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index a46379e9..9d03c728 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -146,7 +146,6 @@ class Manager { this.addDynamicListener("textarea.arg", "drop", this.recipe.textArgDrop, this.recipe); // Input - document.getElementById("input-text").addEventListener("keyup", this.input.debounceInputChange.bind(this.input)); document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 69417b92..6a1b57df 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -41,24 +41,6 @@ class InputWaiter { this.inputTextEl = document.getElementById("input-text"); this.initEditor(); - // Define keys that don't change the input so we don't have to autobake when they are pressed - this.badKeys = [ - 16, // Shift - 17, // Ctrl - 18, // Alt - 19, // Pause - 20, // Caps - 27, // Esc - 33, 34, 35, 36, // PgUp, PgDn, End, Home - 37, 38, 39, 40, // Directional - 44, // PrntScrn - 91, 92, // Win - 93, // Context - 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, // F1-12 - 144, // Num - 145, // Scroll - ]; - this.inputWorker = null; this.loaderWorkers = []; this.workerId = 0; @@ -125,6 +107,8 @@ class InputWaiter { EditorView.updateListener.of(e => { if (e.selectionSet) this.manager.highlighter.selectionChange("input", e); + if (e.docChanged) + this.inputChange(e); }) ] }); @@ -396,7 +380,7 @@ class InputWaiter { this.showLoadingInfo(r.data, true); break; case "setInput": - debounce(this.set, 50, "setInput", this, [r.data.inputObj, r.data.silent])(); + this.set(r.data.inputObj, r.data.silent); break; case "inputAdded": this.inputAdded(r.data.changeTab, r.data.inputNum); @@ -762,41 +746,30 @@ class InputWaiter { }); } - /** - * Handler for input change events. - * Debounces the input so we don't call autobake too often. - * - * @param {event} e - */ - debounceInputChange(e) { - debounce(this.inputChange, 50, "inputChange", this, [e])(); - } - /** * Handler for input change events. * Updates the value stored in the inputWorker + * Debounces the input so we don't call autobake too often. * * @param {event} e * * @fires Manager#statechange */ inputChange(e) { - // Ignore this function if the input is a file - const fileOverlay = document.getElementById("input-file"); - if (fileOverlay.style.display === "block") return; + debounce(function(e) { + // Ignore this function if the input is a file + const fileOverlay = document.getElementById("input-file"); + if (fileOverlay.style.display === "block") return; - const value = this.getInput(); - const activeTab = this.manager.tabs.getActiveInputTab(); + const value = this.getInput(); + const activeTab = this.manager.tabs.getActiveInputTab(); - this.app.progress = 0; + this.updateInputValue(activeTab, value); + this.manager.tabs.updateInputTabHeader(activeTab, value.slice(0, 100).replace(/[\n\r]/g, "")); - this.updateInputValue(activeTab, value); - this.manager.tabs.updateInputTabHeader(activeTab, value.slice(0, 100).replace(/[\n\r]/g, "")); - - if (e && this.badKeys.indexOf(e.keyCode) < 0) { // Fire the statechange event as the input has been modified window.dispatchEvent(this.manager.statechange); - } + }, 20, "inputChange", this, [e])(); } /** diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index d1fd2532..3f031ac7 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -847,9 +847,7 @@ class OutputWaiter { } } - debounce(this.set, 50, "setOutput", this, [inputNum])(); - - this.outputTextEl.scroll(0, 0); // TODO + this.set(inputNum); if (changeInput) { this.manager.input.changeTab(inputNum, false); From 0dc2322269d4fd26bc6b2aa07f6cb0cd9e3cbce6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 11 Jul 2022 13:57:28 +0100 Subject: [PATCH 010/188] Fixed dropping text in the input --- src/web/Manager.mjs | 6 +++--- src/web/waiters/InputWaiter.mjs | 16 ++++++---------- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/src/web/Manager.mjs b/src/web/Manager.mjs index 9d03c728..820b1a8d 100755 --- a/src/web/Manager.mjs +++ b/src/web/Manager.mjs @@ -149,9 +149,9 @@ class Manager { document.getElementById("reset-layout").addEventListener("click", this.app.resetLayout.bind(this.app)); this.addListeners("#clr-io,#btn-close-all-tabs", "click", this.input.clearAllIoClick, this.input); this.addListeners("#open-file,#open-folder", "change", this.input.inputOpen, this.input); - this.addListeners("#input-text,#input-file", "dragover", this.input.inputDragover, this.input); - this.addListeners("#input-text,#input-file", "dragleave", this.input.inputDragleave, this.input); - this.addListeners("#input-text,#input-file", "drop", this.input.inputDrop, this.input); + this.addListeners("#input-wrapper", "dragover", this.input.inputDragover, this.input); + this.addListeners("#input-wrapper", "dragleave", this.input.inputDragleave, this.input); + this.addListeners("#input-wrapper", "drop", this.input.inputDrop, this.input); document.querySelector("#input-file .close").addEventListener("click", this.input.clearIoClick.bind(this.input)); document.getElementById("btn-new-tab").addEventListener("click", this.input.addInputClick.bind(this.input)); document.getElementById("btn-previous-input-tab").addEventListener("mousedown", this.input.previousTabClick.bind(this.input)); diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 6a1b57df..ed8f174b 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -797,7 +797,10 @@ class InputWaiter { inputDragleave(e) { e.stopPropagation(); e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); + // Dragleave often fires when moving between lines in the editor. + // If the target element is within the input-text element, we are still on target. + if (!this.inputTextEl.contains(e.target)) + e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); } /** @@ -813,17 +816,10 @@ class InputWaiter { e.stopPropagation(); e.preventDefault(); - - const text = e.dataTransfer.getData("Text"); - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); - if (text) { - // Append the text to the current input and fire inputChange() - this.setInput(this.getInput() + text); - this.inputChange(e); - return; - } + // Dropped text is handled by the editor itself + if (e.dataTransfer.getData("Text")) return; if (e.dataTransfer.files && e.dataTransfer.files.length > 0) { this.loadUIFiles(e.dataTransfer.files); From 7c8a185a3d0f48275cad43a9b94a60cbfddc04f6 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Mon, 18 Jul 2022 18:39:41 +0100 Subject: [PATCH 011/188] HTML outputs can now be selected and handle control characters correctly --- src/core/Utils.mjs | 21 ++- src/core/operations/Magic.mjs | 2 +- src/core/operations/ROT13BruteForce.mjs | 6 +- src/core/operations/ROT47BruteForce.mjs | 6 +- .../operations/TextEncodingBruteForce.mjs | 2 +- src/core/operations/ToHexdump.mjs | 29 ++-- src/core/operations/XORBruteForce.mjs | 6 +- src/web/html/index.html | 2 - src/web/stylesheets/layout/_io.css | 21 +-- src/web/stylesheets/utils/_overrides.css | 8 ++ src/web/utils/copyOverride.mjs | 125 ++++++++++++++++++ src/web/utils/editorUtils.mjs | 70 +++++++++- src/web/utils/htmlWidget.mjs | 47 ++++++- src/web/waiters/OptionsWaiter.mjs | 7 - src/web/waiters/OutputWaiter.mjs | 88 +++++------- src/web/waiters/RecipeWaiter.mjs | 3 +- 16 files changed, 319 insertions(+), 124 deletions(-) create mode 100644 src/web/utils/copyOverride.mjs diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 5f36cae9..b72a6028 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -174,17 +174,13 @@ class Utils { * @returns {string} */ static printable(str, preserveWs=false, onlyAscii=false) { - if (isWebEnvironment() && window.app && !window.app.options.treatAsUtf8) { - str = Utils.byteArrayToChars(Utils.strToByteArray(str)); - } - if (onlyAscii) { return str.replace(/[^\x20-\x7f]/g, "."); } // eslint-disable-next-line no-misleading-character-class const re = /[\0-\x08\x0B-\x0C\x0E-\x1F\x7F-\x9F\xAD\u0378\u0379\u037F-\u0383\u038B\u038D\u03A2\u0528-\u0530\u0557\u0558\u0560\u0588\u058B-\u058E\u0590\u05C8-\u05CF\u05EB-\u05EF\u05F5-\u0605\u061C\u061D\u06DD\u070E\u070F\u074B\u074C\u07B2-\u07BF\u07FB-\u07FF\u082E\u082F\u083F\u085C\u085D\u085F-\u089F\u08A1\u08AD-\u08E3\u08FF\u0978\u0980\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09FC-\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF2-\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B55\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B78-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BFB-\u0C00\u0C04\u0C0D\u0C11\u0C29\u0C34\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5A-\u0C5F\u0C64\u0C65\u0C70-\u0C77\u0C80\u0C81\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0D01\u0D04\u0D0D\u0D11\u0D3B\u0D3C\u0D45\u0D49\u0D4F-\u0D56\u0D58-\u0D5F\u0D64\u0D65\u0D76-\u0D78\u0D80\u0D81\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DF1\u0DF5-\u0E00\u0E3B-\u0E3E\u0E5C-\u0E80\u0E83\u0E85\u0E86\u0E89\u0E8B\u0E8C\u0E8E-\u0E93\u0E98\u0EA0\u0EA4\u0EA6\u0EA8\u0EA9\u0EAC\u0EBA\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F48\u0F6D-\u0F70\u0F98\u0FBD\u0FCD\u0FDB-\u0FFF\u10C6\u10C8-\u10CC\u10CE\u10CF\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u137D-\u137F\u139A-\u139F\u13F5-\u13FF\u169D-\u169F\u16F1-\u16FF\u170D\u1715-\u171F\u1737-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17DE\u17DF\u17EA-\u17EF\u17FA-\u17FF\u180F\u181A-\u181F\u1878-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191D-\u191F\u192C-\u192F\u193C-\u193F\u1941-\u1943\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DB-\u19DD\u1A1C\u1A1D\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1A9F\u1AAE-\u1AFF\u1B4C-\u1B4F\u1B7D-\u1B7F\u1BF4-\u1BFB\u1C38-\u1C3A\u1C4A-\u1C4C\u1C80-\u1CBF\u1CC8-\u1CCF\u1CF7-\u1CFF\u1DE7-\u1DFB\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FC5\u1FD4\u1FD5\u1FDC\u1FF0\u1FF1\u1FF5\u1FFF\u200B-\u200F\u202A-\u202E\u2060-\u206F\u2072\u2073\u208F\u209D-\u209F\u20BB-\u20CF\u20F1-\u20FF\u218A-\u218F\u23F4-\u23FF\u2427-\u243F\u244B-\u245F\u2700\u2B4D-\u2B4F\u2B5A-\u2BFF\u2C2F\u2C5F\u2CF4-\u2CF8\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D71-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E3C-\u2E7F\u2E9A\u2EF4-\u2EFF\u2FD6-\u2FEF\u2FFC-\u2FFF\u3040\u3097\u3098\u3100-\u3104\u312E-\u3130\u318F\u31BB-\u31BF\u31E4-\u31EF\u321F\u32FF\u4DB6-\u4DBF\u9FCD-\u9FFF\uA48D-\uA48F\uA4C7-\uA4CF\uA62C-\uA63F\uA698-\uA69E\uA6F8-\uA6FF\uA78F\uA794-\uA79F\uA7AB-\uA7F7\uA82C-\uA82F\uA83A-\uA83F\uA878-\uA87F\uA8C5-\uA8CD\uA8DA-\uA8DF\uA8FC-\uA8FF\uA954-\uA95E\uA97D-\uA97F\uA9CE\uA9DA-\uA9DD\uA9E0-\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A\uAA5B\uAA7C-\uAA7F\uAAC3-\uAADA\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F-\uABBF\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBC2-\uFBD2\uFD40-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFE\uFDFF\uFE1A-\uFE1F\uFE27-\uFE2F\uFE53\uFE67\uFE6C-\uFE6F\uFE75\uFEFD-\uFF00\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFDF\uFFE7\uFFEF-\uFFFB\uFFFE\uFFFF]/g; - const wsRe = /[\x09-\x10\x0D\u2028\u2029]/g; + const wsRe = /[\x09-\x10\u2028\u2029]/g; str = str.replace(re, "."); if (!preserveWs) str = str.replace(wsRe, "."); @@ -192,6 +188,21 @@ class Utils { } + /** + * Returns a string with whitespace represented as special characters from the + * Unicode Private Use Area, which CyberChef will display as control characters. + * Private Use Area characters are in the range U+E000..U+F8FF. + * https://en.wikipedia.org/wiki/Private_Use_Areas + * @param {string} str + * @returns {string} + */ + static escapeWhitespace(str) { + return str.replace(/[\x09-\x10]/g, function(c) { + return String.fromCharCode(0xe000 + c.charCodeAt(0)); + }); + } + + /** * Parse a string entered by a user and replace escaped chars with the bytes they represent. * diff --git a/src/core/operations/Magic.mjs b/src/core/operations/Magic.mjs index d5357d95..69cad1db 100644 --- a/src/core/operations/Magic.mjs +++ b/src/core/operations/Magic.mjs @@ -149,7 +149,7 @@ class Magic extends Operation { output += ` ${Utils.generatePrettyRecipe(option.recipe, true)} - ${Utils.escapeHtml(Utils.printable(Utils.truncate(option.data, 99)))} + ${Utils.escapeHtml(Utils.escapeWhitespace(Utils.truncate(option.data, 99)))} ${language}${fileType}${matchingOps}${useful}${validUTF8}${entropy} `; }); diff --git a/src/core/operations/ROT13BruteForce.mjs b/src/core/operations/ROT13BruteForce.mjs index aefe2ab7..7468ee11 100644 --- a/src/core/operations/ROT13BruteForce.mjs +++ b/src/core/operations/ROT13BruteForce.mjs @@ -86,12 +86,12 @@ class ROT13BruteForce extends Operation { } const rotatedString = Utils.byteArrayToUtf8(rotated); if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { - const rotatedStringPrintable = Utils.printable(rotatedString, false); + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); if (printAmount) { const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; - result.push(amountStr + rotatedStringPrintable); + result.push(amountStr + rotatedStringEscaped); } else { - result.push(rotatedStringPrintable); + result.push(rotatedStringEscaped); } } } diff --git a/src/core/operations/ROT47BruteForce.mjs b/src/core/operations/ROT47BruteForce.mjs index 5f346e00..fa1e90dc 100644 --- a/src/core/operations/ROT47BruteForce.mjs +++ b/src/core/operations/ROT47BruteForce.mjs @@ -66,12 +66,12 @@ class ROT47BruteForce extends Operation { } const rotatedString = Utils.byteArrayToUtf8(rotated); if (rotatedString.toLowerCase().indexOf(cribLower) >= 0) { - const rotatedStringPrintable = Utils.printable(rotatedString, false); + const rotatedStringEscaped = Utils.escapeWhitespace(rotatedString); if (printAmount) { const amountStr = "Amount = " + (" " + amount).slice(-2) + ": "; - result.push(amountStr + rotatedStringPrintable); + result.push(amountStr + rotatedStringEscaped); } else { - result.push(rotatedStringPrintable); + result.push(rotatedStringEscaped); } } } diff --git a/src/core/operations/TextEncodingBruteForce.mjs b/src/core/operations/TextEncodingBruteForce.mjs index 18eb071e..ef8b7f80 100644 --- a/src/core/operations/TextEncodingBruteForce.mjs +++ b/src/core/operations/TextEncodingBruteForce.mjs @@ -79,7 +79,7 @@ class TextEncodingBruteForce extends Operation { let table = ""; for (const enc in encodings) { - const value = Utils.escapeHtml(Utils.printable(encodings[enc], true)); + const value = Utils.escapeHtml(Utils.escapeWhitespace(encodings[enc])); table += ``; } diff --git a/src/core/operations/ToHexdump.mjs b/src/core/operations/ToHexdump.mjs index c657adeb..a52b0451 100644 --- a/src/core/operations/ToHexdump.mjs +++ b/src/core/operations/ToHexdump.mjs @@ -63,33 +63,32 @@ class ToHexdump extends Operation { if (length < 1 || Math.round(length) !== length) throw new OperationError("Width must be a positive integer"); - let output = ""; + const lines = []; for (let i = 0; i < data.length; i += length) { - const buff = data.slice(i, i+length); - let hexa = ""; - for (let j = 0; j < buff.length; j++) { - hexa += Utils.hex(buff[j], padding) + " "; - } - let lineNo = Utils.hex(i, 8); + const buff = data.slice(i, i+length); + const hex = []; + buff.forEach(b => hex.push(Utils.hex(b, padding))); + let hexStr = hex.join(" ").padEnd(length*(padding+1), " "); + + const ascii = Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat); + const asciiStr = ascii.padEnd(buff.length, " "); + if (upperCase) { - hexa = hexa.toUpperCase(); + hexStr = hexStr.toUpperCase(); lineNo = lineNo.toUpperCase(); } - output += lineNo + " " + - hexa.padEnd(length*(padding+1), " ") + - " |" + - Utils.printable(Utils.byteArrayToChars(buff), false, unixFormat).padEnd(buff.length, " ") + - "|\n"; + lines.push(`${lineNo} ${hexStr} |${asciiStr}|`); + if (includeFinalLength && i+buff.length === data.length) { - output += Utils.hex(i+buff.length, 8) + "\n"; + lines.push(Utils.hex(i+buff.length, 8)); } } - return output.slice(0, -1); + return lines.join("\n"); } /** diff --git a/src/core/operations/XORBruteForce.mjs b/src/core/operations/XORBruteForce.mjs index 9b548df8..8c097731 100644 --- a/src/core/operations/XORBruteForce.mjs +++ b/src/core/operations/XORBruteForce.mjs @@ -126,11 +126,7 @@ class XORBruteForce extends Operation { if (crib && resultUtf8.toLowerCase().indexOf(crib) < 0) continue; if (printKey) record += "Key = " + Utils.hex(key, (2*keyLength)) + ": "; - if (outputHex) { - record += toHex(result); - } else { - record += Utils.printable(resultUtf8, false); - } + record += outputHex ? toHex(result) : Utils.escapeWhitespace(resultUtf8); output.push(record); } diff --git a/src/web/html/index.html b/src/web/html/index.html index 3eb150e5..a7931de5 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -264,7 +264,6 @@
    -
    @@ -341,7 +340,6 @@
    -
    diff --git a/src/web/stylesheets/layout/_io.css b/src/web/stylesheets/layout/_io.css index cb196709..ea15b6ac 100755 --- a/src/web/stylesheets/layout/_io.css +++ b/src/web/stylesheets/layout/_io.css @@ -177,31 +177,12 @@ } .textarea-wrapper textarea, -.textarea-wrapper #output-text, -.textarea-wrapper #output-highlighter { +.textarea-wrapper #output-text { font-family: var(--fixed-width-font-family); font-size: var(--fixed-width-font-size); color: var(--fixed-width-font-colour); } -#input-highlighter, -#output-highlighter { - position: absolute; - left: 0; - bottom: 0; - width: 100%; - padding: 3px; - margin: 0; - overflow: hidden; - letter-spacing: normal; - white-space: pre-wrap; - word-wrap: break-word; - color: #fff; - background-color: transparent; - border: none; - pointer-events: none; -} - #output-loader { position: absolute; bottom: 0; diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index fa216836..920aab89 100755 --- a/src/web/stylesheets/utils/_overrides.css +++ b/src/web/stylesheets/utils/_overrides.css @@ -232,3 +232,11 @@ optgroup { .colorpicker-color div { height: 100px; } + + +/* CodeMirror */ + +.ͼ2 .cm-specialChar, +.cm-specialChar { + color: red; +} diff --git a/src/web/utils/copyOverride.mjs b/src/web/utils/copyOverride.mjs new file mode 100644 index 00000000..51b2386b --- /dev/null +++ b/src/web/utils/copyOverride.mjs @@ -0,0 +1,125 @@ +/** + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + * + * In order to render whitespace characters as control character pictures in the output, even + * when they are the designated line separator, CyberChef sometimes chooses to represent them + * internally using the Unicode Private Use Area (https://en.wikipedia.org/wiki/Private_Use_Areas). + * See `Utils.escapeWhitespace()` for an example of this. + * + * The `renderSpecialChar()` function understands that it should display these characters as + * control pictures. When copying data from the Output, we need to replace these PUA characters + * with their original values, so we override the DOM "copy" event and modify the copied data + * if required. This handler is based closely on the built-in CodeMirror handler and defers to the + * built-in handler if PUA characters are not present in the copied data, in order to minimise the + * impact of breaking changes. + */ + +import {EditorView} from "@codemirror/view"; + +/** + * Copies the currently selected text from the state doc. + * Based on the built-in implementation with a few unrequired bits taken out: + * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L604 + * + * @param {EditorState} state + * @returns {Object} + */ +function copiedRange(state) { + const content = []; + let linewise = false; + for (const range of state.selection.ranges) if (!range.empty) { + content.push(state.sliceDoc(range.from, range.to)); + } + if (!content.length) { + // Nothing selected, do a line-wise copy + let upto = -1; + for (const {from} of state.selection.ranges) { + const line = state.doc.lineAt(from); + if (line.number > upto) { + content.push(line.text); + } + upto = line.number; + } + linewise = true; + } + + return {text: content.join(state.lineBreak), linewise}; +} + +/** + * Regex to match characters in the Private Use Area of the Unicode table. + */ +const PUARegex = new RegExp("[\ue000-\uf8ff]"); +const PUARegexG = new RegExp("[\ue000-\uf8ff]", "g"); +/** + * Regex tto match Unicode Control Pictures. + */ +const CPRegex = new RegExp("[\u2400-\u243f]"); +const CPRegexG = new RegExp("[\u2400-\u243f]", "g"); + +/** + * Overrides the DOM "copy" handler in the CodeMirror editor in order to return the original + * values of control characters that have been represented in the Unicode Private Use Area for + * visual purposes. + * Based on the built-in copy handler with some modifications: + * https://github.com/codemirror/view/blob/7d9c3e54396242d17b3164a0e244dcc234ee50ee/src/input.ts#L629 + * + * This handler will defer to the built-in version if no PUA characters are present. + * + * @returns {Extension} + */ +export function copyOverride() { + return EditorView.domEventHandlers({ + copy(event, view) { + const {text, linewise} = copiedRange(view.state); + if (!text && !linewise) return; + + // If there are no PUA chars in the copied text, return false and allow the built-in + // copy handler to fire + if (!PUARegex.test(text)) return false; + + // If PUA chars are detected, modify them back to their original values and copy that instead + const rawText = text.replace(PUARegexG, function(c) { + return String.fromCharCode(c.charCodeAt(0) - 0xe000); + }); + + event.preventDefault(); + event.clipboardData.clearData(); + event.clipboardData.setData("text/plain", rawText); + + // Returning true prevents CodeMirror default handlers from firing + return true; + } + }); +} + + +/** + * Handler for copy events in output-html decorations. If there are control pictures present, + * this handler will convert them back to their raw form before copying. If there are no + * control pictures present, it will do nothing and defer to the default browser handler. + * + * @param {ClipboardEvent} event + * @returns {boolean} + */ +export function htmlCopyOverride(event) { + const text = window.getSelection().toString(); + if (!text) return; + + // If there are no control picture chars in the copied text, return false and allow the built-in + // copy handler to fire + if (!CPRegex.test(text)) return false; + + // If control picture chars are detected, modify them back to their original values and copy that instead + const rawText = text.replace(CPRegexG, function(c) { + return String.fromCharCode(c.charCodeAt(0) - 0x2400); + }); + + event.preventDefault(); + event.clipboardData.clearData(); + event.clipboardData.setData("text/plain", rawText); + + return true; +} diff --git a/src/web/utils/editorUtils.mjs b/src/web/utils/editorUtils.mjs index fe6b83d4..cb0ebed1 100644 --- a/src/web/utils/editorUtils.mjs +++ b/src/web/utils/editorUtils.mjs @@ -6,12 +6,41 @@ * @license Apache-2.0 */ +import Utils from "../../core/Utils.mjs"; + +// Descriptions for named control characters +const Names = { + 0: "null", + 7: "bell", + 8: "backspace", + 10: "line feed", + 11: "vertical tab", + 13: "carriage return", + 27: "escape", + 8203: "zero width space", + 8204: "zero width non-joiner", + 8205: "zero width joiner", + 8206: "left-to-right mark", + 8207: "right-to-left mark", + 8232: "line separator", + 8237: "left-to-right override", + 8238: "right-to-left override", + 8233: "paragraph separator", + 65279: "zero width no-break space", + 65532: "object replacement" +}; + +// Regex for Special Characters to be replaced +const UnicodeRegexpSupport = /x/.unicode != null ? "gu" : "g"; +const Specials = new RegExp("[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u061c\u200b\u200e\u200f\u2028\u2029\u202d\u202e\ufeff\ufff9-\ufffc\ue000-\uf8ff]", UnicodeRegexpSupport); + /** * Override for rendering special characters. * Should mirror the toDOM function in * https://github.com/codemirror/view/blob/main/src/special-chars.ts#L150 * But reverts the replacement of line feeds with newline control pictures. + * * @param {number} code * @param {string} desc * @param {string} placeholder @@ -19,10 +48,47 @@ */ export function renderSpecialChar(code, desc, placeholder) { const s = document.createElement("span"); - // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back. - s.textContent = code === 0x0a ? "\u240a" : placeholder; + + // CodeMirror changes 0x0a to "NL" instead of "LF". We change it back along with its description. + if (code === 0x0a) { + placeholder = "\u240a"; + desc = desc.replace("newline", "line feed"); + } + + // Render CyberChef escaped characters correctly - see Utils.escapeWhitespace + if (code >= 0xe000 && code <= 0xf8ff) { + code = code - 0xe000; + placeholder = String.fromCharCode(0x2400 + code); + desc = "Control character " + (Names[code] || "0x" + code.toString(16)); + } + + s.textContent = placeholder; s.title = desc; s.setAttribute("aria-label", desc); s.className = "cm-specialChar"; return s; } + + +/** + * Given a string, returns that string with any control characters replaced with HTML + * renderings of control pictures. + * + * @param {string} str + * @param {boolean} [preserveWs=false] + * @param {string} [lineBreak="\n"] + * @returns {html} + */ +export function escapeControlChars(str, preserveWs=false, lineBreak="\n") { + if (!preserveWs) + str = Utils.escapeWhitespace(str); + + return str.replace(Specials, function(c) { + if (lineBreak.includes(c)) return c; + const code = c.charCodeAt(0); + const desc = "Control character " + (Names[code] || "0x" + code.toString(16)); + const placeholder = code > 32 ? "\u2022" : String.fromCharCode(9216 + code); + const n = renderSpecialChar(code, desc, placeholder); + return n.outerHTML; + }); +} diff --git a/src/web/utils/htmlWidget.mjs b/src/web/utils/htmlWidget.mjs index fbce9b49..5e5c41c1 100644 --- a/src/web/utils/htmlWidget.mjs +++ b/src/web/utils/htmlWidget.mjs @@ -5,6 +5,9 @@ */ import {WidgetType, Decoration, ViewPlugin} from "@codemirror/view"; +import {escapeControlChars} from "./editorUtils.mjs"; +import {htmlCopyOverride} from "./copyOverride.mjs"; + /** * Adds an HTML widget to the Code Mirror editor @@ -14,9 +17,10 @@ class HTMLWidget extends WidgetType { /** * HTMLWidget consructor */ - constructor(html) { + constructor(html, view) { super(); this.html = html; + this.view = view; } /** @@ -27,9 +31,45 @@ class HTMLWidget extends WidgetType { const wrap = document.createElement("span"); wrap.setAttribute("id", "output-html"); wrap.innerHTML = this.html; + + // Find text nodes and replace unprintable chars with control codes + this.walkTextNodes(wrap); + + // Add a handler for copy events to ensure the control codes are copied correctly + wrap.addEventListener("copy", htmlCopyOverride); return wrap; } + /** + * Walks all text nodes in a given element + * @param {DOMNode} el + */ + walkTextNodes(el) { + for (const node of el.childNodes) { + switch (node.nodeType) { + case Node.TEXT_NODE: + this.replaceControlChars(node); + break; + default: + if (node.nodeName !== "SCRIPT" && + node.nodeName !== "STYLE") + this.walkTextNodes(node); + break; + } + } + } + + /** + * Renders control characters in text nodes + * @param {DOMNode} textNode + */ + replaceControlChars(textNode) { + const val = escapeControlChars(textNode.nodeValue, true, this.view.state.lineBreak); + const node = document.createElement("null"); + node.innerHTML = val; + textNode.parentNode.replaceChild(node, textNode); + } + } /** @@ -42,7 +82,7 @@ function decorateHTML(view, html) { const widgets = []; if (html.length) { const deco = Decoration.widget({ - widget: new HTMLWidget(html), + widget: new HTMLWidget(html, view), side: 1 }); widgets.push(deco.range(0)); @@ -79,7 +119,8 @@ export function htmlPlugin(htmlOutput) { } } }, { - decorations: v => v.decorations + decorations: v => v.decorations, + } ); diff --git a/src/web/waiters/OptionsWaiter.mjs b/src/web/waiters/OptionsWaiter.mjs index 7d9a3e2d..36beef7e 100755 --- a/src/web/waiters/OptionsWaiter.mjs +++ b/src/web/waiters/OptionsWaiter.mjs @@ -141,13 +141,6 @@ class OptionsWaiter { setWordWrap() { this.manager.input.setWordWrap(this.app.options.wordWrap); this.manager.output.setWordWrap(this.app.options.wordWrap); - document.getElementById("input-highlighter").classList.remove("word-wrap"); - document.getElementById("output-highlighter").classList.remove("word-wrap"); - - if (!this.app.options.wordWrap) { - document.getElementById("input-highlighter").classList.add("word-wrap"); - document.getElementById("output-highlighter").classList.add("word-wrap"); - } } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index 3f031ac7..deaeaed3 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -5,7 +5,7 @@ * @license Apache-2.0 */ -import Utils, { debounce } from "../../core/Utils.mjs"; +import Utils, {debounce} from "../../core/Utils.mjs"; import Dish from "../../core/Dish.mjs"; import FileSaver from "file-saver"; import ZipWorker from "worker-loader?inline=no-fallback!../workers/ZipWorker.mjs"; @@ -19,8 +19,9 @@ import {bracketMatching} from "@codemirror/language"; import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; import {statusBar} from "../utils/statusBar.mjs"; -import {renderSpecialChar} from "../utils/editorUtils.mjs"; import {htmlPlugin} from "../utils/htmlWidget.mjs"; +import {copyOverride} from "../utils/copyOverride.mjs"; +import {renderSpecialChar} from "../utils/editorUtils.mjs"; /** * Waiter to handle events related to the output @@ -61,7 +62,8 @@ class OutputWaiter { initEditor() { this.outputEditorConf = { eol: new Compartment, - lineWrapping: new Compartment + lineWrapping: new Compartment, + drawSelection: new Compartment }; const initialState = EditorState.create({ @@ -69,9 +71,10 @@ class OutputWaiter { extensions: [ // Editor extensions EditorState.readOnly.of(true), - htmlPlugin(this.htmlOutput), - highlightSpecialChars({render: renderSpecialChar}), - drawSelection(), + highlightSpecialChars({ + render: renderSpecialChar, // Custom character renderer to handle special cases + addSpecialChars: /[\ue000-\uf8ff]/g // Add the Unicode Private Use Area which we use for some whitespace chars + }), rectangularSelection(), crosshairCursor(), bracketMatching(), @@ -79,16 +82,19 @@ class OutputWaiter { search({top: true}), EditorState.allowMultipleSelections.of(true), - // Custom extensiosn + // Custom extensions statusBar({ label: "Output", bakeStats: this.bakeStats, eolHandler: this.eolChange.bind(this) }), + htmlPlugin(this.htmlOutput), + copyOverride(), // Mutable state this.outputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.outputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), + this.outputEditorConf.drawSelection.of(drawSelection()), // Keymap keymap.of([ @@ -153,6 +159,14 @@ class OutputWaiter { * @param {string} data */ setOutput(data) { + // Turn drawSelection back on + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.drawSelection.reconfigure( + drawSelection() + ) + }); + + // Insert data into editor this.outputEditorView.dispatch({ changes: { from: 0, @@ -173,6 +187,11 @@ class OutputWaiter { // triggers the htmlWidget to render the HTML. this.setOutput(""); + // Turn off drawSelection + this.outputEditorView.dispatch({ + effects: this.outputEditorConf.drawSelection.reconfigure([]) + }); + // Execute script sections const scriptElements = document.getElementById("output-html").querySelectorAll("script"); for (let i = 0; i < scriptElements.length; i++) { @@ -414,8 +433,6 @@ class OutputWaiter { if (typeof inputNum !== "number") inputNum = parseInt(inputNum, 10); const outputFile = document.getElementById("output-file"); - const outputHighlighter = document.getElementById("output-highlighter"); - const inputHighlighter = document.getElementById("input-highlighter"); // If pending or baking, show loader and status message // If error, style the tab and handle the error @@ -447,8 +464,6 @@ class OutputWaiter { this.outputTextEl.style.display = "block"; this.outputTextEl.classList.remove("blur"); outputFile.style.display = "none"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; this.clearHTMLOutput(); if (output.error) { @@ -463,8 +478,6 @@ class OutputWaiter { if (output.data === null) { this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.clearHTMLOutput(); this.setOutput(""); @@ -478,15 +491,11 @@ class OutputWaiter { switch (output.data.type) { case "html": outputFile.style.display = "none"; - outputHighlighter.style.display = "none"; - inputHighlighter.style.display = "none"; this.setHTMLOutput(output.data.result); break; case "ArrayBuffer": this.outputTextEl.style.display = "block"; - outputHighlighter.display = "none"; - inputHighlighter.display = "none"; this.clearHTMLOutput(); this.setOutput(""); @@ -497,8 +506,6 @@ class OutputWaiter { default: this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.clearHTMLOutput(); this.setOutput(output.data.result); @@ -1215,8 +1222,6 @@ class OutputWaiter { document.querySelector("#output-loader .loading-msg").textContent = "Loading file slice..."; this.toggleLoader(true); const outputFile = document.getElementById("output-file"), - outputHighlighter = document.getElementById("output-highlighter"), - inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), sliceFromEl = document.getElementById("output-file-slice-from"), sliceToEl = document.getElementById("output-file-slice-to"), @@ -1238,8 +1243,6 @@ class OutputWaiter { this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.toggleLoader(false); } @@ -1251,8 +1254,6 @@ class OutputWaiter { document.querySelector("#output-loader .loading-msg").textContent = "Loading entire file at user instruction. This may cause a crash..."; this.toggleLoader(true); const outputFile = document.getElementById("output-file"), - outputHighlighter = document.getElementById("output-highlighter"), - inputHighlighter = document.getElementById("input-highlighter"), showFileOverlay = document.getElementById("show-file-overlay"), output = this.outputs[this.manager.tabs.getActiveOutputTab()].data; @@ -1270,8 +1271,6 @@ class OutputWaiter { this.outputTextEl.style.display = "block"; outputFile.style.display = "none"; - outputHighlighter.display = "block"; - inputHighlighter.display = "block"; this.toggleLoader(false); } @@ -1319,36 +1318,13 @@ class OutputWaiter { } const output = await dish.get(Dish.STRING); + const self = this; - // Create invisible textarea to populate with the raw dish string (not the printable version that - // contains dots instead of the actual bytes) - const textarea = document.createElement("textarea"); - textarea.style.position = "fixed"; - textarea.style.top = 0; - textarea.style.left = 0; - textarea.style.width = 0; - textarea.style.height = 0; - textarea.style.border = "none"; - - textarea.value = output; - document.body.appendChild(textarea); - - let success = false; - try { - textarea.select(); - success = output && document.execCommand("copy"); - } catch (err) { - success = false; - } - - if (success) { - this.app.alert("Copied raw output successfully.", 2000); - } else { - this.app.alert("Sorry, the output could not be copied.", 3000); - } - - // Clean up - document.body.removeChild(textarea); + navigator.clipboard.writeText(output).then(function() { + self.app.alert("Copied raw output successfully.", 2000); + }, function(err) { + self.app.alert("Sorry, the output could not be copied.", 3000); + }); } /** diff --git a/src/web/waiters/RecipeWaiter.mjs b/src/web/waiters/RecipeWaiter.mjs index f4107e66..d907a67c 100755 --- a/src/web/waiters/RecipeWaiter.mjs +++ b/src/web/waiters/RecipeWaiter.mjs @@ -7,6 +7,7 @@ import HTMLOperation from "../HTMLOperation.mjs"; import Sortable from "sortablejs"; import Utils from "../../core/Utils.mjs"; +import {escapeControlChars} from "../utils/editorUtils.mjs"; /** @@ -568,7 +569,7 @@ class RecipeWaiter { const registerList = []; for (let i = 0; i < registers.length; i++) { - registerList.push(`$R${numPrevRegisters + i} = ${Utils.escapeHtml(Utils.truncate(Utils.printable(registers[i]), 100))}`); + registerList.push(`$R${numPrevRegisters + i} = ${escapeControlChars(Utils.escapeHtml(Utils.truncate(registers[i], 100)))}`); } const registerListEl = `
    ${registerList.join("
    ")} From e93aa42697b5101791b2bc1238f8b687c08cf84f Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 2 Sep 2022 12:56:04 +0100 Subject: [PATCH 012/188] Input and output character encodings can now be set --- src/core/Chef.mjs | 8 +- src/core/ChefWorker.js | 7 +- src/core/Utils.mjs | 8 + src/core/lib/ChrEnc.mjs | 13 +- src/core/operations/DecodeText.mjs | 8 +- src/core/operations/EncodeText.mjs | 8 +- .../operations/TextEncodingBruteForce.mjs | 10 +- src/web/Manager.mjs | 1 - src/web/html/index.html | 3 - src/web/stylesheets/layout/_io.css | 42 ++- src/web/utils/htmlWidget.mjs | 11 +- src/web/utils/statusBar.mjs | 231 +++++++++++++--- src/web/waiters/InputWaiter.mjs | 168 +++++++----- src/web/waiters/OutputWaiter.mjs | 132 ++++----- src/web/workers/InputWorker.mjs | 255 +++++------------- 15 files changed, 482 insertions(+), 423 deletions(-) diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs index 36998cec..140774bc 100755 --- a/src/core/Chef.mjs +++ b/src/core/Chef.mjs @@ -68,16 +68,10 @@ class Chef { // Present the raw result await recipe.present(this.dish); - // Depending on the size of the output, we may send it back as a string or an ArrayBuffer. - // This can prevent unnecessary casting as an ArrayBuffer can be easily downloaded as a file. - // The threshold is specified in KiB. - const threshold = (options.ioDisplayThreshold || 1024) * 1024; const returnType = this.dish.type === Dish.HTML ? Dish.HTML : - this.dish.size > threshold ? - Dish.ARRAY_BUFFER : - Dish.STRING; + Dish.ARRAY_BUFFER; return { dish: rawDish, diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index d46a705d..8989875a 100644 --- a/src/core/ChefWorker.js +++ b/src/core/ChefWorker.js @@ -101,14 +101,17 @@ async function bake(data) { // Ensure the relevant modules are loaded self.loadRequiredModules(data.recipeConfig); try { - self.inputNum = (data.inputNum !== undefined) ? data.inputNum : -1; + self.inputNum = data.inputNum === undefined ? -1 : data.inputNum; const response = await self.chef.bake( data.input, // The user's input data.recipeConfig, // The configuration of the recipe data.options // Options set by the user ); - const transferable = (data.input instanceof ArrayBuffer) ? [data.input] : undefined; + const transferable = (response.dish.value instanceof ArrayBuffer) ? + [response.dish.value] : + undefined; + self.postMessage({ action: "bakeComplete", data: Object.assign(response, { diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index b72a6028..604b7b8c 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -406,6 +406,7 @@ class Utils { * Utils.strToArrayBuffer("你好"); */ static strToArrayBuffer(str) { + log.debug("Converting string to array buffer"); const arr = new Uint8Array(str.length); let i = str.length, b; while (i--) { @@ -432,6 +433,7 @@ class Utils { * Utils.strToUtf8ArrayBuffer("你好"); */ static strToUtf8ArrayBuffer(str) { + log.debug("Converting string to UTF8 array buffer"); const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -461,6 +463,7 @@ class Utils { * Utils.strToByteArray("你好"); */ static strToByteArray(str) { + log.debug("Converting string to byte array"); const byteArray = new Array(str.length); let i = str.length, b; while (i--) { @@ -487,6 +490,7 @@ class Utils { * Utils.strToUtf8ByteArray("你好"); */ static strToUtf8ByteArray(str) { + log.debug("Converting string to UTF8 byte array"); const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -515,6 +519,7 @@ class Utils { * Utils.strToCharcode("你好"); */ static strToCharcode(str) { + log.debug("Converting string to charcode"); const charcode = []; for (let i = 0; i < str.length; i++) { @@ -549,6 +554,7 @@ class Utils { * Utils.byteArrayToUtf8([228,189,160,229,165,189]); */ static byteArrayToUtf8(byteArray) { + log.debug("Converting byte array to UTF8"); const str = Utils.byteArrayToChars(byteArray); try { const utf8Str = utf8.decode(str); @@ -581,6 +587,7 @@ class Utils { * Utils.byteArrayToChars([20320,22909]); */ static byteArrayToChars(byteArray) { + log.debug("Converting byte array to chars"); if (!byteArray) return ""; let str = ""; // String concatenation appears to be faster than an array join @@ -603,6 +610,7 @@ class Utils { * Utils.arrayBufferToStr(Uint8Array.from([104,101,108,108,111]).buffer); */ static arrayBufferToStr(arrayBuffer, utf8=true) { + log.debug("Converting array buffer to str"); const arr = new Uint8Array(arrayBuffer); return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr); } diff --git a/src/core/lib/ChrEnc.mjs b/src/core/lib/ChrEnc.mjs index c5cb5605..8934d137 100644 --- a/src/core/lib/ChrEnc.mjs +++ b/src/core/lib/ChrEnc.mjs @@ -9,7 +9,7 @@ /** * Character encoding format mappings. */ -export const IO_FORMAT = { +export const CHR_ENC_CODE_PAGES = { "UTF-8 (65001)": 65001, "UTF-7 (65000)": 65000, "UTF-16LE (1200)": 1200, @@ -164,6 +164,17 @@ export const IO_FORMAT = { "Simplified Chinese GB18030 (54936)": 54936, }; + +export const CHR_ENC_SIMPLE_LOOKUP = {}; +export const CHR_ENC_SIMPLE_REVERSE_LOOKUP = {}; + +for (const name in CHR_ENC_CODE_PAGES) { + const simpleName = name.match(/(^.+)\([\d/]+\)$/)[1]; + + CHR_ENC_SIMPLE_LOOKUP[simpleName] = CHR_ENC_CODE_PAGES[name]; + CHR_ENC_SIMPLE_REVERSE_LOOKUP[CHR_ENC_CODE_PAGES[name]] = simpleName; +} + /** * Unicode Normalisation Forms * diff --git a/src/core/operations/DecodeText.mjs b/src/core/operations/DecodeText.mjs index 9b01b79f..0fc9d2b5 100644 --- a/src/core/operations/DecodeText.mjs +++ b/src/core/operations/DecodeText.mjs @@ -6,7 +6,7 @@ import Operation from "../Operation.mjs"; import cptable from "codepage"; -import {IO_FORMAT} from "../lib/ChrEnc.mjs"; +import {CHR_ENC_CODE_PAGES} from "../lib/ChrEnc.mjs"; /** * Decode text operation @@ -26,7 +26,7 @@ class DecodeText extends Operation { "

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

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

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

    File details

    + +
    EncodingValue
    ${enc}${value}
    + + + + + + + + + + + + + + + + +
    Name: + ${Utils.escapeHtml(this.fileDetails.name)} +
    Size: + ${Utils.escapeHtml(this.fileDetails.size)} bytes +
    Type: + ${Utils.escapeHtml(this.fileDetails.type)} +
    Loaded: + ${this.status === "error" ? "Error" : this.progress + "%"} +
    + `; + + return dom; + } + + /** + * Render the file thumbnail + */ + renderFileThumb() { + if (!this.renderPreview) { + this.resetFileThumb(); + return; + } + const fileThumb = this.dom.querySelector(".file-details-thumbnail"); + const fileType = this.dom.querySelector(".file-details-type"); + const fileBuffer = new Uint8Array(this.buffer); + const type = isImage(fileBuffer); + + if (type && type !== "image/tiff" && fileBuffer.byteLength <= 512000) { + // Most browsers don't support displaying TIFFs, so ignore them + // Don't render images over 512,000 bytes + const blob = new Blob([fileBuffer], {type: type}), + url = URL.createObjectURL(blob); + fileThumb.src = url; + } else { + this.resetFileThumb(); + } + fileType.textContent = type; + } + + /** + * Reset the file thumbnail to the default icon + */ + resetFileThumb() { + const fileThumb = this.dom.querySelector(".file-details-thumbnail"); + fileThumb.src = require("../static/images/file-128x128.png"); + } + +} + +/** + * A panel constructor factory building a panel that displays file details + * @param {Object} opts + * @returns {Function} + */ +function makePanel(opts) { + const fdPanel = new FileDetailsPanel(opts); + + return (view) => { + return { + dom: fdPanel.dom, + width: 200, + update(update) { + } + }; + }; +} + +/** + * A function that build the extension that enables the panel in an editor. + * @param {Object} opts + * @returns {Extension} + */ +export function fileDetailsPanel(opts) { + const panelMaker = makePanel(opts); + return showSidePanel.of(panelMaker); +} diff --git a/src/web/utils/sidePanel.mjs b/src/web/utils/sidePanel.mjs new file mode 100644 index 00000000..a8de0931 --- /dev/null +++ b/src/web/utils/sidePanel.mjs @@ -0,0 +1,254 @@ +/** + * A modification of the CodeMirror Panel extension to enable panels to the + * left and right of the editor. + * Based on code here: https://github.com/codemirror/view/blob/main/src/panel.ts + * + * @author n1474335 [n1474335@gmail.com] + * @copyright Crown Copyright 2022 + * @license Apache-2.0 + */ + +import {EditorView, ViewPlugin} from "@codemirror/view"; +import {Facet} from "@codemirror/state"; + +const panelConfig = Facet.define({ + combine(configs) { + let leftContainer, rightContainer; + for (const c of configs) { + leftContainer = leftContainer || c.leftContainer; + rightContainer = rightContainer || c.rightContainer; + } + return {leftContainer, rightContainer}; + } +}); + +/** + * Configures the panel-managing extension. + * @param {PanelConfig} config + * @returns Extension + */ +export function panels(config) { + return config ? [panelConfig.of(config)] : []; +} + +/** + * Get the active panel created by the given constructor, if any. + * This can be useful when you need access to your panels' DOM + * structure. + * @param {EditorView} view + * @param {PanelConstructor} panel + * @returns {Panel} + */ +export function getPanel(view, panel) { + const plugin = view.plugin(panelPlugin); + const index = plugin ? plugin.specs.indexOf(panel) : -1; + return index > -1 ? plugin.panels[index] : null; +} + +const panelPlugin = ViewPlugin.fromClass(class { + + /** + * @param {EditorView} view + */ + constructor(view) { + this.input = view.state.facet(showSidePanel); + this.specs = this.input.filter(s => s); + this.panels = this.specs.map(spec => spec(view)); + const conf = view.state.facet(panelConfig); + this.left = new PanelGroup(view, true, conf.leftContainer); + this.right = new PanelGroup(view, false, conf.rightContainer); + this.left.sync(this.panels.filter(p => p.left)); + this.right.sync(this.panels.filter(p => !p.left)); + for (const p of this.panels) { + p.dom.classList.add("cm-panel"); + if (p.mount) p.mount(); + } + } + + /** + * @param {ViewUpdate} update + */ + update(update) { + const conf = update.state.facet(panelConfig); + if (this.left.container !== conf.leftContainer) { + this.left.sync([]); + this.left = new PanelGroup(update.view, true, conf.leftContainer); + } + if (this.right.container !== conf.rightContainer) { + this.right.sync([]); + this.right = new PanelGroup(update.view, false, conf.rightContainer); + } + this.left.syncClasses(); + this.right.syncClasses(); + const input = update.state.facet(showSidePanel); + if (input !== this.input) { + const specs = input.filter(x => x); + const panels = [], left = [], right = [], mount = []; + for (const spec of specs) { + const known = this.specs.indexOf(spec); + let panel; + if (known < 0) { + panel = spec(update.view); + mount.push(panel); + } else { + panel = this.panels[known]; + if (panel.update) panel.update(update); + } + panels.push(panel) + ;(panel.left ? left : right).push(panel); + } + this.specs = specs; + this.panels = panels; + this.left.sync(left); + this.right.sync(right); + for (const p of mount) { + p.dom.classList.add("cm-panel"); + if (p.mount) p.mount(); + } + } else { + for (const p of this.panels) if (p.update) p.update(update); + } + } + + /** + * Destroy panel + */ + destroy() { + this.left.sync([]); + this.right.sync([]); + } +}, { + // provide: PluginField.scrollMargins.from(value => ({left: value.left.scrollMargin(), right: value.right.scrollMargin()})) +}); + +/** + * PanelGroup + */ +class PanelGroup { + + /** + * @param {EditorView} view + * @param {boolean} left + * @param {HTMLElement} container + */ + constructor(view, left, container) { + this.view = view; + this.left = left; + this.container = container; + this.dom = undefined; + this.classes = ""; + this.panels = []; + this.bufferWidth = 0; + this.syncClasses(); + } + + /** + * @param {Panel[]} panels + */ + sync(panels) { + for (const p of this.panels) if (p.destroy && panels.indexOf(p) < 0) p.destroy(); + this.panels = panels; + this.syncDOM(); + } + + /** + * Synchronise the DOM + */ + syncDOM() { + if (this.panels.length === 0) { + if (this.dom) { + this.dom.remove(); + this.dom = undefined; + } + return; + } + + const parent = this.container || this.view.dom; + if (!this.dom) { + this.dom = document.createElement("div"); + this.dom.className = this.left ? "cm-side-panels cm-panels-left" : "cm-side-panels cm-panels-right"; + parent.insertBefore(this.dom, parent.firstChild); + } + + let curDOM = this.dom.firstChild; + for (const panel of this.panels) { + if (panel.dom.parentNode === this.dom) { + while (curDOM !== panel.dom) curDOM = rm(curDOM); + curDOM = curDOM.nextSibling; + } else { + this.dom.insertBefore(panel.dom, curDOM); + this.bufferWidth = panel.width; + panel.dom.style.width = panel.width + "px"; + this.dom.style.width = this.bufferWidth + "px"; + } + } + while (curDOM) curDOM = rm(curDOM); + + const margin = this.left ? "marginLeft" : "marginRight"; + parent.querySelector(".cm-scroller").style[margin] = this.bufferWidth + "px"; + } + + /** + * + */ + scrollMargin() { + return !this.dom || this.container ? 0 : + Math.max(0, this.left ? + this.dom.getBoundingClientRect().right - Math.max(0, this.view.scrollDOM.getBoundingClientRect().left) : + Math.min(innerHeight, this.view.scrollDOM.getBoundingClientRect().right) - this.dom.getBoundingClientRect().left); + } + + /** + * + */ + syncClasses() { + if (!this.container || this.classes === this.view.themeClasses) return; + for (const cls of this.classes.split(" ")) if (cls) this.container.classList.remove(cls); + for (const cls of (this.classes = this.view.themeClasses).split(" ")) if (cls) this.container.classList.add(cls); + } +} + +/** + * @param {ChildNode} node + * @returns HTMLElement + */ +function rm(node) { + const next = node.nextSibling; + node.remove(); + return next; +} + +const baseTheme = EditorView.baseTheme({ + ".cm-side-panels": { + boxSizing: "border-box", + position: "absolute", + height: "100%", + top: 0, + bottom: 0 + }, + "&light .cm-side-panels": { + backgroundColor: "#f5f5f5", + color: "black" + }, + "&light .cm-panels-left": { + borderRight: "1px solid #ddd", + left: 0 + }, + "&light .cm-panels-right": { + borderLeft: "1px solid #ddd", + right: 0 + }, + "&dark .cm-side-panels": { + backgroundColor: "#333338", + color: "white" + } +}); + +/** + * Opening a panel is done by providing a constructor function for + * the panel through this facet. (The panel is closed again when its + * constructor is no longer provided.) Values of `null` are ignored. + */ +export const showSidePanel = Facet.define({ + enables: [panelPlugin, baseTheme] +}); diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index caa1a098..000940a4 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -9,7 +9,6 @@ import LoaderWorker from "worker-loader?inline=no-fallback!../workers/LoaderWork import InputWorker from "worker-loader?inline=no-fallback!../workers/InputWorker.mjs"; import Utils, {debounce} from "../../core/Utils.mjs"; import {toBase64} from "../../core/lib/Base64.mjs"; -import {isImage} from "../../core/lib/FileType.mjs"; import cptable from "codepage"; import { @@ -21,6 +20,7 @@ import {bracketMatching} from "@codemirror/language"; import {search, searchKeymap, highlightSelectionMatches} from "@codemirror/search"; import {statusBar} from "../utils/statusBar.mjs"; +import {fileDetailsPanel} from "../utils/fileDetails.mjs"; import {renderSpecialChar} from "../utils/editorUtils.mjs"; @@ -65,7 +65,8 @@ class InputWaiter { initEditor() { this.inputEditorConf = { eol: new Compartment, - lineWrapping: new Compartment + lineWrapping: new Compartment, + fileDetailsPanel: new Compartment }; const initialState = EditorState.create({ @@ -92,6 +93,7 @@ class InputWaiter { }), // Mutable state + this.inputEditorConf.fileDetailsPanel.of([]), this.inputEditorConf.lineWrapping.of(EditorView.lineWrapping), this.inputEditorConf.eol.of(EditorState.lineSeparator.of("\n")), @@ -466,43 +468,32 @@ class InputWaiter { if (inputNum !== activeTab) return; if (inputData.file) { - this.setFile(inputNum, inputData, silent); + this.setFile(inputNum, inputData); } else { - // TODO Per-tab encodings? - let inputVal; - if (this.inputChrEnc > 0) { - inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); - } else { - inputVal = Utils.arrayBufferToStr(inputData.buffer); - } - - this.setInput(inputVal); - const fileOverlay = document.getElementById("input-file"), - fileName = document.getElementById("input-file-name"), - fileSize = document.getElementById("input-file-size"), - fileType = document.getElementById("input-file-type"), - fileLoaded = document.getElementById("input-file-loaded"); - - fileOverlay.style.display = "none"; - fileName.textContent = ""; - fileSize.textContent = ""; - fileType.textContent = ""; - fileLoaded.textContent = ""; - - this.inputTextEl.classList.remove("blur"); - - // Set URL to current input - if (inputVal.length >= 0 && inputVal.length <= 51200) { - const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); - this.setUrl({ - includeInput: true, - input: inputStr - }); - } - - if (!silent) window.dispatchEvent(this.manager.statechange); + this.clearFile(inputNum); } + // TODO Per-tab encodings? + let inputVal; + if (this.inputChrEnc > 0) { + inputVal = cptable.utils.decode(this.inputChrEnc, new Uint8Array(inputData.buffer)); + } else { + inputVal = Utils.arrayBufferToStr(inputData.buffer); + } + + this.setInput(inputVal); + + // Set URL to current input + if (inputVal.length >= 0 && inputVal.length <= 51200) { + const inputStr = toBase64(inputVal, "A-Za-z0-9+/"); + this.setUrl({ + includeInput: true, + input: inputStr + }); + } + + if (!silent) window.dispatchEvent(this.manager.statechange); + }.bind(this)); } @@ -520,33 +511,38 @@ class InputWaiter { * @param {string} file.type * @param {string} status * @param {number} progress - * @param {boolean} [silent=true] - If false, fires the manager statechange event */ - setFile(inputNum, inputData, silent=true) { + setFile(inputNum, inputData) { const activeTab = this.manager.tabs.getActiveInputTab(); if (inputNum !== activeTab) return; - const fileOverlay = document.getElementById("input-file"), - fileName = document.getElementById("input-file-name"), - fileSize = document.getElementById("input-file-size"), - fileType = document.getElementById("input-file-type"), - fileLoaded = document.getElementById("input-file-loaded"); + // Create file details panel + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure( + fileDetailsPanel({ + fileDetails: inputData.file, + progress: inputData.progress, + status: inputData.status, + buffer: inputData.buffer, + renderPreview: this.app.options.imagePreview + }) + ) + }); + } - fileOverlay.style.display = "block"; - fileName.textContent = inputData.file.name; - fileSize.textContent = inputData.file.size + " bytes"; - fileType.textContent = inputData.file.type; - if (inputData.status === "error") { - fileLoaded.textContent = "Error"; - fileLoaded.style.color = "#FF0000"; - } else { - fileLoaded.style.color = ""; - fileLoaded.textContent = inputData.progress + "%"; - } + /** + * Clears the file details panel + * + * @param {number} inputNum + */ + clearFile(inputNum) { + const activeTab = this.manager.tabs.getActiveInputTab(); + if (inputNum !== activeTab) return; - this.displayFilePreview(inputNum, inputData); - - if (!silent) window.dispatchEvent(this.manager.statechange); + // Clear file details panel + this.inputEditorView.dispatch({ + effects: this.inputEditorConf.fileDetailsPanel.reconfigure([]) + }); } /** @@ -571,60 +567,6 @@ class InputWaiter { this.updateFileProgress(inputNum, 100); } - /** - * Render the input thumbnail - */ - async renderFileThumb() { - const activeTab = this.manager.tabs.getActiveInputTab(), - input = await this.getInputValue(activeTab), - fileThumb = document.getElementById("input-file-thumbnail"); - - if (typeof input === "string" || - !this.app.options.imagePreview) { - this.resetFileThumb(); - return; - } - - const inputArr = new Uint8Array(input), - type = isImage(inputArr); - - if (type && type !== "image/tiff" && inputArr.byteLength <= 512000) { - // Most browsers don't support displaying TIFFs, so ignore them - // Don't render images over 512000 bytes - const blob = new Blob([inputArr], {type: type}), - url = URL.createObjectURL(blob); - fileThumb.src = url; - } else { - this.resetFileThumb(); - } - - } - - /** - * Reset the input thumbnail to the default icon - */ - resetFileThumb() { - const fileThumb = document.getElementById("input-file-thumbnail"); - fileThumb.src = require("../static/images/file-128x128.png").default; - } - - /** - * Shows a chunk of the file in the input behind the file overlay - * - * @param {number} inputNum - The inputNum of the file being displayed - * @param {Object} inputData - Object containing the input data - * @param {string} inputData.stringSample - The first 4096 bytes of input as a string - */ - displayFilePreview(inputNum, inputData) { - const activeTab = this.manager.tabs.getActiveInputTab(), - input = inputData.buffer; - if (inputNum !== activeTab) return; - this.inputTextEl.classList.add("blur"); - this.setInput(input.stringSample); - - this.renderFileThumb(); - } - /** * Updates the displayed load progress for a file * @@ -632,17 +574,19 @@ class InputWaiter { * @param {number | string} progress - Either a number or "error" */ updateFileProgress(inputNum, progress) { - const activeTab = this.manager.tabs.getActiveInputTab(); - if (inputNum !== activeTab) return; + // const activeTab = this.manager.tabs.getActiveInputTab(); + // if (inputNum !== activeTab) return; - const fileLoaded = document.getElementById("input-file-loaded"); - if (progress === "error") { - fileLoaded.textContent = "Error"; - fileLoaded.style.color = "#FF0000"; - } else { - fileLoaded.textContent = progress + "%"; - fileLoaded.style.color = ""; - } + // TODO + + // const fileLoaded = document.getElementById("input-file-loaded"); + // if (progress === "error") { + // fileLoaded.textContent = "Error"; + // fileLoaded.style.color = "#FF0000"; + // } else { + // fileLoaded.textContent = progress + "%"; + // fileLoaded.style.color = ""; + // } } /** @@ -778,10 +722,6 @@ class InputWaiter { */ inputChange(e) { debounce(function(e) { - // Ignore this function if the input is a file - const fileOverlay = document.getElementById("input-file"); - if (fileOverlay.style.display === "block") return; - const value = this.getInput(); const activeTab = this.manager.tabs.getActiveInputTab(); @@ -806,7 +746,7 @@ class InputWaiter { e.stopPropagation(); e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.add("dropping-file"); + e.target.closest("#input-text").classList.add("dropping-file"); } /** @@ -821,7 +761,7 @@ class InputWaiter { // Dragleave often fires when moving between lines in the editor. // If the target element is within the input-text element, we are still on target. if (!this.inputTextEl.contains(e.target)) - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); + e.target.closest("#input-text").classList.remove("dropping-file"); } /** @@ -837,7 +777,7 @@ class InputWaiter { e.stopPropagation(); e.preventDefault(); - e.target.closest("#input-text,#input-file").classList.remove("dropping-file"); + e.target.closest("#input-text").classList.remove("dropping-file"); // Dropped text is handled by the editor itself if (e.dataTransfer.getData("Text")) return; @@ -1063,23 +1003,6 @@ class InputWaiter { window.dispatchEvent(this.manager.statechange); } - /** - * Handler for clear IO click event. - * Resets the input for the current tab - */ - clearIoClick() { - const inputNum = this.manager.tabs.getActiveInputTab(); - if (inputNum === -1) return; - - this.updateInputValue(inputNum, "", true); - - this.set(inputNum, { - buffer: new ArrayBuffer() - }); - - this.manager.tabs.updateInputTabHeader(inputNum, ""); - } - /** * Sets the console log level in the worker. * From 406da9fa2c8bc5b40e16b9dbb7251966f03a413c Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 2 Sep 2022 20:15:07 +0100 Subject: [PATCH 014/188] Efficiency improvements to reduce unnecessary casting --- src/core/Utils.mjs | 9 +++++- src/web/waiters/InputWaiter.mjs | 1 - src/web/waiters/OutputWaiter.mjs | 47 ++++++++++++++++---------------- 3 files changed, 31 insertions(+), 26 deletions(-) diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 604b7b8c..fec3b9be 100755 --- a/src/core/Utils.mjs +++ b/src/core/Utils.mjs @@ -407,6 +407,7 @@ class Utils { */ static strToArrayBuffer(str) { log.debug("Converting string to array buffer"); + if (!str) return new ArrayBuffer; const arr = new Uint8Array(str.length); let i = str.length, b; while (i--) { @@ -434,6 +435,7 @@ class Utils { */ static strToUtf8ArrayBuffer(str) { log.debug("Converting string to UTF8 array buffer"); + if (!str) return new ArrayBuffer; const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -464,6 +466,7 @@ class Utils { */ static strToByteArray(str) { log.debug("Converting string to byte array"); + if (!str) return []; const byteArray = new Array(str.length); let i = str.length, b; while (i--) { @@ -491,6 +494,7 @@ class Utils { */ static strToUtf8ByteArray(str) { log.debug("Converting string to UTF8 byte array"); + if (!str) return []; const utf8Str = utf8.encode(str); if (str.length !== utf8Str.length) { @@ -520,6 +524,7 @@ class Utils { */ static strToCharcode(str) { log.debug("Converting string to charcode"); + if (!str) return []; const charcode = []; for (let i = 0; i < str.length; i++) { @@ -555,6 +560,7 @@ class Utils { */ static byteArrayToUtf8(byteArray) { log.debug("Converting byte array to UTF8"); + if (!byteArray || !byteArray.length) return ""; const str = Utils.byteArrayToChars(byteArray); try { const utf8Str = utf8.decode(str); @@ -588,7 +594,7 @@ class Utils { */ static byteArrayToChars(byteArray) { log.debug("Converting byte array to chars"); - if (!byteArray) return ""; + if (!byteArray || !byteArray.length) return ""; let str = ""; // String concatenation appears to be faster than an array join for (let i = 0; i < byteArray.length;) { @@ -611,6 +617,7 @@ class Utils { */ static arrayBufferToStr(arrayBuffer, utf8=true) { log.debug("Converting array buffer to str"); + if (!arrayBuffer || !arrayBuffer.byteLength) return ""; const arr = new Uint8Array(arrayBuffer); return utf8 ? Utils.byteArrayToUtf8(arr) : Utils.byteArrayToChars(arr); } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index 000940a4..86ad9873 100644 --- a/src/web/waiters/InputWaiter.mjs +++ b/src/web/waiters/InputWaiter.mjs @@ -997,7 +997,6 @@ class InputWaiter { this.setupInputWorker(); this.manager.worker.setupChefWorker(); this.addInput(true); - this.bakeAll(); // Fire the statechange event as the input has been modified window.dispatchEvent(this.manager.statechange); diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index f0b03d72..a247375e 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -49,6 +49,8 @@ class OutputWaiter { html: "", changed: false }; + // Hold a copy of the currently displayed output so that we don't have to update it unnecessarily + this.currentOutputCache = null; this.outputChrEnc = 0; this.initEditor(); @@ -170,9 +172,26 @@ class OutputWaiter { /** * Sets the value of the current output - * @param {string} data + * @param {string|ArrayBuffer} data */ setOutput(data) { + // Don't do anything if the output hasn't changed + if (data === this.currentOutputCache) return; + this.currentOutputCache = data; + + // If data is an ArrayBuffer, convert to a string in the correct character encoding + if (data instanceof ArrayBuffer) { + if (this.outputChrEnc === 0) { + data = Utils.arrayBufferToStr(data); + } else { + try { + data = cptable.utils.decode(this.outputChrEnc, new Uint8Array(data)); + } catch (err) { + data = err; + } + } + } + // Turn drawSelection back on this.outputEditorView.dispatch({ effects: this.outputEditorConf.drawSelection.reconfigure( @@ -508,28 +527,7 @@ class OutputWaiter { this.setHTMLOutput(output.data.result); break; - case "ArrayBuffer": { - this.outputTextEl.style.display = "block"; - outputFile.style.display = "none"; - - this.clearHTMLOutput(); - - let outputVal = ""; - if (this.outputChrEnc === 0) { - outputVal = Utils.arrayBufferToStr(output.data.result); - } else { - try { - outputVal = cptable.utils.decode(this.outputChrEnc, new Uint8Array(output.data.result)); - } catch (err) { - outputVal = err; - } - } - - this.setOutput(outputVal); - - // this.setFile(await this.getDishBuffer(output.data.dish), activeTab); - break; - } + case "ArrayBuffer": case "string": default: this.outputTextEl.style.display = "block"; @@ -1136,7 +1134,8 @@ class OutputWaiter { * @param {number} inputNum */ async displayTabInfo(inputNum) { - if (!this.outputExists(inputNum)) return; + // Don't display anything if there are no, or only one, tabs + if (!this.outputExists(inputNum) || Object.keys(this.outputs).length <= 1) return; const dish = this.getOutputDish(inputNum); let tabStr = ""; From 3893c22275142774cd32d7f946a019ea130e35e0 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 9 Sep 2022 16:35:21 +0100 Subject: [PATCH 015/188] Changing the output encoding no longer triggers a full bake --- src/web/utils/statusBar.mjs | 12 +++++++++--- src/web/waiters/OutputWaiter.mjs | 7 +++++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/web/utils/statusBar.mjs b/src/web/utils/statusBar.mjs index f9be5006..efabea81 100644 --- a/src/web/utils/statusBar.mjs +++ b/src/web/utils/statusBar.mjs @@ -81,6 +81,9 @@ class StatusBarPanel { * @param {Event} e */ eolSelectClick(e) { + // preventDefault is required to stop the URL being modified and popState being triggered + e.preventDefault(); + const eolLookup = { "LF": "\u000a", "VT": "\u000b", @@ -106,6 +109,9 @@ class StatusBarPanel { * @param {Event} e */ chrEncSelectClick(e) { + // preventDefault is required to stop the URL being modified and popState being triggered + e.preventDefault(); // TODO - this breaks the menus when you click the button itself + const chrEncVal = parseInt(e.target.getAttribute("data-val"), 10); if (isNaN(chrEncVal)) return; @@ -366,9 +372,9 @@ function hideOnClickOutside(element, instantiatingEvent) { } }; - if (!Object.keys(elementsWithListeners).includes(element)) { - document.addEventListener("click", outsideClickListener); + if (!Object.prototype.hasOwnProperty.call(elementsWithListeners, element)) { elementsWithListeners[element] = outsideClickListener; + document.addEventListener("click", elementsWithListeners[element], false); } } @@ -378,7 +384,7 @@ function hideOnClickOutside(element, instantiatingEvent) { */ function hideElement(element) { element.classList.remove("show"); - document.removeEventListener("click", elementsWithListeners[element]); + document.removeEventListener("click", elementsWithListeners[element], false); delete elementsWithListeners[element]; } diff --git a/src/web/waiters/OutputWaiter.mjs b/src/web/waiters/OutputWaiter.mjs index a247375e..f1965c77 100755 --- a/src/web/waiters/OutputWaiter.mjs +++ b/src/web/waiters/OutputWaiter.mjs @@ -146,6 +146,8 @@ class OutputWaiter { */ chrEncChange(chrEncVal) { this.outputChrEnc = chrEncVal; + // Reset the output, forcing it to re-decode the data with the new character encoding + this.setOutput(this.currentOutputCache, true); } /** @@ -173,10 +175,11 @@ class OutputWaiter { /** * Sets the value of the current output * @param {string|ArrayBuffer} data + * @param {boolean} [force=false] */ - setOutput(data) { + setOutput(data, force=false) { // Don't do anything if the output hasn't changed - if (data === this.currentOutputCache) return; + if (!force && data === this.currentOutputCache) return; this.currentOutputCache = data; // If data is an ArrayBuffer, convert to a string in the correct character encoding From 08b91fd7ff0e91550eea3321ae9ece8909ea84a9 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 16 Sep 2022 16:00:03 +0100 Subject: [PATCH 016/188] Removed ioDisplayThreshold option --- src/web/html/index.html | 5 ----- src/web/index.js | 1 - 2 files changed, 6 deletions(-) diff --git a/src/web/html/index.html b/src/web/html/index.html index 6e2c60a3..9cf3e878 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -482,11 +482,6 @@
    -
    - - -
    -
    -
    to
    - -
    KiB
    -
    -
    -
    -
    -