diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d41152b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,12 @@ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 2 +indent_style = space +insert_final_newline = true +trim_trailing_whitespace = true + +[{*.js,*.css,*.html}] +indent_size = 4 diff --git a/.github/workflows/build-docker.yml b/.github/workflows/build-docker.yml new file mode 100644 index 0000000..7731628 --- /dev/null +++ b/.github/workflows/build-docker.yml @@ -0,0 +1,18 @@ +# This workflow will do clean build of the docker image + +name: Docker Image CI (also tests build) + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag rofl256/whiteboard:$(date +%s) diff --git a/.github/workflows/linting-code.yml b/.github/workflows/linting-code.yml new file mode 100644 index 0000000..f900057 --- /dev/null +++ b/.github/workflows/linting-code.yml @@ -0,0 +1,21 @@ +# This workflow will do a clean install of node dependencies and check the code style + +name: Linting code CI + +on: + push: + branches: [master] + pull_request: + branches: [master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Use Node.js 12.x + uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: npm ci + - run: npm run style diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..de753c5 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,3 @@ +{ + "printWidth": 100 +} diff --git a/README.md b/README.md index 45a8876..fc7afdc 100644 --- a/README.md +++ b/README.md @@ -1,27 +1,33 @@ # whiteboard + This is a lightweight NodeJS collaborative Whiteboard/Sketchboard witch can easily be customized... ![start](./doc/start.png) -## Demowhiteboard ## +## Demowhiteboard + [HERE](https://cloud13.de/testwhiteboard/) (Reset every night) ## Some Features -* Shows remote user cursors while drawing -* Undo / Redo function for each user -* Drag+Drop / Copy+Paste Images or PDFs from PC and Browsers -* Resize, Move & Draw Images to Canvas or Background -* Write text -* Save Whiteboard to Image and JSON -* Draw angle lines by pressing "shift" while drawing (with line tool) -* Draw square by pressing "shift" while drawing (with rectangle tool) -* Indicator that shows the smallest screen participating -* Keybindings for ALL the functions -* Working on PC, Tablet & Mobile + +- Shows remote user cursors while drawing +- Undo / Redo function for each user +- Drag+Drop / Copy+Paste Images or PDFs from PC and Browsers +- Resize, Move & Draw Images to Canvas or Background +- Write text +- Save Whiteboard to Image and JSON +- Draw angle lines by pressing "shift" while drawing (with line tool) +- Draw square by pressing "shift" while drawing (with rectangle tool) +- Indicator that shows the smallest screen participating +- Keybindings for ALL the functions +- Working on PC, Tablet & Mobile ## Install the App + You can run this app with and without docker + ### Without Docker + 1. install the latest NodeJs 2. Clone the app 3. Run `npm ci` inside the folder @@ -29,6 +35,7 @@ You can run this app with and without docker 5. Surf to http://YOURIP:8080 ### With Docker + 1. `docker run -d -p 8080:8080 rofl256/whiteboard` 2. Surf to http://YOURIP:8080 @@ -37,63 +44,66 @@ You can run this app with and without docker After you have installed the app, run `npm run start:dev` to start the backend and a frontend development server. The website will be accessible on http://locahost:8080. ## Default keyboard shortcuts + Use keyboard shortcuts to become more productive while using Whiteboard. They are especially useful if you work with interactive displays such as XP-Pen Artist, Huion Kamvas and Wacom Cintiq. These devices have quick buttons (6-8 buttons and scrolling). By default, the buttons on these displays are mapped to standard Photoshop keyboard shortcuts. Keys can be configured to function effectively in other software. The following are predefined shortcuts that you can override in the file [./src/js/keybinds.js](./src/js/keybinds.js) -Result | Windows and Linux | macOS ------- | -------------------- | ------- -Clear the whiteboard | Ctrl + Shift + Z | Command + Shift + Z -Undo your last step | Ctrl + Z | Command + Z -Redo your last undo | Ctrl + Y | Command + Y -Select an area | Ctrl + X | Command + X -Take the mouse | Ctrl + M | Command + M -Take the pen | Ctrl + P | Command + P -Draw a line | Ctrl + L | Command + L -Draw a rectangle | Ctrl + R | Command + R -Draw a circle | Ctrl + C | Command + C -Toggle between line, rectangle and circle | Ctrl + Shift + F | Command + Shift + F -Toggle between pen and eraser | Ctrl + Shift + X | Command + Shift + X -Toggle between main clolors (black, blue, green, yellow and red) | Ctrl + Shift + R | Command + Shift + R -Write text | Ctrl + A | Command + A -Take the eraser | Ctrl + E | Command + E -Increase thickness | Ctrl + Up Arrow | Command + Up Arrow -Decrease thickness | Ctrl + Down Arrow | Command + Down Arrow -Colorpicker | Ctrl + Shift + C | Command + Shift + C -Set black color | Ctrl + Shift + 1 | Command + Shift + 1 -Set blue color | Ctrl + Shift + 2 | Command + Shift + 2 -Set green color | Ctrl + Shift + 3 | Command + Shift + 3 -Set yellow color | Ctrl + Shift + 4 | Command + Shift + 4 -Set red color | Ctrl + Shift + 5 | Command + Shift + 5 -Save whiteboard as image | Ctrl + S | Command + S -Save whiteboard as JSON | Ctrl + Shift + K | Command + Shift + K -Save whiteboard to WebDav | Ctrl + Shift + I (i) | Command + Shift + I (i) -Load saved JSON to whiteboard | Ctrl + Shift + J | Command + Shift + J -Share whiteboard | Ctrl + Shift + S | Command + Shift + S -Hide or show toolbar | Tab | Tab -Move selected object up | Up Arrow | Up Arrow -Move selected object down | Down Arrow | Down Arrow -Move selected object left | Left Arrow | Left Arrow -Move selected object right | Right Arrow | Right Arrow -Drop object | Ctrl + Enter | Command + Enter -Add Image to backgroud | Shift + Enter | Shift + Enter -Cancel all actions | Escape | Escape -Delete selected object | Delete | Delete -Use Line tool when pen is active (Not changeable) | Shift (Hold) | Shift (Hold) +| Result | Windows and Linux | macOS | +| ---------------------------------------------------------------- | -------------------- | ----------------------- | +| Clear the whiteboard | Ctrl + Shift + Z | Command + Shift + Z | +| Undo your last step | Ctrl + Z | Command + Z | +| Redo your last undo | Ctrl + Y | Command + Y | +| Select an area | Ctrl + X | Command + X | +| Take the mouse | Ctrl + M | Command + M | +| Take the pen | Ctrl + P | Command + P | +| Draw a line | Ctrl + L | Command + L | +| Draw a rectangle | Ctrl + R | Command + R | +| Draw a circle | Ctrl + C | Command + C | +| Toggle between line, rectangle and circle | Ctrl + Shift + F | Command + Shift + F | +| Toggle between pen and eraser | Ctrl + Shift + X | Command + Shift + X | +| Toggle between main clolors (black, blue, green, yellow and red) | Ctrl + Shift + R | Command + Shift + R | +| Write text | Ctrl + A | Command + A | +| Take the eraser | Ctrl + E | Command + E | +| Increase thickness | Ctrl + Up Arrow | Command + Up Arrow | +| Decrease thickness | Ctrl + Down Arrow | Command + Down Arrow | +| Colorpicker | Ctrl + Shift + C | Command + Shift + C | +| Set black color | Ctrl + Shift + 1 | Command + Shift + 1 | +| Set blue color | Ctrl + Shift + 2 | Command + Shift + 2 | +| Set green color | Ctrl + Shift + 3 | Command + Shift + 3 | +| Set yellow color | Ctrl + Shift + 4 | Command + Shift + 4 | +| Set red color | Ctrl + Shift + 5 | Command + Shift + 5 | +| Save whiteboard as image | Ctrl + S | Command + S | +| Save whiteboard as JSON | Ctrl + Shift + K | Command + Shift + K | +| Save whiteboard to WebDav | Ctrl + Shift + I (i) | Command + Shift + I (i) | +| Load saved JSON to whiteboard | Ctrl + Shift + J | Command + Shift + J | +| Share whiteboard | Ctrl + Shift + S | Command + Shift + S | +| Hide or show toolbar | Tab | Tab | +| Move selected object up | Up Arrow | Up Arrow | +| Move selected object down | Down Arrow | Down Arrow | +| Move selected object left | Left Arrow | Left Arrow | +| Move selected object right | Right Arrow | Right Arrow | +| Drop object | Ctrl + Enter | Command + Enter | +| Add Image to backgroud | Shift + Enter | Shift + Enter | +| Cancel all actions | Escape | Escape | +| Delete selected object | Delete | Delete | +| Use Line tool when pen is active (Not changeable) | Shift (Hold) | Shift (Hold) | ## URL Parameters + Call your site with GET parameters to change the WhiteboardID or the Username `http://YOURIP:8080?whiteboardid=MYID&username=MYNAME` -* whiteboardid => All people with the same ID are drawing on the same board -* username => The name witch is showing to others while drawing -* title => Change the name of the Browser Tab -* randomid => if set to true, a random whiteboardId will be generated if not given aswell +- whiteboardid => All people with the same ID are drawing on the same board +- username => The name witch is showing to others while drawing +- title => Change the name of the Browser Tab +- randomid => if set to true, a random whiteboardId will be generated if not given aswell ## Security - AccessToken (Optional) + To prevent clients who might know or guess the base URL from abusing the server to upload files and stuff..., you can set an accesstoken at server start. Server (Without docker): `node scripts/server.js --accesstoken="mySecToken"` @@ -107,6 +117,7 @@ Then set the same token on the client side as well: Done! ## WebDAV (Optional) + This function allows your users to save the whiteboard directly to a webdav server (Nextcloud) as image without downloading it. To enable it: @@ -125,21 +136,25 @@ Note: For the most owncloud/nextcloud setups you have to set the WebDav-Server U Done! - ## Things you may want to know -* Whiteboards are gone if you restart the Server, so keep that in mind (or save your whiteboard) -* You should be able to customize the layout without ever touching the whiteboard.js (take a look at index.html & main.js) + +- Whiteboards are gone if you restart the Server, so keep that in mind (or save your whiteboard) +- You should be able to customize the layout without ever touching the whiteboard.js (take a look at index.html & main.js) ## All server start parameters (also docker) -* accesstoken => take a look at "Security - AccessToken" for a full explanation -* disablesmallestscreen => set this to "true" if you don't want show the "smallest screen" indicator (A dotted gray line) to the users -* webdav => Enable the function to save to a webdav-server (Must also be enabled on the client; Take a look at the webdav section) + +- accesstoken => take a look at "Security - AccessToken" for a full explanation +- disablesmallestscreen => set this to "true" if you don't want show the "smallest screen" indicator (A dotted gray line) to the users +- webdav => Enable the function to save to a webdav-server (Must also be enabled on the client; Take a look at the webdav section) ## ToDo -* Make undo function more reliable on texts + +- Make undo function more reliable on texts ## Nginx Reverse Proxy configuration + Add this to your server part: + ``` location /whiteboard/ { proxy_set_header HOST $host; @@ -149,22 +164,22 @@ Add this to your server part: proxy_pass http://YOURIP:8080/; } ``` + To run it at /whiteboard. Don't forget to change -> YOURIP! ## Nextcloud integration + 1. Install this app on your server 2. Enable and go to "external sites" (app) on your Nextcloud -2. Add a link to your server: `https://YOURIP/whiteboard/?whiteboardid=WHITEBOARDNAME&username={uid}` -You can give each group its own whiteboard by changeing the WHITEBOARDNAME in the URL if you want. +3. Add a link to your server: `https://YOURIP/whiteboard/?whiteboardid=WHITEBOARDNAME&username={uid}` + You can give each group its own whiteboard by changeing the WHITEBOARDNAME in the URL if you want. Note: You might have to serve the app with https (If your nextcloud server runs https). To do so, its recommend to run this app behind a reverse proxy. (as shown above) #### (Optional) Set whiteboard icon in nextcloud + ![start](https://raw.githubusercontent.com/cracker0dks/whiteboard/master/doc/iconPrev.jpg) Upload both icons present at /doc/nextcloud_icons/ to your nextcloud at the "external sites" admin section. Then set it as symbol on your link. - - - -___ MIT License ___ +**_ MIT License _** diff --git a/config/webpack.base.js b/config/webpack.base.js index 936940f..8c586e7 100644 --- a/config/webpack.base.js +++ b/config/webpack.base.js @@ -1,61 +1,59 @@ const webpack = require("webpack"); -const { CleanWebpackPlugin } = require('clean-webpack-plugin'); -const CopyPlugin = require('copy-webpack-plugin'); -const HtmlWebpackPlugin = require('html-webpack-plugin') +const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const CopyPlugin = require("copy-webpack-plugin"); +const HtmlWebpackPlugin = require("html-webpack-plugin"); const path = require("path"); const config = { - entry: { - main: ["./src/js/index.js"], - }, - output: { - path: path.join(__dirname, "..", "dist"), - filename: "[name]-[hash].js" - }, - resolve: { - extensions: ["*", ".json", ".js"] - }, - module: { - rules: [ - { - test: /\.(js)$/, - exclude: /node_modules/, - loader: "babel-loader", - options: { - compact: true - } - }, - { - test: /\.css$/, - use: ['style-loader', 'css-loader'] - }, - { - test: /\.(png|jpe?g|gif)$/i, - use: [ - { - loader: 'file-loader', - }, + entry: { + main: ["./src/js/index.js"], + }, + output: { + path: path.join(__dirname, "..", "dist"), + filename: "[name]-[hash].js", + }, + resolve: { + extensions: ["*", ".json", ".js"], + }, + module: { + rules: [ + { + test: /\.(js)$/, + exclude: /node_modules/, + loader: "babel-loader", + options: { + compact: true, + }, + }, + { + test: /\.css$/, + use: ["style-loader", "css-loader"], + }, + { + test: /\.(png|jpe?g|gif)$/i, + use: [ + { + loader: "file-loader", + }, + ], + }, ], - } - ] - }, - plugins: [ - new CleanWebpackPlugin(), - new webpack.ProvidePlugin({ - $: 'jquery', - jQuery: 'jquery', - "window.jQuery": "jquery", - "window.$": "jquery", - }), - new CopyPlugin([ - { from: 'assets', to: '' }, - ]), - new HtmlWebpackPlugin({ - template: 'src/index.html', - minify: false, - inject: true - }) - ] + }, + plugins: [ + new CleanWebpackPlugin(), + new webpack.ProvidePlugin({ + $: "jquery", + jQuery: "jquery", + "window.jQuery": "jquery", + "window.$": "jquery", + }), + new CopyPlugin([{ from: "assets", to: "" }]), + new HtmlWebpackPlugin({ + template: "src/index.html", + minify: false, + inject: true, + }), + ], }; module.exports = config; diff --git a/config/webpack.build.js b/config/webpack.build.js index 9a21477..6e0d9c1 100644 --- a/config/webpack.build.js +++ b/config/webpack.build.js @@ -2,10 +2,10 @@ const merge = require("webpack-merge"); const baseConfig = require("./webpack.base"); module.exports = merge(baseConfig, { - mode: "production", - optimization: { - minimize: true, - nodeEnv: "production", - }, - devtool: false + mode: "production", + optimization: { + minimize: true, + nodeEnv: "production", + }, + devtool: false, }); diff --git a/config/webpack.dev.js b/config/webpack.dev.js index a919e81..7822170 100644 --- a/config/webpack.dev.js +++ b/config/webpack.dev.js @@ -3,16 +3,16 @@ const baseConfig = require("./webpack.base"); const webpack = require("webpack"); const devConfig = merge(baseConfig, { - mode: "development", - devtool: "eval-source-map", - optimization: { - minimize: false, - }, - plugins: [ - new webpack.HotModuleReplacementPlugin(), - new webpack.NamedModulesPlugin(), - new webpack.NoEmitOnErrorsPlugin(), - ].concat(baseConfig.plugins), + mode: "development", + devtool: "eval-source-map", + optimization: { + minimize: false, + }, + plugins: [ + new webpack.HotModuleReplacementPlugin(), + new webpack.NamedModulesPlugin(), + new webpack.NoEmitOnErrorsPlugin(), + ].concat(baseConfig.plugins), }); module.exports = devConfig; diff --git a/docker-compose.yml b/docker-compose.yml index bb10f3f..878bd00 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,4 +1,4 @@ -version: '3.1' +version: "3.1" services: whiteboard: image: rofl256/whiteboard diff --git a/package-lock.json b/package-lock.json index 12d94db..26d4ec3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1069,6 +1069,12 @@ "integrity": "sha512-/+CRPXpBDpo2RK9C68N3b2cOvO0Cf5B9aPijHsoDQTHivnGSObdOF2BRQOYjojWTDy6nQvMjmqRXIxH55VjxxA==", "dev": true }, + "@types/color-name": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz", + "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==", + "dev": true + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -1104,6 +1110,12 @@ "integrity": "sha512-WE4IOAC6r/yBZss1oQGM5zs2D7RuKR6Q+w+X2SouPofnWn+LbCqClRyhO3ZE7Ix8nmFgo/oVuuE01cJT2XB13A==", "dev": true }, + "@types/parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==", + "dev": true + }, "@types/source-list-map": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/@types/source-list-map/-/source-list-map-0.1.2.tgz", @@ -1469,6 +1481,12 @@ "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", "dev": true }, + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "dev": true + }, "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", @@ -1505,6 +1523,12 @@ "resolved": "https://registry.npmjs.org/arraybuffer.slice/-/arraybuffer.slice-0.0.7.tgz", "integrity": "sha512-wGUIVQXuehL5TCqQun8OW81jGzAWycqzFF8lFp+GOM5BXLYj3bKNsYC4daB7n6XjCqxQA/qgTJ+8ANR3acjrog==" }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "dev": true + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -2304,6 +2328,12 @@ "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, "camel-case": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.1.tgz", @@ -2377,6 +2407,12 @@ "tslib": "^1.9.0" } }, + "ci-info": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", + "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", + "dev": true + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -2491,6 +2527,12 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "compare-versions": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.6.0.tgz", + "integrity": "sha512-W6Af2Iw1z4CB7q4uU4hv646dW9GQuBM+YpC0UvUCWSD8w90SJjp+ujJuXaEMtAXBtSqGfMPuFOVn4/+FlaqfBA==", + "dev": true + }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", @@ -2764,6 +2806,27 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cosmiconfig": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", + "integrity": "sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==", + "dev": true, + "requires": { + "@types/parse-json": "^4.0.0", + "import-fresh": "^3.1.0", + "parse-json": "^5.0.0", + "path-type": "^4.0.0", + "yaml": "^1.7.2" + }, + "dependencies": { + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + } + } + }, "create-ecdh": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", @@ -3365,6 +3428,15 @@ "prr": "~1.0.1" } }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, "es-abstract": { "version": "1.17.5", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.5.tgz", @@ -3774,6 +3846,15 @@ "locate-path": "^2.0.0" } }, + "find-versions": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-3.2.0.tgz", + "integrity": "sha512-P8WRou2S+oe222TOCHitLy8zj+SIsVJh52VP4lvXkaFVnOFFdoWv1H1Jjvel1aI6NCFOAaeAVm8qrI0odiLcww==", + "dev": true, + "requires": { + "semver-regex": "^2.0.0" + } + }, "findup-sync": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-3.0.0.tgz", @@ -4899,6 +4980,140 @@ "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", "dev": true }, + "husky": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/husky/-/husky-4.2.5.tgz", + "integrity": "sha512-SYZ95AjKcX7goYVZtVZF2i6XiZcHknw50iXvY7b0MiGoj5RwdgRQNEHdb+gPDPCXKlzwrybjFjkL6FOj8uRhZQ==", + "dev": true, + "requires": { + "chalk": "^4.0.0", + "ci-info": "^2.0.0", + "compare-versions": "^3.6.0", + "cosmiconfig": "^6.0.0", + "find-versions": "^3.2.0", + "opencollective-postinstall": "^2.0.2", + "pkg-dir": "^4.2.0", + "please-upgrade-node": "^3.2.0", + "slash": "^3.0.0", + "which-pm-runs": "^1.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", + "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", + "dev": true, + "requires": { + "@types/color-name": "^1.1.1", + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "supports-color": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.1.0.tgz", + "integrity": "sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } + } + }, "iconv-lite": { "version": "0.4.19", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", @@ -4931,6 +5146,24 @@ "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", "dev": true }, + "import-fresh": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz", + "integrity": "sha512-6e1q1cnWP2RXD9/keSkxHScg508CdXqXWgWBaETNhyuBFz+kUZlKboh+ISK+bU++DmbHimVBrOz/zzPe0sZ3sQ==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-2.0.0.tgz", @@ -5073,6 +5306,12 @@ "integrity": "sha512-xPh0Rmt8NE65sNzvyUmWgI1tz3mKq74lGA0mL8LYZcoIzKOzDh6HmrYm3d18k60nHerC8A9Km8kYu87zfSFnLA==", "dev": true }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", @@ -5460,6 +5699,12 @@ "type-check": "~0.3.2" } }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "loader-runner": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", @@ -5628,6 +5873,12 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -5775,6 +6026,12 @@ "run-queue": "^1.0.3" } }, + "mri": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.1.5.tgz", + "integrity": "sha512-d2RKzMD4JNyHMbnbWnznPaa8vbdlq/4pNZ3IgdaGrVbBhebBsGUUE/6qorTMYNS6TwuH3ilfOlD2bf4Igh8CKg==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5796,6 +6053,27 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "dev": true, + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + } + } + }, "nan": { "version": "2.14.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", @@ -6078,6 +6356,21 @@ "wrappy": "1" } }, + "onetime": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.0.tgz", + "integrity": "sha512-5NcSkPHhwTVFIQN+TUqXoS5+dlElHXdpAWu9I0HP20YOtIi+aZ0Ct82jdlILDxjLEAWwvm+qj1m6aEtsDVmm6Q==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "opencollective-postinstall": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz", + "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw==", + "dev": true + }, "opn": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/opn/-/opn-5.5.0.tgz", @@ -6210,6 +6503,15 @@ "tslib": "^1.10.0" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-asn1": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", @@ -6224,6 +6526,18 @@ "safe-buffer": "^5.1.1" } }, + "parse-json": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.0.0.tgz", + "integrity": "sha512-OOY5b7PAEFV0E2Fir1KOkxchnZNCdowAJgQ5NuxjpBKTRP3pQhwkrkxqQjeoKJ+fO7bCpmIZaogI4eZGDMEGOw==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1", + "lines-and-columns": "^1.1.6" + } + }, "parse-passwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", @@ -6452,6 +6766,15 @@ "find-up": "^2.1.0" } }, + "please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "requires": { + "semver-compare": "^1.0.0" + } + }, "pn": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", @@ -6576,6 +6899,12 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=" }, + "prettier": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.5.tgz", + "integrity": "sha512-7PtVymN48hGcO4fGjybyBSIWDsLU4H4XlvOHfq91pz9kkGlonzwTfYkaIEwiRg/dAJF9YlbsduBAgtYLi+8cFg==", + "dev": true + }, "pretty-error": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-2.1.1.tgz", @@ -6586,6 +6915,165 @@ "utila": "~0.4" } }, + "pretty-quick": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pretty-quick/-/pretty-quick-2.0.1.tgz", + "integrity": "sha512-y7bJt77XadjUr+P1uKqZxFWLddvj3SKY6EU4BuQtMxmmEFSMpbN132pUWdSG1g1mtUfO0noBvn7wBf0BVeomHg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "execa": "^2.1.0", + "find-up": "^4.1.0", + "ignore": "^5.1.4", + "mri": "^1.1.4", + "multimatch": "^4.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.2.tgz", + "integrity": "sha512-PD6G8QG3S4FK/XCGFbEQrDqO2AnMMsy0meR7lerlIOHAAbkuavGU/pOqprrlvfTNjvowivTeBsjebAL0NSoMxw==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "execa": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-2.1.0.tgz", + "integrity": "sha512-Y/URAVapfbYy2Xp/gb6A0E7iR8xeqOCXsuuaoMn7A5PzrXUK84E1gyiEfq0wQd/GHA6GsoHWwhNq8anb0mleIw==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^3.0.0", + "onetime": "^5.1.0", + "p-finally": "^2.0.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "get-stream": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.1.0.tgz", + "integrity": "sha512-EXr1FOzrzTfGeL0gQdeFEvOMm2mzMOglyiOXSTpPC+iAjAKftbr3jpCMWynogwYnM+eSj9sHGc6wjIcDvYiygw==", + "dev": true, + "requires": { + "pump": "^3.0.0" + } + }, + "ignore": { + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.4.tgz", + "integrity": "sha512-MzbUSahkTW1u7JpKKjY7LCARd1fU5W2rLdxlM4kdkayuCwZImjkpluF9CM1aLewYJguPDqewLam18Y6AU69A8A==", + "dev": true + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "npm-run-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-3.1.0.tgz", + "integrity": "sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "p-finally": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-2.0.1.tgz", + "integrity": "sha512-vpm09aKwq6H9phqRQzecoDpD8TmVyGw70qmWlyq5onxY7tqyTTFVvxMykxQSQKILBSFlbXpypIw2T1Ml7+DDtw==", + "dev": true + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, "private": { "version": "0.1.8", "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", @@ -7200,6 +7688,18 @@ "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", "dev": true }, + "semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", + "dev": true + }, + "semver-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-2.0.0.tgz", + "integrity": "sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==", + "dev": true + }, "send": { "version": "0.16.1", "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", @@ -7833,6 +8333,12 @@ "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", "dev": true }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, "style-loader": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-1.1.4.tgz", @@ -9269,6 +9775,12 @@ "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", "dev": true }, + "which-pm-runs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz", + "integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=", + "dev": true + }, "wordwrap": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", @@ -9377,6 +9889,15 @@ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", "dev": true }, + "yaml": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.9.2.tgz", + "integrity": "sha512-HPT7cGGI0DuRcsO51qC1j9O16Dh1mZ2bnXwsi0jrSpsLz0WxOLSLXfkABVl6bZO629py3CU+OMJtpNHDLB97kg==", + "dev": true, + "requires": { + "@babel/runtime": "^7.9.2" + } + }, "yargs": { "version": "13.2.4", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz", diff --git a/package.json b/package.json index 97a4131..3a99b54 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,10 @@ "build": "webpack --config config/webpack.build.js", "start:dev": "node scripts/server.js --mode=development", "start:prod": "npm run build && node scripts/server.js --mode=production", - "test": "echo \"No tests needed!\" && exit 1" + "test": "echo \"No tests needed!\" && exit 1", + "pretty-quick": "pretty-quick", + "format": "prettier --write .", + "style": "prettier --check ." }, "repository": { "type": "git", @@ -19,6 +22,11 @@ "Sketchboard", "lightweight" ], + "husky": { + "hooks": { + "pre-commit": "pretty-quick --staged" + } + }, "dependencies": { "dompurify": "^2.0.7", "express": "4.*", @@ -45,9 +53,12 @@ "copy-webpack-plugin": "^5.1.1", "css-loader": "^3.5.2", "html-webpack-plugin": "^4.2.0", + "husky": "^4.2.5", "jquery": "^3.2.1", "jquery-ui": "^1.12.1", "keymage": "^1.1.3", + "prettier": "^2.0.5", + "pretty-quick": "^2.0.1", "style-loader": "^1.1.4", "vanilla-picker": "^2.10.1", "webpack": "^4.42.1", diff --git a/scripts/s_whiteboard.js b/scripts/s_whiteboard.js index 72a9cf4..4299785 100644 --- a/scripts/s_whiteboard.js +++ b/scripts/s_whiteboard.js @@ -7,10 +7,12 @@ module.exports = { var tool = content["t"]; //Tool witch is used var wid = content["wid"]; //whiteboard ID var username = content["username"]; - if (tool === "clear") { //Clear the whiteboard + if (tool === "clear") { + //Clear the whiteboard delete savedBoards[wid]; delete savedUndos[wid]; - } else if (tool === "undo") { //Undo an action + } else if (tool === "undo") { + //Undo an action if (!savedUndos[wid]) { savedUndos[wid] = []; } @@ -19,7 +21,10 @@ module.exports = { if (savedBoards[wid][i]["username"] == username) { var drawId = savedBoards[wid][i]["drawId"]; for (var i = savedBoards[wid].length - 1; i >= 0; i--) { - if (savedBoards[wid][i]["drawId"] == drawId && savedBoards[wid][i]["username"] == username) { + if ( + savedBoards[wid][i]["drawId"] == drawId && + savedBoards[wid][i]["username"] == username + ) { savedUndos[wid].push(savedBoards[wid][i]); savedBoards[wid].splice(i, 1); } @@ -27,7 +32,7 @@ module.exports = { break; } } - if(savedUndos[wid].length > 1000) { + if (savedUndos[wid].length > 1000) { savedUndos[wid].splice(0, savedUndos[wid].length - 1000); } } @@ -42,7 +47,10 @@ module.exports = { if (savedUndos[wid][i]["username"] == username) { var drawId = savedUndos[wid][i]["drawId"]; for (var i = savedUndos[wid].length - 1; i >= 0; i--) { - if (savedUndos[wid][i]["drawId"] == drawId && savedUndos[wid][i]["username"] == username) { + if ( + savedUndos[wid][i]["drawId"] == drawId && + savedUndos[wid][i]["username"] == username + ) { savedBoards[wid].push(savedUndos[wid][i]); savedUndos[wid].splice(i, 1); } @@ -50,14 +58,36 @@ module.exports = { break; } } - } else if (["line", "pen", "rect", "circle", "eraser", "addImgBG", "recSelect", "eraseRec", "addTextBox", "setTextboxText", "removeTextbox", "setTextboxPosition", "setTextboxFontSize", "setTextboxFontColor"].includes(tool)) { //Save all this actions + } else if ( + [ + "line", + "pen", + "rect", + "circle", + "eraser", + "addImgBG", + "recSelect", + "eraseRec", + "addTextBox", + "setTextboxText", + "removeTextbox", + "setTextboxPosition", + "setTextboxFontSize", + "setTextboxFontColor", + ].includes(tool) + ) { + //Save all this actions if (!savedBoards[wid]) { savedBoards[wid] = []; } delete content["wid"]; //Delete id from content so we don't store it twice if (tool === "setTextboxText") { - for (var i = savedBoards[wid].length - 1; i >= 0; i--) { //Remove old textbox tex -> dont store it twice - if (savedBoards[wid][i]["t"] === "setTextboxText" && savedBoards[wid][i]["d"][0] === content["d"][0]) { + for (var i = savedBoards[wid].length - 1; i >= 0; i--) { + //Remove old textbox tex -> dont store it twice + if ( + savedBoards[wid][i]["t"] === "setTextboxText" && + savedBoards[wid][i]["d"][0] === content["d"][0] + ) { savedBoards[wid].splice(i, 1); } } @@ -65,7 +95,8 @@ module.exports = { savedBoards[wid].push(content); } }, - loadStoredData: function (wid) { //Load saved whiteboard + loadStoredData: function (wid) { + //Load saved whiteboard return savedBoards[wid] ? savedBoards[wid] : []; - } -} \ No newline at end of file + }, +}; diff --git a/scripts/server-backend.js b/scripts/server-backend.js index 1877d27..7d84260 100644 --- a/scripts/server-backend.js +++ b/scripts/server-backend.js @@ -5,26 +5,26 @@ function startBackendServer(port) { var accessToken = ""; //Can be set here or as start parameter (node server.js --accesstoken=MYTOKEN) var disableSmallestScreen = false; //Can be set to true if you dont want to show (node server.js --disablesmallestscreen=true) var webdav = false; //Can be set to true if you want to allow webdav save (node server.js --webdav=true) - + var fs = require("fs-extra"); - var express = require('express'); - var formidable = require('formidable'); //form upload processing - - const createDOMPurify = require('dompurify'); //Prevent xss - const { JSDOM } = require('jsdom'); - const window = (new JSDOM('')).window; + var express = require("express"); + var formidable = require("formidable"); //form upload processing + + const createDOMPurify = require("dompurify"); //Prevent xss + const { JSDOM } = require("jsdom"); + const window = new JSDOM("").window; const DOMPurify = createDOMPurify(window); - + const { createClient } = require("webdav"); - + var s_whiteboard = require("./s_whiteboard.js"); - + var app = express(); - app.use(express.static(path.join(__dirname, '..', 'dist'))); - app.use("/uploads", express.static(path.join(__dirname, '..', 'public', 'uploads'))); - var server = require('http').Server(app); + app.use(express.static(path.join(__dirname, "..", "dist"))); + app.use("/uploads", express.static(path.join(__dirname, "..", "public", "uploads"))); + var server = require("http").Server(app); server.listen(port); - var io = require('socket.io')(server, {path: "/ws-api", }); + var io = require("socket.io")(server, { path: "/ws-api" }); console.log("Webserver & socketserver running on port:" + port); if (process.env.accesstoken) { accessToken = process.env.accesstoken; @@ -35,7 +35,7 @@ function startBackendServer(port) { if (process.env.webdav) { webdav = true; } - + var startArgs = getArgs(); if (startArgs["accesstoken"]) { accessToken = startArgs["accesstoken"]; @@ -46,7 +46,7 @@ function startBackendServer(port) { if (startArgs["webdav"]) { webdav = true; } - + if (accessToken !== "") { console.log("AccessToken set to: " + accessToken); } @@ -56,8 +56,8 @@ function startBackendServer(port) { if (webdav) { console.log("Webdav save is enabled!"); } - - app.get('/api/loadwhiteboard', function (req, res) { + + app.get("/api/loadwhiteboard", function (req, res) { var wid = req["query"]["wid"]; var at = req["query"]["at"]; //accesstoken if (accessToken === "" || accessToken == at) { @@ -65,31 +65,32 @@ function startBackendServer(port) { res.send(ret); res.end(); } else { - res.status(401); //Unauthorized + res.status(401); //Unauthorized res.end(); } }); - - app.post('/api/upload', function (req, res) { //File upload + + app.post("/api/upload", function (req, res) { + //File upload var form = new formidable.IncomingForm(); //Receive form var formData = { files: {}, - fields: {} - } - - form.on('file', function (name, file) { + fields: {}, + }; + + form.on("file", function (name, file) { formData["files"][file.name] = file; }); - - form.on('field', function (name, value) { + + form.on("field", function (name, value) { formData["fields"][name] = value; }); - - form.on('error', function (err) { - console.log('File uplaod Error!'); + + form.on("error", function (err) { + console.log("File uplaod Error!"); }); - - form.on('end', function () { + + form.on("end", function () { if (accessToken === "" || accessToken == formData["fields"]["at"]) { progressUploadFormData(formData, function (err) { if (err) { @@ -104,22 +105,22 @@ function startBackendServer(port) { } }); } else { - res.status(401); //Unauthorized + res.status(401); //Unauthorized res.end(); } //End file upload }); form.parse(req); }); - + function progressUploadFormData(formData, callback) { console.log("Progress new Form Data"); var fields = escapeAllContentStrings(formData.fields); var files = formData.files; var whiteboardId = fields["whiteboardId"]; - + var name = fields["name"] || ""; - var date = fields["date"] || (+new Date()); + var date = fields["date"] || +new Date(); var filename = whiteboardId + "_" + date + ".png"; var webdavaccess = fields["webdavaccess"] || false; try { @@ -133,24 +134,33 @@ function startBackendServer(port) { return; } var imagedata = fields["imagedata"]; - if (imagedata && imagedata != "") { //Save from base64 data - imagedata = imagedata.replace(/^data:image\/png;base64,/, "").replace(/^data:image\/jpeg;base64,/, ""); + if (imagedata && imagedata != "") { + //Save from base64 data + imagedata = imagedata + .replace(/^data:image\/png;base64,/, "") + .replace(/^data:image\/jpeg;base64,/, ""); console.log(filename, "uploaded"); - fs.writeFile('./public/uploads/' + filename, imagedata, 'base64', function (err) { + fs.writeFile("./public/uploads/" + filename, imagedata, "base64", function (err) { if (err) { console.log("error", err); callback(err); } else { - if (webdavaccess) { //Save image to webdav + if (webdavaccess) { + //Save image to webdav if (webdav) { - saveImageToWebdav('./public/uploads/' + filename, filename, webdavaccess, function (err) { - if (err) { - console.log("error", err); - callback(err); - } else { - callback(); + saveImageToWebdav( + "./public/uploads/" + filename, + filename, + webdavaccess, + function (err) { + if (err) { + console.log("error", err); + callback(err); + } else { + callback(); + } } - }) + ); } else { callback("Webdav is not enabled on the server!"); } @@ -165,78 +175,92 @@ function startBackendServer(port) { } }); } - + function saveImageToWebdav(imagepath, filename, webdavaccess, callback) { if (webdavaccess) { var webdavserver = webdavaccess["webdavserver"] || ""; var webdavpath = webdavaccess["webdavpath"] || "/"; var webdavusername = webdavaccess["webdavusername"] || ""; var webdavpassword = webdavaccess["webdavpassword"] || ""; - - const client = createClient( - webdavserver, - { - username: webdavusername, - password: webdavpassword - } - ) - client.getDirectoryContents(webdavpath).then((items) => { - var cloudpath = webdavpath+ '' + filename; - console.log("webdav saving to:", cloudpath); - fs.createReadStream(imagepath).pipe(client.createWriteStream(cloudpath)); - callback(); - }).catch((error) => { - callback("403"); - console.log("Could not connect to webdav!") + + const client = createClient(webdavserver, { + username: webdavusername, + password: webdavpassword, }); + client + .getDirectoryContents(webdavpath) + .then((items) => { + var cloudpath = webdavpath + "" + filename; + console.log("webdav saving to:", cloudpath); + fs.createReadStream(imagepath).pipe(client.createWriteStream(cloudpath)); + callback(); + }) + .catch((error) => { + callback("403"); + console.log("Could not connect to webdav!"); + }); } else { - callback("Error: no access data!") + callback("Error: no access data!"); } } - + var smallestScreenResolutions = {}; - io.on('connection', function (socket) { + io.on("connection", function (socket) { var whiteboardId = null; - - socket.on('disconnect', function () { - if (smallestScreenResolutions && smallestScreenResolutions[whiteboardId] && socket && socket.id) { + + socket.on("disconnect", function () { + if ( + smallestScreenResolutions && + smallestScreenResolutions[whiteboardId] && + socket && + socket.id + ) { delete smallestScreenResolutions[whiteboardId][socket.id]; } - socket.compress(false).broadcast.emit('refreshUserBadges', null); //Removes old user Badges + socket.compress(false).broadcast.emit("refreshUserBadges", null); //Removes old user Badges sendSmallestScreenResolution(); }); - - socket.on('drawToWhiteboard', function (content) { + + socket.on("drawToWhiteboard", function (content) { content = escapeAllContentStrings(content); if (accessToken === "" || accessToken == content["at"]) { - socket.compress(false).broadcast.to(whiteboardId).emit('drawToWhiteboard', content); //Send to all users in the room (not own socket) + socket.compress(false).broadcast.to(whiteboardId).emit("drawToWhiteboard", content); //Send to all users in the room (not own socket) s_whiteboard.handleEventsAndData(content); //save whiteboardchanges on the server } else { - socket.emit('wrongAccessToken', true); + socket.emit("wrongAccessToken", true); } }); - - socket.on('joinWhiteboard', function (content) { + + socket.on("joinWhiteboard", function (content) { content = escapeAllContentStrings(content); if (accessToken === "" || accessToken == content["at"]) { whiteboardId = content["wid"]; socket.join(whiteboardId); //Joins room name=wid - smallestScreenResolutions[whiteboardId] = smallestScreenResolutions[whiteboardId] ? smallestScreenResolutions[whiteboardId] : {}; - smallestScreenResolutions[whiteboardId][socket.id] = content["windowWidthHeight"] || { w: 10000, h: 10000 }; + smallestScreenResolutions[whiteboardId] = smallestScreenResolutions[whiteboardId] + ? smallestScreenResolutions[whiteboardId] + : {}; + smallestScreenResolutions[whiteboardId][socket.id] = content[ + "windowWidthHeight" + ] || { w: 10000, h: 10000 }; sendSmallestScreenResolution(); } else { - socket.emit('wrongAccessToken', true); + socket.emit("wrongAccessToken", true); } }); - - socket.on('updateScreenResolution', function (content) { + + socket.on("updateScreenResolution", function (content) { content = escapeAllContentStrings(content); - if (smallestScreenResolutions[whiteboardId] && (accessToken === "" || accessToken == content["at"])) { - smallestScreenResolutions[whiteboardId][socket.id] = content["windowWidthHeight"] || { w: 10000, h: 10000 }; + if ( + smallestScreenResolutions[whiteboardId] && + (accessToken === "" || accessToken == content["at"]) + ) { + smallestScreenResolutions[whiteboardId][socket.id] = content[ + "windowWidthHeight" + ] || { w: 10000, h: 10000 }; sendSmallestScreenResolution(); } }); - + function sendSmallestScreenResolution() { if (disableSmallestScreen) { return; @@ -244,35 +268,44 @@ function startBackendServer(port) { var smallestWidth = 10000; var smallestHeight = 10000; for (var i in smallestScreenResolutions[whiteboardId]) { - smallestWidth = smallestWidth > smallestScreenResolutions[whiteboardId][i]["w"] ? smallestScreenResolutions[whiteboardId][i]["w"] : smallestWidth; - smallestHeight = smallestHeight > smallestScreenResolutions[whiteboardId][i]["h"] ? smallestScreenResolutions[whiteboardId][i]["h"] : smallestHeight; + smallestWidth = + smallestWidth > smallestScreenResolutions[whiteboardId][i]["w"] + ? smallestScreenResolutions[whiteboardId][i]["w"] + : smallestWidth; + smallestHeight = + smallestHeight > smallestScreenResolutions[whiteboardId][i]["h"] + ? smallestScreenResolutions[whiteboardId][i]["h"] + : smallestHeight; } - io.to(whiteboardId).emit('updateSmallestScreenResolution', { w: smallestWidth, h: smallestHeight }); + io.to(whiteboardId).emit("updateSmallestScreenResolution", { + w: smallestWidth, + h: smallestHeight, + }); } }); - + //Prevent cross site scripting (xss) function escapeAllContentStrings(content, cnt) { - if (!cnt) - cnt = 0; - - if (typeof (content) === "string") { + if (!cnt) cnt = 0; + + if (typeof content === "string") { return DOMPurify.sanitize(content); } for (var i in content) { - if (typeof (content[i]) === "string") { + if (typeof content[i] === "string") { content[i] = DOMPurify.sanitize(content[i]); - } if (typeof (content[i]) === "object" && cnt < 10) { + } + if (typeof content[i] === "object" && cnt < 10) { content[i] = escapeAllContentStrings(content[i], ++cnt); } } return content; } - - process.on('unhandledRejection', error => { + + process.on("unhandledRejection", (error) => { // Will print "unhandledRejection err is not defined" - console.log('unhandledRejection', error.message); - }) + console.log("unhandledRejection", error.message); + }); } -module.exports = startBackendServer; \ No newline at end of file +module.exports = startBackendServer; diff --git a/scripts/server-frontend-dev.js b/scripts/server-frontend-dev.js index 0e90065..ca45fee 100644 --- a/scripts/server-frontend-dev.js +++ b/scripts/server-frontend-dev.js @@ -1,34 +1,33 @@ const devServerConfig = { - hot: true, - inline: true, - stats: { - children: false, - maxModules: 0 - }, - proxy: { - // proxies for the backend - '/api': 'http://localhost:3000', - '/uploads': 'http://localhost:3000', - '/ws-api': { - target: 'ws://localhost:3000', - ws: true, - } - } -} + hot: true, + inline: true, + stats: { + children: false, + maxModules: 0, + }, + proxy: { + // proxies for the backend + "/api": "http://localhost:3000", + "/uploads": "http://localhost:3000", + "/ws-api": { + target: "ws://localhost:3000", + ws: true, + }, + }, +}; function startFrontendDevServer(port) { - // require here to prevent prod dependency to webpack - const webpack = require("webpack"); - const WebpackDevServer = require("webpack-dev-server"); - const config = require("../config/webpack.dev"); + // require here to prevent prod dependency to webpack + const webpack = require("webpack"); + const WebpackDevServer = require("webpack-dev-server"); + const config = require("../config/webpack.dev"); - new WebpackDevServer(webpack(config), devServerConfig) - .listen(port, (err) => { - if (err) { - console.log(err); - } + new WebpackDevServer(webpack(config), devServerConfig).listen(port, (err) => { + if (err) { + console.log(err); + } - console.log("Listening on port " + port); + console.log("Listening on port " + port); }); } diff --git a/scripts/server.js b/scripts/server.js index e5a52cc..58414ae 100644 --- a/scripts/server.js +++ b/scripts/server.js @@ -4,23 +4,23 @@ const startBackendServer = require("./server-backend"); const SERVER_MODES = { PRODUCTION: 1, - DEVELOPMENT: 2 -} + DEVELOPMENT: 2, +}; const args = getArgs(); -if ( typeof args.mode === "undefined") { +if (typeof args.mode === "undefined") { // default to production mode args.mode = "production"; } if (args.mode !== "production" && args.mode !== "development") { - throw new Error("--mode can only be 'development' or 'production'") + throw new Error("--mode can only be 'development' or 'production'"); } const server_mode = args.mode === "production" ? SERVER_MODES.PRODUCTION : SERVER_MODES.DEVELOPMENT; -if (server_mode === SERVER_MODES.DEVELOPMENT){ +if (server_mode === SERVER_MODES.DEVELOPMENT) { console.info("Starting server in development mode."); startFrontendDevServer(8080); // this time, it's the frontend server that is on port 8080 diff --git a/scripts/utils.js b/scripts/utils.js index dad47b3..d18a01b 100644 --- a/scripts/utils.js +++ b/scripts/utils.js @@ -1,22 +1,20 @@ function getArgs() { - const args = {} - process.argv - .slice(2, process.argv.length) - .forEach(arg => { - // long arg - if (arg.slice(0, 2) === '--') { - const longArg = arg.split('=') - args[longArg[0].slice(2, longArg[0].length)] = longArg[1] - } - // flags - else if (arg[0] === '-') { - const flags = arg.slice(1, arg.length).split('') - flags.forEach(flag => { - args[flag] = true - }) - } - }) - return args + const args = {}; + process.argv.slice(2, process.argv.length).forEach((arg) => { + // long arg + if (arg.slice(0, 2) === "--") { + const longArg = arg.split("="); + args[longArg[0].slice(2, longArg[0].length)] = longArg[1]; + } + // flags + else if (arg[0] === "-") { + const flags = arg.slice(1, arg.length).split(""); + flags.forEach((flag) => { + args[flag] = true; + }); + } + }); + return args; } -module.exports.getArgs = getArgs; \ No newline at end of file +module.exports.getArgs = getArgs; diff --git a/src/css/main.css b/src/css/main.css index 9ea45c6..a2d36f1 100644 --- a/src/css/main.css +++ b/src/css/main.css @@ -36,12 +36,11 @@ button::-moz-focus-inner { .whiteboard-edit-group.group-disabled { background: repeating-linear-gradient( 45deg, - rgba(255, 166, 0, 0.366) , + rgba(255, 166, 0, 0.366), rgba(255, 166, 0, 0.366) 10px, rgba(255, 166, 0, 0.666) 10px, rgba(255, 166, 0, 0.666) 20px ); - } /* @@ -90,19 +89,21 @@ button { background: transparent; outline: none; opacity: 1; - -webkit-transition: opacity .15s ease-in-out; - transition: opacity .15s ease-in-out; + -webkit-transition: opacity 0.15s ease-in-out; + transition: opacity 0.15s ease-in-out; } .textBox.active { border: 1px dashed gray; } -.textBox>.removeIcon, .textBox>.moveIcon { +.textBox > .removeIcon, +.textBox > .moveIcon { display: none; } -.textBox.active>.removeIcon, .textBox.active>.moveIcon { +.textBox.active > .removeIcon, +.textBox.active > .moveIcon { display: block; } diff --git a/src/index.html b/src/index.html index 9ef0286..f34965d 100644 --- a/src/index.html +++ b/src/index.html @@ -1,140 +1,255 @@ + + Whiteboard + + + - - Whiteboard - - - + + +
- - -
+ +
+
+ + +
- -
-
- - -
+
+ + + + +
-
- - - - -
+
+ + + + + + + + +
-
- - - - - - - - -
+
+ +
-
- -
+
+ -
- + - + +
- -
+
+ + + -
- - +
- - - - +
+ +
+
- -
- -
- -
-
- -
-

# msg. sent to server: 0

-

# msg. received from server: 0

-
- - - \ No newline at end of file +
+

# msg. sent to server: 0

+

# msg. received from server: 0

+
+ + diff --git a/src/js/classes/Point.js b/src/js/classes/Point.js index c7ec8cf..55f0717 100644 --- a/src/js/classes/Point.js +++ b/src/js/classes/Point.js @@ -1,4 +1,4 @@ -import {computeDist} from "../utils"; +import { computeDist } from "../utils"; class Point { /** @@ -42,7 +42,8 @@ class Point { let x = (e.offsetX || e.pageX - $(e.target).offset().left) + epsilon; let y = (e.offsetY || e.pageY - $(e.target).offset().top) + epsilon; - if (Number.isNaN(x) || Number.isNaN(y) || (x === epsilon && y === epsilon)) { // if it's a touch actually + if (Number.isNaN(x) || Number.isNaN(y) || (x === epsilon && y === epsilon)) { + // if it's a touch actually if (e.touches && e.touches.length && e.touches.length > 0) { const touch = e.touches[0]; x = touch.clientX - $("#mouseOverlay").offset().left; @@ -65,7 +66,7 @@ class Point { /** * Compute euclidean distance between points - * + * * @param {Point} otherPoint * @returns {number} */ @@ -74,4 +75,4 @@ class Point { } } -export default Point; \ No newline at end of file +export default Point; diff --git a/src/js/icons.js b/src/js/icons.js index 5947079..c6bbf98 100644 --- a/src/js/icons.js +++ b/src/js/icons.js @@ -17,7 +17,7 @@ import { faSortDown, faExpandArrowsAlt, faLock, - faLockOpen + faLockOpen, } from "@fortawesome/free-solid-svg-icons"; import { faSquare, @@ -53,4 +53,4 @@ library.add( faLockOpen ); -dom.i2svg() \ No newline at end of file +dom.i2svg(); diff --git a/src/js/index.js b/src/js/index.js index d9b980a..ccced5e 100644 --- a/src/js/index.js +++ b/src/js/index.js @@ -8,19 +8,19 @@ import "./icons"; import main from "./main"; $(document).ready(function () { - // Set correct width height on mobile browsers const isChrome = /Chrome/.test(navigator.userAgent) && /Google Inc/.test(navigator.vendor); if (isChrome) { - $('head').append(''); + $("head").append( + '' + ); } else { - $('head').append(''); + $("head").append(''); } main(); }); - if (module.hot) { module.hot.accept(); } diff --git a/src/js/keybinds.js b/src/js/keybinds.js index 7274154..1a033db 100644 --- a/src/js/keybinds.js +++ b/src/js/keybinds.js @@ -1,48 +1,48 @@ /* ----------- KEYBINDINGS ------------ */ +----------- */ //> defmod is "command" on OS X and "ctrl" elsewhere -//Advanced Example: 'defmod-k j' -> For this to fire you have to first press both ctrl and k, and then j. +//Advanced Example: 'defmod-k j' -> For this to fire you have to first press both ctrl and k, and then j. const keybinds = { // 'key(s)' : 'function', - 'defmod-shift-z' : 'clearWhiteboard', - 'defmod-z' : 'undoStep', - 'defmod-y' : 'redoStep', - 'defmod-x' : 'setTool_recSelect', - 'defmod-m' : 'setTool_mouse', - 'defmod-p' : 'setTool_pen', - 'defmod-l' : 'setTool_line', - 'defmod-r' : 'setTool_rect', - 'defmod-c' : 'setTool_circle', - 'defmod-shift-f' : 'toggleLineRecCircle', - 'defmod-shift-x' : 'togglePenEraser', - 'defmod-shift-r' : 'toggleMainColors', - 'defmod-a' : 'setTool_text', - 'defmod-e' : 'setTool_eraser', - 'defmod-up' : 'thickness_bigger', - 'defmod-down' : 'thickness_smaller', - 'defmod-shift-c' : 'openColorPicker', - 'defmod-shift-1' : 'setDrawColorBlack', - 'defmod-shift-2' : 'setDrawColorBlue', - 'defmod-shift-3' : 'setDrawColorGreen', - 'defmod-shift-4' : 'setDrawColorYellow', - 'defmod-shift-5' : 'setDrawColorRed', - 'defmod-s' : 'saveWhiteboardAsImage', - 'defmod-shift-k' : 'saveWhiteboardAsJson', - 'defmod-shift-i' : 'uploadWhiteboardToWebDav', - 'defmod-shift-j' : 'uploadJsonToWhiteboard', - 'defmod-shift-s' : 'shareWhiteboard', - 'tab' : 'hideShowControls', - 'up' : 'moveDraggableUp', - 'down' : 'moveDraggableDown', - 'left' : 'moveDraggableLeft', - 'right' : 'moveDraggableRight', - 'defmod-enter' : 'dropDraggable', - 'shift-enter' : 'addToBackground', - 'escape' : 'cancelAllActions', - 'del' : 'deleteSelection' -} + "defmod-shift-z": "clearWhiteboard", + "defmod-z": "undoStep", + "defmod-y": "redoStep", + "defmod-x": "setTool_recSelect", + "defmod-m": "setTool_mouse", + "defmod-p": "setTool_pen", + "defmod-l": "setTool_line", + "defmod-r": "setTool_rect", + "defmod-c": "setTool_circle", + "defmod-shift-f": "toggleLineRecCircle", + "defmod-shift-x": "togglePenEraser", + "defmod-shift-r": "toggleMainColors", + "defmod-a": "setTool_text", + "defmod-e": "setTool_eraser", + "defmod-up": "thickness_bigger", + "defmod-down": "thickness_smaller", + "defmod-shift-c": "openColorPicker", + "defmod-shift-1": "setDrawColorBlack", + "defmod-shift-2": "setDrawColorBlue", + "defmod-shift-3": "setDrawColorGreen", + "defmod-shift-4": "setDrawColorYellow", + "defmod-shift-5": "setDrawColorRed", + "defmod-s": "saveWhiteboardAsImage", + "defmod-shift-k": "saveWhiteboardAsJson", + "defmod-shift-i": "uploadWhiteboardToWebDav", + "defmod-shift-j": "uploadJsonToWhiteboard", + "defmod-shift-s": "shareWhiteboard", + tab: "hideShowControls", + up: "moveDraggableUp", + down: "moveDraggableDown", + left: "moveDraggableLeft", + right: "moveDraggableRight", + "defmod-enter": "dropDraggable", + "shift-enter": "addToBackground", + escape: "cancelAllActions", + del: "deleteSelection", +}; -export default keybinds; \ No newline at end of file +export default keybinds; diff --git a/src/js/main.js b/src/js/main.js index 7fa022c..221ecfd 100644 --- a/src/js/main.js +++ b/src/js/main.js @@ -1,5 +1,5 @@ import keymage from "keymage"; -import io from 'socket.io-client'; +import io from "socket.io-client"; import whiteboard from "./whiteboard"; import keybinds from "./keybinds"; import Picker from "vanilla-picker"; @@ -9,13 +9,16 @@ import shortcutFunctions from "./shortcutFunctions"; import ReadOnlyService from "./services/ReadOnlyService"; function main() { - var whiteboardId = getQueryVariable("whiteboardid"); var randomid = getQueryVariable("randomid"); - if (randomid && !whiteboardId) { //set random whiteboard on empty whiteboardid - whiteboardId = Array(2).fill(null).map(() => Math.random().toString(36).substr(2)).join(''); + if (randomid && !whiteboardId) { + //set random whiteboard on empty whiteboardid + whiteboardId = Array(2) + .fill(null) + .map(() => Math.random().toString(36).substr(2)) + .join(""); const urlParams = new URLSearchParams(window.location.search); - urlParams.set('whiteboardid', whiteboardId); + urlParams.set("whiteboardid", whiteboardId); window.location.search = urlParams; } @@ -33,40 +36,44 @@ function main() { document.title = decodeURIComponent(title); } - var url = document.URL.substr(0, document.URL.lastIndexOf('/')); + var url = document.URL.substr(0, document.URL.lastIndexOf("/")); var signaling_socket = null; var urlSplit = url.split("/"); var subdir = ""; for (var i = 3; i < urlSplit.length; i++) { - subdir = subdir + '/' + urlSplit[i]; + subdir = subdir + "/" + urlSplit[i]; } - signaling_socket = io("", { "path": subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy + signaling_socket = io("", { path: subdir + "/ws-api" }); // Connect even if we are in a subdir behind a reverse proxy - signaling_socket.on('connect', function () { + signaling_socket.on("connect", function () { console.log("Websocket connected!"); let messageReceivedCount = 0; - signaling_socket.on('drawToWhiteboard', function (content) { + signaling_socket.on("drawToWhiteboard", function (content) { whiteboard.handleEventsAndData(content, true); - $('#messageReceivedCount')[0].innerText = String(messageReceivedCount++); + $("#messageReceivedCount")[0].innerText = String(messageReceivedCount++); }); - signaling_socket.on('refreshUserBadges', function () { + signaling_socket.on("refreshUserBadges", function () { whiteboard.refreshUserBadges(); }); - signaling_socket.on('wrongAccessToken', function () { + signaling_socket.on("wrongAccessToken", function () { if (!accessDenied) { accessDenied = true; - showBasicAlert("Access denied! Wrong accessToken!") + showBasicAlert("Access denied! Wrong accessToken!"); } }); - signaling_socket.on('updateSmallestScreenResolution', function (widthHeight) { + signaling_socket.on("updateSmallestScreenResolution", function (widthHeight) { whiteboard.updateSmallestScreenResolution(widthHeight["w"], widthHeight["h"]); }); - signaling_socket.emit('joinWhiteboard', { wid: whiteboardId, at: accessToken, windowWidthHeight: { w: $(window).width(), h: $(window).height() } }); + signaling_socket.emit("joinWhiteboard", { + wid: whiteboardId, + at: accessToken, + windowWidthHeight: { w: $(window).width(), h: $(window).height() }, + }); }); $(document).ready(function () { @@ -78,29 +85,35 @@ function main() { } let messageSentCount = 0; - whiteboard.loadWhiteboard("#whiteboardContainer", { //Load the whiteboard + whiteboard.loadWhiteboard("#whiteboardContainer", { + //Load the whiteboard whiteboardId: whiteboardId, username: btoa(myUsername), sendFunction: function (content) { if (ReadOnlyService.readOnlyActive) return; - //ADD IN LATER THROUGH CONFIG - // if (content.t === 'cursor') { + //ADD IN LATER THROUGH CONFIG + // if (content.t === 'cursor') { // if (whiteboard.drawFlag) return; // } content["at"] = accessToken; - signaling_socket.emit('drawToWhiteboard', content); - $('#messageSentCount')[0].innerText = String(messageSentCount++); - } + signaling_socket.emit("drawToWhiteboard", content); + $("#messageSentCount")[0].innerText = String(messageSentCount++); + }, }); // request whiteboard from server - $.get(subdir + "/api/loadwhiteboard", { wid: whiteboardId, at: accessToken }).done(function (data) { - whiteboard.loadData(data) - }); + $.get(subdir + "/api/loadwhiteboard", { wid: whiteboardId, at: accessToken }).done( + function (data) { + whiteboard.loadData(data); + } + ); $(window).resize(function () { - signaling_socket.emit('updateScreenResolution', { at: accessToken, windowWidthHeight: { w: $(window).width(), h: $(window).height() } }); - }) + signaling_socket.emit("updateScreenResolution", { + at: accessToken, + windowWidthHeight: { w: $(window).width(), h: $(window).height() }, + }); + }); /*----------------/ Whiteboard actions @@ -115,12 +128,18 @@ function main() { tempLineTool = true; whiteboard.ownCursor.hide(); if (whiteboard.drawFlag) { - whiteboard.mouseup({ offsetX: whiteboard.prevPos.x, offsetY: whiteboard.prevPos.y }) + whiteboard.mouseup({ + offsetX: whiteboard.prevPos.x, + offsetY: whiteboard.prevPos.y, + }); shortcutFunctions.setTool_line(); - whiteboard.mousedown({ offsetX: whiteboard.prevPos.x, offsetY: whiteboard.prevPos.y }) + whiteboard.mousedown({ + offsetX: whiteboard.prevPos.x, + offsetY: whiteboard.prevPos.y, + }); } else { shortcutFunctions.setTool_line(); - } + } } whiteboard.pressedKeys["shift"] = true; //Used for straight lines... } else if (e.which == 17) { @@ -147,9 +166,15 @@ function main() { if (associatedShortcutFunction) { keymage(key, associatedShortcutFunction, { preventDefault: true }); } else { - console.error("Function you want to keybind on key:", key, "named:", functionName, "is not available!") + console.error( + "Function you want to keybind on key:", + key, + "named:", + functionName, + "is not available!" + ); } - }) + }); // whiteboard clear button $("#whiteboardTrashBtn").click(function () { @@ -211,15 +236,18 @@ function main() { $("#saveAsImageBtn").click(function () { var imgData = whiteboard.getImageDataBase64(); - var w = window.open('about:blank'); //Firefox will not allow downloads without extra window - setTimeout(function () { //FireFox seems to require a setTimeout for this to work. - var a = document.createElement('a'); + var w = window.open("about:blank"); //Firefox will not allow downloads without extra window + setTimeout(function () { + //FireFox seems to require a setTimeout for this to work. + var a = document.createElement("a"); a.href = imgData; - a.download = 'whiteboard.png'; + a.download = "whiteboard.png"; w.document.body.appendChild(a); a.click(); w.document.body.removeChild(a); - setTimeout(function () { w.close(); }, 100); + setTimeout(function () { + w.close(); + }, 100); }, 0); }); @@ -227,15 +255,18 @@ function main() { $("#saveAsJSONBtn").click(function () { var imgData = whiteboard.getImageDataJson(); - var w = window.open('about:blank'); //Firefox will not allow downloads without extra window - setTimeout(function () { //FireFox seems to require a setTimeout for this to work. - var a = document.createElement('a'); - a.href = window.URL.createObjectURL(new Blob([imgData], { type: 'text/json' })); - a.download = 'whiteboard.json'; + var w = window.open("about:blank"); //Firefox will not allow downloads without extra window + setTimeout(function () { + //FireFox seems to require a setTimeout for this to work. + var a = document.createElement("a"); + a.href = window.URL.createObjectURL(new Blob([imgData], { type: "text/json" })); + a.download = "whiteboard.json"; w.document.body.appendChild(a); a.click(); w.document.body.removeChild(a); - setTimeout(function () { w.close(); }, 100); + setTimeout(function () { + w.close(); + }, 100); }, 0); }); @@ -244,57 +275,67 @@ function main() { return; } - var webdavserver = localStorage.getItem('webdavserver') || "" - var webdavpath = localStorage.getItem('webdavpath') || "/" - var webdavusername = localStorage.getItem('webdavusername') || "" - var webdavpassword = localStorage.getItem('webdavpassword') || "" - var webDavHtml = $('
' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '' + - '
Server URL:
Path:path always have to start & end with "/"
Username:
Password:
Note: You have to generate and use app credentials if you have 2 Factor Auth activated on your dav/nextcloud server!
' + - '
'); + var webdavserver = localStorage.getItem("webdavserver") || ""; + var webdavpath = localStorage.getItem("webdavpath") || "/"; + var webdavusername = localStorage.getItem("webdavusername") || ""; + var webdavpassword = localStorage.getItem("webdavpassword") || ""; + var webDavHtml = $( + "
" + + "" + + "" + + "" + + '' + + "" + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + '' + + "" + + "" + + "" + + '' + + "" + + "
Server URL:
Path:path always have to start & end with "/"
Username:
Password:
Note: You have to generate and use app credentials if you have 2 Factor Auth activated on your dav/nextcloud server!
" + + "
" + ); webDavHtml.find(".webdavUploadBtn").click(function () { var webdavserver = webDavHtml.find(".webdavserver").val(); - localStorage.setItem('webdavserver', webdavserver); + localStorage.setItem("webdavserver", webdavserver); var webdavpath = webDavHtml.find(".webdavpath").val(); - localStorage.setItem('webdavpath', webdavpath); + localStorage.setItem("webdavpath", webdavpath); var webdavusername = webDavHtml.find(".webdavusername").val(); - localStorage.setItem('webdavusername', webdavusername); + localStorage.setItem("webdavusername", webdavusername); var webdavpassword = webDavHtml.find(".webdavpassword").val(); - localStorage.setItem('webdavpassword', webdavpassword); + localStorage.setItem("webdavpassword", webdavpassword); var base64data = whiteboard.getImageDataBase64(); var webdavaccess = { webdavserver: webdavserver, webdavpath: webdavpath, webdavusername: webdavusername, - webdavpassword: webdavpassword - } + webdavpassword: webdavpassword, + }; webDavHtml.find(".loadingWebdavText").show(); webDavHtml.find(".webdavUploadBtn").hide(); saveWhiteboardToWebdav(base64data, webdavaccess, function (err) { @@ -305,12 +346,12 @@ function main() { webDavHtml.parents(".basicalert").remove(); } }); - }) + }); showBasicAlert(webDavHtml, { header: "Save to Webdav", okBtnText: "cancel", - headercolor: "#0082c9" - }) + headercolor: "#0082c9", + }); // render newly added icons dom.i2svg(); }); @@ -330,10 +371,15 @@ function main() { endSplit = endSplit.splice(1, 1); urlStart += "&" + endSplit.join("&"); } - $("