merge 8.0.1 release -> node-lib

This commit is contained in:
d98762625 2018-08-11 22:15:09 +01:00
commit 7c1ac4392e
63 changed files with 4608 additions and 4331 deletions

36
CHANGELOG.md Normal file
View File

@ -0,0 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
## [8.0.0] - 2018-08-05
- Codebase rewritten using [ES modules](https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/) and [classes](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes) #284
- Operation architecture restructured to make adding new operations a lot simpler #284
- A script has been added to aid in the creation of new operations by running `npm run newop` @n1474335 #284
- 'Magic' operation added - [automated detection of encoded data](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) @n1474335 #239
- UI updated to use [Bootstrap Material Design](https://fezvrasta.github.io/bootstrap-material-design/) @n1474335 #248
- `JSON`, `File` and `List<File>` Dish types added @n1474335 #284
- `OperationError` type added for better handling of errors thrown by operations @d98762625 #296
- A `present()` method has been added, allowing operations to pass machine-friendly data to subsequent operations whilst presenting human-friendly data to the user @n1474335 #284
- Set operations added @d98762625 #281
- 'To Table' operation added @JustAnotherMark #294
- 'Haversine distance' operation added @Dachande663 #325
- Started keeping a changelog @n1474335
## [7.0.0] - 2017-12-28
- Added support for loading, processing and downloading files up to 500MB @n1474335 #224
## [6.0.0] - 2017-09-19
- Added threading support, moving all recipe processing into a [Web Worker](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers) to increase performance and allow long-running operations to be cancelled @n1474335 #173
- Created modules so that operations relying on large libraries can be downloaded separately as required, reducing the initial loading time for the app @n1474335 #173
## [5.0.0] - 2017-03-30
- Configured Webpack build process, Babel transpilation and ES6 imports and exports @n1474335 #95
## [4.0.0] - 2016-11-28
- Initial open source commit @n1474335
[8.0.0]: https://github.com/gchq/CyberChef/releases/tag/v8.0.0
[7.0.0]: https://github.com/gchq/CyberChef/releases/tag/v7.0.0
[6.0.0]: https://github.com/gchq/CyberChef/releases/tag/v6.0.0
[5.0.0]: https://github.com/gchq/CyberChef/releases/tag/v5.0.0
[4.0.0]: https://github.com/gchq/CyberChef/commit/b1d73a725dc7ab9fb7eb789296efd2b7e4b08306

View File

@ -12,7 +12,7 @@
CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more. CyberChef is a simple, intuitive web app for carrying out all manner of "cyber" operations within a web browser. These operations include simple encoding like XOR or Base64, more complex encryption like AES, DES and Blowfish, creating binary and hexdumps, compression and decompression of data, calculating hashes and checksums, IPv6 and X.509 parsing, changing character encodings, and much more.
The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years. Every effort has been made to structure the code in a readable and extendable format, however it should be noted that the analyst is not a professional developer. The tool is designed to enable both technical and non-technical analysts to manipulate data in complex ways without having to deal with complex tools or algorithms. It was conceived, designed, built and incrementally improved by an analyst in their 10% innovation time over several years.
## Live demo ## Live demo
@ -43,16 +43,19 @@ You can use as many operations as you like in simple or complex ways. Some examp
- [Carry out different operations on data of different types][8] - [Carry out different operations on data of different types][8]
- [Use parts of the input as arguments to operations][9] - [Use parts of the input as arguments to operations][9]
- [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10] - [Perform AES decryption, extracting the IV from the beginning of the cipher stream][10]
- [Automagically detect several layers of nested encoding][12]
## Features ## Features
- Drag and drop - Drag and drop
- Operations can be dragged in and out of the recipe list, or reorganised. - Operations can be dragged in and out of the recipe list, or reorganised.
- Files can be dragged over the input box to load them directly into the browser. - Files up to 500MB can be dragged over the input box to load them directly into the browser.
- Auto Bake - Auto Bake
- Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately. - Whenever you modify the input or the recipe, CyberChef will automatically "bake" for you and produce the output immediately.
- This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance). - This can be turned off and operated manually if it is affecting performance (if the input is very large, for instance).
- Automated encoding detection
- CyberChef uses [a number of techniques](https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic) to attempt to automatically detect which encodings your data is under. If it finds a suitable operation which can make sense of your data, it displays the 'magic' icon in the Output field which you can click to decode your data.
- Breakpoints - Breakpoints
- You can set breakpoints on any operation in your recipe to pause execution before running it. - You can set breakpoints on any operation in your recipe to pause execution before running it.
- You can also step through the recipe one operation at a time to see what the data looks like at each stage. - You can also step through the recipe one operation at a time to see what the data looks like at each stage.
@ -81,6 +84,8 @@ CyberChef is built to support
## Contributing ## Contributing
Contributing a new operation to CyberChef is super easy! There is a quickstart script which will walk you through the process. If you can write basic JavaScript, you can write a CyberChef operation.
An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki). An installation walkthrough, how-to guides for adding new operations and themes, descriptions of the repository structure, available data types and coding conventions can all be found in the project [wiki pages](https://github.com/gchq/CyberChef/wiki).
- Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0) - Sign the [GCHQ Contributor Licence Agreement](https://github.com/gchq/Gaffer/wiki/GCHQ-OSS-Contributor-License-Agreement-V1.0)
@ -104,3 +109,4 @@ CyberChef is released under the [Apache 2.0 Licence](https://www.apache.org/lice
[9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ [9]: https://gchq.github.io/CyberChef/#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ
[10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg [10]: https://gchq.github.io/CyberChef/#recipe=Register('(.%7B32%7D)',true,false)Drop_bytes(0,32,false)AES_Decrypt(%7B'option':'Hex','string':'1748e7179bd56570d51fa4ba287cc3e5'%7D,%7B'option':'Hex','string':'$R0'%7D,'CTR','Hex','Raw',%7B'option':'Hex','string':''%7D)&input=NTFlMjAxZDQ2MzY5OGVmNWY3MTdmNzFmNWI0NzEyYWYyMGJlNjc0YjNiZmY1M2QzODU0NjM5NmVlNjFkYWFjNDkwOGUzMTljYTNmY2Y3MDg5YmZiNmIzOGVhOTllNzgxZDI2ZTU3N2JhOWRkNmYzMTFhMzk0MjBiODk3OGU5MzAxNGIwNDJkNDQ3MjZjYWVkZjU0MzZlYWY2NTI0MjljMGRmOTRiNTIxNjc2YzdjMmNlODEyMDk3YzI3NzI3M2M3YzcyY2Q4OWFlYzhkOWZiNGEyNzU4NmNjZjZhYTBhZWUyMjRjMzRiYTNiZmRmN2FlYjFkZGQ0Nzc2MjJiOTFlNzJjOWU3MDlhYjYwZjhkYWY3MzFlYzBjYzg1Y2UwZjc0NmZmMTU1NGE1YTNlYzI5MWNhNDBmOWU2MjlhODcyNTkyZDk4OGZkZDgzNDUzNGFiYTc5YzFhZDE2NzY3NjlhN2MwMTBiZjA0NzM5ZWNkYjY1ZDk1MzAyMzcxZDYyOWQ5ZTM3ZTdiNGEzNjFkYTQ2OGYxZWQ1MzU4OTIyZDJlYTc1MmRkMTFjMzY2ZjMwMTdiMTRhYTAxMWQyYWYwM2M0NGY5NTU3OTA5OGExNWUzY2Y5YjQ0ODZmOGZmZTljMjM5ZjM0ZGU3MTUxZjZjYTY1MDBmZTRiODUwYzNmMWMwMmU4MDFjYWYzYTI0NDY0NjE0ZTQyODAxNjE1YjhmZmFhMDdhYzgyNTE0OTNmZmRhN2RlNWRkZjMzNjg4ODBjMmI5NWIwMzBmNDFmOGYxNTA2NmFkZDA3MWE2NmNmNjBlNWY0NmYzYTIzMGQzOTdiNjUyOTYzYTIxYTUzZg
[11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4 [11]: https://gchq.github.io/CyberChef/#recipe=XOR(%7B'option':'Hex','string':'3a'%7D,'Standard',false)To_Hexdump(16,false,false)&input=VGhlIGFuc3dlciB0byB0aGUgdWx0aW1hdGUgcXVlc3Rpb24gb2YgbGlmZSwgdGhlIFVuaXZlcnNlLCBhbmQgZXZlcnl0aGluZyBpcyA0Mi4
[12]: https://gchq.github.io/CyberChef/#recipe=Magic(3,false,false)&input=V1VhZ3dzaWFlNm1QOGdOdENDTFVGcENwQ0IyNlJtQkRvREQ4UGFjZEFtekF6QlZqa0syUXN0RlhhS2hwQzZpVVM3UkhxWHJKdEZpc29SU2dvSjR3aGptMWFybTg2NHFhTnE0UmNmVW1MSHJjc0FhWmM1VFhDWWlmTmRnUzgzZ0RlZWpHWDQ2Z2FpTXl1QlY2RXNrSHQxc2NnSjg4eDJ0TlNvdFFEd2JHWTFtbUNvYjJBUkdGdkNLWU5xaU45aXBNcTFaVTFtZ2tkYk51R2NiNzZhUnRZV2hDR1VjOGc5M1VKdWRoYjhodHNoZVpud1RwZ3FoeDgzU1ZKU1pYTVhVakpUMnptcEM3dVhXdHVtcW9rYmRTaTg4WXRrV0RBYzFUb291aDJvSDRENGRkbU5LSldVRHBNd21uZ1VtSzE0eHdtb21jY1BRRTloTTE3MkFQblNxd3hkS1ExNzJSa2NBc3lzbm1qNWdHdFJtVk5OaDJzMzU5d3I2bVMyUVJQ

6415
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "cyberchef", "name": "cyberchef",
"version": "7.11.1", "version": "8.0.1",
"description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.", "description": "The Cyber Swiss Army Knife for encryption, encoding, compression and data analysis.",
"author": "n1474335 <n1474335@gmail.com>", "author": "n1474335 <n1474335@gmail.com>",
"homepage": "https://gchq.github.io/CyberChef", "homepage": "https://gchq.github.io/CyberChef",
@ -31,12 +31,14 @@
"module": "src/node/index.mjs", "module": "src/node/index.mjs",
"bugs": "https://github.com/gchq/CyberChef/issues", "bugs": "https://github.com/gchq/CyberChef/issues",
"devDependencies": { "devDependencies": {
"autoprefixer": "^9.1.0",
"babel-core": "^6.26.3", "babel-core": "^6.26.3",
"babel-loader": "^7.1.4", "babel-loader": "^7.1.5",
"babel-preset-env": "^1.6.1", "babel-preset-env": "^1.7.0",
"colors": "^1.3.0", "bootstrap": "^4.1.3",
"css-loader": "^0.28.11", "colors": "^1.3.1",
"eslint": "^4.19.1", "css-loader": "^1.0.0",
"eslint": "^5.3.0",
"exports-loader": "^0.7.0", "exports-loader": "^0.7.0",
"extract-text-webpack-plugin": "^4.0.0-alpha0", "extract-text-webpack-plugin": "^4.0.0-alpha0",
"file-loader": "^1.1.11", "file-loader": "^1.1.11",
@ -46,49 +48,49 @@
"grunt-concurrent": "^2.3.1", "grunt-concurrent": "^2.3.1",
"grunt-contrib-clean": "~1.1.0", "grunt-contrib-clean": "~1.1.0",
"grunt-contrib-copy": "~1.0.0", "grunt-contrib-copy": "~1.0.0",
"grunt-contrib-watch": "^1.0.1", "grunt-contrib-watch": "^1.1.0",
"grunt-eslint": "^20.1.0", "grunt-eslint": "^21.0.0",
"grunt-exec": "~3.0.0", "grunt-exec": "~3.0.0",
"grunt-jsdoc": "^2.2.1", "grunt-jsdoc": "^2.2.1",
"grunt-webpack": "^3.1.1", "grunt-webpack": "^3.1.2",
"html-webpack-plugin": "^3.2.0", "html-webpack-plugin": "^3.2.0",
"imports-loader": "^0.8.0", "imports-loader": "^0.8.0",
"ink-docstrap": "^1.3.2", "ink-docstrap": "^1.3.2",
"js-to-mjs": "^0.2.0", "js-to-mjs": "^0.2.0",
"jsdoc-babel": "^0.4.0", "jsdoc-babel": "^0.4.0",
"less": "^3.0.2", "node-sass": "^4.9.2",
"less-loader": "^4.1.0", "postcss-css-variables": "^0.9.0",
"postcss-css-variables": "^0.8.1", "postcss-import": "^12.0.0",
"postcss-import": "^11.1.0", "postcss-loader": "^2.1.6",
"postcss-loader": "^2.1.4",
"prompt": "^1.0.0", "prompt": "^1.0.0",
"sass-loader": "^7.1.0",
"sitemap": "^1.13.0", "sitemap": "^1.13.0",
"style-loader": "^0.21.0", "style-loader": "^0.21.0",
"url-loader": "^1.0.1", "url-loader": "^1.0.1",
"web-resource-inliner": "^4.2.1", "web-resource-inliner": "^4.2.1",
"webpack": "^4.6.0", "webpack": "^4.16.4",
"webpack-dev-server": "^3.1.3", "webpack-dev-server": "^3.1.5",
"webpack-node-externals": "^1.7.2", "webpack-node-externals": "^1.7.2",
"worker-loader": "^1.1.1" "worker-loader": "^2.0.0"
}, },
"dependencies": { "dependencies": {
"arrive": "^2.4.1",
"babel-plugin-transform-builtin-extend": "1.1.2", "babel-plugin-transform-builtin-extend": "1.1.2",
"babel-polyfill": "^6.26.0", "babel-polyfill": "^6.26.0",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bignumber.js": "^7.0.1", "bignumber.js": "^7.2.1",
"bootstrap": "^3.3.7", "bootstrap-colorpicker": "^2.5.3",
"bootstrap-colorpicker": "^2.5.2", "bootstrap-material-design": "^4.1.1",
"bootstrap-switch": "^3.3.4", "bson": "^3.0.2",
"bson": "^2.0.6",
"chi-squared": "^1.1.0", "chi-squared": "^1.1.0",
"crypto-api": "^0.8.0", "crypto-api": "^0.8.0",
"crypto-js": "^3.1.9-1", "crypto-js": "^3.1.9-1",
"ctph.js": "0.0.5", "ctph.js": "0.0.5",
"diff": "^3.5.0", "diff": "^3.5.0",
"es6-promisify": "^6.0.0", "es6-promisify": "^6.0.0",
"escodegen": "^1.9.1", "escodegen": "^1.11.0",
"esmangle": "^1.0.1", "esmangle": "^1.0.1",
"esprima": "^4.0.0", "esprima": "^4.0.1",
"exif-parser": "^0.1.12", "exif-parser": "^0.1.12",
"file-saver": "^1.3.8", "file-saver": "^1.3.8",
"highlight.js": "^9.12.0", "highlight.js": "^9.12.0",
@ -103,22 +105,24 @@
"lodash": "^4.17.10", "lodash": "^4.17.10",
"loglevel": "^1.6.1", "loglevel": "^1.6.1",
"loglevel-message-prefix": "^3.0.0", "loglevel-message-prefix": "^3.0.0",
"moment": "^2.22.1", "moment": "^2.22.2",
"moment-timezone": "^0.5.16", "moment-timezone": "^0.5.21",
"node-forge": "^0.7.5", "node-forge": "^0.7.5",
"node-md6": "^0.1.0", "node-md6": "^0.1.0",
"nwmatcher": "^1.4.4", "nwmatcher": "^1.4.4",
"otp": "^0.1.3", "otp": "^0.1.3",
"popper.js": "^1.14.4",
"scryptsy": "^2.0.0", "scryptsy": "^2.0.0",
"snackbarjs": "^1.1.0",
"sortablejs": "^1.7.0", "sortablejs": "^1.7.0",
"split.js": "^1.3.5", "split.js": "^1.3.5",
"ssdeep.js": "0.0.2", "ssdeep.js": "0.0.2",
"ua-parser-js": "^0.7.17", "ua-parser-js": "^0.7.18",
"utf8": "^3.0.0", "utf8": "^3.0.0",
"vkbeautify": "^0.99.3", "vkbeautify": "^0.99.3",
"xmldom": "^0.1.27", "xmldom": "^0.1.27",
"xpath": "0.0.27", "xpath": "0.0.27",
"xregexp": "^4.1.1", "xregexp": "^4.2.0",
"zlibjs": "^0.3.1" "zlibjs": "^0.3.1"
}, },
"scripts": { "scripts": {

View File

@ -96,7 +96,7 @@ class Chef {
const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING; const returnType = this.dish.size > threshold ? Dish.ARRAY_BUFFER : Dish.STRING;
// Create a raw version of the dish, unpresented // Create a raw version of the dish, unpresented
const rawDish = new Dish(this.dish); const rawDish = this.dish.clone();
// Present the raw result // Present the raw result
await recipe.present(this.dish); await recipe.present(this.dish);

View File

@ -301,6 +301,69 @@ class Dish {
} }
} }
/**
* Returns a deep clone of the current Dish.
*
* @returns {Dish}
*/
clone() {
const newDish = new Dish();
switch (this.type) {
case Dish.STRING:
case Dish.HTML:
case Dish.NUMBER:
case Dish.BIG_NUMBER:
// These data types are immutable so it is acceptable to copy them by reference
newDish.set(
this.value,
this.type
);
break;
case Dish.BYTE_ARRAY:
case Dish.JSON:
// These data types are mutable so they need to be copied by value
newDish.set(
JSON.parse(JSON.stringify(this.value)),
this.type
);
break;
case Dish.ARRAY_BUFFER:
// Slicing an ArrayBuffer returns a new ArrayBuffer with a copy its contents
newDish.set(
this.value.slice(0),
this.type
);
break;
case Dish.FILE:
// A new file can be created by copying over all the values from the original
newDish.set(
new File([this.value], this.value.name, {
"type": this.value.type,
"lastModified": this.value.lastModified
}),
this.type
);
break;
case Dish.LIST_FILE:
newDish.set(
this.value.map(f =>
new File([f], f.name, {
"type": f.type,
"lastModified": f.lastModified
})
),
this.type
);
break;
default:
throw new Error("Cannot clone Dish, unknown type");
}
return newDish;
}
} }

View File

@ -21,7 +21,10 @@ class Ingredient {
this.name = ""; this.name = "";
this.type = ""; this.type = "";
this._value = null; this._value = null;
this.disabled = false;
this.hint = "";
this.toggleValues = []; this.toggleValues = [];
this.target = null;
if (ingredientConfig) { if (ingredientConfig) {
this._parseConfig(ingredientConfig); this._parseConfig(ingredientConfig);
@ -39,7 +42,10 @@ class Ingredient {
this.name = ingredientConfig.name; this.name = ingredientConfig.name;
this.type = ingredientConfig.type; this.type = ingredientConfig.type;
this.defaultValue = ingredientConfig.value; this.defaultValue = ingredientConfig.value;
this.disabled = !!ingredientConfig.disabled;
this.hint = ingredientConfig.hint || false;
this.toggleValues = ingredientConfig.toggleValues; this.toggleValues = ingredientConfig.toggleValues;
this.target = typeof ingredientConfig.target !== "undefined" ? ingredientConfig.target : null;
} }

View File

@ -170,12 +170,17 @@ class Operation {
*/ */
get args() { get args() {
return this._ingList.map(ing => { return this._ingList.map(ing => {
return { const conf = {
name: ing.name, name: ing.name,
type: ing.type, type: ing.type,
value: ing.defaultValue, value: ing.defaultValue
toggleValues: ing.toggleValues || []
}; };
if (ing.toggleValues) conf.toggleValues = ing.toggleValues;
if (ing.hint) conf.hint = ing.hint;
if (ing.disabled) conf.disabled = ing.disabled;
if (ing.target) conf.target = ing.target;
return conf;
}); });
} }

View File

@ -828,11 +828,11 @@ class Utils {
*/ */
static async displayFilesAsHTML(files) { static async displayFilesAsHTML(files) {
const formatDirectory = function(file) { const formatDirectory = function(file) {
const html = `<div class='panel panel-default' style='white-space: normal;'> const html = `<div class='card' style='white-space: normal;'>
<div class='panel-heading' role='tab'> <div class='card-header'>
<h4 class='panel-title'> <h6 class="mb-0">
${Utils.escapeHtml(file.name)} ${Utils.escapeHtml(file.name)}
</h4> </h6>
</div> </div>
</div>`; </div>`;
return html; return html;
@ -845,29 +845,29 @@ class Utils {
{type: "octet/stream"} {type: "octet/stream"}
); );
const html = `<div class='panel panel-default' style='white-space: normal;'> const html = `<div class='card' style='white-space: normal;'>
<div class='panel-heading' role='tab' id='heading${i}'> <div class='card-header' id='heading${i}'>
<h4 class='panel-title'> <h6 class='mb-0'>
<div> <a class='collapsed'
<a href='#collapse${i}' data-toggle='collapse'
class='collapsed' href='#collapse${i}'
data-toggle='collapse' aria-expanded='false'
aria-expanded='true' aria-controls='collapse${i}'
aria-controls='collapse${i}' title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">
title="Show/hide contents of '${Utils.escapeHtml(file.name)}'">${Utils.escapeHtml(file.name)}</a> ${Utils.escapeHtml(file.name)}</a>
<a href='${URL.createObjectURL(blob)}' <span class='float-right' style="margin-top: -3px">
title='Download ${Utils.escapeHtml(file.name)}' ${file.size.toLocaleString()} bytes
download='${Utils.escapeHtml(file.name)}'>&#x1f4be;</a> <a title="Download ${Utils.escapeHtml(file.name)}"
<span class='pull-right'> href='${URL.createObjectURL(blob)}'
${file.size.toLocaleString()} bytes download='${Utils.escapeHtml(file.name)}'>
</span> <i class="material-icons" style="vertical-align: bottom">save</i>
</div> </a>
</h4> </span>
</h6>
</div> </div>
<div id='collapse${i}' class='panel-collapse collapse' <div id='collapse${i}' class='collapse' aria-labelledby='heading${i}' data-parent="#files">
role='tabpanel' aria-labelledby='heading${i}'> <div class='card-body'>
<div class='panel-body'> <pre>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</pre>
<pre><code>${Utils.escapeHtml(Utils.arrayBufferToStr(buff.buffer))}</code></pre>
</div> </div>
</div> </div>
</div>`; </div>`;
@ -875,8 +875,8 @@ class Utils {
}; };
let html = `<div style='padding: 5px; white-space: normal;'> let html = `<div style='padding: 5px; white-space: normal;'>
${files.length} file(s) found<NL> ${files.length} file(s) found
</div>`; </div><div id="files" style="padding: 20px">`;
for (let i = 0; i < files.length; i++) { for (let i = 0; i < files.length; i++) {
if (files[i].name.endsWith("/")) { if (files[i].name.endsWith("/")) {
@ -886,7 +886,7 @@ class Utils {
} }
} }
return html; return html += "</div>";
} }

View File

@ -328,6 +328,7 @@
"Generate UUID", "Generate UUID",
"Generate TOTP", "Generate TOTP",
"Generate HOTP", "Generate HOTP",
"Haversine distance",
"Render Image", "Render Image",
"Remove EXIF", "Remove EXIF",
"Extract EXIF", "Extract EXIF",

View File

@ -48,7 +48,7 @@ export function drawBarChart(canvas, scores, xAxisLabel, yAxisLabel, numXLabels,
leftPadding = canvas.width * 0.08, leftPadding = canvas.width * 0.08,
rightPadding = canvas.width * 0.03, rightPadding = canvas.width * 0.03,
topPadding = canvas.height * 0.08, topPadding = canvas.height * 0.08,
bottomPadding = canvas.height * 0.15, bottomPadding = canvas.height * 0.2,
graphHeight = canvas.height - topPadding - bottomPadding, graphHeight = canvas.height - topPadding - bottomPadding,
graphWidth = canvas.width - leftPadding - rightPadding, graphWidth = canvas.width - leftPadding - rightPadding,
base = topPadding + graphHeight, base = topPadding + graphHeight,
@ -146,7 +146,7 @@ export function drawScaleBar(canvas, score, max, markings) {
leftPadding = canvas.width * 0.01, leftPadding = canvas.width * 0.01,
rightPadding = canvas.width * 0.01, rightPadding = canvas.width * 0.01,
topPadding = canvas.height * 0.1, topPadding = canvas.height * 0.1,
bottomPadding = canvas.height * 0.3, bottomPadding = canvas.height * 0.35,
barHeight = canvas.height - topPadding - bottomPadding, barHeight = canvas.height - topPadding - bottomPadding,
barWidth = canvas.width - leftPadding - rightPadding; barWidth = canvas.width - leftPadding - rightPadding;

View File

@ -49,8 +49,8 @@ export const DATETIME_FORMATS = [
* MomentJS DateTime formatting examples. * MomentJS DateTime formatting examples.
*/ */
export const FORMAT_EXAMPLES = `Format string tokens: export const FORMAT_EXAMPLES = `Format string tokens:
<table class="table table-striped table-hover table-condensed table-bordered" style="font-family: sans-serif"> <table class="table table-striped table-hover table-sm table-bordered" style="font-family: sans-serif">
<thead> <thead class="thead-dark">
<tr> <tr>
<th>Category</th> <th>Category</th>
<th>Token</th> <th>Token</th>

View File

@ -287,6 +287,8 @@ class Magic {
useful: useful useful: useful
}); });
const prevOp = recipeConfig[recipeConfig.length - 1];
// Execute each of the matching operations, then recursively call the speculativeExecution() // Execute each of the matching operations, then recursively call the speculativeExecution()
// method on the resulting data, recording the properties of each option. // method on the resulting data, recording the properties of each option.
await Promise.all(matchingOps.map(async op => { await Promise.all(matchingOps.map(async op => {
@ -294,8 +296,14 @@ class Magic {
op: op.op, op: op.op,
args: op.args args: op.args
}, },
output = await this._runRecipe([opConfig]), output = await this._runRecipe([opConfig]);
magic = new Magic(output, this.opPatterns),
// If the recipe is repeating and returning the same data, do not continue
if (prevOp && op.op === prevOp.op && _buffersEqual(output, this.inputBuffer)) {
return;
}
const magic = new Magic(output, this.opPatterns),
speculativeResults = await magic.speculativeExecution( speculativeResults = await magic.speculativeExecution(
depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful); depth-1, extLang, intensive, [...recipeConfig, opConfig], op.useful);
@ -315,13 +323,16 @@ class Magic {
})); }));
} }
// Prune branches that do not match anything // Prune branches that result in unhelpful outputs
results = results.filter(r => results = results.filter(r =>
r.languageScores[0].probability > 0 || (r.useful || r.data.length > 0) && // The operation resulted in ""
r.fileType || ( // One of the following must be true
r.isUTF8 || r.languageScores[0].probability > 0 || // Some kind of language was found
r.matchingOps.length || r.fileType || // A file was found
r.useful); r.isUTF8 || // UTF-8 was found
r.matchingOps.length // A matching op was found
)
);
// Return a sorted list of possible recipes along with their properties // Return a sorted list of possible recipes along with their properties
return results.sort((a, b) => { return results.sort((a, b) => {
@ -374,7 +385,7 @@ class Magic {
const recipe = new Recipe(recipeConfig); const recipe = new Recipe(recipeConfig);
try { try {
await recipe.execute(dish, 0); await recipe.execute(dish);
return dish.get(Dish.ARRAY_BUFFER); return dish.get(Dish.ARRAY_BUFFER);
} catch (err) { } catch (err) {
// If there are errors, return an empty buffer // If there are errors, return an empty buffer
@ -395,7 +406,10 @@ class Magic {
let i = len; let i = len;
const counts = new Array(256).fill(0); const counts = new Array(256).fill(0);
if (!len) return counts; if (!len) {
this.freqDist = counts;
return this.freqDist;
}
while (i--) { while (i--) {
counts[this.inputBuffer[i]]++; counts[this.inputBuffer[i]]++;

View File

@ -47,9 +47,10 @@ class Diff extends Operation {
"value": true "value": true
}, },
{ {
"name": "Ignore whitespace (relevant for word and line)", "name": "Ignore whitespace",
"type": "boolean", "type": "boolean",
"value": false "value": false,
"hint": "Relevant for word and line"
} }
]; ];
} }

View File

@ -20,7 +20,7 @@ class Entropy extends Operation {
this.name = "Entropy"; this.name = "Entropy";
this.module = "Default"; this.module = "Default";
this.description = "Calculates the Shannon entropy of the input data which gives an idea of its randomness. 8 is the maximum."; this.description = "Shannon Entropy, in the context of information theory, is a measure of the rate at which information is produced by a source of data. It can be used, in a broad sense, to detect whether data is likely to be structured or unstructured. 8 is the maximum, representing highly unstructured, 'random' data. English language text usually falls somewhere between 3.5 and 5. Properly encrypted or compressed data should have an entropy of over 7.5.";
this.inputType = "byteArray"; this.inputType = "byteArray";
this.outputType = "number"; this.outputType = "number";
this.presentType = "html"; this.presentType = "html";

View File

@ -26,7 +26,7 @@ class FromHexdump extends Operation {
this.args = []; this.args = [];
this.patterns = [ this.patterns = [
{ {
match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?)+$", match: "^(?:(?:[\\dA-F]{4,16}h?:?)?[ \\t]*((?:[\\dA-F]{2} ){1,8}(?:[ \\t]|[\\dA-F]{2}-)(?:[\\dA-F]{2} ){1,8}|(?:[\\dA-F]{4} )*[\\dA-F]{4}|(?:[\\dA-F]{2} )*[\\dA-F]{2})[^\\n]*\\n?){2,}$",
flags: "i", flags: "i",
args: [] args: []
}, },

View File

@ -0,0 +1,58 @@
/**
* @author Dachande663 [dachande663@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import Operation from "../Operation";
import OperationError from "../errors/OperationError";
/**
* HaversineDistance operation
*/
class HaversineDistance extends Operation {
/**
* HaversineDistance constructor
*/
constructor() {
super();
this.name = "Haversine distance";
this.module = "Default";
this.description = "Returns the distance between two pairs of GPS latitude and longitude co-ordinates in metres.<br><br>e.g. <code>51.487263,-0.124323, 38.9517,-77.1467</code>";
this.inputType = "string";
this.outputType = "number";
this.args = [];
}
/**
* @param {string} input
* @param {Object[]} args
* @returns {number}
*/
run(input, args) {
const values = input.match(/^(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?), ?(-?\d+(\.\d+)?)$/);
if (!values) {
throw new OperationError("Input must in the format lat1, lng1, lat2, lng2");
}
const lat1 = parseFloat(values[1]);
const lng1 = parseFloat(values[3]);
const lat2 = parseFloat(values[6]);
const lng2 = parseFloat(values[8]);
const TO_RAD = Math.PI / 180;
const dLat = (lat2-lat1) * TO_RAD;
const dLng = (lng2-lng1) * TO_RAD;
const a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1 * TO_RAD) * Math.cos(lat2 * TO_RAD) * Math.sin(dLng/2) * Math.sin(dLng/2);
const metres = 6371000 * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return metres;
}
}
export default HaversineDistance;

View File

@ -23,7 +23,7 @@ class Magic extends Operation {
this.name = "Magic"; this.name = "Magic";
this.flowControl = true; this.flowControl = true;
this.module = "Default"; this.module = "Default";
this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.<br><br><b>Options</b><br><u>Depth:</u> If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.<br><br><u>Intensive mode:</u> When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.<br><br><u>Extensive language support:</u> At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar."; this.description = "The Magic operation attempts to detect various properties of the input data and suggests which operations could help to make more sense of it.<br><br><b>Options</b><br><u>Depth:</u> If an operation appears to match the data, it will be run and the result will be analysed further. This argument controls the maximum number of levels of recursion.<br><br><u>Intensive mode:</u> When this is turned on, various operations like XOR, bit rotates, and character encodings are brute-forced to attempt to detect valid data underneath. To improve performance, only the first 100 bytes of the data is brute-forced.<br><br><u>Extensive language support:</u> At each stage, the relative byte frequencies of the data will be compared to average frequencies for a number of languages. The default set consists of ~40 of the most commonly used languages on the Internet. The extensive list consists of 284 languages and can result in many languages matching the data if their byte frequencies are similar.<br><br>A technical explanation of the Magic operation can be found <a href='https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic'>here</a>.";
this.inputType = "ArrayBuffer"; this.inputType = "ArrayBuffer";
this.outputType = "JSON"; this.outputType = "JSON";
this.presentType = "html"; this.presentType = "html";
@ -77,7 +77,7 @@ class Magic extends Operation {
const currentRecipeConfig = this.state.opList.map(op => op.config); const currentRecipeConfig = this.state.opList.map(op => op.config);
let output = `<table let output = `<table
class='table table-hover table-condensed table-bordered' class='table table-hover table-sm table-bordered'
style='table-layout: fixed;'> style='table-layout: fixed;'>
<tr> <tr>
<th>Recipe (click to load)</th> <th>Recipe (click to load)</th>

View File

@ -113,7 +113,7 @@ CMYK: ${cmyk}
document.getElementById('input-text').value = 'rgba(' + document.getElementById('input-text').value = 'rgba(' +
color.r + ', ' + color.g + ', ' + color.b + ', ' + color.a + ')'; color.r + ', ' + color.g + ', ' + color.b + ', ' + color.a + ')';
window.app.autoBake(); window.app.autoBake();
}); }).children(".colorpicker").removeClass('dropdown-menu');
</script>`; </script>`;
} }

View File

@ -7,7 +7,6 @@
import Operation from "../Operation"; import Operation from "../Operation";
import moment from "moment-timezone"; import moment from "moment-timezone";
import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime";
import OperationError from "../errors/OperationError";
/** /**
* Parse DateTime operation * Parse DateTime operation
@ -60,7 +59,7 @@ class ParseDateTime extends Operation {
date = moment.tz(input, inputFormat, inputTimezone); date = moment.tz(input, inputFormat, inputTimezone);
if (!date || date.format() === "Invalid date") throw Error; if (!date || date.format() === "Invalid date") throw Error;
} catch (err) { } catch (err) {
throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`); return `Invalid format.\n\n${FORMAT_EXAMPLES}`;
} }
output += "Date: " + date.format("dddd Do MMMM YYYY") + output += "Date: " + date.format("dddd Do MMMM YYYY") +

View File

@ -97,7 +97,7 @@ class ParseIPv4Header extends Operation {
checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")"; checksumResult = givenChecksum + " (incorrect, should be " + correctChecksum + ")";
} }
output = `<table class='table table-hover table-condensed table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr> output = `<table class='table table-hover table-sm table-bordered table-nonfluid'><tr><th>Field</th><th>Value</th></tr>
<tr><td>Version</td><td>${version}</td></tr> <tr><td>Version</td><td>${version}</td></tr>
<tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr> <tr><td>Internet Header Length (IHL)</td><td>${ihl} (${ihl * 4} bytes)</td></tr>
<tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr> <tr><td>Differentiated Services Code Point (DSCP)</td><td>${dscp}</td></tr>

View File

@ -26,7 +26,8 @@ class RenderImage extends Operation {
this.module = "Image"; this.module = "Image";
this.description = "Displays the input as an image. Supports the following formats:<br><br><ul><li>jpg/jpeg</li><li>png</li><li>gif</li><li>webp</li><li>bmp</li><li>ico</li></ul>"; this.description = "Displays the input as an image. Supports the following formats:<br><br><ul><li>jpg/jpeg</li><li>png</li><li>gif</li><li>webp</li><li>bmp</li><li>ico</li></ul>";
this.inputType = "string"; this.inputType = "string";
this.outputType = "html"; this.outputType = "byteArray";
this.presentType = "html";
this.args = [ this.args = [
{ {
"name": "Input format", "name": "Input format",
@ -51,9 +52,8 @@ class RenderImage extends Operation {
*/ */
run(input, args) { run(input, args) {
const inputFormat = args[0]; const inputFormat = args[0];
let dataURI = "data:";
if (!input.length) return ""; if (!input.length) return [];
// Convert input to raw bytes // Convert input to raw bytes
switch (inputFormat) { switch (inputFormat) {
@ -73,6 +73,26 @@ class RenderImage extends Operation {
// Determine file type // Determine file type
const type = Magic.magicFileType(input); const type = Magic.magicFileType(input);
if (!(type && type.mime.indexOf("image") === 0)) {
throw new OperationError("Invalid file type");
}
return input;
}
/**
* Displays the image using HTML for web apps.
*
* @param {byteArray} data
* @returns {html}
*/
async present(data) {
if (!data.length) return "";
let dataURI = "data:";
// Determine file type
const type = Magic.magicFileType(data);
if (type && type.mime.indexOf("image") === 0) { if (type && type.mime.indexOf("image") === 0) {
dataURI += type.mime + ";"; dataURI += type.mime + ";";
} else { } else {
@ -80,7 +100,7 @@ class RenderImage extends Operation {
} }
// Add image data to URI // Add image data to URI
dataURI += "base64," + toBase64(input); dataURI += "base64," + toBase64(data);
return "<img src='" + dataURI + "'>"; return "<img src='" + dataURI + "'>";
} }

View File

@ -147,12 +147,12 @@ class ToTable extends Operation {
*/ */
function htmlOutput(tableData) { function htmlOutput(tableData) {
// Start the HTML output with suitable classes for styling. // Start the HTML output with suitable classes for styling.
let output = "<table class='table table-hover table-condensed table-bordered table-nonfluid'>"; let output = "<table class='table table-hover table-sm table-bordered table-nonfluid'>";
// If the first row is a header then put it in <thead> with <th> cells. // If the first row is a header then put it in <thead> with <th> cells.
if (firstRowHeader) { if (firstRowHeader) {
const row = tableData.shift(); const row = tableData.shift();
output += "<thead>"; output += "<thead class='thead-light'>";
output += outputRow(row, "th"); output += outputRow(row, "th");
output += "</thead>"; output += "</thead>";
} }

View File

@ -7,7 +7,6 @@
import Operation from "../Operation"; import Operation from "../Operation";
import moment from "moment-timezone"; import moment from "moment-timezone";
import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime"; import {DATETIME_FORMATS, FORMAT_EXAMPLES} from "../lib/DateTime";
import OperationError from "../errors/OperationError";
/** /**
* Translate DateTime Format operation * Translate DateTime Format operation
@ -68,7 +67,7 @@ class TranslateDateTimeFormat extends Operation {
date = moment.tz(input, inputFormat, inputTimezone); date = moment.tz(input, inputFormat, inputTimezone);
if (!date || date.format() === "Invalid date") throw Error; if (!date || date.format() === "Invalid date") throw Error;
} catch (err) { } catch (err) {
throw new OperationError(`Invalid format.\n\n${FORMAT_EXAMPLES}`); return `Invalid format.\n\n${FORMAT_EXAMPLES}`;
} }
return date.tz(outputTimezone).format(outputFormat); return date.tz(outputTimezone).format(outputFormat);

20
src/test.mjs Normal file
View File

@ -0,0 +1,20 @@
import Dish from "./core/Dish";
const a = new Dish();
const i = "original";
a.set(i, Dish.STRING);
console.log(a);
const b = a.clone();
console.log(b);
console.log("changing a");
a.value.toUpperCase();
// const c = new Uint8Array([1,2,3,4,5,6,7,8,9,0]).buffer;
// a.set(c, Dish.ARRAY_BUFFER);
console.log(a);
console.log(b);

View File

@ -105,7 +105,7 @@ class App {
handleError(err, logToConsole) { handleError(err, logToConsole) {
if (logToConsole) log.error(err); if (logToConsole) log.error(err);
const msg = err.displayStr || err.toString(); const msg = err.displayStr || err.toString();
this.alert(msg, "danger", this.options.errorTimeout, !this.options.showErrors); this.alert(msg, this.options.errorTimeout, !this.options.showErrors);
} }
@ -183,9 +183,10 @@ class App {
* Sets the user's input data. * Sets the user's input data.
* *
* @param {string} input - The string to set the input to * @param {string} input - The string to set the input to
* @param {boolean} [silent=false] - Suppress statechange event
*/ */
setInput(input) { setInput(input, silent=false) {
this.manager.input.set(input); this.manager.input.set(input, silent);
} }
@ -240,17 +241,16 @@ class App {
initialiseSplitter() { initialiseSplitter() {
this.columnSplitter = Split(["#operations", "#recipe", "#IO"], { this.columnSplitter = Split(["#operations", "#recipe", "#IO"], {
sizes: [20, 30, 50], sizes: [20, 30, 50],
minSize: [240, 325, 450], minSize: [240, 370, 450],
gutterSize: 4, gutterSize: 4,
onDrag: function() { onDrag: function() {
this.manager.controls.adjustWidth(); this.manager.recipe.adjustWidth();
this.manager.output.adjustWidth();
}.bind(this) }.bind(this)
}); });
this.ioSplitter = Split(["#input", "#output"], { this.ioSplitter = Split(["#input", "#output"], {
direction: "vertical", direction: "vertical",
gutterSize: 4, gutterSize: 4
}); });
this.resetLayout(); this.resetLayout();
@ -320,8 +320,8 @@ class App {
if (this.operations.hasOwnProperty(favourites[i])) { if (this.operations.hasOwnProperty(favourites[i])) {
validFavs.push(favourites[i]); validFavs.push(favourites[i]);
} else { } else {
this.alert("The operation \"" + Utils.escapeHtml(favourites[i]) + this.alert(`The operation "${Utils.escapeHtml(favourites[i])}" is no longer available. ` +
"\" is no longer available. It has been removed from your favourites.", "info"); "It has been removed from your favourites.");
} }
} }
return validFavs; return validFavs;
@ -337,7 +337,6 @@ class App {
if (!this.isLocalStorageAvailable()) { if (!this.isLocalStorageAvailable()) {
this.alert( this.alert(
"Your security settings do not allow access to local storage so your favourites cannot be saved.", "Your security settings do not allow access to local storage so your favourites cannot be saved.",
"danger",
5000 5000
); );
return false; return false;
@ -368,7 +367,7 @@ class App {
const favourites = JSON.parse(localStorage.favourites); const favourites = JSON.parse(localStorage.favourites);
if (favourites.indexOf(name) >= 0) { if (favourites.indexOf(name) >= 0) {
this.alert("'" + name + "' is already in your favourites", "info", 2000); this.alert(`'${name}' is already in your favourites`, 3000);
return; return;
} }
@ -421,12 +420,12 @@ class App {
if (this.uriParams.input) { if (this.uriParams.input) {
try { try {
const inputData = fromBase64(this.uriParams.input); const inputData = fromBase64(this.uriParams.input);
this.setInput(inputData); this.setInput(inputData, true);
} catch (err) {} } catch (err) {}
} }
this.autoBakePause = false; this.autoBakePause = false;
this.autoBake(); window.dispatchEvent(this.manager.statechange);
} }
@ -476,9 +475,8 @@ class App {
} else if (args[j].classList.contains("toggle-string")) { } else if (args[j].classList.contains("toggle-string")) {
// toggleString // toggleString
args[j].value = recipeConfig[i].args[j].string; args[j].value = recipeConfig[i].args[j].string;
args[j].previousSibling.children[0].innerHTML = args[j].parentNode.parentNode.querySelector("button").innerHTML =
Utils.escapeHtml(recipeConfig[i].args[j].option) + Utils.escapeHtml(recipeConfig[i].args[j].option);
" <span class='caret'></span>";
} else { } else {
// all others // all others
args[j].value = recipeConfig[i].args[j]; args[j].value = recipeConfig[i].args[j];
@ -507,9 +505,7 @@ class App {
resetLayout() { resetLayout() {
this.columnSplitter.setSizes([20, 30, 50]); this.columnSplitter.setSizes([20, 30, 50]);
this.ioSplitter.setSizes([50, 50]); this.ioSplitter.setSizes([50, 50]);
this.manager.recipe.adjustWidth();
this.manager.controls.adjustWidth();
this.manager.output.adjustWidth();
} }
@ -529,9 +525,9 @@ class App {
else if (prev[1] > 0) prev[1]--; else if (prev[1] > 0) prev[1]--;
else prev[0]--; else prev[0]--;
const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`; //const compareURL = `https://github.com/gchq/CyberChef/compare/v${prev.join(".")}...v${PKG_VERSION}`;
let compileInfo = `<a href='${compareURL}'>Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago</a>`; let compileInfo = `<a href='https://github.com/gchq/CyberChef/blob/master/CHANGELOG.md'>Last build: ${timeSinceCompile.substr(0, 1).toUpperCase() + timeSinceCompile.substr(1)} ago</a>`;
if (window.compileMessage !== "") { if (window.compileMessage !== "") {
compileInfo += " - " + window.compileMessage; compileInfo += " - " + window.compileMessage;
@ -561,63 +557,35 @@ class App {
* Pops up a message to the user and writes it to the console log. * Pops up a message to the user and writes it to the console log.
* *
* @param {string} str - The message to display (HTML supported) * @param {string} str - The message to display (HTML supported)
* @param {string} style - The colour of the popup * @param {number} timeout - The number of milliseconds before the alert closes automatically
* "danger" = red
* "warning" = amber
* "info" = blue
* "success" = green
* @param {number} timeout - The number of milliseconds before the popup closes automatically
* 0 for never (until the user closes it) * 0 for never (until the user closes it)
* @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the * @param {boolean} [silent=false] - Don't show the message in the popup, only print it to the
* console * console
* *
* @example * @example
* // Pops up a red box with the message "[current time] Error: Something has gone wrong!" * // Pops up a box with the message "Error: Something has gone wrong!" that will need to be
* // that will need to be dismissed by the user. * // dismissed by the user.
* this.alert("Error: Something has gone wrong!", "danger", 0); * this.alert("Error: Something has gone wrong!", 0);
* *
* // Pops up a blue information box with the message "[current time] Happy Christmas!" * // Pops up a box with the message "Happy Christmas!" that will disappear after 5 seconds.
* // that will disappear after 5 seconds. * this.alert("Happy Christmas!", 5000);
* this.alert("Happy Christmas!", "info", 5000);
*/ */
alert(str, style, timeout, silent) { alert(str, timeout, silent) {
const time = new Date(); const time = new Date();
log.info("[" + time.toLocaleString() + "] " + str); log.info("[" + time.toLocaleString() + "] " + str);
if (silent) return; if (silent) return;
style = style || "danger";
timeout = timeout || 0; timeout = timeout || 0;
const alertEl = document.getElementById("alert"), this.currentSnackbar = $.snackbar({
alertContent = document.getElementById("alert-content"); content: str,
timeout: timeout,
alertEl.classList.remove("alert-danger"); htmlAllowed: true,
alertEl.classList.remove("alert-warning"); onClose: () => {
alertEl.classList.remove("alert-info"); this.currentSnackbar.remove();
alertEl.classList.remove("alert-success"); }
alertEl.classList.add("alert-" + style); });
// If the box hasn't been closed, append to it rather than replacing
if (alertEl.style.display === "block") {
alertContent.innerHTML +=
"<br><br>[" + time.toLocaleTimeString() + "] " + str;
} else {
alertContent.innerHTML =
"[" + time.toLocaleTimeString() + "] " + str;
}
// Stop the animation if it is in progress
$("#alert").stop();
alertEl.style.display = "block";
alertEl.style.opacity = 1;
if (timeout > 0) {
clearTimeout(this.alertTimeout);
this.alertTimeout = setTimeout(function(){
$("#alert").slideUp(100);
}, timeout);
}
} }
@ -658,15 +626,6 @@ class App {
} }
/**
* Handler for the alert close button click event.
* Closes the alert box.
*/
alertCloseClick() {
document.getElementById("alert").style.display = "none";
}
/** /**
* Handler for CyerChef statechange events. * Handler for CyerChef statechange events.
* Fires whenever the input or recipe changes in any way. * Fires whenever the input or recipe changes in any way.
@ -712,42 +671,6 @@ class App {
this.loadURIParams(); this.loadURIParams();
} }
/**
* Function to call an external API from this view.
*/
callApi(url, type, data, dataType, contentType) {
type = type || "POST";
data = data || {};
dataType = dataType || undefined;
contentType = contentType || "application/json";
let response = null,
success = false;
$.ajax({
url: url,
async: false,
type: type,
data: data,
dataType: dataType,
contentType: contentType,
success: function(data) {
success = true;
response = data;
},
error: function(data) {
success = false;
response = data;
},
});
return {
success: success,
response: response
};
}
} }
export default App; export default App;

View File

@ -120,7 +120,7 @@ class BackgroundWorkerWaiter {
* @param {string|ArrayBuffer} input * @param {string|ArrayBuffer} input
*/ */
magic(input) { magic(input) {
// If we're still working on the previous bake, cancel it before stating a new one. // If we're still working on the previous bake, cancel it before starting a new one.
if (this.completedCallback + 1 < this.callbackID) { if (this.completedCallback + 1 < this.callbackID) {
clearTimeout(this.timeout); clearTimeout(this.timeout);
this.cancelBake(); this.cancelBake();

View File

@ -26,44 +26,16 @@ class ControlsWaiter {
/** /**
* Adjusts the display properties of the control buttons so that they fit within the current width * Initialise Bootstrap componenets
* without wrapping or overflowing.
*/ */
adjustWidth() { initComponents() {
const controls = document.getElementById("controls"); $("body").bootstrapMaterialDesign();
const step = document.getElementById("step"); $("[data-toggle=tooltip]").tooltip({
const clrBreaks = document.getElementById("clr-breaks"); animation: false,
const saveImg = document.querySelector("#save img"); container: "body",
const loadImg = document.querySelector("#load img"); boundary: "viewport",
const stepImg = document.querySelector("#step img"); trigger: "hover"
const clrRecipImg = document.querySelector("#clr-recipe img"); });
const clrBreaksImg = document.querySelector("#clr-breaks img");
if (controls.clientWidth < 470) {
step.childNodes[1].nodeValue = " Step";
} else {
step.childNodes[1].nodeValue = " Step through";
}
if (controls.clientWidth < 400) {
saveImg.style.display = "none";
loadImg.style.display = "none";
stepImg.style.display = "none";
clrRecipImg.style.display = "none";
clrBreaksImg.style.display = "none";
} else {
saveImg.style.display = "inline";
loadImg.style.display = "inline";
stepImg.style.display = "inline";
clrRecipImg.style.display = "inline";
clrBreaksImg.style.display = "inline";
}
if (controls.clientWidth < 330) {
clrBreaks.childNodes[1].nodeValue = " Clear breaks";
} else {
clrBreaks.childNodes[1].nodeValue = " Clear breakpoints";
}
} }
@ -105,18 +77,7 @@ class ControlsWaiter {
* Handler for changes made to the Auto Bake checkbox. * Handler for changes made to the Auto Bake checkbox.
*/ */
autoBakeChange() { autoBakeChange() {
const autoBakeLabel = document.getElementById("auto-bake-label"); this.app.autoBake_ = document.getElementById("auto-bake").checked;
const autoBakeCheckbox = document.getElementById("auto-bake");
this.app.autoBake_ = autoBakeCheckbox.checked;
if (autoBakeCheckbox.checked) {
autoBakeLabel.classList.add("btn-success");
autoBakeLabel.classList.remove("btn-default");
} else {
autoBakeLabel.classList.add("btn-default");
autoBakeLabel.classList.remove("btn-success");
}
} }
@ -128,20 +89,6 @@ class ControlsWaiter {
} }
/**
* Handler for the 'Clear breakpoints' command. Removes all breakpoints from operations in the
* recipe.
*/
clearBreaksClick() {
const bps = document.querySelectorAll("#rec-list li.operation .breakpoint");
for (let i = 0; i < bps.length; i++) {
bps[i].setAttribute("break", "false");
bps[i].classList.remove("breakpoint-selected");
}
}
/** /**
* Populates the save disalog box with a URL incorporating the recipe and input. * Populates the save disalog box with a URL incorporating the recipe and input.
* *
@ -264,7 +211,6 @@ class ControlsWaiter {
if (!this.app.isLocalStorageAvailable()) { if (!this.app.isLocalStorageAvailable()) {
this.app.alert( this.app.alert(
"Your security settings do not allow access to local storage so your recipe cannot be saved.", "Your security settings do not allow access to local storage so your recipe cannot be saved.",
"danger",
5000 5000
); );
return false; return false;
@ -274,7 +220,7 @@ class ControlsWaiter {
const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value; const recipeStr = document.querySelector("#save-texts .tab-pane.active textarea").value;
if (!recipeName) { if (!recipeName) {
this.app.alert("Please enter a recipe name", "danger", 2000); this.app.alert("Please enter a recipe name", 3000);
return; return;
} }
@ -291,7 +237,7 @@ class ControlsWaiter {
localStorage.savedRecipes = JSON.stringify(savedRecipes); localStorage.savedRecipes = JSON.stringify(savedRecipes);
localStorage.recipeId = recipeId; localStorage.recipeId = recipeId;
this.app.alert("Recipe saved as \"" + recipeName + "\".", "success", 2000); this.app.alert(`Recipe saved as "${recipeName}".`, 3000);
} }
@ -323,7 +269,10 @@ class ControlsWaiter {
} }
// Populate textarea with first recipe // Populate textarea with first recipe
document.getElementById("load-text").value = savedRecipes.length ? savedRecipes[0].recipe : ""; const loadText = document.getElementById("load-text");
const evt = new Event("change");
loadText.value = savedRecipes.length ? savedRecipes[0].recipe : "";
loadText.dispatchEvent(evt);
} }
@ -372,7 +321,7 @@ class ControlsWaiter {
$("#rec-list [data-toggle=popover]").popover(); $("#rec-list [data-toggle=popover]").popover();
} catch (e) { } catch (e) {
this.app.alert("Invalid recipe", "danger", 2000); this.app.alert("Invalid recipe", 2000);
} }
} }
@ -391,7 +340,7 @@ class ControlsWaiter {
if (reportBugInfo) { if (reportBugInfo) {
reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")} reportBugInfo.innerHTML = `* Version: ${PKG_VERSION + (typeof INLINE === "undefined" ? "" : "s")}
* Compile time: ${COMPILE_TIME} * Compile time: ${COMPILE_TIME}
* User-Agent: * User-Agent:
${navigator.userAgent} ${navigator.userAgent}
* [Link to reproduce](${saveLink}) * [Link to reproduce](${saveLink})
@ -406,9 +355,7 @@ ${navigator.userAgent}
*/ */
showStaleIndicator() { showStaleIndicator() {
const staleIndicator = document.getElementById("stale-indicator"); const staleIndicator = document.getElementById("stale-indicator");
staleIndicator.classList.remove("hidden");
staleIndicator.style.visibility = "visible";
staleIndicator.style.opacity = 1;
} }
@ -418,9 +365,7 @@ ${navigator.userAgent}
*/ */
hideStaleIndicator() { hideStaleIndicator() {
const staleIndicator = document.getElementById("stale-indicator"); const staleIndicator = document.getElementById("stale-indicator");
staleIndicator.classList.add("hidden");
staleIndicator.style.opacity = 0;
staleIndicator.style.visibility = "hidden";
} }

View File

@ -39,13 +39,12 @@ class HTMLCategory {
*/ */
toHtml() { toHtml() {
const catName = "cat" + this.name.replace(/[\s/-:_]/g, ""); const catName = "cat" + this.name.replace(/[\s/-:_]/g, "");
let html = "<div class='panel category'>\ let html = `<div class="panel category">
<a class='category-title' data-toggle='collapse'\ <a class="category-title" data-toggle="collapse" data-target="#${catName}">
data-parent='#categories' href='#" + catName + "'>\ ${this.name}
" + this.name + "\ </a>
</a>\ <div id="${catName}" class="panel-collapse collapse ${(this.selected ? " show" : "")}" data-parent="#categories">
<div id='" + catName + "' class='panel-collapse collapse\ <ul class="op-list">`;
" + (this.selected ? " in" : "") + "'><ul class='op-list'>";
for (let i = 0; i < this.opList.length; i++) { for (let i = 0; i < this.opList.length; i++) {
html += this.opList[i].toStubHtml(); html += this.opList[i].toStubHtml();

View File

@ -24,8 +24,7 @@ class HTMLIngredient {
this.type = config.type; this.type = config.type;
this.value = config.value; this.value = config.value;
this.disabled = config.disabled || false; this.disabled = config.disabled || false;
this.disableArgs = config.disableArgs || false; this.hint = config.hint || false;
this.placeholder = config.placeholder || false;
this.target = config.target; this.target = config.target;
this.toggleValues = config.toggleValues; this.toggleValues = config.toggleValues;
this.id = "ing-" + this.app.nextIngId(); this.id = "ing-" + this.app.nextIngId();
@ -38,154 +37,180 @@ class HTMLIngredient {
* @returns {string} * @returns {string}
*/ */
toHtml() { toHtml() {
const inline = ( let html = "",
this.type === "boolean" ||
this.type === "number" ||
this.type === "option" ||
this.type === "shortString" ||
this.type === "binaryShortString"
);
let html = inline ? "" : "<div class='clearfix'>&nbsp;</div>",
i, m; i, m;
html += "<div class='arg-group" + (inline ? " inline-args" : "") +
(this.type === "text" ? " arg-group-text" : "") + "'><label class='arg-label' for='" +
this.id + "'>" + this.name + "</label>";
switch (this.type) { switch (this.type) {
case "string": case "string":
case "binaryString": case "binaryString":
case "byteArray": case "byteArray":
html += "<input type='text' id='" + this.id + "' class='arg arg-input' arg-name='" + html += `<div class="form-group">
this.name + "' value='" + this.value + "'" + <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
(this.disabled ? " disabled='disabled'" : "") + <input type="text"
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">"; class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break; break;
case "shortString": case "shortString":
case "binaryShortString": case "binaryShortString":
html += "<input type='text' id='" + this.id + html += `<div class="form-group inline">
"'class='arg arg-input short-string' arg-name='" + this.name + "'value='" + <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
this.value + "'" + (this.disabled ? " disabled='disabled'" : "") + <input type="text"
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">"; class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break; break;
case "toggleString": case "toggleString":
html += "<div class='input-group'><div class='input-group-btn'>\ html += `<div class="form-group input-group">
<button type='button' class='btn btn-default dropdown-toggle' data-toggle='dropdown'\ <div class="toggle-string">
aria-haspopup='true' aria-expanded='false'" + <label for="${this.id}" class="bmd-label-floating toggle-string">${this.name}</label>
(this.disabled ? " disabled='disabled'" : "") + ">" + this.toggleValues[0] + <input type="text"
" <span class='caret'></span></button><ul class='dropdown-menu'>"; class="form-control arg toggle-string"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>
<div class="input-group-append">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">${this.toggleValues[0]}</button>
<div class="dropdown-menu toggle-dropdown">`;
for (i = 0; i < this.toggleValues.length; i++) { for (i = 0; i < this.toggleValues.length; i++) {
html += "<li><a href='#'>" + this.toggleValues[i] + "</a></li>"; html += `<a class="dropdown-item" href="#">${this.toggleValues[i]}</a>`;
} }
html += "</ul></div><input type='text' class='arg arg-input toggle-string'" + html += `</div>
(this.disabled ? " disabled='disabled'" : "") + </div>
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + "></div>";
</div>`;
break; break;
case "number": case "number":
html += "<input type='number' id='" + this.id + "'class='arg arg-input' arg-name='" + html += `<div class="form-group inline">
this.name + "'value='" + this.value + "'" + <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
(this.disabled ? " disabled='disabled'" : "") + <input type="number"
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">"; class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
value="${this.value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break; break;
case "boolean": case "boolean":
html += "<input type='checkbox' id='" + this.id + "'class='arg' arg-name='" + html += `<div class="form-group inline boolean-arg">
this.name + "'" + (this.value ? " checked='checked' " : "") + <div class="checkbox">
(this.disabled ? " disabled='disabled'" : "") + ">"; <label>
<input type="checkbox"
if (this.disableArgs) { class="arg"
this.manager.addDynamicListener("#" + this.id, "click", this.toggleDisableArgs, this); id="${this.id}"
} arg-name="${this.name}"
${this.value ? " checked" : ""}
${this.disabled ? " disabled" : ""}
value="${this.name}"> ${this.name}
</label>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>
</div>`;
break; break;
case "option": case "option":
html += "<select class='arg' id='" + this.id + "'arg-name='" + this.name + "'" + html += `<div class="form-group inline">
(this.disabled ? " disabled='disabled'" : "") + ">"; <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
<select
class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>`;
for (i = 0; i < this.value.length; i++) { for (i = 0; i < this.value.length; i++) {
if ((m = this.value[i].match(/\[([a-z0-9 -()^]+)\]/i))) { if ((m = this.value[i].match(/\[([a-z0-9 -()^]+)\]/i))) {
html += "<optgroup label='" + m[1] + "'>"; html += `<optgroup label="${m[1]}">`;
} else if ((m = this.value[i].match(/\[\/([a-z0-9 -()^]+)\]/i))) { } else if ((m = this.value[i].match(/\[\/([a-z0-9 -()^]+)\]/i))) {
html += "</optgroup>"; html += "</optgroup>";
} else { } else {
html += "<option>" + this.value[i] + "</option>"; html += `<option>${this.value[i]}</option>`;
} }
} }
html += "</select>"; html += `</select>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break; break;
case "populateOption": case "populateOption":
html += "<select class='arg' id='" + this.id + "'arg-name='" + this.name + "'" + html += `<div class="form-group">
(this.disabled ? " disabled='disabled'" : "") + ">"; <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
<select
class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>`;
for (i = 0; i < this.value.length; i++) { for (i = 0; i < this.value.length; i++) {
if ((m = this.value[i].name.match(/\[([a-z0-9 -()^]+)\]/i))) { if ((m = this.value[i].name.match(/\[([a-z0-9 -()^]+)\]/i))) {
html += "<optgroup label='" + m[1] + "'>"; html += `<optgroup label="${m[1]}">`;
} else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) { } else if ((m = this.value[i].name.match(/\[\/([a-z0-9 -()^]+)\]/i))) {
html += "</optgroup>"; html += "</optgroup>";
} else { } else {
html += "<option populate-value='" + this.value[i].value + "'>" + html += `<option populate-value="${this.value[i].value}">${this.value[i].name}</option>`;
this.value[i].name + "</option>";
} }
} }
html += "</select>"; html += `</select>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this); this.manager.addDynamicListener("#" + this.id, "change", this.populateOptionChange, this);
break; break;
case "editableOption": case "editableOption":
html += "<div class='editable-option'>"; html += `<div class="form-group input-group inline">
html += "<select class='editable-option-select' id='sel-" + this.id + "'" + <label for="${this.id}" class="bmd-label-floating inline">${this.name}</label>
(this.disabled ? " disabled='disabled'" : "") + ">"; <input type="text"
class="form-control arg inline"
id="${this.id}"
arg-name="${this.name}"
value="${this.value[0].value}"
${this.disabled ? "disabled" : ""}>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
<div class="input-group-append inline">
<button type="button"
class="btn btn-secondary dropdown-toggle dropdown-toggle-split"
data-toggle="dropdown"
data-boundary="scrollParent"
aria-haspopup="true"
aria-expanded="false">
<span class="sr-only">Toggle Dropdown</span>
</button>
<div class="dropdown-menu editable-option-menu">`;
for (i = 0; i < this.value.length; i++) { for (i = 0; i < this.value.length; i++) {
html += "<option value='" + this.value[i].value + "'>" + this.value[i].name + "</option>"; html += `<a class="dropdown-item" href="#" value="${this.value[i].value}">${this.value[i].name}</a>`;
} }
html += "</select>"; html += `</div>
html += "<input class='arg arg-input editable-option-input' id='" + this.id + </div>
"'arg-name='" + this.name + "'" + " value='" + this.value[0].value + "'" + </div>`;
(this.disabled ? " disabled='disabled'" : "") +
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">";
html += "</div>";
this.manager.addDynamicListener(".editable-option-menu a", "click", this.editableOptionClick, this);
this.manager.addDynamicListener("#sel-" + this.id, "change", this.editableOptionChange, this);
break; break;
case "text": case "text":
html += "<textarea id='" + this.id + "' class='arg' arg-name='" + html += `<div class="form-group">
this.name + "'" + (this.disabled ? " disabled='disabled'" : "") + <label for="${this.id}" class="bmd-label-floating">${this.name}</label>
(this.placeholder ? " placeholder='" + this.placeholder + "'" : "") + ">" + <textarea
this.value + "</textarea>"; class="form-control arg"
id="${this.id}"
arg-name="${this.name}"
${this.disabled ? "disabled" : ""}>${this.value}</textarea>
${this.hint ? "<span class='bmd-help'>" + this.hint + "</span>" : ""}
</div>`;
break; break;
default: default:
break; break;
} }
html += "</div>";
return html; return html;
} }
/**
* Handler for argument disable toggle.
* Toggles disabled state for all arguments in the disableArgs list for this ingredient.
*
* @param {event} e
*/
toggleDisableArgs(e) {
const el = e.target;
const op = el.parentNode.parentNode;
const args = op.querySelectorAll(".arg-group");
for (let i = 0; i < this.disableArgs.length; i++) {
const els = args[this.disableArgs[i]].querySelectorAll("input, select, button");
for (let j = 0; j < els.length; j++) {
if (els[j].getAttribute("disabled")) {
els[j].removeAttribute("disabled");
} else {
els[j].setAttribute("disabled", "disabled");
}
}
}
this.manager.recipe.ingChange();
}
/** /**
* Handler for populate option changes. * Handler for populate option changes.
* Populates the relevant argument with the specified value. * Populates the relevant argument with the specified value.
@ -195,25 +220,32 @@ class HTMLIngredient {
populateOptionChange(e) { populateOptionChange(e) {
const el = e.target; const el = e.target;
const op = el.parentNode.parentNode; const op = el.parentNode.parentNode;
const target = op.querySelectorAll(".arg-group")[this.target].querySelector("input, select, textarea"); const target = op.querySelectorAll(".arg")[this.target];
target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value"); target.value = el.childNodes[el.selectedIndex].getAttribute("populate-value");
const evt = new Event("change");
target.dispatchEvent(evt);
this.manager.recipe.ingChange(); this.manager.recipe.ingChange();
} }
/** /**
* Handler for editable option changes. * Handler for editable option clicks.
* Populates the input box with the selected value. * Populates the input box with the selected value.
* *
* @param {event} e * @param {event} e
*/ */
editableOptionChange(e) { editableOptionClick(e) {
const select = e.target, e.preventDefault();
input = select.nextSibling; e.stopPropagation();
input.value = select.childNodes[select.selectedIndex].value; const link = e.target,
input = link.parentNode.parentNode.parentNode.querySelector("input");
input.value = link.getAttribute("value");
const evt = new Event("change");
input.dispatchEvent(evt);
this.manager.recipe.ingChange(); this.manager.recipe.ingChange();
} }

View File

@ -6,9 +6,6 @@
import HTMLIngredient from "./HTMLIngredient"; import HTMLIngredient from "./HTMLIngredient";
const INFO_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAByElEQVR4XqVTzWoaYRQ9KZJmoVaS1J1QiYTIuOgqi9lEugguQhYhdGs3hTyAi0CWJTvJIks30ZBNsimUtlqkVLoQCuJsphRriyFjabWtEyf/Rv3iWcwwymTlgQuH851z5hu43wRGkEwmXwCIA4hiGAUAmUQikQbhEHwyGCWVSglVVUW73RYmyKnxjB56ncJ6NpsVxHGrI/ZLuniVb3DIqQmCHnrNkgcggNeSJPlisRgyJR2b737j/TcDsQUPwv6H5NR4BnroZcb6Z16N2PvyX6yna9Z8qp6JQ0Uf0ughmGHWBSAuyzJqrQ7eqKewY/dzE363C71e39LoWQq5wUwul4uzIBoIBHD01RgyrkZ8eDbvwUWnj623v2DHx4qB51IAzLIAXq8XP/7W0bUVVJtXWIk8wvlN364TA+/1IDMLwmWK/Hq3axmhaBdoGLeklm73ElaBYRgIzkyifHIOO4QQJKM3oJcZq6CgaVp0OTyHw9K/kQI4FiyHfdC0n2CWe5ApFosIPZ7C2tNpXpcDOehGyD/FIbd0euhlhllzFxRzC3fydbG4XRYbB9/tQ41n9m1U7l3lyp9LkfygiZeZCoecmtMqj/+Yxn7Od3v0j50qCO3zAAAAAElFTkSuQmCC";
const REMOVE_ICON = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABwklEQVR42qRTPU8CQRB9K2CCMRJ6NTQajOUaqfxIbLCRghhjQixosLAgFNBQ3l8wsabxLxBJbCyVUBiMCVQEQkOEKBbCnefM3p4eohWXzM3uvHlv52b2hG3bmOWZw4yPn1/XQkCQ9wFxcgZZ0QLKpifpN8Z1n1L13griBBjHhYK0nMT4b+wom53ClAAFQacZJ/m8rNfrSOZy0vxJjPP6IJ2WzWYTO6mUwiwtILiJJSHUKVSWkchkZK1WQzQaxU2pVGUglkjIbreLUCiEx0qlStlFCpfPiPstYDtVKJH9ZFI2Gw1FGA6H6LTbCAaDeGu1FJl6UuYjpwTGzucokZW1NfnS66kyfT4fXns9RaZmlgNcuhZQU+jowLzuOK/HgwEW3E5ZlhLXVWKk11P3wNYNWw+HZdA0sUgx1zjGmD05nckx0ilGjBJdUq3fr7K5e8bGf43RdL7fOPSQb4lI8SLbrUfkUIuY32VTI1bJn5BqDnh4Dodt9ryPUDzyD7aquWoKQohl2i9sAbubwPkTcHkP3FHsg+yT+7sN7G0AF3Xg6sHB3onbdgWWKBDQg/BcTuVt51dQA/JrnIcyIu6rmPV3/hJgACPc0BMEYTg+AAAAAElFTkSuQmCC";
/** /**
* Object to handle the creation of operations. * Object to handle the creation of operations.
@ -49,19 +46,15 @@ class HTMLOperation {
let html = "<li class='operation'"; let html = "<li class='operation'";
if (this.description) { if (this.description) {
html += " data-container='body' data-toggle='popover' data-placement='auto right'\ html += ` data-container='body' data-toggle='popover' data-placement='right'
data-content=\"" + this.description + "\" data-html='true' data-trigger='hover'"; data-content="${this.description}" data-html='true' data-trigger='hover'
data-boundary='viewport'`;
} }
html += ">" + this.name; html += ">" + this.name;
if (removeIcon) { if (removeIcon) {
html += "<img src='data:image/png;base64," + REMOVE_ICON + html += "<i class='material-icons remove-icon op-icon'>delete</i>";
"' class='op-icon remove-icon'>";
}
if (this.description) {
html += "<img src='data:image/png;base64," + INFO_ICON + "' class='op-icon'>";
} }
html += "</li>"; html += "</li>";
@ -76,19 +69,19 @@ class HTMLOperation {
* @returns {string} * @returns {string}
*/ */
toFullHtml() { toFullHtml() {
let html = "<div class='arg-title'>" + this.name + "</div>"; let html = `<div class="op-title">${this.name}</div>
<div class="ingredients">`;
for (let i = 0; i < this.ingList.length; i++) { for (let i = 0; i < this.ingList.length; i++) {
html += this.ingList[i].toHtml(); html += this.ingList[i].toHtml();
} }
html += "<div class='recip-icons'>\ html += `</div>
<div class='breakpoint' title='Set breakpoint' break='false'></div>\ <div class="recip-icons">
<div class='disable-icon recip-icon' title='Disable operation'\ <i class="material-icons breakpoint" title="Set breakpoint" break="false">pause</i>
disabled='false'></div>"; <i class="material-icons disable-icon" title="Disable operation" disabled="false">not_interested</i>
</div>
html += "</div>\ <div class="clearfix">&nbsp;</div>`;
<div class='clearfix'>&nbsp;</div>";
return html; return html;
} }

View File

@ -60,10 +60,11 @@ class InputWaiter {
* Sets the input in the input area. * Sets the input in the input area.
* *
* @param {string|File} input * @param {string|File} input
* @param {boolean} [silent=false] - Suppress statechange event
* *
* @fires Manager#statechange * @fires Manager#statechange
*/ */
set(input) { set(input, silent=false) {
const inputText = document.getElementById("input-text"); const inputText = document.getElementById("input-text");
if (input instanceof File) { if (input instanceof File) {
this.setFile(input); this.setFile(input);
@ -72,7 +73,7 @@ class InputWaiter {
} else { } else {
inputText.value = input; inputText.value = input;
this.closeFile(); this.closeFile();
window.dispatchEvent(this.manager.statechange); if (!silent) window.dispatchEvent(this.manager.statechange);
const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ? const lines = input.length < (this.app.options.ioDisplayThreshold * 1024) ?
input.count("\n") + 1 : null; input.count("\n") + 1 : null;
this.setInputInfo(input.length, lines); this.setInputInfo(input.length, lines);
@ -264,7 +265,7 @@ class InputWaiter {
} }
if (r.hasOwnProperty("error")) { if (r.hasOwnProperty("error")) {
this.app.alert(r.error, "danger", 10000); this.app.alert(r.error, 10000);
} }
if (r.hasOwnProperty("fileBuffer")) { if (r.hasOwnProperty("fileBuffer")) {

View File

@ -84,6 +84,7 @@ class Manager {
setup() { setup() {
this.worker.registerChefWorker(); this.worker.registerChefWorker();
this.recipe.initialiseOperationDragNDrop(); this.recipe.initialiseOperationDragNDrop();
this.controls.initComponents();
this.controls.autoBakeChange(); this.controls.autoBakeChange();
this.bindings.updateKeybList(); this.bindings.updateKeybList();
this.background.registerChefWorker(); this.background.registerChefWorker();
@ -107,7 +108,6 @@ class Manager {
document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls)); document.getElementById("auto-bake").addEventListener("change", this.controls.autoBakeChange.bind(this.controls));
document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls)); document.getElementById("step").addEventListener("click", this.controls.stepClick.bind(this.controls));
document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls)); document.getElementById("clr-recipe").addEventListener("click", this.controls.clearRecipeClick.bind(this.controls));
document.getElementById("clr-breaks").addEventListener("click", this.controls.clearBreaksClick.bind(this.controls));
document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls)); document.getElementById("save").addEventListener("click", this.controls.saveClick.bind(this.controls));
document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls)); document.getElementById("save-button").addEventListener("click", this.controls.saveButtonClick.bind(this.controls));
document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls)); document.getElementById("save-link-recipe-checkbox").addEventListener("change", this.controls.slrCheckChange.bind(this.controls));
@ -125,8 +125,6 @@ class Manager {
document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops)); document.getElementById("edit-favourites").addEventListener("click", this.ops.editFavouritesClick.bind(this.ops));
document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops)); document.getElementById("save-favourites").addEventListener("click", this.ops.saveFavouritesClick.bind(this.ops));
document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops)); document.getElementById("reset-favourites").addEventListener("click", this.ops.resetFavouritesClick.bind(this.ops));
this.addDynamicListener(".op-list .op-icon", "mouseover", this.ops.opIconMouseover, this.ops);
this.addDynamicListener(".op-list .op-icon", "mouseleave", this.ops.opIconMouseleave, this.ops);
this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops); this.addDynamicListener(".op-list", "oplistcreate", this.ops.opListCreate, this.ops);
this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe); this.addDynamicListener("li.operation", "operationadd", this.recipe.opAdd, this.recipe);
@ -137,7 +135,7 @@ class Manager {
this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe); this.addDynamicListener(".breakpoint", "click", this.recipe.breakpointClick, this.recipe);
this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe); this.addDynamicListener("#rec-list li.operation", "dblclick", this.recipe.operationDblclick, this.recipe);
this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe); this.addDynamicListener("#rec-list li.operation > div", "dblclick", this.recipe.operationChildDblclick, this.recipe);
this.addDynamicListener("#rec-list .input-group .dropdown-menu a", "click", this.recipe.dropdownToggleClick, this.recipe); this.addDynamicListener("#rec-list .dropdown-menu.toggle-dropdown a", "click", this.recipe.dropdownToggleClick, this.recipe);
this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe)); this.addDynamicListener("#rec-list", "operationremove", this.recipe.opRemove.bind(this.recipe));
// Input // Input
@ -160,6 +158,7 @@ class Manager {
document.getElementById("switch").addEventListener("click", this.output.switchClick.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("undo-switch").addEventListener("click", this.output.undoSwitchClick.bind(this.output));
document.getElementById("maximise-output").addEventListener("click", this.output.maximiseOutputClick.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("scroll", this.highlighter.outputScroll.bind(this.highlighter));
document.getElementById("output-text").addEventListener("mouseup", this.highlighter.outputMouseup.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-text").addEventListener("mousemove", this.highlighter.outputMousemove.bind(this.highlighter));
@ -168,15 +167,15 @@ class Manager {
this.addMultiEventListener("#output-text", "mousedown dblclick select", this.highlighter.outputMousedown, 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.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-download", "click", this.output.downloadFile, this.output);
this.addDynamicListener("#output-file-slice", "click", this.output.displayFileSlice, this.output); this.addDynamicListener("#output-file-slice i", "click", this.output.displayFileSlice, this.output);
document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output)); document.getElementById("show-file-overlay").addEventListener("click", this.output.showFileOverlayClick.bind(this.output));
// Options // Options
document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options)); document.getElementById("options").addEventListener("click", this.options.optionsClick.bind(this.options));
document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options)); document.getElementById("reset-options").addEventListener("click", this.options.resetOptionsClick.bind(this.options));
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.switchChange.bind(this.options)); this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.switchChange, this.options);
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox", this.options.setWordWrap.bind(this.options)); this.addDynamicListener(".option-item input[type=checkbox]", "change", this.options.setWordWrap, this.options);
$(document).on("switchChange.bootstrapSwitch", ".option-item input:checkbox#useMetaKey", this.bindings.updateKeybList.bind(this.bindings)); this.addDynamicListener(".option-item input[type=checkbox]#useMetaKey", "change", this.bindings.updateKeybList, this.bindings);
this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options); this.addDynamicListener(".option-item input[type=number]", "keyup", this.options.numberChange, this.options);
this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options); this.addDynamicListener(".option-item input[type=number]", "change", this.options.numberChange, this.options);
this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options); this.addDynamicListener(".option-item select", "change", this.options.selectChange, this.options);
@ -185,7 +184,6 @@ class Manager {
// Misc // Misc
window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings)); window.addEventListener("keydown", this.bindings.parseInput.bind(this.bindings));
document.getElementById("alert-close").addEventListener("click", this.app.alertCloseClick.bind(this.app));
} }

View File

@ -79,12 +79,12 @@ class OperationsWaiter {
while (searchResultsEl.firstChild) { while (searchResultsEl.firstChild) {
try { try {
$(searchResultsEl.firstChild).popover("destroy"); $(searchResultsEl.firstChild).popover("dispose");
} catch (err) {} } catch (err) {}
searchResultsEl.removeChild(searchResultsEl.firstChild); searchResultsEl.removeChild(searchResultsEl.firstChild);
} }
$("#categories .in").collapse("hide"); $("#categories .show").collapse("hide");
if (str) { if (str) {
const matchedOps = this.filterOperations(str, true); const matchedOps = this.filterOperations(str, true);
const matchedOpsHtml = matchedOps const matchedOpsHtml = matchedOps
@ -185,7 +185,7 @@ class OperationsWaiter {
setTimeout(function() { setTimeout(function() {
// Determine if the popover associated with this element is being hovered over // Determine if the popover associated with this element is being hovered over
if ($(_this).data("bs.popover") && if ($(_this).data("bs.popover") &&
($(_this).data("bs.popover").$tip && !$(_this).data("bs.popover").$tip.is(":hover"))) { ($(_this).data("bs.popover").tip && !$($(_this).data("bs.popover").tip).is(":hover"))) {
$(_this).popover("hide"); $(_this).popover("hide");
} }
}, 50); }, 50);
@ -237,13 +237,13 @@ class OperationsWaiter {
onFilter: function (evt) { onFilter: function (evt) {
const el = editableList.closest(evt.item); const el = editableList.closest(evt.item);
if (el && el.parentNode) { if (el && el.parentNode) {
$(el).popover("destroy"); $(el).popover("dispose");
el.parentNode.removeChild(el); el.parentNode.removeChild(el);
} }
}, },
onEnd: function(evt) { onEnd: function(evt) {
if (this.removeIntent) { if (this.removeIntent) {
$(evt.item).popover("destroy"); $(evt.item).popover("dispose");
evt.item.remove(); evt.item.remove();
} }
}.bind(this), }.bind(this),
@ -268,7 +268,7 @@ class OperationsWaiter {
*/ */
saveFavouritesClick() { saveFavouritesClick() {
const favs = document.querySelectorAll("#edit-favourites-list li"); const favs = document.querySelectorAll("#edit-favourites-list li");
const favouritesList = Array.from(favs, e => e.textContent); const favouritesList = Array.from(favs, e => e.childNodes[0].textContent);
this.app.saveFavourites(favouritesList); this.app.saveFavourites(favouritesList);
this.app.loadFavourites(); this.app.loadFavourites();
@ -285,37 +285,6 @@ class OperationsWaiter {
this.app.resetFavourites(); this.app.resetFavourites();
} }
/**
* Handler for opIcon mouseover events.
* Hides any popovers already showing on the operation so that there aren't two at once.
*
* @param {event} e
*/
opIconMouseover(e) {
const opEl = e.target.parentNode;
if (e.target.getAttribute("data-toggle") === "popover") {
$(opEl).popover("hide");
}
}
/**
* Handler for opIcon mouseleave events.
* If this icon created a popover and we're moving back to the operation element, display the
* operation popover again.
*
* @param {event} e
*/
opIconMouseleave(e) {
const opEl = e.target.parentNode;
const toEl = e.toElement || e.relatedElement;
if (e.target.getAttribute("data-toggle") === "popover" && toEl === opEl) {
$(opEl).popover("show");
}
}
} }
export default OperationsWaiter; export default OperationsWaiter;

View File

@ -20,11 +20,6 @@ const OptionsWaiter = function(app, manager) {
* @param {Object} options * @param {Object} options
*/ */
OptionsWaiter.prototype.load = function(options) { OptionsWaiter.prototype.load = function(options) {
$(".option-item input:checkbox").bootstrapSwitch({
size: "small",
animate: false,
});
for (const option in options) { for (const option in options) {
this.app.options[option] = options[option]; this.app.options[option] = options[option];
} }
@ -33,7 +28,7 @@ OptionsWaiter.prototype.load = function(options) {
const cboxes = document.querySelectorAll("#options-body input[type=checkbox]"); const cboxes = document.querySelectorAll("#options-body input[type=checkbox]");
let i; let i;
for (i = 0; i < cboxes.length; i++) { for (i = 0; i < cboxes.length; i++) {
$(cboxes[i]).bootstrapSwitch("state", this.app.options[cboxes[i].getAttribute("option")]); cboxes[i].checked = this.app.options[cboxes[i].getAttribute("option")];
} }
const nboxes = document.querySelectorAll("#options-body input[type=number]"); const nboxes = document.querySelectorAll("#options-body input[type=number]");
@ -81,11 +76,11 @@ OptionsWaiter.prototype.resetOptionsClick = function() {
* Modifies the option state and saves it to local storage. * Modifies the option state and saves it to local storage.
* *
* @param {event} e * @param {event} e
* @param {boolean} state
*/ */
OptionsWaiter.prototype.switchChange = function(e, state) { OptionsWaiter.prototype.switchChange = function(e) {
const el = e.target; const el = e.target;
const option = el.getAttribute("option"); const option = el.getAttribute("option");
const state = el.checked;
log.debug(`Setting ${option} to ${state}`); log.debug(`Setting ${option} to ${state}`);
this.app.options[option] = state; this.app.options[option] = state;

View File

@ -228,35 +228,6 @@ class OutputWaiter {
} }
/**
* Adjusts the display properties of the output buttons so that they fit within the current width
* without wrapping or overflowing.
*/
adjustWidth() {
const output = document.getElementById("output");
const saveToFile = document.getElementById("save-to-file");
const copyOutput = document.getElementById("copy-output");
const switchIO = document.getElementById("switch");
const undoSwitch = document.getElementById("undo-switch");
const maximiseOutput = document.getElementById("maximise-output");
if (output.clientWidth < 680) {
saveToFile.childNodes[1].nodeValue = "";
copyOutput.childNodes[1].nodeValue = "";
switchIO.childNodes[1].nodeValue = "";
undoSwitch.childNodes[1].nodeValue = "";
maximiseOutput.childNodes[1].nodeValue = "";
} else {
saveToFile.childNodes[1].nodeValue = " Save to file";
copyOutput.childNodes[1].nodeValue = " Copy output";
switchIO.childNodes[1].nodeValue = " Move output to input";
undoSwitch.childNodes[1].nodeValue = " Undo";
maximiseOutput.childNodes[1].nodeValue =
maximiseOutput.getAttribute("title") === "Maximise" ? " Max" : " Restore";
}
}
/** /**
* Handler for save click events. * Handler for save click events.
* Saves the current output to a file. * Saves the current output to a file.
@ -296,9 +267,9 @@ class OutputWaiter {
} }
if (success) { if (success) {
this.app.alert("Copied raw output successfully.", "success", 2000); this.app.alert("Copied raw output successfully.", 2000);
} else { } else {
this.app.alert("Sorry, the output could not be copied.", "danger", 2000); this.app.alert("Sorry, the output could not be copied.", 3000);
} }
// Clean up // Clean up
@ -334,7 +305,9 @@ class OutputWaiter {
*/ */
undoSwitchClick() { undoSwitchClick() {
this.app.setInput(this.switchOrigData); this.app.setInput(this.switchOrigData);
document.getElementById("undo-switch").disabled = true; const undoSwitch = document.getElementById("undo-switch");
undoSwitch.disabled = true;
$(undoSwitch).tooltip("hide");
} }
@ -345,17 +318,16 @@ class OutputWaiter {
maximiseOutputClick(e) { maximiseOutputClick(e) {
const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode; const el = e.target.id === "maximise-output" ? e.target : e.target.parentNode;
if (el.getAttribute("title") === "Maximise") { if (el.getAttribute("data-original-title").indexOf("Maximise") === 0) {
this.app.columnSplitter.collapse(0); this.app.columnSplitter.collapse(0);
this.app.columnSplitter.collapse(1); this.app.columnSplitter.collapse(1);
this.app.ioSplitter.collapse(0); this.app.ioSplitter.collapse(0);
el.setAttribute("title", "Restore"); $(el).attr("data-original-title", "Restore output pane");
el.innerHTML = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAlUlEQVQ4y93RwQpBQRQG4C9ba1fxBteGPIj38BTejFJKLFnwCJIiCsW1mcV0k9yx82/OzGK+OXMGOpiiLTFjFNiilQI0sQ7IJiAjLKsgGVYB2YdaVO0kwy46/BVQi9ZDNPyQWen2ub/KufS8y7shfkq9tF9U7SC+/YluKvAI9YZeFeCECXJcA3JHP2WgMXJM/ZUcBwxeM+YuSWTgMtUAAAAASUVORK5CYII='> Restore"; el.querySelector("i").innerHTML = "fullscreen_exit";
this.adjustWidth();
} else { } else {
el.setAttribute("title", "Maximise"); $(el).attr("data-original-title", "Maximise output pane");
el.innerHTML = "<img src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAi0lEQVQ4y83TMQrCQBCF4S+5g4rJEdJ7KE+RQ1lrIQQCllroEULuoM0Ww3a7aXwwLAzMPzDvLcz4hnooUItT1rsoVNy+4lgLWNL7RlcCmDBij2eCfNCrUITc0dRCrhj8m5otw0O6SV8LuAV3uhrAAa8sJ2Np7KPFawhgscVLjH9bCDhjt8WNKft88w/HjCvuVqu53QAAAABJRU5ErkJggg=='> Max"; el.querySelector("i").innerHTML = "fullscreen";
this.app.resetLayout(); this.app.resetLayout();
} }
} }
@ -450,6 +422,9 @@ class OutputWaiter {
* Triggers the BackgroundWorker to attempt Magic on the current output. * Triggers the BackgroundWorker to attempt Magic on the current output.
*/ */
backgroundMagic() { backgroundMagic() {
this.hideMagicButton();
if (!this.app.options.autoMagic) return;
const sample = this.dishStr ? this.dishStr.slice(0, 1000) : const sample = this.dishStr ? this.dishStr.slice(0, 1000) :
this.dishBuffer ? this.dishBuffer.slice(0, 1000) : ""; this.dishBuffer ? this.dishBuffer.slice(0, 1000) : "";
@ -469,16 +444,52 @@ class OutputWaiter {
!options[0].recipe.length) !options[0].recipe.length)
return; return;
//console.log(options);
const currentRecipeConfig = this.app.getRecipeConfig(); const currentRecipeConfig = this.app.getRecipeConfig();
const newRecipeConfig = currentRecipeConfig.concat(options[0].recipe); const newRecipeConfig = currentRecipeConfig.concat(options[0].recipe);
const recipeURL = "#recipe=" + Utils.encodeURIFragment(Utils.generatePrettyRecipe(newRecipeConfig));
const opSequence = options[0].recipe.map(o => o.op).join(", "); const opSequence = options[0].recipe.map(o => o.op).join(", ");
log.log(`Running <a href="${recipeURL}">${opSequence}</a> will result in "${Utils.truncate(options[0].data, 20)}"`); this.showMagicButton(opSequence, options[0].data, newRecipeConfig);
//this.app.setRecipeConfig(newRecipeConfig); }
//this.app.autoBake();
/**
* Handler for Magic click events.
*
* Loads the Magic recipe.
*
* @fires Manager#statechange
*/
magicClick() {
const magicButton = document.getElementById("magic");
this.app.setRecipeConfig(JSON.parse(magicButton.getAttribute("data-recipe")));
window.dispatchEvent(this.manager.statechange);
this.hideMagicButton();
}
/**
* Displays the Magic button with a title and adds a link to a complete recipe.
*
* @param {string} opSequence
* @param {string} result
* @param {Object[]} recipeConfig
*/
showMagicButton(opSequence, result, recipeConfig) {
const magicButton = document.getElementById("magic");
magicButton.setAttribute("data-original-title", `<i>${opSequence}</i> will produce <span class="data-text">"${Utils.truncate(result, 30)}"</span>`);
magicButton.setAttribute("data-recipe", JSON.stringify(recipeConfig), null, "");
magicButton.classList.remove("hidden");
}
/**
* Hides the Magic button and resets its values.
*/
hideMagicButton() {
const magicButton = document.getElementById("magic");
magicButton.classList.add("hidden");
magicButton.setAttribute("data-recipe", "");
magicButton.setAttribute("data-original-title", "Magic!");
} }
} }

View File

@ -39,10 +39,10 @@ class RecipeWaiter {
sort: true, sort: true,
animation: 0, animation: 0,
delay: 0, delay: 0,
filter: ".arg-input,.arg", filter: ".arg",
preventOnFilter: false, preventOnFilter: false,
setData: function(dataTransfer, dragEl) { setData: function(dataTransfer, dragEl) {
dataTransfer.setData("Text", dragEl.querySelector(".arg-title").textContent); dataTransfer.setData("Text", dragEl.querySelector(".op-title").textContent);
}, },
onEnd: function(evt) { onEnd: function(evt) {
if (this.removeIntent) { if (this.removeIntent) {
@ -100,9 +100,15 @@ class RecipeWaiter {
// Removes popover element and event bindings from the dragged operation but not the // Removes popover element and event bindings from the dragged operation but not the
// event bindings from the one left in the operations list. Without manually removing // event bindings from the one left in the operations list. Without manually removing
// these bindings, we cannot re-initialise the popover on the stub operation. // these bindings, we cannot re-initialise the popover on the stub operation.
$(evt.item).popover("destroy").removeData("bs.popover").off("mouseenter").off("mouseleave"); $(evt.item)
$(evt.clone).off(".popover").removeData("bs.popover"); .popover("dispose")
evt.item.setAttribute("data-toggle", "popover-disabled"); .removeData("bs.popover")
.off("mouseenter")
.off("mouseleave")
.attr("data-toggle", "popover-disabled");
$(evt.clone)
.off(".popover")
.removeData("bs.popover");
}, },
onEnd: this.opSortEnd.bind(this) onEnd: this.opSortEnd.bind(this)
}); });
@ -299,7 +305,7 @@ class RecipeWaiter {
} else if (ingList[j].classList.contains("toggle-string")) { } else if (ingList[j].classList.contains("toggle-string")) {
// toggleString // toggleString
ingredients[j] = { ingredients[j] = {
option: ingList[j].previousSibling.children[0].textContent.slice(0, -1), option: ingList[j].parentNode.parentNode.querySelector("button").textContent,
string: ingList[j].value string: ingList[j].value
}; };
} else if (ingList[j].getAttribute("type") === "number") { } else if (ingList[j].getAttribute("type") === "number") {
@ -312,7 +318,7 @@ class RecipeWaiter {
} }
item = { item = {
op: operations[i].querySelector(".arg-title").textContent, op: operations[i].querySelector(".op-title").textContent,
args: ingredients args: ingredients
}; };
@ -366,7 +372,7 @@ class RecipeWaiter {
// Disable auto-bake if this is a manual op // Disable auto-bake if this is a manual op
if (op.manualBake && this.app.autoBake_) { if (op.manualBake && this.app.autoBake_) {
this.manager.controls.setAutoBake(false); this.manager.controls.setAutoBake(false);
this.app.alert("Auto-Bake is disabled by default when using this operation.", "info", 5000); this.app.alert("Auto-Bake is disabled by default when using this operation.", 5000);
} }
} }
@ -411,10 +417,13 @@ class RecipeWaiter {
* @param {event} e * @param {event} e
*/ */
dropdownToggleClick(e) { dropdownToggleClick(e) {
const el = e.target; e.stopPropagation();
const button = el.parentNode.parentNode.previousSibling; e.preventDefault();
button.innerHTML = el.textContent + " <span class='caret'></span>"; const el = e.target;
const button = el.parentNode.parentNode.querySelector("button");
button.innerHTML = el.textContent;
this.ingChange(); this.ingChange();
} }
@ -427,7 +436,7 @@ class RecipeWaiter {
* @param {event} e * @param {event} e
*/ */
opAdd(e) { opAdd(e) {
log.debug(`'${e.target.querySelector(".arg-title").textContent}' added to recipe`); log.debug(`'${e.target.querySelector(".op-title").textContent}' added to recipe`);
window.dispatchEvent(this.manager.statechange); window.dispatchEvent(this.manager.statechange);
} }
@ -470,6 +479,44 @@ class RecipeWaiter {
op.insertAdjacentHTML("beforeend", registerListEl); op.insertAdjacentHTML("beforeend", registerListEl);
} }
/**
* Adjusts the number of ingredient columns as the width of the recipe changes.
*/
adjustWidth() {
const recList = document.getElementById("rec-list");
if (!this.ingredientRuleID) {
this.ingredientRuleID = null;
this.ingredientChildRuleID = null;
// Find relevant rules in the stylesheet
for (const i in document.styleSheets[0].cssRules) {
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients") {
this.ingredientRuleID = i;
}
if (document.styleSheets[0].cssRules[i].selectorText === ".ingredients > div") {
this.ingredientChildRuleID = i;
}
}
}
if (!this.ingredientRuleID || !this.ingredientChildRuleID) return;
const ingredientRule = document.styleSheets[0].cssRules[this.ingredientRuleID],
ingredientChildRule = document.styleSheets[0].cssRules[this.ingredientChildRuleID];
if (recList.clientWidth < 450) {
ingredientRule.style.gridTemplateColumns = "auto auto";
ingredientChildRule.style.gridColumn = "1 / span 2";
} else if (recList.clientWidth < 620) {
ingredientRule.style.gridTemplateColumns = "auto auto auto";
ingredientChildRule.style.gridColumn = "1 / span 3";
} else {
ingredientRule.style.gridTemplateColumns = "auto auto auto auto";
ingredientChildRule.style.gridColumn = "1 / span 4";
}
}
} }
export default RecipeWaiter; export default RecipeWaiter;

View File

@ -143,21 +143,19 @@
<div id="preloader-error" class="loading-error"></div> <div id="preloader-error" class="loading-error"></div>
</div> </div>
<!-- End preloader overlay --> <!-- End preloader overlay -->
<span id="edit-favourites" class="btn btn-default btn-sm"><img aria-hidden="true" src="<%- require('../static/images/favourite-16x16.png') %>" alt="Star Icon"/> Edit</span> <button type="button" class="btn btn-warning bmd-btn-icon" id="edit-favourites" data-toggle="tooltip" title="Edit favourites">
<div id="alert" class="alert alert-danger"> <i class="material-icons">star</i>
<button type="button" class="close" id="alert-close">&times;</button> </button>
<span id="alert-content"></span>
</div>
<div id="content-wrapper"> <div id="content-wrapper">
<div id="banner"> <div id="banner" class="row">
<div class="col-md-4" style="text-align: left; padding-left: 10px;"> <div class="col" style="text-align: left; padding-left: 10px;">
<% if (htmlWebpackPlugin.options.inline) { %> <% if (htmlWebpackPlugin.options.inline) { %>
<span>Version <%= htmlWebpackPlugin.options.version %></span> <span>Version <%= htmlWebpackPlugin.options.version %></span>
<% } else { %> <% } else { %>
<a href="cyberchef.htm" download>Download CyberChef<img aria-hidden="true" src="<%- require('../static/images/download-24x24.png') %>" alt="Download Icon"/></a> <a href="cyberchef.htm" download>Download CyberChef <i class="material-icons">file_download</i></a>
<% } %> <% } %>
</div> </div>
<div class="col-md-4" style="text-align: center;"> <div class="col" style="text-align: center;">
<span id="notice"> <span id="notice">
<script type="text/javascript"> <script type="text/javascript">
// Must be text/javascript rather than application/javascript otherwise IE won't recognise it... // Must be text/javascript rather than application/javascript otherwise IE won't recognise it...
@ -169,9 +167,9 @@
<noscript>JavaScript is not enabled. Good luck.</noscript> <noscript>JavaScript is not enabled. Good luck.</noscript>
</span> </span>
</div> </div>
<div class="col-md-4" style="text-align: right; padding-right: 0;"> <div class="col" style="text-align: right; padding-right: 0;">
<a href="#" id="options">Options<img aria-hidden="true" src="<%- require('../static/images/settings-22x22.png') %>" alt="Settings Icon"/></a> <a href="#" id="options">Options <i class="material-icons">settings</i></a>
<a href="#" id="support" data-toggle="modal" data-target="#support-modal">About / Support<img aria-hidden="true" src="<%- require('../static/images/help-22x22.png') %>" alt="Question Mark Icon"/></a> <a href="#" id="support" data-toggle="modal" data-target="#support-modal">About / Support <i class="material-icons">help</i></a>
</div> </div>
</div> </div>
<div id="workspace-wrapper"> <div id="workspace-wrapper">
@ -183,32 +181,41 @@
</div> </div>
<div id="recipe" class="split split-horizontal no-select"> <div id="recipe" class="split split-horizontal no-select">
<div class="title no-select">Recipe</div> <div class="title no-select">
Recipe
<span class="float-right">
<button type="button" class="btn btn-primary bmd-btn-icon" id="save" data-toggle="tooltip" title="Save recipe">
<i class="material-icons">save</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="load" data-toggle="tooltip" title="Load recipe">
<i class="material-icons">folder</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="clr-recipe" data-toggle="tooltip" title="Clear recipe">
<i class="material-icons">delete</i>
</button>
</span>
</div>
<ul id="rec-list" class="list-area no-select"></ul> <ul id="rec-list" class="list-area no-select"></ul>
<div id="controls" class="no-select"> <div id="controls" class="no-select">
<div id="operational-controls"> <div class="d-flex align-items-center">
<div id="bake-group"> <button type="button" class="mx-2 btn btn-lg btn-secondary" id="step" data-toggle="tooltip" title="Step through the recipe">
<button type="button" class="btn btn-success btn-lg" id="bake"> Step
<img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/> </button>
<span>Bake!</span>
</button>
<label class="btn btn-success btn-lg" id="auto-bake-label" for="auto-bake">
<input type="checkbox" checked="checked" id="auto-bake">
<div>Auto Bake</div>
</label>
</div>
<div class="btn-group" style="padding-top: 10px;"> <button type="button" class="mx-2 btn btn-lg btn-success btn-raised btn-block" id="bake">
<button type="button" class="btn btn-default" id="step"><img aria-hidden="true" src="<%- require('../static/images/step-16x16.png') %>" alt="Footstep Icon"/> Step through</button> <img aria-hidden="true" src="<%- require('../static/images/cook_male-32x32.png') %>" alt="Chef Icon"/>
<button type="button" class="btn btn-default" id="clr-breaks"><img aria-hidden="true" src="<%- require('../static/images/erase-16x16.png') %>" alt="Eraser Icon"/> Clear breakpoints</button> <span>Bake!</span>
</div> </button>
</div>
<div class="btn-group-vertical" id="extra-controls"> <div class="form-group" style="display: contents;">
<button type="button" class="btn btn-default" id="save"><img aria-hidden="true" src="<%- require('../static/images/save-16x16.png') %>" alt="Save Icon"/> Save recipe</button> <div class="mx-1 checkbox">
<button type="button" class="btn btn-default" id="load"><img aria-hidden="true" src="<%- require('../static/images/open_yellow-16x16.png') %>" alt="Open Icon"/> Load recipe</button> <label id="auto-bake-label">
<button type="button" class="btn btn-default" id="clr-recipe"><img aria-hidden="true" src="<%- require('../static/images/clean-16x16.png') %>" alt="Broom Icon"/> Clear recipe</button> <input type="checkbox" checked="checked" id="auto-bake">
<br>Auto Bake
</label>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
@ -217,10 +224,14 @@
<div id="input" class="split no-select"> <div id="input" class="split no-select">
<div class="title no-select"> <div class="title no-select">
<label for="input-text">Input</label> <label for="input-text">Input</label>
<div class="btn-group io-btn-group"> <span class="float-right">
<button type="button" class="btn btn-default btn-sm" id="clr-io"><img aria-hidden="true" src="<%- require('../static/images/recycle-16x16.png') %>" alt="Recycle Icon"/> Clear I/O</button> <button type="button" class="btn btn-primary bmd-btn-icon" id="clr-io" data-toggle="tooltip" title="Clear input and output">
<button type="button" class="btn btn-default btn-sm" id="reset-layout"><img aria-hidden="true" src="<%- require('../static/images/layout-16x16.png') %>" alt="Grid Icon"/> Reset layout</button> <i class="material-icons">delete</i>
</div> </button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="reset-layout" data-toggle="tooltip" title="Reset pane layout">
<i class="material-icons">view_compact</i>
</button>
</span>
<div class="io-info" id="input-info"></div> <div class="io-info" id="input-info"></div>
<div class="io-info" id="input-selection-info"></div> <div class="io-info" id="input-selection-info"></div>
</div> </div>
@ -230,7 +241,7 @@
<div id="input-file"> <div id="input-file">
<div class="file-overlay"></div> <div class="file-overlay"></div>
<div style="position: relative; height: 100%;"> <div style="position: relative; height: 100%;">
<div class="card"> <div class="io-card card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/> <img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body"> <div class="card-body">
<button type="button" class="close" id="input-file-close">&times;</button> <button type="button" class="close" id="input-file-close">&times;</button>
@ -248,16 +259,33 @@
<div id="output" class="split"> <div id="output" class="split">
<div class="title no-select"> <div class="title no-select">
<label for="output-text">Output</label> <label for="output-text">Output</label>
<div class="btn-group io-btn-group"> <span class="float-right">
<button type="button" class="btn btn-default btn-sm" id="save-to-file" title="Save to file"><img aria-hidden="true" src="<%- require('../static/images/save_as-16x16.png') %>" alt="Save Icon"/> Save to file</button> <button type="button" class="btn btn-primary bmd-btn-icon" id="save-to-file" data-toggle="tooltip" title="Save output to file">
<button type="button" class="btn btn-default btn-sm" id="copy-output" title="Copy output"><img aria-hidden="true" src="<%- require('../static/images/copy-16x16.png') %>" alt="Copy Icon"/> Copy raw output</button> <i class="material-icons">save</i>
<button type="button" class="btn btn-default btn-sm" id="switch" title="Move output to input"><img aria-hidden="true" src="<%- require('../static/images/switch-16x16.png') %>" alt="Switch Icon"/> Move output to input</button> </button>
<button type="button" class="btn btn-default btn-sm" id="undo-switch" title="Undo move" disabled="disabled"><img aria-hidden="true" src="<%- require('../static/images/undo-16x16.png') %>" alt="Undo Icon"/> Undo</button> <button type="button" class="btn btn-primary bmd-btn-icon" id="copy-output" data-toggle="tooltip" title="Copy raw output to the clipboard">
<button type="button" class="btn btn-default btn-sm" id="maximise-output" title="Maximise"><img aria-hidden="true" src="<%- require('../static/images/maximise-16x16.png') %>" alt="Maximise Icon"/> Max</button> <i class="material-icons">content_copy</i>
</div> </button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="switch" data-toggle="tooltip" title="Move output to input">
<i class="material-icons">loop</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="undo-switch" data-toggle="tooltip" title="Undo" disabled="disabled">
<i class="material-icons">undo</i>
</button>
<button type="button" class="btn btn-primary bmd-btn-icon" id="maximise-output" data-toggle="tooltip" title="Maximise output pane">
<i class="material-icons">fullscreen</i>
</button>
</span>
<div class="io-info" id="output-info"></div> <div class="io-info" id="output-info"></div>
<div class="io-info" id="output-selection-info"></div> <div class="io-info" id="output-selection-info"></div>
<span id="stale-indicator" title="The output is stale.&#10;The input or recipe has changed since this output was generated. Bake again to get the new value.">&#x1F551;</span> <button type="button" class="btn btn-primary bmd-btn-icon hidden" id="magic" data-toggle="tooltip" title="Magic!" data-html="true">
<svg width="22" height="22" viewBox="0 0 24 24">
<path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" />
</svg>
</button>
<span id="stale-indicator" class="hidden" data-toggle="tooltip" title="The output is stale. The input or recipe has changed since this output was generated. Bake again to get the new value.">
<i class="material-icons">access_time</i>
</span>
</div> </div>
<div class="textarea-wrapper"> <div class="textarea-wrapper">
<div id="output-highlighter" class="no-select"></div> <div id="output-highlighter" class="no-select"></div>
@ -267,14 +295,16 @@
<div id="output-file"> <div id="output-file">
<div class="file-overlay"></div> <div class="file-overlay"></div>
<div style="position: relative; height: 100%;"> <div style="position: relative; height: 100%;">
<div class="card"> <div class="io-card card">
<img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/> <img aria-hidden="true" src="<%- require('../static/images/file-128x128.png') %>" alt="File icon"/>
<div class="card-body"> <div class="card-body">
Size: <span id="output-file-size"></span><br> Size: <span id="output-file-size"></span><br>
<button id="output-file-download" type="button" class="btn btn-primary">Download</button> <button id="output-file-download" type="button" class="btn btn-primary btn-outline">Download</button>
<div class="input-group"> <div class="input-group">
<span class="input-group-btn"> <span class="input-group-btn">
<button id="output-file-slice" type="button" class="btn btn-default" title="View slice">&#x1f50d;</button> <button id="output-file-slice" type="button" class="btn btn-secondary bmd-btn-icon" title="View slice">
<i class="material-icons">search</i>
</button>
</span> </span>
<input type="number" class="form-control" id="output-file-slice-from" placeholder="From" value="0" step="1024" min="0"> <input type="number" class="form-control" id="output-file-slice-from" placeholder="From" value="0" step="1024" min="0">
<div class="input-group-addon">to</div> <div class="input-group-addon">to</div>
@ -295,19 +325,23 @@
</div> </div>
<div class="modal" id="save-modal" tabindex="-1" role="dialog"> <div class="modal" id="save-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/save-22x22.png') %>" alt="Save Icon"/> <h5 class="modal-title">Save recipe</h5>
<h4 class="modal-title">Save recipe</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<label for="save-text">Save your recipe to local storage or copy the following string to load later</label>
<ul class="nav nav-tabs" role="tablist"> <ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#chef-format" role="tab" data-toggle="tab">Chef format</a></li> <li class="nav-item">
<li role="presentation"><a href="#clean-json" role="tab" data-toggle="tab">Clean JSON</a></li> <a class="nav-link active" href="#chef-format" role="tab" data-toggle="tab">Chef format</a>
<li role="presentation"><a href="#compact-json" role="tab" data-toggle="tab">Compact JSON</a></li> </li>
<li class="nav-item">
<a class="nav-link" href="#clean-json" role="tab" data-toggle="tab">Clean JSON</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#compact-json" role="tab" data-toggle="tab">Compact JSON</a>
</li>
</ul> </ul>
<div class="tab-content" id="save-texts"> <div class="tab-content" id="save-texts">
<div role="tabpanel" class="tab-pane active" id="chef-format"> <div role="tabpanel" class="tab-pane active" id="chef-format">
@ -322,22 +356,27 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="save-name">Recipe name</label> <label for="save-name" class="bmd-label-floating">Recipe name</label>
<input type="text" class="form-control" id="save-name" placeholder="Recipe name"> <input type="text" class="form-control" id="save-name">
<span class="bmd-help">Save your recipe to local storage using this name, or copy it to load later</span>
</div> </div>
</div> </div>
<div class="modal-footer" id="save-footer"> <div class="modal-footer" id="save-footer">
<button type="button" class="btn btn-primary" id="save-button" data-dismiss="modal">Save</button> <button type="button" class="btn btn-primary" id="save-button" data-dismiss="modal">Save</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Done</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Done</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group" id="save-link-group"> <div class="form-group" id="save-link-group">
<label>Data link</label> <h6 style="display: inline">Data link</h6>
<div class="save-link-options"> <div class="save-link-options">
<input type="checkbox" id="save-link-recipe-checkbox" checked> <label for="save-link-recipe-checkbox"> Include recipe </label> <label class="checkbox-inline">
<input type="checkbox" id="save-link-input-checkbox" checked> <label for="save-link-input-checkbox"> Include input </label> <input type="checkbox" id="save-link-recipe-checkbox" checked> Include recipe
</label>
<label class="checkbox-inline">
<input type="checkbox" id="save-link-input-checkbox" checked> Include input
</label>
</div> </div>
<br> <br><br>
<a id="save-link" style="word-wrap: break-word;"></a> <a id="save-link" style="word-wrap: break-word;"></a>
</div> </div>
</div> </div>
@ -346,82 +385,63 @@
</div> </div>
<div class="modal" id="load-modal" tabindex="-1" role="dialog"> <div class="modal" id="load-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/open_yellow-24x24.png') %>" alt="Open Icon"/> <h5 class="modal-title">Load recipe</h5>
<h4 class="modal-title">Load recipe</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<div class="form-group"> <div class="form-group">
<label for="load-name">Load your recipe from local storage by selecting its name from the drop-down</label> <label for="load-name" class="bmd-label-floating">Recipe name</label>
<select class="form-control" id="load-name"></select> <select class="form-control" id="load-name"></select>
<span class="bmd-help">Load your recipe from local storage by selecting its name from the drop-down</span>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="load-text">Load your recipe by pasting it in the box below</label> <label for="load-text" class="bmd-label-floating">Recipe</label>
<textarea class="form-control" id="load-text" rows="5"></textarea> <textarea class="form-control" id="load-text" rows="5"></textarea>
<span class="bmd-help">Load your recipe by pasting it into this box</span>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-primary" id="load-button" data-dismiss="modal">Load</button> <button type="button" class="btn btn-primary" id="load-button" data-dismiss="modal">Load</button>
<button type="button" class="btn btn-danger" id="load-delete-button">Delete</button> <button type="button" class="btn btn-danger" id="load-delete-button">Delete</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal" id="options-modal" tabindex="-1" role="dialog"> <div class="modal" id="options-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/settings-22x22.png') %>" alt="Settings Icon"/> <h5 class="modal-title">Options</h5>
<h4 class="modal-title">Options</h4>
</div> </div>
<div class="modal-body" id="options-body"> <div class="modal-body" id="options-body">
<p style="font-weight: bold">Please note that these options will persist between sessions.</p> <p style="font-weight: bold">Please note that these options will persist between sessions.</p>
<div class="option-item">
<select option="theme" id="theme"> <div class="form-group option-item">
<label for="theme" class="bmd-label-floating"> Theme (only supported in modern browsers)</label>
<select class="form-control" option="theme" id="theme">
<option value="classic">Classic</option> <option value="classic">Classic</option>
<option value="dark">Dark</option> <option value="dark">Dark</option>
<option value="geocities">GeoCities</option> <option value="geocities">GeoCities</option>
</select> </select>
<label for="theme"> Theme (only supported in modern browsers)</label>
</div> </div>
<div class="option-item">
<input type="checkbox" option="updateUrl" id="updateUrl" checked /> <div class="form-group option-item">
<label for="updateUrl"> Update the URL when the input or recipe changes</label> <label for="errorTimeout" class="bmd-label-floating">Operation error timeout in ms (0 for never)</label>
<input type="number" class="form-control" option="errorTimeout" id="errorTimeout">
</div> </div>
<div class="option-item">
<input type="checkbox" option="showHighlighter" id="showHighlighter" checked /> <div class="form-group option-item">
<label for="showHighlighter"> Highlight selected bytes in output and input (when possible)</label> <label for="ioDisplayThreshold" class="bmd-label-floating">Size threshold for treating the input and output as a file (KiB)</label>
<input type="number" class="form-control" option="ioDisplayThreshold" id="ioDisplayThreshold">
</div> </div>
<div class="option-item">
<input type="checkbox" option="treatAsUtf8" id="treatAsUtf8" checked /> <div class="form-group option-item">
<label for="treatAsUtf8"> Treat output as UTF-8 if possible</label> <label for="logLevel" class="bmd-label-floating">Console logging level</label>
</div> <select class="form-control" option="logLevel" id="logLevel">
<div class="option-item">
<input type="checkbox" option="wordWrap" id="wordWrap" checked />
<label for="wordWrap"> Word wrap the input and output</label>
</div>
<div class="option-item">
<input type="checkbox" option="showErrors" id="showErrors" checked />
<label for="showErrors"> Operation error reporting (recommended)</label>
</div>
<div class="option-item">
<input type="checkbox" option="useMetaKey" id="useMetaKey" />
<label for="useMetaKey"> Use meta key for keybindings (Windows ⊞/Command ⌘)</label>
</div>
<div class="option-item">
<input type="number" option="errorTimeout" id="errorTimeout" />
<label for="errorTimeout"> Operation error timeout in ms (0 for never)</label>
</div>
<div class="option-item">
<input type="number" option="ioDisplayThreshold" id="ioDisplayThreshold" />
<label for="ioDisplayThreshold"> Size threshold for treating the input and output as a file (KiB)</label>
</div>
<div class="option-item">
<select option="logLevel" id="logLevel">
<option value="silent">Silent</option> <option value="silent">Silent</option>
<option value="error">Error</option> <option value="error">Error</option>
<option value="warn">Warn</option> <option value="warn">Warn</option>
@ -429,23 +449,70 @@
<option value="debug">Debug</option> <option value="debug">Debug</option>
<option value="trace">Trace</option> <option value="trace">Trace</option>
</select> </select>
<label for="logLevel"> Console logging level</label> </div>
<div class="checkbox option-item">
<label for="updateUrl">
<input type="checkbox" option="updateUrl" id="updateUrl" checked>
Update the URL when the input or recipe changes
</label>
</div>
<div class="checkbox option-item">
<label for="showHighlighter">
<input type="checkbox" option="showHighlighter" id="showHighlighter" checked>
Highlight selected bytes in output and input (when possible)
</label>
</div>
<div class="checkbox option-item">
<label for="treatAsUtf8">
<input type="checkbox" option="treatAsUtf8" id="treatAsUtf8" checked>
Treat output as UTF-8 if possible
</label>
</div>
<div class="checkbox option-item">
<label for="wordWrap">
<input type="checkbox" option="wordWrap" id="wordWrap" checked>
Word wrap the input and output
</label>
</div>
<div class="checkbox option-item">
<label for="showErrors">
<input type="checkbox" option="showErrors" id="showErrors" checked>
Operation error reporting (recommended)
</label>
</div>
<div class="checkbox option-item">
<label for="useMetaKey">
<input type="checkbox" option="useMetaKey" id="useMetaKey">
Use meta key for keybindings (Windows ⊞/Command ⌘)
</label>
</div>
<div class="checkbox option-item">
<label for="autoMagic">
<input type="checkbox" option="autoMagic" id="autoMagic">
Attempt to detect encoded data automagically
</label>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" id="reset-options">Reset options to default</button> <button type="button" class="btn btn-secondary" id="reset-options">Reset options to default</button>
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal" id="favourites-modal" tabindex="-1" role="dialog"> <div class="modal" id="favourites-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/favourite-24x24.png') %>" alt="Star Icon"/> <h5 class="modal-title">Edit Favourites</h5>
<h4 class="modal-title">Edit Favourites</h4>
</div> </div>
<div class="modal-body" id="favourites-body"> <div class="modal-body" id="favourites-body">
<ul> <ul>
@ -459,7 +526,7 @@
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal" id="reset-favourites">Reset favourites to default</button> <button type="button" class="btn btn-secondary" data-dismiss="modal" id="reset-favourites">Reset favourites to default</button>
<button type="button" class="btn btn-success" data-dismiss="modal" id="save-favourites">Save</button> <button type="button" class="btn btn-success" data-dismiss="modal" id="save-favourites">Save</button>
<button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button> <button type="button" class="btn btn-danger" data-dismiss="modal">Cancel</button>
</div> </div>
@ -468,11 +535,10 @@
</div> </div>
<div class="modal" id="support-modal" tabindex="-1" role="dialog"> <div class="modal" id="support-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<img aria-hidden="true" class="pull-right" src="<%- require('../static/images/help-22x22.png') %>" alt="Question Mark Icon"/> <h5 class="modal-title">CyberChef - The Cyber Swiss Army Knife</h5>
<h4 class="modal-title">CyberChef - The Cyber Swiss Army Knife</h4>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<img aria-hidden="true" class="about-img-left" src="<%- require('../static/images/cyberchef-128x128.png') %>" alt="CyberChef Logo"/> <img aria-hidden="true" class="about-img-left" src="<%- require('../static/images/cyberchef-128x128.png') %>" alt="CyberChef Logo"/>
@ -482,115 +548,120 @@
</p> </p>
<p>&copy; Crown Copyright 2016.</p> <p>&copy; Crown Copyright 2016.</p>
<p>Released under the Apache Licence, Version 2.0.</p> <p>Released under the Apache Licence, Version 2.0.</p>
<p> <p><a href="https://gitter.im/gchq/CyberChef">
<a href="https://gitter.im/gchq/CyberChef"> <img src="<%- require('../static/images/gitter-badge.svg') %>">
<img src="<%- require('../static/images/gitter-badge.svg') %>"> </a></p>
</a>
</p> <ul class="nav nav-tabs" role="tablist">
<br> <li class="nav-item" role="presentation">
<br> <a class="nav-link active" href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
<div>
<ul class="nav nav-tabs" role="tablist">
<li role="presentation" class="active"><a href="#faqs" aria-controls="profile" role="tab" data-toggle="tab">
<img aria-hidden="true" src="<%- require('../static/images/help-16x16.png') %>" alt="Question Mark Icon"/>
FAQs FAQs
</a></li> </a>
<li role="presentation"><a href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab"> </li>
<img aria-hidden="true" src="<%- require('../static/images/bug-16x16.png') %>" alt="Bug Icon"/> <li class="nav-item" role="presentation">
<a class="nav-link" href="#report-bug" aria-controls="messages" role="tab" data-toggle="tab">
Report a bug Report a bug
</a></li> </a>
<li role="presentation"><a href="#about" aria-controls="messages" role="tab" data-toggle="tab"> </li>
<img aria-hidden="true" src="<%- require('../static/images/speech-16x16.png') %>" alt="Speech Balloon Icon"/> <li class="nav-item" role="presentation">
<a class="nav-link" href="#about" aria-controls="messages" role="tab" data-toggle="tab">
About About
</a></li> </a>
<li role="presentation"><a href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab"> </li>
<img aria-hidden="true" src="<%- require('../static/images/code-16x16.png') %>" alt="List Icon"/> <li class="nav-item" role="presentation">
<a class="nav-link" href="#keybindings" aria-controls="messages" role="tab" data-toggle="tab">
Keybindings Keybindings
</a></li> </a>
</ul> </li>
<div class="tab-content"> </ul>
<div role="tabpanel" class="tab-pane active" id="faqs"> <div class="tab-content">
<br> <div role="tabpanel" class="tab-pane active" id="faqs">
<blockquote> <br>
<a data-toggle="collapse" data-target="#faq-examples"> <a class="btn btn-primary" data-toggle="collapse" data-target="#faq-examples">
What sort of things can I do with CyberChef? What sort of things can I do with CyberChef?
</a> </a>
</blockquote> <div class="collapse" id="faq-examples">
<div class="collapse" id="faq-examples"> <p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p>
<p>There are around 200 operations in CyberChef allowing you to carry out simple and complex tasks easily. Here are some examples:</p> <ul>
<ul> <li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li>
<li><a href="#recipe=From_Base64('A-Za-z0-9%2B/%3D',true)&input=VTI4Z2JHOXVaeUJoYm1RZ2RHaGhibXR6SUdadmNpQmhiR3dnZEdobElHWnBjMmd1">Decode a Base64-encoded string</a></li> <li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li>
<li><a href="#recipe=Translate_DateTime_Format('Standard%20date%20and%20time','DD/MM/YYYY%20HH:mm:ss','UTC','dddd%20Do%20MMMM%20YYYY%20HH:mm:ss%20Z%20z','Australia/Queensland')&input=MTUvMDYvMjAxNSAyMDo0NTowMA">Convert a date and time to a different time zone</a></li> <li><a href="#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy">Parse a Teredo IPv6 address</a></li>
<li><a href="#recipe=Parse_IPv6_address()&input=MjAwMTowMDAwOjQxMzY6ZTM3ODo4MDAwOjYzYmY6M2ZmZjpmZGQy">Parse a Teredo IPv6 address</a></li> <li><a href="#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw">Convert data from a hexdump, then decompress</a></li>
<li><a href="#recipe=From_Hexdump()Gunzip()&input=MDAwMDAwMDAgIDFmIDhiIDA4IDAwIDEyIGJjIGYzIDU3IDAwIGZmIDBkIGM3IGMxIDA5IDAwIDIwICB8Li4uLi6881cu/y7HwS4uIHwKMDAwMDAwMTAgIDA4IDA1IGQwIDU1IGZlIDA0IDJkIGQzIDA0IDFmIGNhIDhjIDQ0IDIxIDViIGZmICB8Li7QVf4uLdMuLsouRCFb/3wKMDAwMDAwMjAgIDYwIGM3IGQ3IDAzIDE2IGJlIDQwIDFmIDc4IDRhIDNmIDA5IDg5IDBiIDlhIDdkICB8YMfXLi6%2BQC54Sj8uLi4ufXwKMDAwMDAwMzAgIDRlIGM4IDRlIDZkIDA1IDFlIDAxIDhiIDRjIDI0IDAwIDAwIDAwICAgICAgICAgICB8TshObS4uLi5MJC4uLnw">Convert data from a hexdump, then decompress</a></li> <li><a href="#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ">Decrypt and disassemble shellcode</a></li>
<li><a href="#recipe=RC4(%7B'option':'UTF8','string':'secret'%7D,'Hex','Hex')Disassemble_x86('64','Full%20x86%20architecture',16,0,true,true)&input=MjFkZGQyNTQwMTYwZWU2NWZlMDc3NzEwM2YyYTM5ZmJlNWJjYjZhYTBhYWJkNDE0ZjkwYzZjYWY1MzEyNzU0YWY3NzRiNzZiM2JiY2QxOTNjYjNkZGZkYmM1YTI2NTMzYTY4NmI1OWI4ZmVkNGQzODBkNDc0NDIwMWFlYzIwNDA1MDcxMzhlMmZlMmIzOTUwNDQ2ZGIzMWQyYmM2MjliZTRkM2YyZWIwMDQzYzI5M2Q3YTVkMjk2MmMwMGZlNmRhMzAwNzJkOGM1YTZiNGZlN2Q4NTlhMDQwZWVhZjI5OTczMzYzMDJmNWEwZWMxOQ">Decrypt and disassemble shellcode</a></li> <li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Display multiple timestamps as full dates</a></li>
<li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Display multiple timestamps as full dates</a></li> <li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA">Carry out different operations on data of different types</a></li>
<li><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)Conditional_Jump('1',false,'base64',10)To_Hex('Space')Return()Label('base64')To_Base64('A-Za-z0-9%2B/%3D')&input=U29tZSBkYXRhIHdpdGggYSAxIGluIGl0ClNvbWUgZGF0YSB3aXRoIGEgMiBpbiBpdA">Carry out different operations on data of different types</a></li> <li><a href="#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ">Use parts of the input as arguments to operations</a></li>
<li><a href="#recipe=Register('key%3D(%5B%5C%5Cda-f%5D*)',true,false)Find_/_Replace(%7B'option':'Regex','string':'.*data%3D(.*)'%7D,'$1',true,false,true)RC4(%7B'option':'Hex','string':'$R0'%7D,'Hex','Latin1')&input=aHR0cDovL21hbHdhcmV6LmJpei9iZWFjb24ucGhwP2tleT0wZTkzMmE1YyZkYXRhPThkYjdkNWViZTM4NjYzYTU0ZWNiYjMzNGUzZGIxMQ">Use parts of the input as arguments to operations</a></li> </ul>
</ul>
</div>
<blockquote>
<a data-toggle="collapse" data-target="#faq-load-files">
Can I load input directly from files?
</a>
</blockquote>
<div class="collapse" id="faq-load-files">
<p>Yes! Just drag your file over the input box and drop it.</p>
<p>CyberChef can handle files up to around 500MB (depending on your browser), however some of the operations may take a very long time to run over this much data.</p>
<p>If the output is larger than a certain threshold (default 1MiB), it will be presented to you as a file available for download. Slices of the file can be viewed in the output if you need to inspect them.</p>
</div>
<blockquote>
<a data-toggle="collapse" data-target="#faq-fork">
How do I run operation X over multiple inputs at once?
</a>
</blockquote>
<div class="collapse" id="faq-fork">
<p>Maybe you have 10 timestamps that you want to parse or 16 encoded strings that all have the same key.</p>
<p>The 'Fork' operation (found in the 'Flow control' category) splits up the input line by line and runs all subsequent operations on each line separately. Each output is then displayed on a separate line. These delimiters can be changed, so if your inputs are separated by commas, you can change the split delimiter to a comma instead.</p>
<p><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Click here</a> for an example.</p>
</div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="report-bug"> <br>
<br>
<p>If you find a bug in CyberChef, please raise an issue in our GitHub repository explaining it in as much detail as possible. Copy and include the following information if relevant.</p> <a class="btn btn-primary" data-toggle="collapse" data-target="#faq-load-files">
<br> Can I load input directly from files?
<pre id="report-bug-info"></pre> </a>
<br> <div class="collapse" id="faq-load-files">
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new" role="button">Raise issue on GitHub</a> <p>Yes! Just drag your file over the input box and drop it.</p>
<p>CyberChef can handle files up to around 500MB (depending on your browser), however some of the operations may take a very long time to run over this much data.</p>
<p>If the output is larger than a certain threshold (default 1MiB), it will be presented to you as a file available for download. Slices of the file can be viewed in the output if you need to inspect them.</p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="about" style="padding: 20px;"> <br>
<h5><strong>What</strong></h5>
<p>A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.</p><br>
<h5><strong>Why</strong></h5> <a class="btn btn-primary" data-toggle="collapse" data-target="#faq-fork">
<p>Digital data comes in all shapes, sizes and formats in the modern world CyberChef helps to make sense of this data all on one easy-to-use platform.</p><br> How do I run operation X over multiple inputs at once?
</a>
<div class="collapse" id="faq-fork">
<h5><strong>How</strong></h5> <p>Maybe you have 10 timestamps that you want to parse or 16 encoded strings that all have the same key.</p>
<p>The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a "recipe", potentially resulting in complex analysis, which can be shared with other users and used with their input.</p> <p>The 'Fork' operation (found in the 'Flow control' category) splits up the input line by line and runs all subsequent operations on each line separately. Each output is then displayed on a separate line. These delimiters can be changed, so if your inputs are separated by commas, you can change the split delimiter to a comma instead.</p>
<p>For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.</p><br> <p><a href="#recipe=Fork('%5C%5Cn','%5C%5Cn',false)From_UNIX_Timestamp('Seconds%20(s)')&input=OTc4MzQ2ODAwCjEwMTI2NTEyMDAKMTA0NjY5NjQwMAoxMDgxMDg3MjAwCjExMTUzMDUyMDAKMTE0OTYwOTYwMA">Click here</a> for an example.</p>
<h5><strong>Who</strong></h5>
<p>It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.</p><br>
<h5><strong>Aim</strong></h5>
<p>It is hoped that by releasing CyberChef through <a href="https://github.com/gchq/CyberChef">GitHub</a>, contributions can be added which can be rolled out into future versions of the tool.</p><br>
<br>
<p>There are around 200 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>Its the Cyber Swiss Army Knife.</p>
</div> </div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;"> <br>
<table class="table table-condensed table-bordered table-hover" id="keybList"></table>
<a class="btn btn-primary" data-toggle="collapse" data-target="#faq-magic">
How does the 'Magic' operation work?
</a>
<div class="collapse" id="faq-magic">
<p>The 'Magic' operation uses a number of methods to detect encoded data and the operations which can be used to make sense of it. A technical description of these methods can be found <a href="https://github.com/gchq/CyberChef/wiki/Automatic-detection-of-encoded-data-using-CyberChef-Magic">here</a>.</p>
</div> </div>
</div> </div>
<div role="tabpanel" class="tab-pane" id="report-bug">
<br>
<p>If you find a bug in CyberChef, please raise an issue in our GitHub repository explaining it in as much detail as possible. Copy and include the following information if relevant.</p>
<br>
<pre id="report-bug-info"></pre>
<br>
<a class="btn btn-primary" href="https://github.com/gchq/CyberChef/issues/new" role="button">Raise issue on GitHub</a>
</div>
<div role="tabpanel" class="tab-pane" id="about" style="padding: 20px;">
<h5><strong>What</strong></h5>
<p>A simple, intuitive web app for analysing and decoding data without having to deal with complex tools or programming languages. CyberChef encourages both technical and non-technical people to explore data formats, encryption and compression.</p><br>
<h5><strong>Why</strong></h5>
<p>Digital data comes in all shapes, sizes and formats in the modern world CyberChef helps to make sense of this data all on one easy-to-use platform.</p><br>
<h5><strong>How</strong></h5>
<p>The interface is designed with simplicity at its heart. Complex techniques are now as trivial as drag-and-drop. Simple functions can be combined to build up a "recipe", potentially resulting in complex analysis, which can be shared with other users and used with their input.</p>
<p>For those comfortable writing code, CyberChef is a quick and efficient way to prototype solutions to a problem which can then be scripted once proven to work.</p><br>
<h5><strong>Who</strong></h5>
<p>It is expected that CyberChef will be useful for cybersecurity and antivirus companies. It should also appeal to the academic world and any individuals or companies involved in the analysis of digital data, be that software developers, analysts, mathematicians or casual puzzle solvers.</p><br>
<h5><strong>Aim</strong></h5>
<p>It is hoped that by releasing CyberChef through <a href="https://github.com/gchq/CyberChef">GitHub</a>, contributions can be added which can be rolled out into future versions of the tool.</p><br>
<br>
<p>There are around 200 useful operations in CyberChef for anyone working on anything vaguely Internet-related, whether you just want to convert a timestamp to a different format, decompress gzipped data, create a SHA3 hash, or parse an X.509 certificate to find out who issued it.</p>
<p>Its the Cyber Swiss Army Knife.</p>
</div>
<div role="tabpanel" class="tab-pane" id="keybindings" style="padding: 20px;">
<table class="table table-condensed table-bordered table-hover" id="keybList"></table>
</div>
</div> </div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
</div> </div>
<a href="https://github.com/gchq/CyberChef"> <a href="https://github.com/gchq/CyberChef">
<img aria-hidden="true" style="position: absolute; top: 0; right: 0; border: 0;" src="<%- require('../static/images/fork_me.png') %>" alt="Fork me on GitHub"> <img aria-hidden="true" style="position: absolute; top: 0; right: 0; border: 0;" src="<%- require('../static/images/fork_me.png') %>" alt="Fork me on GitHub">
@ -600,19 +671,17 @@
</div> </div>
<div class="modal" id="confirm-modal" tabindex="-1" role="dialog"> <div class="modal" id="confirm-modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg"> <div class="modal-dialog modal-lg" role="document">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
<h4 class="modal-title" id="confirm-title"></h4> <h5 class="modal-title" id="confirm-title"></h5>
</div> </div>
<div class="modal-body" id="confirm-body"></div> <div class="modal-body" id="confirm-body"></div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-success" id="confirm-yes"> <button type="button" class="btn btn-success" id="confirm-yes">
<img aria-hidden="true" src="<%- require('../static/images/thumb_up-16x16.png') %>" alt="Thumbs Up"/>
Yes Yes
</button> </button>
<button type="button" class="btn btn-danger" id="confirm-no" data-dismiss="modal"> <button type="button" class="btn btn-danger" id="confirm-no" data-dismiss="modal">
<img aria-hidden="true" src="<%- require('../static/images/thumb_down-16x16.png') %>" alt="Thumbs Down"/>
No No
</button> </button>
</div> </div>

View File

@ -9,8 +9,9 @@ import "./stylesheets/index.js";
// Libs // Libs
import "babel-polyfill"; import "babel-polyfill";
import "bootstrap"; import "arrive";
import "bootstrap-switch"; import "snackbarjs";
import "bootstrap-material-design";
import "bootstrap-colorpicker"; import "bootstrap-colorpicker";
import moment from "moment-timezone"; import moment from "moment-timezone";
import * as CanvasComponents from "../core/lib/CanvasComponents"; import * as CanvasComponents from "../core/lib/CanvasComponents";
@ -50,7 +51,8 @@ function main() {
theme: "classic", theme: "classic",
useMetaKey: false, useMetaKey: false,
ioDisplayThreshold: 512, ioDisplayThreshold: 512,
logLevel: "info" logLevel: "info",
autoMagic: true,
}; };
document.removeEventListener("DOMContentLoaded", main, false); document.removeEventListener("DOMContentLoaded", main, false);

Binary file not shown.

View File

@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="24" height="24" viewBox="0 0 24 24"><path d="M7.5,5.6L5,7L6.4,4.5L5,2L7.5,3.4L10,2L8.6,4.5L10,7L7.5,5.6M19.5,15.4L22,14L20.6,16.5L22,19L19.5,17.6L17,19L18.4,16.5L17,14L19.5,15.4M22,2L20.6,4.5L22,7L19.5,5.6L17,7L18.4,4.5L17,2L19.5,3.4L22,2M13.34,12.78L15.78,10.34L13.66,8.22L11.22,10.66L13.34,12.78M14.37,7.29L16.71,9.63C17.1,10 17.1,10.65 16.71,11.04L5.04,22.71C4.65,23.1 4,23.1 3.63,22.71L1.29,20.37C0.9,20 0.9,19.35 1.29,18.96L12.96,7.29C13.35,6.9 14,6.9 14.37,7.29Z" /></svg>

After

Width:  |  Height:  |  Size: 719 B

View File

@ -1,22 +0,0 @@
/**
* Alert styles
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
#alert {
position: fixed;
width: 30%;
margin: 30px auto;
top: 10px;
left: 0;
right: 0;
z-index: 2000;
display: none;
}
#alert a {
text-decoration: underline;
}

View File

@ -7,7 +7,7 @@
*/ */
.operation { .operation {
cursor: pointer; cursor: grab;
padding: 10px; padding: 10px;
list-style-type: none; list-style-type: none;
position: relative; position: relative;
@ -18,110 +18,154 @@
border-right: none; border-right: none;
} }
.arg-group { #rec-list .operation {
display: table; padding: 14px;
width: 100%;
margin-top: 10px;
} }
.arg-group-text { .op-title {
display: block; font-weight: var(--op-title-font-weight);
} }
.inline-args { .ingredients {
float: left; display: grid;
width: auto; grid-template-columns: auto auto auto;
margin-right: 30px; grid-column-gap: 14px;
height: 34px;
} }
.inline-args input[type="checkbox"] { .ingredients > div {
margin-top: 10px; grid-column: 1 / span 3;
} }
.inline-args input[type="number"] { .ingredients > div.inline {
width: 100px; grid-column: unset;
} }
.arg-title { .ingredients .form-group {
font-weight: var(--arg-title-font-weight); margin-top: 1rem;
padding-top: 0;
} }
.arg-input { .arg {
display: table-cell;
width: 100%;
padding: 6px 12px;
vertical-align: middle;
height: var(--arg-input-height);
font-size: var(--arg-input-font-size);
line-height: var(--arg-input-line-height);
color: var(--arg-font-colour);
background-color: var(--arg-background);
border: 1px solid var(--arg-border-colour);
font-family: var(--fixed-width-font-family); font-family: var(--fixed-width-font-family);
text-overflow: ellipsis; text-overflow: ellipsis;
} }
.short-string { select.arg {
width: 150px; font-family: var(--primary-font-family);
} min-width: 100px;
select {
display: block;
padding: 6px 8px;
height: 34px;
border: 1px solid var(--arg-border-colour);
background-color: var(--arg-background);
color: var(--arg-font-colour);
}
.arg[disabled] {
cursor: not-allowed;
opacity: 1;
background-color: var(--arg-disabled-background);
} }
textarea.arg { textarea.arg {
width: 100%; min-height: 74px;
min-height: 50px;
height: 70px;
margin-top: 5px;
border: 1px solid var(--arg-border-colour);
resize: vertical; resize: vertical;
color: var(--arg-font-colour); }
div.toggle-string {
flex: 1;
}
.operation [class^='bmd-label'],
.operation [class*=' bmd-label'] {
top: 13px !important;
left: 12px;
z-index: 10;
}
.operation label,
.operation .checkbox label {
color: var(--arg-label-colour);
}
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused [class^='bmd-label'],
.operation .is-focused [class*=' bmd-label'],
.operation .is-focused label,
.operation .checkbox label:hover {
color: #1976d2;
}
.operation .form-control {
padding: 20px 12px 6px 12px;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
background-image: none;
background-color: var(--arg-background); background-color: var(--arg-background);
font-family: var(--fixed-width-font-family); background-position-y: 100%, 100%;
color: var(--arg-font-colour);
} }
.arg-label { .operation .form-control:hover {
display: table-cell; background-image:
width: 1px; linear-gradient(to top, #1976d2 2px, rgba(25, 118, 210, 0) 2px),
padding-right: 10px; linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
font-weight: normal; filter: brightness(97%);
vertical-align: middle;
white-space: pre;
} }
.editable-option { .operation .form-control:focus {
position: relative; background-color: var(--arg-background);
display: inline-block; background-image:
linear-gradient(to top, #1976d2 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, rgba(0, 0, 0, 0.26) 1px, rgba(0, 0, 0, 0) 1px);
filter: brightness(100%);
} }
.editable-option-select { .operation .bmd-form-group.is-filled label.bmd-label-floating,
min-width: 250px; .operation .bmd-form-group.is-focused label.bmd-label-floating {
top: 4px !important;
left: 12px;
} }
.editable-option-input { .operation .bmd-form-group .bmd-help {
position: absolute; margin-top: -17px;
top: 1px;
left: 1px;
width: calc(100% - 20px);
height: calc(100% - 2px) !important;
border: none !important;
} }
button.dropdown-toggle { .input-group .form-control {
background-color: var(--secondary-background-colour); border-top-left-radius: 4px !important;
}
.input-group-append button {
border-top-right-radius: 4px;
background-color: var(--arg-background) !important;
margin: unset;
}
.input-group-append button:hover {
filter: brightness(97%);
}
.editable-option-menu {
height: auto;
max-height: 350px;
overflow-x: hidden;
}
.editable-option-menu .dropdown-item {
padding: 0.3rem 1rem 0.3rem 1rem;
min-height: 1.6rem;
max-width: 20rem;
}
.ingredients .dropdown-toggle-split {
height: 41px !important;
}
.boolean-arg {
height: 46px;
}
.boolean-arg .checkbox {
height: 100%;
}
.boolean-arg .checkbox label {
height: 100%;
display: flex;
align-items: center;
}
.boolean-arg .checkbox-decorator {
top: 13px;
} }
.register-list { .register-list {
@ -132,8 +176,9 @@ button.dropdown-toggle {
.op-icon { .op-icon {
float: right; float: right;
margin-left: 10px; color: #f44336;
margin-top: 3px; font-size: 18px;
cursor: pointer;
} }
.recip-icons { .recip-icons {
@ -143,33 +188,28 @@ button.dropdown-toggle {
height: 16px; height: 16px;
} }
.recip-icon { .recip-icons i {
margin-right: 10px; margin-right: 10px;
vertical-align: baseline; vertical-align: baseline;
float: right; float: right;
font-size: 18px;
cursor: pointer;
} }
.disable-icon { .disable-icon {
width: 16px; color: #9e9e9e;
height: 16px;
margin-top: -1px;
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAACfElEQVQ4y6WTPWgTYRjHf8nlfVvuoDVIP4Yuki4WHKoUqggFRUTsUEGkVG2hmCq6OnTwIxYHB+eijZOKdLNDW1pKKyGigh8dBHUJElxyBgx3vEnukvdyDrUhRXDxGR+e/+/583xEwjDkfyIGwNVTzURm4tYAMA6MAoN/0tvAMrA48uL+l2bx4w0iYRjSuHKC6OnTZLqHk8CcaZq9bW1tSCkBqNVq+L5PpVIpAHdGfr5LN9bXiT7Z2nGgteb1/qFkLBJZ6OjowHEc8vk8pVIJgHg8TldXF52dnb2u6y5s7R/iuF5JSyAKkLl4eyAMwznLsrBtm1wu99Z13amk+BFJih8R13WXANrb27EsizAM5zIXbw+wC9Baj0spe5VSFAqFt4ZhXJ6ufXuK55E5cDKVSCTGenp6yGazKKWQUvZqrcebgCAIRqWUOI6DEOLR1K8POapVMgfPpoC7u2LLspYcx0FKSRAEo60OBg3DwPd9Jr5vPqWvj8zh83vEwL2J75vnfN/HMAy01oPNNQZBQBAEO1OvVsl0D/8lTuZfpYDd7gRBQKuD7XK5jGmarB679PIv8deVFJUKq8cuTZqmSblcRmu93QpYVkohhMCyrLE94n2/UlSrbJy5kRBCXBNCoJRCa73cClh0XbfgeR6WZZHNZunv719KvnmeYnWVVxdmJ2Ox2DMhxFHP83Bdt6C1XgR2LvHzQDvvb84npZQL8Xgc0zSJRqN7br7RaFCpVCiVStRqtZmhh9fTh754TQdMr82nPc+bsW27UCwWUUpRr9ep1+sopSgWi9i2XfA8b2Z6bT6ttabp4GMi0uz0aXbhn890+MFM85mO5MIdwP/Eb1pMUCdctYRzAAAAAElFTkSuQmCC') no-repeat;
} }
.disable-icon-selected { .disable-icon-selected {
background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAACFUlEQVR4XqWTP0tbURjGn9zY3mjBwsUhBQtS6XKxiNypIGZJ6SKYUYdaKWg7OrrE3pYO+Qit3dpFuuQO6lI7Z4nESQdjlJbkJh0MksSb3Jvk9H0gjZFu9YWH83LO7zn/3nNCSincJobAeP1sEDBFi6J50UyPy4l2RNuioz756Ts0tt1OB4jH2a52Ne2HGh9PwrJm2EcxZx/HyPRYMDgB2u02/N3d1c7w8BZMM1ptNJBPp3GwsUExB/s4RoYsPf0JOkFgdoH34YkJ/D48xC/HyTTOzl5ayWSIktwxqlVo0SjIkKWnP0Hg+4swjGitVMJFNpu5o+svptfXv6DZBDIZezoWS3Db3A0ZsvRcH8H354dGR9EoFHA3EvlorqycwvOAXM4G8Pav+f7YmEOGLD1gsIzl54+V+vBK/Yw9ZAv1LQW1FrdFSnKVfQTK5liPUfRI9I8ArqiPjLAF9vcHVybyzlpasgcZeq7voNXKNSsV3DMMXB4fp/8xLyzYuLri2DIZsvQM3sFOzXURiUR4zsQNcyrFleFVKpNyP2/IkKVnsArbF65bbkqplJSJZrl5x5qbs7G3h3artSyV+arr+lMyZOnpP2Wp6ZFos3R+vvUgCGDNzgKalkA4rECIr07662J2i0X4nrfJJ33jJT6Zmvpcr9XWCicn5WI+j7rrAmKgmLOPY2TI0sPgb8TBZOi/PpN1qnDr7/wH3jxgB/FKIXkAAAAASUVORK5CYII=') no-repeat; color: #f44336;
} }
.breakpoint { .breakpoint {
float: right; color: #9e9e9e;
width: 14px;
height: 14px;
background-color: #eee;
border: 1px solid #aaa;
} }
.breakpoint-selected { .breakpoint-selected {
background: #eee url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAAXVBMVEXIUkvzUVHzTEzzn5785eXrbW24BgbzWVnze3vzVVXzY2Pyion509PzbW3zXV1UMxj0l5f1srKbRTRgOxzJDg796ur74ODfIyP5zs6LLx3pNTXYGxuxdkVZNhn////sCC1eAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAACxMAAAsTAQCanBgAAABWSURBVBjTnc+7EoAgDERRTOQVxMEZFAf//z8FjAUFDbfb060QU2FwxngimxnCea3bjegSgz+0tguAfBgIy64QGfZQdg91dgAtqUZgnfz6IacYVWvu2AvR4wNAv0nxrAAAAABJRU5ErkJggg==') no-repeat -2px -2px; color: #f44336;
} }
.break { .break {
@ -178,30 +218,40 @@ button.dropdown-toggle {
border-color: var(--breakpoint-border-colour) !important; border-color: var(--breakpoint-border-colour) !important;
} }
.break .form-group * { color: var(--breakpoint-font-colour) !important; }
.selected-op { .selected-op {
color: var(--selected-operation-font-color) !important; color: var(--selected-operation-font-color) !important;
background-color: var(--selected-operation-bg-colour) !important; background-color: var(--selected-operation-bg-colour) !important;
border-color: var(--selected-operation-border-colour) !important; border-color: var(--selected-operation-border-colour) !important;
} }
.selected-op .form-group * { color: var(--selected-operation-font-color) !important; }
.flow-control-op { .flow-control-op {
color: var(--fc-operation-font-colour) !important; color: var(--fc-operation-font-colour) !important;
background-color: var(--fc-operation-bg-colour) !important; background-color: var(--fc-operation-bg-colour) !important;
border-color: var(--fc-operation-border-colour) !important; border-color: var(--fc-operation-border-colour) !important;
} }
.flow-control-op .form-group *:not(.arg) { color: var(--fc-operation-font-colour) }
.flow-control-op.break { .flow-control-op.break {
color: var(--fc-breakpoint-operation-font-colour) !important; color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-bg-colour) !important; background-color: var(--fc-breakpoint-operation-bg-colour) !important;
border-color: var(--fc-breakpoint-operation-border-colour) !important; border-color: var(--fc-breakpoint-operation-border-colour) !important;
} }
.flow-control-op.break .form-group * { color: var(--fc-breakpoint-operation-font-colour) !important; }
.disabled { .disabled {
color: var(--disabled-font-colour) !important; color: var(--disabled-font-colour) !important;
background-color: var(--disabled-bg-colour) !important; background-color: var(--disabled-bg-colour) !important;
border-color: var(--disabled-border-colour) !important; border-color: var(--disabled-border-colour) !important;
} }
.disabled .form-group * { color: var(--disabled-font-colour) !important; }
.break .register-list { .break .register-list {
color: var(--fc-breakpoint-operation-font-colour) !important; color: var(--fc-breakpoint-operation-font-colour) !important;
background-color: var(--fc-breakpoint-operation-border-colour) !important; background-color: var(--fc-breakpoint-operation-border-colour) !important;

View File

@ -7,16 +7,25 @@
*/ */
:root { :root {
--title-height: 43px; --title-height: 48px;
} }
.title { .title {
padding: 10px; padding: 8px;
padding-left: 12px;
padding-right: 12px;
height: var(--title-height); height: var(--title-height);
border-bottom: 1px solid var(--primary-border-colour); border-bottom: 1px solid var(--primary-border-colour);
font-weight: var(--title-weight); font-weight: var(--title-weight);
font-size: var(--title-size);
color: var(--title-colour); color: var(--title-colour);
background-color: var(--title-background-colour); background-color: var(--title-background-colour);
line-height: calc(var(--title-height) - 14px);
}
.title>span,
.title>.btn {
margin-top: -4px;
} }
.list-area { .list-area {
@ -29,7 +38,7 @@
padding: 0; padding: 0;
} }
.card { .io-card.card {
box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2); box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);
transition: 0.3s; transition: 0.3s;
width: 400px; width: 400px;
@ -42,13 +51,14 @@
color: var(--primary-font-colour); color: var(--primary-font-colour);
line-height: 30px; line-height: 30px;
background-color: var(--primary-background-colour); background-color: var(--primary-background-colour);
flex-direction: row;
} }
.card:hover { .io-card.card:hover {
box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2);
} }
.card>img { .io-card.card>img {
float: left; float: left;
width: 128px; width: 128px;
height: 128px; height: 128px;
@ -56,13 +66,13 @@
margin-top: 11px; margin-top: 11px;
} }
.card-body .close { .io-card.card .card-body .close {
position: absolute; position: absolute;
right: 10px; right: 10px;
top: 10px; top: 4px;
} }
.card-body { .io-card.card .card-body {
float: left; float: left;
padding: 16px; padding: 16px;
width: 250px; width: 250px;
@ -72,12 +82,12 @@
user-select: text; user-select: text;
} }
.card-body>.btn { .io-card.card .card-body>.btn {
margin-bottom: 15px; margin-bottom: 5px;
margin-top: 5px; margin-top: 5px;
} }
.card input[type=number] { .io-card.card input[type=number] {
padding-right: 6px; padding-right: 6px;
padding-left: 6px; padding-left: 6px;
} }

View File

@ -19,7 +19,6 @@
@import "./preloader.css"; @import "./preloader.css";
/* Components */ /* Components */
@import "./components/_alert.css";
@import "./components/_button.css"; @import "./components/_button.css";
@import "./components/_list.css"; @import "./components/_list.css";
@import "./components/_operation.css"; @import "./components/_operation.css";

View File

@ -10,8 +10,7 @@
import "highlight.js/styles/vs.css"; import "highlight.js/styles/vs.css";
/* Frameworks */ /* Frameworks */
import "./vendors/bootstrap.less"; import "./vendors/bootstrap.scss";
import "bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.css";
import "bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css"; import "bootstrap-colorpicker/dist/css/bootstrap-colorpicker.css";
/* CyberChef styles */ /* CyberChef styles */

View File

@ -14,11 +14,10 @@
border-bottom: 1px solid var(--primary-border-colour); border-bottom: 1px solid var(--primary-border-colour);
color: var(--banner-font-colour); color: var(--banner-font-colour);
background-color: var(--banner-bg-colour); background-color: var(--banner-bg-colour);
margin: 0;
} }
#banner img { #banner i {
margin-bottom: 2px; vertical-align: middle;
margin-left: 8px;
padding-right: 10px; padding-right: 10px;
} }

View File

@ -7,8 +7,7 @@
*/ */
:root { :root {
--controls-height: 120px; --controls-height: 75px;
--controls-division: 65%;
} }
#controls { #controls {
@ -17,49 +16,30 @@
height: var(--controls-height); height: var(--controls-height);
bottom: 0; bottom: 0;
padding: 10px; padding: 10px;
padding-top: 12px;
border-top: 1px solid var(--primary-border-colour); border-top: 1px solid var(--primary-border-colour);
background-color: var(--secondary-background-colour); background-color: var(--secondary-background-colour);
} }
#operational-controls { #auto-bake-label {
width: var(--controls-division); display: inline-block;
float: left; width: 100px;
padding: 0;
margin: 0;
text-align: center; text-align: center;
color: var(--primary-font-colour);
font-size: 14px;
cursor: pointer;
} }
#bake-group { #auto-bake-label .checkbox-decorator {
display: table; position: relative;
width: 100%;
} }
#bake { #bake {
display: table-cell; box-shadow: none;
width: 100%;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
} }
#auto-bake-label { #controls .btn {
display: table-cell; border-radius: 30px;
padding: 1px;
line-height: 1.35;
width: 60px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
border-left: 1px solid transparent;
}
#auto-bake-label:hover {
border-left-color: var(--btn-success-hover-border-colour);
}
#auto-bake-label div {
font-size: 10px;
padding: 2px;
}
#extra-controls {
float: right;
width: calc(100% - var(--controls-division));
padding-left: 10px;
} }

View File

@ -32,7 +32,7 @@
.textarea-wrapper { .textarea-wrapper {
position: absolute; position: absolute;
top: 43px; top: var(--title-height);
bottom: 0; bottom: 0;
width: 100%; width: 100%;
overflow: hidden; overflow: hidden;
@ -103,18 +103,13 @@
display: none; display: none;
} }
.io-btn-group {
float: right;
margin-top: -4px;
}
.io-info { .io-info {
margin-right: 20px; margin-right: 20px;
margin-top: -4px; margin-top: 1px;
float: right; float: right;
height: 30px; height: 30px;
text-align: right; text-align: right;
line-height: 10px; line-height: 12px;
font-family: var(--fixed-width-font-family); font-family: var(--fixed-width-font-family);
font-weight: normal; font-weight: normal;
font-size: 8pt; font-size: 8pt;
@ -131,20 +126,47 @@
} }
#stale-indicator { #stale-indicator {
visibility: hidden; opacity: 1;
transition: all 0.3s; visibility: visibile;
transition: margin 0s, opacity 0.3s;
margin-left: 5px; margin-left: 5px;
font-size: larger;
font-weight: normal;
cursor: help; cursor: help;
} }
#stale-indicator i {
vertical-align: middle;
margin-bottom: 5px;
}
#output-loader .loading-msg { #output-loader .loading-msg {
opacity: 1; opacity: 1;
font-family: var(--primary-font-family); font-family: var(--primary-font-family);
line-height: var(--primary-line-height); line-height: var(--primary-line-height);
color: var(--primary-font-colour); color: var(--primary-font-colour);
top: 50%; top: 50%;
transition: all 0.5s ease; transition: all 0.5s ease;
} }
#magic {
opacity: 1;
visibility: visibile;
transition: margin 0s 0.3s, opacity 0.3s 0.3s, visibility 0.3s 0.3s;
margin-left: 5px;
margin-bottom: 5px;
}
#magic.hidden,
#stale-indicator.hidden {
visibility: hidden;
transition: opacity 0.3s, margin 0.3s 0.3s, visibility 0.3s;
opacity: 0;
}
#magic.hidden {
margin-left: -32px;
}
#magic svg path {
fill: var(--primary-font-colour);
}

View File

@ -6,34 +6,12 @@
* @license Apache-2.0 * @license Apache-2.0
*/ */
.option-item .bootstrap-switch { .modal-content {
margin: 15px 10px; background-color: var(--primary-background-colour);
} }
.option-item button { .option-item {
margin: 10px; margin-bottom: 20px;
}
.option-item label {
font-weight: normal;
}
.option-item input[type=number] {
margin: 15px 10px;
width: 80px;
height: 28px;
padding: 3px 10px;
vertical-align: middle;
font-size: calc(var(--arg-input-font-size) - 1px);
line-height: var(--arg-input-line-height);
color: var(--arg-font-colour);
background-color: var(--arg-background);
border: 1px solid var(--primary-border-colour);
}
.option-item select {
margin: 10px;
display: inline-block;
} }
#edit-favourites-list { #edit-favourites-list {
@ -60,11 +38,15 @@
margin: 10px 0 20px 20px; margin: 10px 0 20px 20px;
} }
#save-link-group {
padding-top: 0;
}
.save-link-options { .save-link-options {
float: right; float: right;
} }
.save-link-options input{ .save-link-options label {
margin-left: 10px; margin-left: 10px;
} }
@ -84,7 +66,14 @@
} }
#save-texts textarea { #save-texts textarea {
border-top: none;
box-shadow: none;
height: 200px; height: 200px;
} }
#faqs a.btn {
text-transform: unset;
}
#faqs > div {
padding: 20px;
border-left: 2px solid var(--primary-border-colour);
}

View File

@ -13,15 +13,16 @@
} }
#search { #search {
border-radius: 0; padding-left: 10px;
border: none; padding-right: 10px;
border-bottom: 1px solid var(--primary-border-colour); background-image:
color: var(--primary-font-colour); linear-gradient(to top, #1976d2 2px, rgba(25, 118, 210, 0) 2px),
linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);
} }
#edit-favourites { #edit-favourites {
float: right; float: right;
margin-top: -5px; margin-top: -7px;
} }
.favourites-hover { .favourites-hover {
@ -30,3 +31,13 @@
border: 2px dashed var(--rec-list-operation-font-colour) !important; border: 2px dashed var(--rec-list-operation-font-colour) !important;
padding: 8px 8px 9px 8px; padding: 8px 8px 9px 8px;
} }
#categories a {
color: #1976d2;
cursor: pointer;
}
#categories a:hover,
.op-list .operation:hover {
filter: brightness(98%);
}

View File

@ -8,12 +8,14 @@
:root, :root,
:root.classic { :root.classic {
--primary-font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; --primary-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
Roboto, "Helvetica Neue", Arial, sans-serif;
--primary-font-colour: #333; --primary-font-colour: #333;
--primary-font-size: 14px; --primary-font-size: 14px;
--primary-line-height: 20px; --primary-line-height: 20px;
--fixed-width-font-family: "Consolas", monospace; --fixed-width-font-family: SFMono-Regular, Menlo, Monaco, Consolas,
"Liberation Mono", "Courier New", monospace;
--fixed-width-font-colour: inherit; --fixed-width-font-colour: inherit;
--fixed-width-font-size: inherit; --fixed-width-font-size: inherit;
@ -28,6 +30,7 @@
--title-colour: #424242; --title-colour: #424242;
--title-weight: bold; --title-weight: bold;
--title-size: 16px;
--title-background-colour: #fafafa; --title-background-colour: #fafafa;
--banner-font-colour: #468847; --banner-font-colour: #468847;
@ -41,7 +44,7 @@
--rec-list-operation-font-colour: #468847; --rec-list-operation-font-colour: #468847;
--rec-list-operation-bg-colour: #dff0d8; --rec-list-operation-bg-colour: #dff0d8;
--rec-list-operation-border-colour: #d6e9c6; --rec-list-operation-border-colour: #d3e8c0;
--selected-operation-font-color: #c09853; --selected-operation-font-color: #c09853;
--selected-operation-bg-colour: #fcf8e3; --selected-operation-bg-colour: #fcf8e3;
@ -65,14 +68,12 @@
/* Operation arguments */ /* Operation arguments */
--arg-title-font-weight: bold; --op-title-font-weight: bold;
--arg-input-height: 34px;
--arg-input-line-height: 20px;
--arg-input-font-size: 15px;
--arg-font-colour: #424242; --arg-font-colour: #424242;
--arg-background: #fff; --arg-background: #fff;
--arg-border-colour: #ddd; --arg-border-colour: #ddd;
--arg-disabled-background: #eee; --arg-disabled-background: #eee;
--arg-label-colour: #388e3c;
/* Buttons */ /* Buttons */

View File

@ -64,14 +64,12 @@
/* Operation arguments */ /* Operation arguments */
--arg-title-font-weight: bold; --op-title-font-weight: bold;
--arg-input-height: 34px;
--arg-input-line-height: 20px;
--arg-input-font-size: 15px;
--arg-font-colour: #bbb; --arg-font-colour: #bbb;
--arg-background: #3c3c3c; --arg-background: #3c3c3c;
--arg-border-colour: #3c3c3c; --arg-border-colour: #3c3c3c;
--arg-disabled-background: #4f4f4f; --arg-disabled-background: #4f4f4f;
--arg-label-colour: rgb(25, 118, 210);
/* Buttons */ /* Buttons */

View File

@ -64,14 +64,12 @@
/* Operation arguments */ /* Operation arguments */
--arg-title-font-weight: bold; --op-title-font-weight: bold;
--arg-input-height: 34px;
--arg-input-line-height: 20px;
--arg-input-font-size: 15px;
--arg-font-colour: white; --arg-font-colour: white;
--arg-background: black; --arg-background: black;
--arg-border-colour: lime; --arg-border-colour: lime;
--arg-disabled-background: grey; --arg-disabled-background: grey;
--arg-label-colour: red;
/* Buttons */ /* Buttons */

View File

@ -20,6 +20,10 @@ body {
color: var(--subtext-font-colour); color: var(--subtext-font-colour);
} }
.data-text {
font-family: var(--fixed-width-font-family);
}
.word-wrap { .word-wrap {
white-space: pre !important; white-space: pre !important;
word-wrap: normal !important; word-wrap: normal !important;
@ -29,6 +33,7 @@ body {
.clearfix { .clearfix {
clear: both; clear: both;
height: 0; height: 0;
line-height: 0;
} }
.blur { .blur {
@ -36,7 +41,7 @@ body {
text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important; text-shadow: rgba(0, 0, 0, 0.95) 0 0 10px !important;
} }
.no-select { .no-select {
user-select: none; user-select: none;
} }

View File

@ -8,98 +8,104 @@
/* Bootstrap */ /* Bootstrap */
/* fallback */
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: url("../static/fonts/MaterialIcons-Regular.woff2") format('woff2');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: 24px;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
font-feature-settings: 'liga';
-webkit-font-smoothing: antialiased;
}
.form-group {
margin-bottom: 0;
}
button, button,
a:focus { a:focus {
outline: none; outline: none;
} }
.btn-default { .btn.btn-raised.btn-secondary {
color: var(--btn-default-font-colour); color: var(--btn-default-font-colour);
background-color: var(--btn-default-bg-colour); background-color: var(--btn-default-bg-colour);
border-color: var(--btn-default-border-colour); border-color: var(--btn-default-border-colour);
} }
.btn-default:hover, .btn.btn-raised.btn-secondary:hover,
.btn-default:active, .btn.btn-raised.btn-secondary:active,
.btn-default:hover:active, .btn.btn-raised.btn-secondary:hover:active {
.open>.dropdown-toggle.btn-default {
color: var(--btn-default-hover-font-colour); color: var(--btn-default-hover-font-colour);
background-color: var(--btn-default-hover-bg-colour); background-color: var(--btn-default-hover-bg-colour);
border-color: var(--btn-default-hover-border-colour); border-color: var(--btn-default-hover-border-colour);
} }
.btn-default:focus, .btn.btn-raised.btn-secondary:focus {
.open>.dropdown-toggle.btn-default:hover,
.open>.dropdown-toggle.btn-default:focus {
color: var(--btn-default-font-colour); color: var(--btn-default-font-colour);
background-color: var(--btn-default-bg-colour); background-color: var(--btn-default-bg-colour);
border-color: var(--btn-default-hover-border-colour); border-color: var(--btn-default-hover-border-colour);
} }
.btn-default[disabled]:hover { .btn.btn-raised.btn-secondary[disabled]:hover {
background-color: var(--primary-background-colour); background-color: var(--primary-background-colour);
border-color: var(--primary-border-colour); border-color: var(--primary-border-colour);
} }
.btn-success { .btn.btn-raised.btn-success {
color: var(--btn-success-font-colour); color: var(--btn-success-font-colour);
background-color: var(--btn-success-bg-colour); background-color: var(--btn-success-bg-colour);
border-color: var(--btn-success-border-colour); border-color: var(--btn-success-border-colour);
} }
.btn-success:hover, .btn.btn-raised.btn-success:hover,
.btn-success:active, .btn.btn-raised.btn-success:active,
.btn-success:focus, .btn.btn-raised.btn-success:focus,
.btn-success:hover:active { .btn.btn-raised.btn-success:hover:active {
color: var(--btn-success-hover-font-colour); color: var(--btn-success-hover-font-colour);
background-color: var(--btn-success-hover-bg-colour); background-color: var(--btn-success-hover-bg-colour);
border-color: var(--btn-success-hover-border-colour); border-color: var(--btn-success-hover-border-colour);
} }
.btn,
.btn-lg,
.nav-tabs>li>a,
.form-control,
.popover,
.alert,
.panel,
.modal-content,
.tooltip-inner,
.dropdown-menu,
.input-group-addon {
border-radius: 0 !important;
}
.btn.dropdown-toggle {
height: 34px;
}
input[type="search"] { input[type="search"] {
-webkit-appearance: searchfield; appearance: searchfield;
box-shadow: none;
} }
input[type="search"]::-webkit-search-cancel-button { input[type="search"]::-webkit-search-cancel-button {
-webkit-appearance: searchfield-cancel-button; appearance: searchfield-cancel-button;
} }
.modal { select.form-control:not([size]):not([multiple]), select.custom-file-control:not([size]):not([multiple]) {
overflow-y: auto; height: unset !important;
} }
.modal-content { .checkbox label,
background-color: var(--primary-background-colour); .checkbox-inline,
} .is-focused .checkbox-inline,
.is-focused .checkbox-inline:hover,
.modal-header, [class^="bmd-label"],
.modal-footer { .form-control,
border-color: var(--primary-border-colour); .is-focused .form-control {
color: var(--primary-font-colour);
} }
.form-control { .form-control {
background-color: transparent; background-image: linear-gradient(to top, rgb(25, 118, 210) 2px, rgba(25, 118, 210, 0) 2px), linear-gradient(to top, var(--primary-border-colour) 1px, rgba(0, 0, 0, 0) 1px);
border-color: var(--primary-border-colour);
color: var(--primary-font-colour);
} }
code { code {
@ -144,22 +150,27 @@ optgroup {
border-color: var(--popover-border-colour); border-color: var(--popover-border-colour);
} }
.popover-content { .popover-body {
max-height: 90vh; max-height: 95vh;
overflow-y: auto; overflow-y: auto;
color: var(--primary-font-colour);
} }
.popover.right>.arrow { .bs-popover-right>.arrow {
border-right-color: var(--popover-border-colour); border-right-color: var(--popover-border-colour);
} }
.popover.right>.arrow:after { .bs-popover-right>.arrow:after {
border-right-color: var(--popover-background); border-right-color: var(--popover-background);
} }
.nav-tabs>li.active>a, .nav-tabs>li.active>a:focus, .nav-tabs>li.active>a:hover { .nav-tabs .nav-link {
background-color: var(--primary-background-colour); color: var(--subtext-font-colour);
border-color: var(--primary-border-colour); }
.nav-tabs>li>a.nav-link.active, .nav-tabs>li>a.nav-link.active:focus, .nav-tabs>li>a.nav-link.active:hover {
background-color: var(--secondary-background-colour);
border-color: var(--secondary-border-colour);
border-bottom-color: transparent; border-bottom-color: transparent;
color: var(--primary-font-colour); color: var(--primary-font-colour);
} }
@ -168,11 +179,11 @@ optgroup {
border-color: var(--primary-border-colour); border-color: var(--primary-border-colour);
} }
.nav>li>a:focus, .nav>li>a:hover { .nav a.nav-link:focus, .nav a.nav-link:hover {
background-color: var(--secondary-border-colour); background-color: var(--secondary-border-colour);
} }
.nav-tabs>li>a:hover { .nav-tabs a.nav-link:hover {
border-color: var(--secondary-border-colour) var(--secondary-border-colour) var(--primary-border-colour); border-color: var(--secondary-border-colour) var(--secondary-border-colour) var(--primary-border-colour);
} }
@ -180,11 +191,11 @@ optgroup {
background-color: var(--primary-background-colour); background-color: var(--primary-background-colour);
} }
.dropdown-menu>li>a { .dropdown-menu a {
color: var(--primary-font-colour); color: var(--primary-font-colour);
} }
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { .dropdown-menu a:focus, .dropdown-menu a:hover {
background-color: var(--secondary-background-colour); background-color: var(--secondary-background-colour);
color: var(--primary-font-colour); color: var(--primary-font-colour);
} }
@ -199,30 +210,6 @@ optgroup {
} }
/* Bootstrap-switch */
.bootstrap-switch,
.bootstrap-switch-container,
.bootstrap-switch-handle-on,
.bootstrap-switch-handle-off,
.bootstrap-switch-label {
border-radius: 0 !important;
}
.bootstrap-switch .bootstrap-switch-label {
background-color: transparent;
}
.bootstrap-switch {
border-color: var(--primary-border-colour);
}
.bootstrap-switch .bootstrap-switch-handle-off.bootstrap-switch-default {
background-color: var(--primary-border-colour);
color: var(--primary-font-colour);
}
/* Sortable */ /* Sortable */
.sortable-ghost { .sortable-ghost {

View File

@ -1,58 +0,0 @@
/**
* Bootstrap imports
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2017
* @license Apache-2.0
*/
// Core variables and mixins
@import "~bootstrap/less/variables.less";
@import "~bootstrap/less/mixins.less";
// Reset and dependencies
@import "~bootstrap/less/normalize.less";
@import "~bootstrap/less/print.less";
// @import "~bootstrap/less/glyphicons.less";
// Core CSS
@import "~bootstrap/less/scaffolding.less";
@import "~bootstrap/less/type.less";
@import "~bootstrap/less/code.less";
@import "~bootstrap/less/grid.less";
@import "~bootstrap/less/tables.less";
@import "~bootstrap/less/forms.less";
@import "~bootstrap/less/buttons.less";
// Components
@import "~bootstrap/less/component-animations.less";
@import "~bootstrap/less/dropdowns.less";
@import "~bootstrap/less/button-groups.less";
@import "~bootstrap/less/input-groups.less";
@import "~bootstrap/less/navs.less";
// @import "~bootstrap/less/navbar.less";
// @import "~bootstrap/less/breadcrumbs.less";
// @import "~bootstrap/less/pagination.less";
// @import "~bootstrap/less/pager.less";
@import "~bootstrap/less/labels.less";
// @import "~bootstrap/less/badges.less";
// @import "~bootstrap/less/jumbotron.less";
// @import "~bootstrap/less/thumbnails.less";
@import "~bootstrap/less/alerts.less";
@import "~bootstrap/less/progress-bars.less";
// @import "~bootstrap/less/media.less";
@import "~bootstrap/less/list-group.less";
@import "~bootstrap/less/panels.less";
// @import "~bootstrap/less/responsive-embed.less";
// @import "~bootstrap/less/wells.less";
@import "~bootstrap/less/close.less";
// Components w/ JavaScript
@import "~bootstrap/less/modals.less";
@import "~bootstrap/less/tooltip.less";
@import "~bootstrap/less/popovers.less";
// @import "~bootstrap/less/carousel.less";
// Utility classes
@import "~bootstrap/less/utilities.less";
// @import "~bootstrap/less/responsive-utilities.less";

View File

@ -0,0 +1,23 @@
/**
* Bootstrap Material Design with overrides
*
* @author n1474335 [n1474335@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
@import "~bootstrap-material-design/scss/variables/colors";
$theme-colors: (
primary: $blue-700,
success: $green,
info: $light-blue,
warning: $deep-orange,
danger: $red,
light: $grey-100,
dark: $grey-800
);
$bmd-form-line-height: 1.25;
@import "~bootstrap-material-design/scss/core";

View File

@ -78,11 +78,15 @@ class TestRegister {
ret.output = "Expected an error but did not receive one."; ret.output = "Expected an error but did not receive one.";
} else if (result.result === test.expectedOutput) { } else if (result.result === test.expectedOutput) {
ret.status = "passing"; ret.status = "passing";
} else if (test.hasOwnProperty("expectedMatch") && test.expectedMatch.test(result.result)) {
ret.status = "passing";
} else { } else {
ret.status = "failing"; ret.status = "failing";
const expected = test.expectedOutput ? test.expectedOutput :
test.expectedMatch ? test.expectedMatch.toString() : "unknown";
ret.output = [ ret.output = [
"Expected", "Expected",
"\t" + test.expectedOutput.replace(/\n/g, "\n\t"), "\t" + expected.replace(/\n/g, "\n\t"),
"Received", "Received",
"\t" + result.result.replace(/\n/g, "\n\t"), "\t" + result.result.replace(/\n/g, "\n\t"),
].join("\n"); ].join("\n");

View File

@ -44,6 +44,7 @@ import "./tests/operations/ConditionalJump";
import "./tests/operations/Register"; import "./tests/operations/Register";
import "./tests/operations/Comment"; import "./tests/operations/Comment";
import "./tests/operations/Hash"; import "./tests/operations/Hash";
import "./tests/operations/HaversineDistance";
import "./tests/operations/Hexdump"; import "./tests/operations/Hexdump";
import "./tests/operations/Image"; import "./tests/operations/Image";
import "./tests/operations/MorseCode"; import "./tests/operations/MorseCode";
@ -61,6 +62,7 @@ import "./tests/operations/SetDifference";
import "./tests/operations/SetIntersection"; import "./tests/operations/SetIntersection";
import "./tests/operations/SetUnion"; import "./tests/operations/SetUnion";
import "./tests/operations/SymmetricDifference"; import "./tests/operations/SymmetricDifference";
import "./tests/operations/Magic";
import "./tests/nodeApi/nodeApi"; import "./tests/nodeApi/nodeApi";
import "./tests/nodeApi/ops"; import "./tests/nodeApi/ops";

View File

@ -0,0 +1,22 @@
/**
* Haversine distance tests.
*
* @author Dachande663 [dachande663@gmail.com]
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Haversine distance",
input: "51.487263,-0.124323, 38.9517,-77.1467",
expectedOutput: "5619355.701829259",
recipeConfig: [
{
"op": "Haversine distance",
"args": []
}
],
}
]);

View File

@ -0,0 +1,57 @@
/**
* Magic tests.
*
* @author n1474335 [n1474335@gmail.com]
*
* @copyright Crown Copyright 2018
* @license Apache-2.0
*/
import TestRegister from "../../TestRegister";
TestRegister.addTests([
{
name: "Magic: nothing",
input: "",
expectedOutput: "Nothing of interest could be detected about the input data.\nHave you tried modifying the operation arguments?",
recipeConfig: [
{
op: "Magic",
args: [3, false, false]
}
],
},
{
name: "Magic: hex",
input: "41 42 43 44 45",
expectedMatch: /"#recipe=From_Hex\('Space'\)"/,
recipeConfig: [
{
op: "Magic",
args: [3, false, false]
}
],
},
{
name: "Magic: jpeg",
input: "\xFF\xD8\xFF",
expectedMatch: /Render_Image\('Raw'\)/,
recipeConfig: [
{
op: "Magic",
args: [3, false, false]
}
],
},
{
name: "Magic: mojibake",
input: "d091d18bd100d182d180d0b0d10020d0bad0bed180d0b8d187d0bdd0b5d0b2d0b0d10020d0bbd0b8d100d0b020d0bfd180d18bd0b3d0b0d0b5d18220d187d0b5d180d0b5d0b720d0bbd0b5d0bdd0b8d0b2d183d18e20d100d0bed0b1d0b0d0bad1832e",
expectedMatch: /Быртрар коричневар лира прыгает через ленивую робаку./,
recipeConfig: [
{
op: "Magic",
args: [3, true, false]
}
],
},
]);

View File

@ -61,22 +61,25 @@ module.exports = {
test: /forge.min.js$/, test: /forge.min.js$/,
loader: "imports-loader?jQuery=>null" loader: "imports-loader?jQuery=>null"
}, },
{
test: /bootstrap-material-design/,
loader: "imports-loader?Popper=popper.js/dist/umd/popper.js"
},
{ {
test: /\.css$/, test: /\.css$/,
use: ExtractTextPlugin.extract({ use: ExtractTextPlugin.extract({
use: [ use: [
{ loader: "css-loader?minimize" }, { loader: "css-loader" },
{ loader: "postcss-loader" }, { loader: "postcss-loader" },
] ]
}) })
}, },
{ {
test: /\.less$/, test: /\.scss$/,
use: ExtractTextPlugin.extract({ use: ExtractTextPlugin.extract({
use: [ use: [
{ loader: "css-loader?minimize" }, { loader: "css-loader" },
{ loader: "postcss-loader" }, { loader: "sass-loader" }
{ loader: "less-loader" }
] ]
}) })
}, },