From 85ffe487432a21dd56c6133e16db4c828d718009 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Wed, 29 Jun 2022 18:02:49 +0100 Subject: [PATCH 01/79] 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 ffde1368d5..1875995603 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 cea3fb1929..8e89248a6b 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 9cf40ba786..045d8f0526 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 e1e07dfda2..08a35d756d 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 0000000000..8a837a5148 --- /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 + + +
+ + keyboard_return + + +
`; +} + +/** + * 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 d222fee111..3d237bddcf 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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

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

- needed to position the dropup content */ +.cm-status-bar-select { + position: relative; + display: inline-block; +} + +/* Dropup content (Hidden by Default) */ +.cm-status-bar-select-content { + display: none; + position: absolute; + bottom: 20px; + right: 0; + background-color: #f1f1f1; + min-width: 200px; + box-shadow: 0px 4px 4px 0px rgba(0,0,0,0.2); + z-index: 1; +} + +/* Links inside the dropup */ +.cm-status-bar-select-content a { + color: black; + padding: 2px 5px; + text-decoration: none; + display: block; +} + +/* Change color of dropup links on hover */ +.cm-status-bar-select-content a:hover { + background-color: #ddd +} + +/* Show the dropup menu on hover */ +.cm-status-bar-select:hover .cm-status-bar-select-content { + display: block; +} + +/* Change the background color of the dropup button when the dropup content is shown */ +.cm-status-bar-select:hover .cm-status-bar-select-btn { + background-color: #f1f1f1; +} diff --git a/src/web/stylesheets/utils/_overrides.css b/src/web/stylesheets/utils/_overrides.css index c06d3b8cce..fa2168365d 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 5a9533f559..426107bb9a 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 664daef8ea..9f83b55c0d 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 b421d8d87b..e8e71b12fe 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 5ef517d4ad..52b81ab494 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 0eb6baeca7..8996edb0c5 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 41aff9b29d..ba6f5204e0 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 bb18dc5d84..d0933bb672 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 02/79] 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 9d4813e071..2d45d1f196 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 c410704b9e..1edc41b522 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 bd70d10fe4..339da0745d 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 426107bb9a..2879089ab2 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 03/79] 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 | 187 +++++++---------- 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, 672 insertions(+), 502 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 66a98c364c..5f36cae947 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 08a35d756d..2477bb60f8 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 8a837a5148..0000000000 --- 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 3d237bddcf..3eb150e531 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 625b81f74a..ba670f3d5a 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 0000000000..fe6b83d457 --- /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 0000000000..fbce9b4916 --- /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 0000000000..431d8a3db8 --- /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 9f83b55c0d..d1340165dc 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; - - // 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; - - // 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; + // 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; + + // 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; + + // 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; } } diff --git a/src/web/waiters/InputWaiter.mjs b/src/web/waiters/InputWaiter.mjs index e8e71b12fe..0dc44dbe55 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 52b81ab494..7d9a3e2da4 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 8996edb0c5..496b0ac5d9 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 ba6f5204e0..e63a803657 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 d0933bb672..64f8e0368c 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 04/79] 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 | 417 +++++--------------------- 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, 103 insertions(+), 392 deletions(-) diff --git a/src/core/ChefWorker.js b/src/core/ChefWorker.js index f4a17f63c1..d46a705dd1 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 71893105f4..092155a906 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 2477bb60f8..a46379e95b 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 d1340165dc..8b4375fe54 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. + * 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 {event} e + * @param {string} io + * @param {ViewUpdate} e */ - inputMouseup(e) { - this.mouseButtonDown = false; - } + selectionChange(io, e) { + // Confirm we are not currently baking + if (!this.app.autoBake_ || this.app.baking) return false; + // 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; - /** - * Handler for output mouseup events. - * - * @param {event} e - */ - outputMouseup(e) { - this.mouseButtonDown = false; - } + const view = io === "input" ? + this.manager.output.outputEditorView : + this.manager.input.inputEditorView; + this.currentSelectionRanges = []; - /** - * 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) + // 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 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}]); + // 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); } } - /** - * 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} + * Resets the current set of selections in the given view + * @param {EditorView} view */ - 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, " "); + resetSelections(view) { + this.currentSelectionRanges = []; - 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. - * 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. - */ - highlightOutput(pos) { - if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "forward", 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. - */ - highlightInput(pos) { - if (!this.app.autoBake_ || this.app.baking) return false; - this.manager.worker.highlight(this.app.getRecipeConfig(), "reverse", pos); + // 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. - */ - async highlight(textarea, highlighter, pos) { - // 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; - - // 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; - - // 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, "") + " "; + * @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(io, ranges) { + if (!this.app.options.showHighlighter) return false; + if (!this.app.options.attemptHighlight) return false; + if (!ranges || !ranges.length) return false; + + const view = io === "input" ? + this.manager.input.inputEditorView : + this.manager.output.outputEditorView; + + // 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) + ); + } - // // 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 0dc44dbe55..ff512f696f 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 496b0ac5d9..d1fd2532c5 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 384b1ab76c..f5b0efd463 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 7fcaa5099b..a63bfc1f02 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 05/79] 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 ba670f3d5a..cb196709ba 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 3b3bd5550b..971c1c574c 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 8b4375fe54..189d37779a 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 ff512f696f..69417b92f9 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 06/79] 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 045d8f0526..31e575a1b5 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 2d45d1f196..4ead8bc4f3 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 a46379e95b..9d03c728d2 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 69417b92f9..6a1b57df21 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; - - const value = this.getInput(); - const activeTab = this.manager.tabs.getActiveInputTab(); + debounce(function(e) { + // Ignore this function if the input is a file + const fileOverlay = document.getElementById("input-file"); + if (fileOverlay.style.display === "block") return; - this.app.progress = 0; + const value = this.getInput(); + const activeTab = this.manager.tabs.getActiveInputTab(); - 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 d1fd2532c5..3f031ac7c6 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 07/79] 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 9d03c728d2..820b1a8d40 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 6a1b57df21..ed8f174b3a 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 08/79] 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 | 27 ++-- 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, 318 insertions(+), 123 deletions(-) create mode 100644 src/web/utils/copyOverride.mjs diff --git a/src/core/Utils.mjs b/src/core/Utils.mjs index 5f36cae947..b72a602822 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 d5357d957c..69cad1db1f 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 aefe2ab717..7468ee1107 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 5f346e0063..fa1e90dce9 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 18eb071ea4..ef8b7f807e 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 c657adeb9a..a52b045117 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) { + let lineNo = Utils.hex(i, 8); + const buff = data.slice(i, i+length); - let hexa = ""; - for (let j = 0; j < buff.length; j++) { - hexa += Utils.hex(buff[j], padding) + " "; - } + const hex = []; + buff.forEach(b => hex.push(Utils.hex(b, padding))); + let hexStr = hex.join(" ").padEnd(length*(padding+1), " "); - let lineNo = Utils.hex(i, 8); + 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 9b548df80d..8c09773140 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 3eb150e531..a7931de5a7 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 cb196709ba..ea15b6ac02 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 fa2168365d..920aab890e 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 0000000000..51b2386bbb --- /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 fe6b83d457..cb0ebed161 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 fbce9b4916..5e5c41c1c4 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 7d9a3e2da4..36beef7e70 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 3f031ac7c6..deaeaed3fd 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 f4107e66b1..d907a67c08 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 09/79] 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 | 253 +++++------------- 15 files changed, 481 insertions(+), 422 deletions(-) diff --git a/src/core/Chef.mjs b/src/core/Chef.mjs index 36998cecf2..140774bc2c 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 d46a705dd1..8989875aed 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 b72a602822..604b7b8c91 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 c5cb560593..8934d137fb 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 9b01b79f14..0fc9d2b53e 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 8fc61fceec..8cc1450f07 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 ef8b7f807e..ae96fd0a29 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 820b1a8d40..793b61defb 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 a7931de5a7..68d69a7813 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 ea15b6ac02..185b3bdbaa 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 5e5c41c1c4..3480093331 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 431d8a3db8..f9be500660 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 ed8f174b3a..caa1a09811 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 deaeaed3fd..f0b03d721c 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 9912995bf0..e1c75de90f 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 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; + if (!Object.prototype.hasOwnProperty.call(self.inputs, inputNum)) + throw new Error(`No input with ID ${inputNum} exists`); - self.inputs[inputNum].data = data; + self.inputs[inputNum].buffer = inputData.buffer; + if (!("stringSample" in inputData)) { + inputData.stringSample = Utils.arrayBufferToStr(inputData.buffer.slice(0, 4096)); + } + 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 10/79] 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 | 209 ++++++++---------------- 6 files changed, 498 insertions(+), 164 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 793b61defb..730d6e2e39 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 68d69a7813..6e2c60a30b 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 185b3bdbaa..9c64fe85fc 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 0000000000..f8e3003b11 --- /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 0000000000..a8de0931a0 --- /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 caa1a09811..000940a4a8 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.clearFile(inputNum); + } - 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 - }); - } + // 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); + } - if (!silent) window.dispatchEvent(this.manager.statechange); + 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"); - - 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 + "%"; - } + // 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 + }) + ) + }); + } - this.displayFilePreview(inputNum, inputData); + /** + * Clears the file details panel + * + * @param {number} inputNum + */ + clearFile(inputNum) { + const activeTab = this.manager.tabs.getActiveInputTab(); + if (inputNum !== activeTab) return; - 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 11/79] 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 604b7b8c91..fec3b9beec 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 000940a4a8..86ad9873d7 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 f0b03d721c..a247375eb0 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 12/79] 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 f9be500660..efabea81c1 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 a247375eb0..f1965c77b7 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 13/79] 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 6e2c60a30b..9cf3e87844 100755 --- a/src/web/html/index.html +++ b/src/web/html/index.html @@ -482,11 +482,6 @@
    -
    - - -
    -
    -
    to
    - -
    KiB
    -
    -
    -
    -
    -
    diff --git a/src/web/stylesheets/components/_pane.css b/src/web/stylesheets/components/_pane.css index f251fa27a5..54e67b3bd6 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 2d40fe4c48..b6cc74bf00 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 f36841e1ba..843adcf0ec 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 28/79] 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 78c265323f..5cf9428f9d 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 4ed5d5ebd8..54662c0092 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 c1745eeb78..affc372d1c 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 43c5f89e27..d58e8d68bd 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 843adcf0ec..e88052d8c4 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 29/79] 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 b6cc74bf00..c29f855fd5 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 d58e8d68bd..8fe4e34822 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 e88052d8c4..6f888c4901 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 f2bd838596756fb232f5a85187405e01efb75661 Mon Sep 17 00:00:00 2001 From: n1474335 Date: Fri, 13 Jan 2023 13:12:01 +0000 Subject: [PATCH 30/79] 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 10340ea860..8ebfb1326d 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 3b7d4338d3..5bb18d2e09 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 00b8609159..f884c3e88f 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 8fe4e34822..4af09cf61f 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 31/79] 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 9d8273961b..3a63321da4 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 b3ac4e4a94..a24c7cd8de 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 32/79] 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 6f888c4901..608ad087c6 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 33/79] Updated dependencies --- package-lock.json | 2239 +++++++++++++++++++++++++---------- package.json | 56 +- src/web/utils/statusBar.mjs | 12 +- 3 files changed, 1633 insertions(+), 674 deletions(-) diff --git a/package-lock.json b/package-lock.json index d2cd4b9b30..502de66bf1 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,14 +8653,94 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsesc": { - "version": "3.0.2", - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" + "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", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" } }, "node_modules/json-parse-even-better-errors": { @@ -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 54662c0092..93f93dce9e 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 4af09cf61f..2110c60dde 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 34/79] 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 140774bc2c..dbde3e3f32 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 1afdef0103..11b1ff9f6d 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 d6f67698e2..3bb8122cd0 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 703b09800b..e1b32d6404 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 8769a69aa8..e3ea31b80b 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 d7768859fe..7de8810da4 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 849b575676..d89e3c0b22 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 a5897840f9..a762ea96d5 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)
    +
    + + +
    +