mirror of
https://github.com/gchq/CyberChef
synced 2025-01-15 22:13:56 +00:00
Input now uses CodeMirror editor
This commit is contained in:
parent
54fdc05e3a
commit
85ffe48743
17 changed files with 666 additions and 182 deletions
197
package-lock.json
generated
197
package-lock.json
generated
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"));
|
||||
});
|
||||
</script>`;
|
||||
|
|
|
@ -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
|
||||
|
|
190
src/web/extensions/statusBar.mjs
Normal file
190
src/web/extensions/statusBar.mjs
Normal file
|
@ -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 `<span data-toggle="tooltip" title="Input length">
|
||||
<i class="material-icons">abc</i>
|
||||
<span id="stats-length-value"></span>
|
||||
</span>
|
||||
<span data-toggle="tooltip" title="Number of lines">
|
||||
<i class="material-icons">sort</i>
|
||||
<span id="stats-lines-value"></span>
|
||||
</span>
|
||||
|
||||
<span id="sel-info" data-toggle="tooltip" title="Selection">
|
||||
<i class="material-icons">highlight_alt</i>
|
||||
<span id="sel-start-value"></span>\u279E<span id="sel-end-value"></span>
|
||||
(<span id="sel-length-value"></span> selected)
|
||||
</span>
|
||||
<span id="cur-offset-info" data-toggle="tooltip" title="Cursor offset">
|
||||
<i class="material-icons">location_on</i>
|
||||
<span id="cur-offset-value"></span>
|
||||
</span>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds the Right-hand-side widgets
|
||||
* Event listener set up in Manager
|
||||
* @returns {string}
|
||||
*/
|
||||
function constructRHS() {
|
||||
return `<span data-toggle="tooltip" title="Input character encoding">
|
||||
<i class="material-icons">language</i>
|
||||
<span id="char-enc-value">UTF-16</span>
|
||||
</span>
|
||||
|
||||
<div class="cm-status-bar-select eol-select">
|
||||
<span class="cm-status-bar-select-btn" data-toggle="tooltip" data-placement="bottom" title="End of line sequence">
|
||||
<i class="material-icons">keyboard_return</i> <span id="eol-value"></span>
|
||||
</span>
|
||||
<div class="cm-status-bar-select-content">
|
||||
<a href="#" data-val="LF">Line Feed, U+000A</a>
|
||||
<a href="#" data-val="VT">Vertical Tab, U+000B</a>
|
||||
<a href="#" data-val="FF">Form Feed, U+000C</a>
|
||||
<a href="#" data-val="CR">Carriage Return, U+000D</a>
|
||||
<a href="#" data-val="CRLF">CR+LF, U+000D U+000A</a>
|
||||
<!-- <a href="#" data-val="NL">Next Line, U+0085</a> This causes problems. -->
|
||||
<a href="#" data-val="LS">Line Separator, U+2028</a>
|
||||
<a href="#" data-val="PS">Paragraph Separator, U+2029</a>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
|
@ -219,8 +219,6 @@
|
|||
<label for="input-text">Input</label>
|
||||
<span class="pane-controls">
|
||||
<div class="io-info" id="input-files-info"></div>
|
||||
<div class="io-info" id="input-selection-info"></div>
|
||||
<div class="io-info" id="input-info"></div>
|
||||
<button type="button" class="btn btn-primary bmd-btn-icon" id="btn-new-tab" data-toggle="tooltip" title="Add a new input tab">
|
||||
<i class="material-icons">add</i>
|
||||
</button>
|
||||
|
@ -267,7 +265,7 @@
|
|||
</div>
|
||||
<div class="textarea-wrapper no-select input-wrapper" id="input-wrapper">
|
||||
<div id="input-highlighter" class="no-select"></div>
|
||||
<textarea id="input-text" class="input-text" spellcheck="false" tabindex="1" autofocus></textarea>
|
||||
<div id="input-text"></div>
|
||||
<div class="input-file" id="input-file">
|
||||
<div class="file-overlay" id="file-overlay"></div>
|
||||
<div style="position: relative; height: 100%;">
|
||||
|
|
BIN
src/web/static/fonts/MaterialIcons-Regular.ttf
Normal file
BIN
src/web/static/fonts/MaterialIcons-Regular.ttf
Normal file
Binary file not shown.
Binary file not shown.
|
@ -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 <div> - 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;
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 = "";
|
||||
}
|
||||
|
||||
|
|
|
@ -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 += "<br>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 <a href='#' onclick='document.getElementById(\"options\").click()'>Options pane</a>";
|
||||
const preserveStr = `A carriage return (\\r, 0x0d) was detected in your input. To preserve it, editing has been disabled.<br>${optionsStr}`;
|
||||
const dontPreserveStr = `A carriage return (\\r, 0x0d) was detected in your input. It has not been preserved.<br>${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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in a new issue