diff --git a/.github/workflows/lint-js.yml b/.github/workflows/lint-js.yml new file mode 100644 index 000000000..a8bd8abb1 --- /dev/null +++ b/.github/workflows/lint-js.yml @@ -0,0 +1,16 @@ +name: lint-js + +on: [push, pull_request] + +jobs: + build: + if: ${{ github.ref != 'refs/heads/l10n_development' }} + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v1 + + - name: Install NPM deps + run: npm ci + + - name: Run formatting check + run: npm run lint diff --git a/dev/docs/development.md b/dev/docs/development.md index b68f2664a..a68ae50b4 100644 --- a/dev/docs/development.md +++ b/dev/docs/development.md @@ -33,6 +33,10 @@ If the codebase needs to be tested with deprecations, this can be done via uncom ## Code Standards +We use tools to manage code standards and formatting within the project. If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools. + +### PHP + PHP code standards are managed by [using PHP_CodeSniffer](https://github.com/squizlabs/PHP_CodeSniffer). Static analysis is in place using [PHPStan](https://phpstan.org/) & [Larastan](https://github.com/nunomaduro/larastan). The below commands can be used to utilise these tools: @@ -51,7 +55,19 @@ composer format composer check-static ``` -If submitting a PR, formatting as per our project standards would help for clarity but don't worry too much about using/understanding these tools as we can always address issues at a later stage when they're picked up by our automated tools. +### JavaScript + +JavaScript code standards use managed using [ESLint](https://eslint.org/). +The ESLint rule configuration is managed within the `package.json` file. +The below commands can be used to lint and format: + +```bash +# Run code linting using ESLint +npm run lint + +# Fix code where possible using ESLint +npm run fix +``` ## Development using Docker diff --git a/package-lock.json b/package-lock.json index 3fb33e36e..bb8b6049b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@codemirror/state": "^6.2.0", "@codemirror/theme-one-dark": "^6.1.1", "@codemirror/view": "^6.9.4", + "@lezer/highlight": "^1.1.4", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "codemirror": "^6.0.1", @@ -31,6 +32,9 @@ "@lezer/generator": "^1.2.2", "chokidar-cli": "^3.0", "esbuild": "^0.17.16", + "eslint": "^8.38.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5", "livereload": "^0.9.3", "npm-run-all": "^4.1.5", "punycode": "^2.3.0", @@ -571,6 +575,95 @@ "node": ">=12" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.0.tgz", + "integrity": "sha512-vITaYzIcNmjn5tF5uxcZ/ft7/RXGrMUIS9HalWckEOF6ESiwXKoMzAQf2UW0aVd6rnOeExTJVd5hmWXucBKGXQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.2.tgz", + "integrity": "sha512-3W4f5tDUra+pA+FzgugqL2pRimUTDJWKr7BINqOpkZrC0uYI0NIc0/JFgBROCU07HR6GieA5m3/rsPIhDmCXTQ==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.5.1", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.38.0.tgz", + "integrity": "sha512-IoD2MfUnOV58ghIHCiil01PcohxjbYR/qCxsoC+xNgUwh1EY8jOOrYmu3d3a71+tJJ23uscEV4X2HJWMsPJu4g==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.8", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", + "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@lezer/common": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.0.2.tgz", @@ -669,6 +762,41 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@ssddanbrown/codemirror-lang-smarty": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@ssddanbrown/codemirror-lang-smarty/-/codemirror-lang-smarty-1.0.0.tgz", @@ -684,6 +812,49 @@ "@lezer/lr": "^1.0.0" } }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "node_modules/ansi-regex": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", @@ -736,6 +907,61 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/array-includes": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", + "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "get-intrinsic": "^1.1.3", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", + "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", + "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-shim-unscopables": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", @@ -798,6 +1024,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", @@ -912,6 +1147,12 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/confusing-browser-globals": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/confusing-browser-globals/-/confusing-browser-globals-1.0.11.tgz", + "integrity": "sha512-JsPKdmh8ZkmnHxDk55FZ1TqVLvEQTvoByJZRN9jzI0UjxK/QgAmsphz7PGtqgPieQZ/CQcHWXCR7ATDNhGe+YA==", + "dev": true + }, "node_modules/crelt": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.5.tgz", @@ -933,6 +1174,23 @@ "node": ">=4.8" } }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", @@ -942,6 +1200,12 @@ "node": ">=0.10.0" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/define-properties": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", @@ -958,6 +1222,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dropzone": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/dropzone/-/dropzone-5.9.3.tgz", @@ -1051,6 +1327,15 @@ "node": ">= 0.4" } }, + "node_modules/es-shim-unscopables": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz", + "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + } + }, "node_modules/es-to-primitive": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", @@ -1114,6 +1399,566 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.38.0.tgz", + "integrity": "sha512-pIdsD2jwlUGf/U38Jv97t8lq6HpaU/G9NKbYmpWpZGw3LdTNhZLbJePqxOXGB5+JEKfOPU/XLxYxFh03nr1KTg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.0.2", + "@eslint/js": "8.38.0", + "@humanwhocodes/config-array": "^0.11.8", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.4.0", + "espree": "^9.5.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-airbnb-base": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-airbnb-base/-/eslint-config-airbnb-base-15.0.0.tgz", + "integrity": "sha512-xaX3z4ZZIcFLvh2oUNvcX5oEofXda7giYmuplVxoOg5A7EXJMrUyqRgR+mhDhPK8LZ4PttFOBvCYDbX3sUoUig==", + "dev": true, + "dependencies": { + "confusing-browser-globals": "^1.0.10", + "object.assign": "^4.1.2", + "object.entries": "^1.1.5", + "semver": "^6.3.0" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + }, + "peerDependencies": { + "eslint": "^7.32.0 || ^8.2.0", + "eslint-plugin-import": "^2.25.2" + } + }, + "node_modules/eslint-config-airbnb-base/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.7.tgz", + "integrity": "sha512-gozW2blMLJCeFpBwugLTGyvVjNoeo1knonXAcatC6bjPBZitotxdWf7Gimr25N4c0AAOo4eOUfaG82IJPDpqCA==", + "dev": true, + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.11.0", + "resolve": "^1.22.1" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-module-utils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", + "dev": true, + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.27.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.27.5.tgz", + "integrity": "sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==", + "dev": true, + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "array.prototype.flatmap": "^1.3.1", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.7", + "eslint-module-utils": "^2.7.4", + "has": "^1.0.3", + "is-core-module": "^2.11.0", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.values": "^1.1.6", + "resolve": "^1.22.1", + "semver": "^6.3.0", + "tsconfig-paths": "^3.14.1" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.0.tgz", + "integrity": "sha512-HPpKPUBQcAsZOsHAFwTtIKcYlCje62XB7SEAcxjtmW6TD1WVpkS6i6/hOVtTZIl4zGj/mBqpFVGvaDneik+VoQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/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 + }, + "node_modules/eslint/node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/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, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/espree": { + "version": "9.5.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.1.tgz", + "integrity": "sha512-5yxtHSZXRSW5pvv3hAlXM5+/Oswi1AUFqBmbibKb5s6bp3rGIDkyXU6xCoyuuLhijr4SFwPrXRoZjz0AZDN9tg==", + "dev": true, + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -1138,6 +1983,25 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, "node_modules/for-each": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", @@ -1147,6 +2011,12 @@ "is-callable": "^1.1.3" } }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true + }, "node_modules/fsevents": { "version": "2.3.2", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", @@ -1233,6 +2103,26 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/glob-parent": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", @@ -1245,6 +2135,21 @@ "node": ">= 6" } }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", @@ -1278,6 +2183,12 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==", + "dev": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -1365,12 +2276,62 @@ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/immutable": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", "dev": true }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, "node_modules/internal-slot": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", @@ -1550,6 +2511,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-regex": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", @@ -1645,12 +2615,71 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/js-sdsl": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", + "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/js-sdsl" + } + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", "dev": true }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, + "node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", + "dev": true, + "dependencies": { + "minimist": "^1.2.0" + }, + "bin": { + "json5": "lib/cli.js" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/linkify-it": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-4.0.1.tgz", @@ -1717,6 +2746,12 @@ "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -1769,6 +2804,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -1857,6 +2913,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.entries": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz", + "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", + "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/opts": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/opts/-/opts-2.0.2.tgz", @@ -1899,6 +3012,18 @@ "node": ">=6" } }, + "node_modules/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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", @@ -1921,6 +3046,15 @@ "node": ">=4" } }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", @@ -1981,6 +3115,15 @@ "node": ">=4" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/punycode": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", @@ -1990,6 +3133,26 @@ "node": ">=6" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/read-pkg": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", @@ -2065,6 +3228,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/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, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-regex-test": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", @@ -2306,6 +3526,18 @@ "node": ">=4" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/style-mod": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.0.3.tgz", @@ -2335,6 +3567,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -2347,6 +3585,42 @@ "node": ">=8.0" } }, + "node_modules/tsconfig-paths": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.14.2.tgz", + "integrity": "sha512-o/9iXgCYc5L/JxCHPe3Hvh8Q/2xm5Z+p18PESBU6Ff33695QnCHBEjcytY2q19ua7Mbl/DavtBOLq+oG0RCL+g==", + "dev": true, + "dependencies": { + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/typed-array-length": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", @@ -2381,6 +3655,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, "node_modules/validate-npm-package-license": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", @@ -2450,6 +3733,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/wrap-ansi": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", @@ -2464,6 +3756,12 @@ "node": ">=6" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true + }, "node_modules/ws": { "version": "7.5.9", "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", @@ -2518,6 +3816,18 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/package.json b/package.json index a8533113c..39f088234 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,17 @@ "dev": "npm-run-all --parallel watch livereload", "watch": "npm-run-all --parallel build:*:watch", "livereload": "livereload ./public/dist/", - "permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads" + "permissions": "chown -R $USER:$USER bootstrap/cache storage public/uploads", + "lint": "eslint \"resources/**/*.js\" \"resources/**/*.mjs\"", + "fix": "eslint --fix \"resources/**/*.js\" \"resources/**/*.mjs\"" }, "devDependencies": { "@lezer/generator": "^1.2.2", "chokidar-cli": "^3.0", "esbuild": "^0.17.16", + "eslint": "^8.38.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-plugin-import": "^2.27.5", "livereload": "^0.9.3", "npm-run-all": "^4.1.5", "punycode": "^2.3.0", @@ -37,6 +42,7 @@ "@codemirror/state": "^6.2.0", "@codemirror/theme-one-dark": "^6.1.1", "@codemirror/view": "^6.9.4", + "@lezer/highlight": "^1.1.4", "@ssddanbrown/codemirror-lang-smarty": "^1.0.0", "@ssddanbrown/codemirror-lang-twig": "^1.0.0", "codemirror": "^6.0.1", @@ -45,5 +51,85 @@ "markdown-it-task-lists": "^2.1.1", "snabbdom": "^3.5.1", "sortablejs": "^1.15.0" + }, + "eslintConfig": { + "root": true, + "env": { + "browser": true, + "es2021": true + }, + "extends": "airbnb-base", + "ignorePatterns": [ + "resources/**/*-stub.js" + ], + "overrides": [], + "parserOptions": { + "ecmaVersion": "latest", + "sourceType": "module" + }, + "rules": { + "indent": [ + "error", + 4 + ], + "arrow-parens": [ + "error", + "as-needed" + ], + "padded-blocks": [ + "error", + { + "blocks": "never", + "classes": "always" + } + ], + "object-curly-spacing": [ + "error", + "never" + ], + "space-before-function-paren": [ + "error", + { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + } + ], + "import/prefer-default-export": "off", + "no-plusplus": [ + "error", + { + "allowForLoopAfterthoughts": true + } + ], + "arrow-body-style": "off", + "no-restricted-syntax": "off", + "no-continue": "off", + "prefer-destructuring": "off", + "class-methods-use-this": "off", + "no-param-reassign": "off", + "no-console": [ + "warn", + { + "allow": [ + "error", + "warn" + ] + } + ], + "no-new": "off", + "max-len": [ + "error", + { + "code": 110, + "tabWidth": 4, + "ignoreUrls": true, + "ignoreComments": false, + "ignoreRegExpLiterals": true, + "ignoreStrings": true, + "ignoreTemplateLiterals": true + } + ] + } } } diff --git a/resources/js/app.js b/resources/js/app.js index e49bf5e95..5b822e900 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -1,34 +1,37 @@ +import * as events from './services/events'; +import * as httpInstance from './services/http'; +import Translations from './services/translations'; + +import * as components from './services/components'; +import * as componentMap from './components'; + // Url retrieval function -window.baseUrl = function(path) { +window.baseUrl = function baseUrl(path) { + let targetPath = path; let basePath = document.querySelector('meta[name="base-url"]').getAttribute('content'); - if (basePath[basePath.length-1] === '/') basePath = basePath.slice(0, basePath.length-1); - if (path[0] === '/') path = path.slice(1); - return basePath + '/' + path; + if (basePath[basePath.length - 1] === '/') basePath = basePath.slice(0, basePath.length - 1); + if (targetPath[0] === '/') targetPath = targetPath.slice(1); + return `${basePath}/${targetPath}`; }; -window.importVersioned = function(moduleName) { +window.importVersioned = function importVersioned(moduleName) { const version = document.querySelector('link[href*="/dist/styles.css?version="]').href.split('?version=').pop(); const importPath = window.baseUrl(`dist/${moduleName}.js?version=${version}`); return import(importPath); }; // Set events and http services on window -import events from "./services/events" -import httpInstance from "./services/http" window.$http = httpInstance; window.$events = events; // Translation setup -// Creates a global function with name 'trans' to be used in the same way as Laravel's translation system -import Translations from "./services/translations" +// Creates a global function with name 'trans' to be used in the same way as the Laravel translation system const translator = new Translations(); window.trans = translator.get.bind(translator); window.trans_choice = translator.getPlural.bind(translator); window.trans_plural = translator.parsePlural.bind(translator); -// Load Components -import * as components from "./services/components" -import * as componentMap from "./components"; +// Load & initialise components components.register(componentMap); window.$components = components; components.init(); diff --git a/resources/js/code/index.mjs b/resources/js/code/index.mjs index 32c25d401..e51472dc4 100644 --- a/resources/js/code/index.mjs +++ b/resources/js/code/index.mjs @@ -1,29 +1,41 @@ -import {EditorView, keymap} from "@codemirror/view"; +import {EditorView, keymap} from '@codemirror/view'; -import {copyTextToClipboard} from "../services/clipboard.js" -import {viewerExtensions, editorExtensions} from "./setups.js"; -import {createView} from "./views.js"; -import {SimpleEditorInterface} from "./simple-editor-interface.js"; +import {copyTextToClipboard} from '../services/clipboard'; +import {viewerExtensions, editorExtensions} from './setups'; +import {createView} from './views'; +import {SimpleEditorInterface} from './simple-editor-interface'; /** - * Highlight pre elements on a page + * Add a button to a CodeMirror instance which copies the contents to the clipboard upon click. + * @param {EditorView} editorView */ -export function highlight() { - const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre'); - for (const codeBlock of codeBlocks) { - highlightElem(codeBlock); - } -} +function addCopyIcon(editorView) { + const copyIcon = ''; + const checkIcon = ''; + const copyButton = document.createElement('button'); + copyButton.setAttribute('type', 'button'); + copyButton.classList.add('cm-copy-button'); + copyButton.innerHTML = copyIcon; + editorView.dom.appendChild(copyButton); -/** - * Highlight all code blocks within the given parent element - * @param {HTMLElement} parent - */ -export function highlightWithin(parent) { - const codeBlocks = parent.querySelectorAll('pre'); - for (const codeBlock of codeBlocks) { - highlightElem(codeBlock); - } + const notifyTime = 620; + const transitionTime = 60; + copyButton.addEventListener('click', () => { + copyTextToClipboard(editorView.state.doc.toString()); + copyButton.classList.add('success'); + + setTimeout(() => { + copyButton.innerHTML = checkIcon; + }, transitionTime / 2); + + setTimeout(() => { + copyButton.classList.remove('success'); + }, notifyTime); + + setTimeout(() => { + copyButton.innerHTML = copyIcon; + }, notifyTime + (transitionTime / 2)); + }); } /** @@ -32,7 +44,7 @@ export function highlightWithin(parent) { */ function highlightElem(elem) { const innerCodeElem = elem.querySelector('code[class^=language-]'); - elem.innerHTML = elem.innerHTML.replace(//gi ,'\n'); + elem.innerHTML = elem.innerHTML.replace(//gi, '\n'); const content = elem.textContent.trimEnd(); let langName = ''; @@ -57,36 +69,24 @@ function highlightElem(elem) { } /** - * Add a button to a CodeMirror instance which copies the contents to the clipboard upon click. - * @param {EditorView} editorView + * Highlight all code blocks within the given parent element + * @param {HTMLElement} parent */ -function addCopyIcon(editorView) { - const copyIcon = ``; - const checkIcon = ``; - const copyButton = document.createElement('button'); - copyButton.setAttribute('type', 'button') - copyButton.classList.add('cm-copy-button'); - copyButton.innerHTML = copyIcon; - editorView.dom.appendChild(copyButton); +export function highlightWithin(parent) { + const codeBlocks = parent.querySelectorAll('pre'); + for (const codeBlock of codeBlocks) { + highlightElem(codeBlock); + } +} - const notifyTime = 620; - const transitionTime = 60; - copyButton.addEventListener('click', event => { - copyTextToClipboard(editorView.state.doc.toString()); - copyButton.classList.add('success'); - - setTimeout(() => { - copyButton.innerHTML = checkIcon; - }, transitionTime / 2); - - setTimeout(() => { - copyButton.classList.remove('success'); - }, notifyTime); - - setTimeout(() => { - copyButton.innerHTML = copyIcon; - }, notifyTime + (transitionTime / 2)); - }); +/** + * Highlight pre elements on a page + */ +export function highlight() { + const codeBlocks = document.querySelectorAll('.page-content pre, .comment-box .content pre'); + for (const codeBlock of codeBlocks) { + highlightElem(codeBlock); + } } /** @@ -112,7 +112,6 @@ export function wysiwygView(cmContainer, shadowRoot, content, language) { return editor; } - /** * Create a CodeMirror instance to show in the WYSIWYG pop-up editor * @param {HTMLElement} elem @@ -126,7 +125,7 @@ export function popupEditor(elem, modeSuggestion) { doc: content, extensions: [ ...editorExtensions(elem.parentElement), - EditorView.updateListener.of((v) => { + EditorView.updateListener.of(v => { if (v.docChanged) { // textArea.value = v.state.doc.toString(); } @@ -155,7 +154,7 @@ export function inlineEditor(textArea, mode) { doc: content, extensions: [ ...editorExtensions(textArea.parentElement), - EditorView.updateListener.of((v) => { + EditorView.updateListener.of(v => { if (v.docChanged) { textArea.value = v.state.doc.toString(); } @@ -188,7 +187,7 @@ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { extensions: [ keymap.of(keyBindings), ...editorExtensions(elem.parentElement), - EditorView.updateListener.of((v) => { + EditorView.updateListener.of(v => { onChange(v); }), EditorView.domEventHandlers(domEventHandlers), @@ -204,4 +203,4 @@ export function markdownEditor(elem, onChange, domEventHandlers, keyBindings) { elem.style.display = 'none'; return ev; -} \ No newline at end of file +} diff --git a/resources/js/code/languages.js b/resources/js/code/languages.js index e7bac2a18..0703cbcde 100644 --- a/resources/js/code/languages.js +++ b/resources/js/code/languages.js @@ -1,20 +1,19 @@ -import {StreamLanguage} from "@codemirror/language" +import {StreamLanguage} from '@codemirror/language'; import {css} from '@codemirror/lang-css'; import {json} from '@codemirror/lang-json'; import {javascript} from '@codemirror/lang-javascript'; -import {html} from "@codemirror/lang-html"; +import {html} from '@codemirror/lang-html'; import {markdown} from '@codemirror/lang-markdown'; import {php} from '@codemirror/lang-php'; -import {twig} from "@ssddanbrown/codemirror-lang-twig"; -import {xml} from "@codemirror/lang-xml"; +import {twig} from '@ssddanbrown/codemirror-lang-twig'; +import {xml} from '@codemirror/lang-xml'; -const legacyLoad = async (mode) => { +const legacyLoad = async mode => { const modes = await window.importVersioned('legacy-modes'); return StreamLanguage.define(modes[mode]); }; - // Mapping of possible languages or formats from user input to their codemirror modes. // Value can be a mode string or a function that will receive the code content & return the mode string. // The function option is used in the event the exact mode could be dynamic depending on the code. @@ -58,7 +57,7 @@ const modeMap = { pascal: () => legacyLoad('pascal'), perl: () => legacyLoad('perl'), pgsql: () => legacyLoad('pgSQL'), - php: async (code) => { + php: async code => { const hasTags = code.includes(' { + onChildEvent(this.$el, this.removeSelector, 'click', e => { const row = e.target.closest(this.rowSelector); row.remove(); }); @@ -44,9 +45,10 @@ export class AddRemoveRows extends Component { */ setClonedInputNames(clone) { const rowId = uniqueId(); - const randRowIdElems = clone.querySelectorAll(`[name*="randrowid"]`); + const randRowIdElems = clone.querySelectorAll('[name*="randrowid"]'); for (const elem of randRowIdElems) { elem.name = elem.name.split('randrowid').join(rowId); } } -} \ No newline at end of file + +} diff --git a/resources/js/components/ajax-delete-row.js b/resources/js/components/ajax-delete-row.js index f1af7f6cb..aa2801f19 100644 --- a/resources/js/components/ajax-delete-row.js +++ b/resources/js/components/ajax-delete-row.js @@ -1,7 +1,8 @@ -import {onSelect} from "../services/dom"; -import {Component} from "./component"; +import {onSelect} from '../services/dom'; +import {Component} from './component'; export class AjaxDeleteRow extends Component { + setup() { this.row = this.$el; this.url = this.$opts.url; @@ -19,9 +20,10 @@ export class AjaxDeleteRow extends Component { window.$events.emit('success', resp.data.message); } this.row.remove(); - }).catch(err => { + }).catch(() => { this.row.style.opacity = null; this.row.style.pointerEvents = null; }); } -} \ No newline at end of file + +} diff --git a/resources/js/components/ajax-form.js b/resources/js/components/ajax-form.js index 6f4e5af08..583dde572 100644 --- a/resources/js/components/ajax-form.js +++ b/resources/js/components/ajax-form.js @@ -1,5 +1,5 @@ -import {onEnterPress, onSelect} from "../services/dom"; -import {Component} from "./component"; +import {onEnterPress, onSelect} from '../services/dom'; +import {Component} from './component'; /** * Ajax Form @@ -11,6 +11,7 @@ import {Component} from "./component"; * otherwise will act as a fake form element. */ export class AjaxForm extends Component { + setup() { this.container = this.$el; this.responseContainer = this.container; @@ -27,7 +28,6 @@ export class AjaxForm extends Component { } setupListeners() { - if (this.container.tagName === 'FORM') { this.container.addEventListener('submit', this.submitRealForm.bind(this)); return; @@ -43,7 +43,7 @@ export class AjaxForm extends Component { submitFakeForm() { const fd = new FormData(); - const inputs = this.container.querySelectorAll(`[name]`); + const inputs = this.container.querySelectorAll('[name]'); for (const input of inputs) { fd.append(input.getAttribute('name'), input.value); } @@ -76,4 +76,4 @@ export class AjaxForm extends Component { this.responseContainer.style.pointerEvents = null; } -} \ No newline at end of file +} diff --git a/resources/js/components/attachments-list.js b/resources/js/components/attachments-list.js index dfefd9b7f..4db09977f 100644 --- a/resources/js/components/attachments-list.js +++ b/resources/js/components/attachments-list.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; /** * Attachments List @@ -13,11 +13,11 @@ export class AttachmentsList extends Component { } setupListeners() { - const isExpectedKey = (event) => event.key === 'Control' || event.key === 'Meta'; + const isExpectedKey = event => event.key === 'Control' || event.key === 'Meta'; window.addEventListener('keydown', event => { - if (isExpectedKey(event)) { + if (isExpectedKey(event)) { this.addOpenQueryToLinks(); - } + } }, {passive: true}); window.addEventListener('keyup', event => { if (isExpectedKey(event)) { @@ -30,7 +30,7 @@ export class AttachmentsList extends Component { const links = this.container.querySelectorAll('a.attachment-file'); for (const link of links) { if (link.href.split('?')[1] !== 'open=true') { - link.href = link.href + '?open=true'; + link.href += '?open=true'; link.setAttribute('target', '_blank'); } } @@ -43,4 +43,5 @@ export class AttachmentsList extends Component { link.removeAttribute('target'); } } -} \ No newline at end of file + +} diff --git a/resources/js/components/attachments.js b/resources/js/components/attachments.js index d8a506270..9555a59e8 100644 --- a/resources/js/components/attachments.js +++ b/resources/js/components/attachments.js @@ -1,5 +1,5 @@ -import {showLoading} from "../services/dom"; -import {Component} from "./component"; +import {showLoading} from '../services/dom'; +import {Component} from './component'; export class Attachments extends Component { @@ -27,7 +27,7 @@ export class Attachments extends Component { this.startEdit(event.detail.id); }); - this.container.addEventListener('event-emit-select-edit-back', event => { + this.container.addEventListener('event-emit-select-edit-back', () => { this.stopEdit(); }); @@ -73,4 +73,4 @@ export class Attachments extends Component { this.listContainer.classList.remove('hidden'); } -} \ No newline at end of file +} diff --git a/resources/js/components/auto-submit.js b/resources/js/components/auto-submit.js index c8726ca7e..c78ef5549 100644 --- a/resources/js/components/auto-submit.js +++ b/resources/js/components/auto-submit.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class AutoSubmit extends Component { @@ -8,4 +8,4 @@ export class AutoSubmit extends Component { this.form.submit(); } -} \ No newline at end of file +} diff --git a/resources/js/components/auto-suggest.js b/resources/js/components/auto-suggest.js index 2ebf59f5d..92a6c6af3 100644 --- a/resources/js/components/auto-suggest.js +++ b/resources/js/components/auto-suggest.js @@ -1,7 +1,7 @@ -import {escapeHtml} from "../services/util"; -import {onChildEvent} from "../services/dom"; -import {Component} from "./component"; -import {KeyboardNavigationHandler} from "../services/keyboard-navigation"; +import {escapeHtml} from '../services/util'; +import {onChildEvent} from '../services/dom'; +import {Component} from './component'; +import {KeyboardNavigationHandler} from '../services/keyboard-navigation'; const ajaxCache = {}; @@ -9,6 +9,7 @@ const ajaxCache = {}; * AutoSuggest */ export class AutoSuggest extends Component { + setup() { this.parent = this.$el.parentElement; this.container = this.$el; @@ -24,7 +25,7 @@ export class AutoSuggest extends Component { setupListeners() { const navHandler = new KeyboardNavigationHandler( this.list, - event => { + () => { this.input.focus(); setTimeout(() => this.hideSuggestions(), 1); }, @@ -67,9 +68,7 @@ export class AutoSuggest extends Component { const search = this.input.value.toLowerCase(); const suggestions = await this.loadSuggestions(search, nameFilter); - const toShow = suggestions.filter(val => { - return search === '' || val.toLowerCase().startsWith(search); - }).slice(0, 10); + const toShow = suggestions.filter(val => search === '' || val.toLowerCase().startsWith(search)).slice(0, 10); this.displaySuggestions(toShow); } @@ -105,7 +104,8 @@ export class AutoSuggest extends Component { */ displaySuggestions(suggestions) { if (suggestions.length === 0) { - return this.hideSuggestions(); + this.hideSuggestions(); + return; } // This used to use `; + const localTime = (new Date(parseInt(key, 10))).toLocaleTimeString(); + return `
  • `; }).join(''); } @@ -191,4 +193,4 @@ export class CodeEditor extends Component { window.sessionStorage.setItem(this.historyKey, historyString); } -} \ No newline at end of file +} diff --git a/resources/js/components/code-highlighter.js b/resources/js/components/code-highlighter.js index 14bfc97f0..e12d77044 100644 --- a/resources/js/components/code-highlighter.js +++ b/resources/js/components/code-highlighter.js @@ -1,6 +1,6 @@ -import {Component} from "./component"; +import {Component} from './component'; -export class CodeHighlighter extends Component{ +export class CodeHighlighter extends Component { setup() { const container = this.$el; @@ -8,9 +8,9 @@ export class CodeHighlighter extends Component{ const codeBlocks = container.querySelectorAll('pre'); if (codeBlocks.length > 0) { window.importVersioned('code').then(Code => { - Code.highlightWithin(container); + Code.highlightWithin(container); }); } } -} \ No newline at end of file +} diff --git a/resources/js/components/code-textarea.js b/resources/js/components/code-textarea.js index 0e49aec17..2f536da0b 100644 --- a/resources/js/components/code-textarea.js +++ b/resources/js/components/code-textarea.js @@ -2,14 +2,14 @@ * A simple component to render a code editor within the textarea * this exists upon. */ -import {Component} from "./component"; +import {Component} from './component'; export class CodeTextarea extends Component { async setup() { - const mode = this.$opts.mode; + const {mode} = this.$opts; const Code = await window.importVersioned('code'); Code.inlineEditor(this.$el, mode); } -} \ No newline at end of file +} diff --git a/resources/js/components/collapsible.js b/resources/js/components/collapsible.js index bb8ed477f..6f740ed71 100644 --- a/resources/js/components/collapsible.js +++ b/resources/js/components/collapsible.js @@ -1,5 +1,5 @@ -import {slideDown, slideUp} from "../services/animations"; -import {Component} from "./component"; +import {slideDown, slideUp} from '../services/animations'; +import {Component} from './component'; /** * Collapsible @@ -45,4 +45,4 @@ export class Collapsible extends Component { } } -} \ No newline at end of file +} diff --git a/resources/js/components/component.js b/resources/js/components/component.js index 292bbb624..654f41a96 100644 --- a/resources/js/components/component.js +++ b/resources/js/components/component.js @@ -51,8 +51,9 @@ export class Component { const componentName = this.$name; const event = new CustomEvent(`${componentName}-${eventName}`, { bubbles: true, - detail: data + detail: data, }); this.$el.dispatchEvent(event); } -} \ No newline at end of file + +} diff --git a/resources/js/components/confirm-dialog.js b/resources/js/components/confirm-dialog.js index 572945d5a..184618fcc 100644 --- a/resources/js/components/confirm-dialog.js +++ b/resources/js/components/confirm-dialog.js @@ -1,5 +1,5 @@ -import {onSelect} from "../services/dom"; -import {Component} from "./component"; +import {onSelect} from '../services/dom'; +import {Component} from './component'; /** * Custom equivalent of window.confirm() using our popup component. @@ -25,8 +25,8 @@ export class ConfirmDialog extends Component { this.sendResult(false); }); - return new Promise((res, rej) => { - this.res = res; + return new Promise(res => { + this.res = res; }); } @@ -42,9 +42,9 @@ export class ConfirmDialog extends Component { */ sendResult(result) { if (this.res) { - this.res(result) + this.res(result); this.res = null; } } -} \ No newline at end of file +} diff --git a/resources/js/components/custom-checkbox.js b/resources/js/components/custom-checkbox.js index 99804c4bc..a5f1d5664 100644 --- a/resources/js/components/custom-checkbox.js +++ b/resources/js/components/custom-checkbox.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class CustomCheckbox extends Component { @@ -30,4 +30,4 @@ export class CustomCheckbox extends Component { this.display.setAttribute('aria-checked', checked); } -} \ No newline at end of file +} diff --git a/resources/js/components/details-highlighter.js b/resources/js/components/details-highlighter.js index 6466fb584..71c202629 100644 --- a/resources/js/components/details-highlighter.js +++ b/resources/js/components/details-highlighter.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class DetailsHighlighter extends Component { @@ -19,4 +19,5 @@ export class DetailsHighlighter extends Component { } this.dealtWith = true; } -} \ No newline at end of file + +} diff --git a/resources/js/components/dropdown-search.js b/resources/js/components/dropdown-search.js index 30a2aadc1..2344619f5 100644 --- a/resources/js/components/dropdown-search.js +++ b/resources/js/components/dropdown-search.js @@ -1,6 +1,6 @@ -import {debounce} from "../services/util"; -import {transitionHeight} from "../services/animations"; -import {Component} from "./component"; +import {debounce} from '../services/util'; +import {transitionHeight} from '../services/animations'; +import {Component} from './component'; export class DropdownSearch extends Component { @@ -40,7 +40,7 @@ export class DropdownSearch extends Component { runLocalSearch(searchTerm) { const listItems = this.listContainerElem.querySelectorAll(this.localSearchSelector); - for (let listItem of listItems) { + for (const listItem of listItems) { const match = !searchTerm || listItem.textContent.toLowerCase().includes(searchTerm); listItem.style.display = match ? 'flex' : 'none'; listItem.classList.toggle('hidden', !match); @@ -79,4 +79,4 @@ export class DropdownSearch extends Component { this.loadingElem.style.display = show ? 'block' : 'none'; } -} \ No newline at end of file +} diff --git a/resources/js/components/dropdown.js b/resources/js/components/dropdown.js index ed69088b2..b68f332b6 100644 --- a/resources/js/components/dropdown.js +++ b/resources/js/components/dropdown.js @@ -1,6 +1,6 @@ -import {onSelect} from "../services/dom"; -import {KeyboardNavigationHandler} from "../services/keyboard-navigation"; -import {Component} from "./component"; +import {onSelect} from '../services/dom'; +import {KeyboardNavigationHandler} from '../services/keyboard-navigation'; +import {Component} from './component'; /** * Dropdown @@ -41,7 +41,11 @@ export class Dropdown extends Component { this.menu.style.position = 'fixed'; this.menu.style.width = `${menuOriginalRect.width}px`; this.menu.style.left = `${menuOriginalRect.left}px`; - heightOffset = dropUpwards ? (window.innerHeight - menuOriginalRect.top - toggleHeight / 2) : menuOriginalRect.top; + if (dropUpwards) { + heightOffset = (window.innerHeight - menuOriginalRect.top - toggleHeight / 2); + } else { + heightOffset = menuOriginalRect.top; + } } // Adjust menu to display upwards if near the bottom of the screen @@ -55,8 +59,8 @@ export class Dropdown extends Component { // Set listener to hide on mouse leave or window click this.menu.addEventListener('mouseleave', this.hide); - window.addEventListener('click', event => { - if (!this.menu.contains(event.target)) { + window.addEventListener('click', clickEvent => { + if (!this.menu.contains(clickEvent.target)) { this.hide(); } }); @@ -76,7 +80,7 @@ export class Dropdown extends Component { } hideAll() { - for (let dropdown of window.$components.get('dropdown')) { + for (const dropdown of window.$components.get('dropdown')) { dropdown.hide(); } } @@ -100,13 +104,13 @@ export class Dropdown extends Component { } setupListeners() { - const keyboardNavHandler = new KeyboardNavigationHandler(this.container, (event) => { + const keyboardNavHandler = new KeyboardNavigationHandler(this.container, event => { this.hide(); this.toggle.focus(); if (!this.bubbleEscapes) { event.stopPropagation(); } - }, (event) => { + }, event => { if (event.target.nodeName === 'INPUT') { event.preventDefault(); event.stopPropagation(); @@ -120,10 +124,10 @@ export class Dropdown extends Component { // Hide menu on option click this.container.addEventListener('click', event => { - const possibleChildren = Array.from(this.menu.querySelectorAll('a')); - if (possibleChildren.includes(event.target)) { - this.hide(); - } + const possibleChildren = Array.from(this.menu.querySelectorAll('a')); + if (possibleChildren.includes(event.target)) { + this.hide(); + } }); onSelect(this.toggle, event => { diff --git a/resources/js/components/dropzone.js b/resources/js/components/dropzone.js index 911a033c7..e7aae769e 100644 --- a/resources/js/components/dropzone.js +++ b/resources/js/components/dropzone.js @@ -1,8 +1,9 @@ -import DropZoneLib from "dropzone"; -import {fadeOut} from "../services/animations"; -import {Component} from "./component"; +import DropZoneLib from 'dropzone'; +import {fadeOut} from '../services/animations'; +import {Component} from './component'; export class Dropzone extends Component { + setup() { this.container = this.$el; this.url = this.$opts.url; @@ -12,7 +13,7 @@ export class Dropzone extends Component { this.uploadLimitMessage = this.$opts.uploadLimitMessage; this.timeoutMessage = this.$opts.timeoutMessage; - const _this = this; + const component = this; this.dz = new DropZoneLib(this.container, { addRemoveLinks: true, dictRemoveFile: this.removeMessage, @@ -22,22 +23,21 @@ export class Dropzone extends Component { withCredentials: true, init() { this.dz = this; - this.dz.on('sending', _this.onSending.bind(_this)); - this.dz.on('success', _this.onSuccess.bind(_this)); - this.dz.on('error', _this.onError.bind(_this)); - } + this.dz.on('sending', component.onSending.bind(component)); + this.dz.on('success', component.onSuccess.bind(component)); + this.dz.on('error', component.onError.bind(component)); + }, }); } onSending(file, xhr, data) { - const token = window.document.querySelector('meta[name=token]').getAttribute('content'); data.append('_token', token); - xhr.ontimeout = (e) => { + xhr.ontimeout = () => { this.dz.emit('complete', file); this.dz.emit('error', file, this.timeoutMessage); - } + }; } onSuccess(file, data) { @@ -55,10 +55,10 @@ export class Dropzone extends Component { onError(file, errorMessage, xhr) { this.$emit('error', {file, errorMessage, xhr}); - const setMessage = (message) => { + const setMessage = message => { const messsageEl = file.previewElement.querySelector('[data-dz-errormessage]'); messsageEl.textContent = message; - } + }; if (xhr && xhr.status === 413) { setMessage(this.uploadLimitMessage); @@ -70,4 +70,5 @@ export class Dropzone extends Component { removeAll() { this.dz.removeAllFiles(true); } -} \ No newline at end of file + +} diff --git a/resources/js/components/editor-toolbox.js b/resources/js/components/editor-toolbox.js index a581ae7b4..4d3c0ae75 100644 --- a/resources/js/components/editor-toolbox.js +++ b/resources/js/components/editor-toolbox.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class EditorToolbox extends Component { @@ -35,11 +35,10 @@ export class EditorToolbox extends Component { } setActiveTab(tabName, openToolbox = false) { - // Set button visibility for (const button of this.buttons) { button.classList.remove('active'); - const bName = button.dataset.tab; + const bName = button.dataset.tab; if (bName === tabName) button.classList.add('active'); } @@ -55,4 +54,4 @@ export class EditorToolbox extends Component { } } -} \ No newline at end of file +} diff --git a/resources/js/components/entity-permissions.js b/resources/js/components/entity-permissions.js index d4a616ff1..7ab99a2a7 100644 --- a/resources/js/components/entity-permissions.js +++ b/resources/js/components/entity-permissions.js @@ -1,5 +1,5 @@ -import {htmlToDom} from "../services/dom"; -import {Component} from "./component"; +import {htmlToDom} from '../services/dom'; +import {Component} from './component'; export class EntityPermissions extends Component { @@ -29,12 +29,12 @@ export class EntityPermissions extends Component { this.container.addEventListener('click', event => { const button = event.target.closest('button'); if (button && button.dataset.roleId) { - this.removeRowOnButtonClick(button) + this.removeRowOnButtonClick(button); } }); // Role select change - this.roleSelect.addEventListener('change', event => { + this.roleSelect.addEventListener('change', () => { const roleId = this.roleSelect.value; if (roleId) { this.addRoleRow(roleId); @@ -61,8 +61,8 @@ export class EntityPermissions extends Component { removeRowOnButtonClick(button) { const row = button.closest('.item-list-row'); - const roleId = button.dataset.roleId; - const roleName = button.dataset.roleName; + const {roleId} = button.dataset; + const {roleName} = button.dataset; const option = document.createElement('option'); option.value = roleId; @@ -72,4 +72,4 @@ export class EntityPermissions extends Component { row.remove(); } -} \ No newline at end of file +} diff --git a/resources/js/components/entity-search.js b/resources/js/components/entity-search.js index b0e42401d..7a5044470 100644 --- a/resources/js/components/entity-search.js +++ b/resources/js/components/entity-search.js @@ -1,7 +1,8 @@ -import {onSelect} from "../services/dom"; -import {Component} from "./component"; +import {onSelect} from '../services/dom'; +import {Component} from './component'; export class EntitySearch extends Component { + setup() { this.entityId = this.$opts.entityId; this.entityType = this.$opts.entityType; @@ -30,7 +31,8 @@ export class EntitySearch extends Component { runSearch() { const term = this.searchInput.value.trim(); if (term.length === 0) { - return this.clearSearch(); + this.clearSearch(); + return; } this.searchView.classList.remove('hidden'); @@ -51,4 +53,5 @@ export class EntitySearch extends Component { this.loadingBlock.classList.add('hidden'); this.searchInput.value = ''; } -} \ No newline at end of file + +} diff --git a/resources/js/components/entity-selector-popup.js b/resources/js/components/entity-selector-popup.js index d455f7ee7..e21e67fb3 100644 --- a/resources/js/components/entity-selector-popup.js +++ b/resources/js/components/entity-selector-popup.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class EntitySelectorPopup extends Component { @@ -57,4 +57,5 @@ export class EntitySelectorPopup extends Component { this.getSelector().reset(); if (this.callback && entity) this.callback(entity); } -} \ No newline at end of file + +} diff --git a/resources/js/components/entity-selector.js b/resources/js/components/entity-selector.js index 09d14b233..f12108fbb 100644 --- a/resources/js/components/entity-selector.js +++ b/resources/js/components/entity-selector.js @@ -1,5 +1,5 @@ -import {onChildEvent} from "../services/dom"; -import {Component} from "./component"; +import {onChildEvent} from '../services/dom'; +import {Component} from './component'; /** * Entity Selector @@ -29,7 +29,7 @@ export class EntitySelector extends Component { this.elem.addEventListener('click', this.onClick.bind(this)); let lastSearch = 0; - this.searchInput.addEventListener('input', event => { + this.searchInput.addEventListener('input', () => { lastSearch = Date.now(); this.showLoading(); setTimeout(() => { @@ -43,35 +43,35 @@ export class EntitySelector extends Component { }); // Keyboard navigation - onChildEvent(this.$el, '[data-entity-type]', 'keydown', (e, el) => { - if (e.ctrlKey && e.code === 'Enter') { + onChildEvent(this.$el, '[data-entity-type]', 'keydown', event => { + if (event.ctrlKey && event.code === 'Enter') { const form = this.$el.closest('form'); if (form) { form.submit(); - e.preventDefault(); + event.preventDefault(); return; } } - if (e.code === 'ArrowDown') { + if (event.code === 'ArrowDown') { this.focusAdjacent(true); } - if (e.code === 'ArrowUp') { + if (event.code === 'ArrowUp') { this.focusAdjacent(false); } }); - this.searchInput.addEventListener('keydown', e => { - if (e.code === 'ArrowDown') { + this.searchInput.addEventListener('keydown', event => { + if (event.code === 'ArrowDown') { this.focusAdjacent(true); } - }) + }); } focusAdjacent(forward = true) { const items = Array.from(this.resultsContainer.querySelectorAll('[data-entity-type]')); const selectedIndex = items.indexOf(document.activeElement); - const newItem = items[selectedIndex+ (forward ? 1 : -1)] || items[0]; + const newItem = items[selectedIndex + (forward ? 1 : -1)] || items[0]; if (newItem) { newItem.focus(); } @@ -101,7 +101,7 @@ export class EntitySelector extends Component { window.$http.get(this.searchUrl()).then(resp => { this.resultsContainer.innerHTML = resp.data; this.hideLoading(); - }) + }); } searchUrl() { @@ -144,13 +144,13 @@ export class EntitySelector extends Component { const link = item.getAttribute('href'); const name = item.querySelector('.entity-list-item-name').textContent; - const data = {id: Number(id), name: name, link: link}; + const data = {id: Number(id), name, link}; if (isSelected) { item.classList.add('selected'); this.selectedItemData = data; } else { - window.$events.emit('entity-select-change', null) + window.$events.emit('entity-select-change', null); } if (!isDblClick && !isSelected) return; @@ -159,7 +159,7 @@ export class EntitySelector extends Component { this.confirmSelection(data); } if (isSelected) { - window.$events.emit('entity-select-change', data) + window.$events.emit('entity-select-change', data); } } @@ -175,4 +175,4 @@ export class EntitySelector extends Component { this.selectedItemData = null; } -} \ No newline at end of file +} diff --git a/resources/js/components/event-emit-select.js b/resources/js/components/event-emit-select.js index 2e6fd5fdb..2097c0528 100644 --- a/resources/js/components/event-emit-select.js +++ b/resources/js/components/event-emit-select.js @@ -1,5 +1,5 @@ -import {onSelect} from "../services/dom"; -import {Component} from "./component"; +import {onSelect} from '../services/dom'; +import {Component} from './component'; /** * EventEmitSelect @@ -12,15 +12,15 @@ import {Component} from "./component"; * All options will be set as the "detail" of the event with * their values included. */ -export class EventEmitSelect extends Component{ +export class EventEmitSelect extends Component { + setup() { this.container = this.$el; this.name = this.$opts.name; - onSelect(this.$el, () => { this.$emit(this.name, this.$opts); }); } -} \ No newline at end of file +} diff --git a/resources/js/components/expand-toggle.js b/resources/js/components/expand-toggle.js index ab4d38ab1..0d2018b9d 100644 --- a/resources/js/components/expand-toggle.js +++ b/resources/js/components/expand-toggle.js @@ -1,9 +1,9 @@ -import {slideUp, slideDown} from "../services/animations"; -import {Component} from "./component"; +import {slideUp, slideDown} from '../services/animations'; +import {Component} from './component'; export class ExpandToggle extends Component { - setup(elem) { + setup() { this.targetSelector = this.$opts.targetSelector; this.isOpen = this.$opts.isOpen === 'true'; this.updateEndpoint = this.$opts.updateEndpoint; @@ -24,8 +24,9 @@ export class ExpandToggle extends Component { event.preventDefault(); const matchingElems = document.querySelectorAll(this.targetSelector); - for (let match of matchingElems) { - this.isOpen ? this.close(match) : this.open(match); + for (const match of matchingElems) { + const action = this.isOpen ? this.close : this.open; + action(match); } this.isOpen = !this.isOpen; @@ -34,8 +35,8 @@ export class ExpandToggle extends Component { updateSystemAjax(isOpen) { window.$http.patch(this.updateEndpoint, { - expand: isOpen ? 'true' : 'false' + expand: isOpen ? 'true' : 'false', }); } -} \ No newline at end of file +} diff --git a/resources/js/components/global-search.js b/resources/js/components/global-search.js index 7bc8a1d45..798bd7aac 100644 --- a/resources/js/components/global-search.js +++ b/resources/js/components/global-search.js @@ -1,7 +1,7 @@ -import {htmlToDom} from "../services/dom"; -import {debounce} from "../services/util"; -import {KeyboardNavigationHandler} from "../services/keyboard-navigation"; -import {Component} from "./component"; +import {htmlToDom} from '../services/dom'; +import {debounce} from '../services/util'; +import {KeyboardNavigationHandler} from '../services/keyboard-navigation'; +import {Component} from './component'; /** * Global (header) search box handling. @@ -25,12 +25,12 @@ export class GlobalSearch extends Component { // Handle search input changes this.input.addEventListener('input', () => { - const value = this.input.value; + const {value} = this.input; if (value.length > 0) { this.loadingWrap.style.display = 'block'; this.suggestionResultsWrap.style.opacity = '0.5'; updateSuggestionsDebounced(value); - } else { + } else { this.hideSuggestions(); } }); @@ -55,7 +55,7 @@ export class GlobalSearch extends Component { if (!this.input.value) { return; } - + const resultDom = htmlToDom(results); this.suggestionResultsWrap.innerHTML = ''; @@ -71,7 +71,7 @@ export class GlobalSearch extends Component { this.container.classList.add('search-active'); window.requestAnimationFrame(() => { this.suggestions.classList.add('search-suggestions-animation'); - }) + }); } hideSuggestions() { @@ -79,4 +79,5 @@ export class GlobalSearch extends Component { this.suggestions.classList.remove('search-suggestions-animation'); this.suggestionResultsWrap.innerHTML = ''; } -} \ No newline at end of file + +} diff --git a/resources/js/components/header-mobile-toggle.js b/resources/js/components/header-mobile-toggle.js index 11b23cca6..f94f897f6 100644 --- a/resources/js/components/header-mobile-toggle.js +++ b/resources/js/components/header-mobile-toggle.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class HeaderMobileToggle extends Component { @@ -19,10 +19,10 @@ export class HeaderMobileToggle extends Component { this.toggleButton.setAttribute('aria-expanded', this.open ? 'true' : 'false'); if (this.open) { this.elem.addEventListener('keydown', this.onKeyDown); - window.addEventListener('click', this.onWindowClick) + window.addEventListener('click', this.onWindowClick); } else { this.elem.removeEventListener('keydown', this.onKeyDown); - window.removeEventListener('click', this.onWindowClick) + window.removeEventListener('click', this.onWindowClick); } event.stopPropagation(); } @@ -37,4 +37,4 @@ export class HeaderMobileToggle extends Component { this.onToggle(event); } -} \ No newline at end of file +} diff --git a/resources/js/components/image-manager.js b/resources/js/components/image-manager.js index 418b7c98a..3cb99f0e2 100644 --- a/resources/js/components/image-manager.js +++ b/resources/js/components/image-manager.js @@ -1,5 +1,7 @@ -import {onChildEvent, onSelect, removeLoading, showLoading} from "../services/dom"; -import {Component} from "./component"; +import { + onChildEvent, onSelect, removeLoading, showLoading, +} from '../services/dom'; +import {Component} from './component'; export class ImageManager extends Component { @@ -48,20 +50,20 @@ export class ImageManager extends Component { event.preventDefault(); }); - onSelect(this.cancelSearch, event => { + onSelect(this.cancelSearch, () => { this.resetListView(); this.resetSearchView(); this.loadGallery(); this.cancelSearch.classList.remove('active'); }); - this.searchInput.addEventListener('input', event => { + this.searchInput.addEventListener('input', () => { this.cancelSearch.classList.toggle('active', this.searchInput.value.trim()); }); onChildEvent(this.listContainer, '.load-more', 'click', async event => { showLoading(event.target); - this.page++; + this.page += 1; await this.loadGallery(); event.target.remove(); }); @@ -69,7 +71,7 @@ export class ImageManager extends Component { this.listContainer.addEventListener('event-emit-select-image', this.onImageSelectEvent.bind(this)); this.listContainer.addEventListener('error', event => { - event.target.src = baseUrl('loading_error.png'); + event.target.src = window.baseUrl('loading_error.png'); }, true); onSelect(this.selectButton, () => { @@ -79,7 +81,7 @@ export class ImageManager extends Component { this.hide(); }); - onChildEvent(this.formContainer, '#image-manager-delete', 'click', event => { + onChildEvent(this.formContainer, '#image-manager-delete', 'click', () => { if (this.lastSelected) { this.loadImageEditForm(this.lastSelected.id, true); } @@ -210,4 +212,4 @@ export class ImageManager extends Component { window.$components.init(this.formContainer); } -} \ No newline at end of file +} diff --git a/resources/js/components/image-picker.js b/resources/js/components/image-picker.js index 03d9567d2..d25e01dd7 100644 --- a/resources/js/components/image-picker.js +++ b/resources/js/components/image-picker.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class ImagePicker extends Component { @@ -31,7 +31,7 @@ export class ImagePicker extends Component { this.removeInput.setAttribute('disabled', 'disabled'); } - for (let file of this.imageInput.files) { + for (const file of this.imageInput.files) { this.imageElem.src = window.URL.createObjectURL(file); } this.imageElem.classList.remove('none'); @@ -54,4 +54,4 @@ export class ImagePicker extends Component { this.resetInput.setAttribute('disabled', 'disabled'); } -} \ No newline at end of file +} diff --git a/resources/js/components/index.js b/resources/js/components/index.js index 82136184b..803714e62 100644 --- a/resources/js/components/index.js +++ b/resources/js/components/index.js @@ -1,59 +1,59 @@ -export {AddRemoveRows} from "./add-remove-rows.js" -export {AjaxDeleteRow} from "./ajax-delete-row.js" -export {AjaxForm} from "./ajax-form.js" -export {Attachments} from "./attachments.js" -export {AttachmentsList} from "./attachments-list.js" -export {AutoSuggest} from "./auto-suggest.js" -export {AutoSubmit} from "./auto-submit.js" -export {BackToTop} from "./back-to-top.js" -export {BookSort} from "./book-sort.js" -export {ChapterContents} from "./chapter-contents.js" -export {CodeEditor} from "./code-editor.js" -export {CodeHighlighter} from "./code-highlighter.js" -export {CodeTextarea} from "./code-textarea.js" -export {Collapsible} from "./collapsible.js" -export {ConfirmDialog} from "./confirm-dialog" -export {CustomCheckbox} from "./custom-checkbox.js" -export {DetailsHighlighter} from "./details-highlighter.js" -export {Dropdown} from "./dropdown.js" -export {DropdownSearch} from "./dropdown-search.js" -export {Dropzone} from "./dropzone.js" -export {EditorToolbox} from "./editor-toolbox.js" -export {EntityPermissions} from "./entity-permissions" -export {EntitySearch} from "./entity-search.js" -export {EntitySelector} from "./entity-selector.js" -export {EntitySelectorPopup} from "./entity-selector-popup.js" -export {EventEmitSelect} from "./event-emit-select.js" -export {ExpandToggle} from "./expand-toggle.js" -export {GlobalSearch} from "./global-search.js" -export {HeaderMobileToggle} from "./header-mobile-toggle.js" -export {ImageManager} from "./image-manager.js" -export {ImagePicker} from "./image-picker.js" -export {ListSortControl} from "./list-sort-control.js" -export {MarkdownEditor} from "./markdown-editor.js" -export {NewUserPassword} from "./new-user-password.js" -export {Notification} from "./notification.js" -export {OptionalInput} from "./optional-input.js" -export {PageComments} from "./page-comments.js" -export {PageDisplay} from "./page-display.js" -export {PageEditor} from "./page-editor.js" -export {PagePicker} from "./page-picker.js" -export {PermissionsTable} from "./permissions-table.js" -export {Pointer} from "./pointer.js" -export {Popup} from "./popup.js" -export {SettingAppColorScheme} from "./setting-app-color-scheme.js" -export {SettingColorPicker} from "./setting-color-picker.js" -export {SettingHomepageControl} from "./setting-homepage-control.js" -export {ShelfSort} from "./shelf-sort.js" -export {Shortcuts} from "./shortcuts" -export {ShortcutInput} from "./shortcut-input" -export {SortableList} from "./sortable-list.js" -export {SubmitOnChange} from "./submit-on-change.js" -export {Tabs} from "./tabs.js" -export {TagManager} from "./tag-manager.js" -export {TemplateManager} from "./template-manager.js" -export {ToggleSwitch} from "./toggle-switch.js" -export {TriLayout} from "./tri-layout.js" -export {UserSelect} from "./user-select.js" -export {WebhookEvents} from "./webhook-events" -export {WysiwygEditor} from "./wysiwyg-editor.js" +export {AddRemoveRows} from './add-remove-rows'; +export {AjaxDeleteRow} from './ajax-delete-row'; +export {AjaxForm} from './ajax-form'; +export {Attachments} from './attachments'; +export {AttachmentsList} from './attachments-list'; +export {AutoSuggest} from './auto-suggest'; +export {AutoSubmit} from './auto-submit'; +export {BackToTop} from './back-to-top'; +export {BookSort} from './book-sort'; +export {ChapterContents} from './chapter-contents'; +export {CodeEditor} from './code-editor'; +export {CodeHighlighter} from './code-highlighter'; +export {CodeTextarea} from './code-textarea'; +export {Collapsible} from './collapsible'; +export {ConfirmDialog} from './confirm-dialog'; +export {CustomCheckbox} from './custom-checkbox'; +export {DetailsHighlighter} from './details-highlighter'; +export {Dropdown} from './dropdown'; +export {DropdownSearch} from './dropdown-search'; +export {Dropzone} from './dropzone'; +export {EditorToolbox} from './editor-toolbox'; +export {EntityPermissions} from './entity-permissions'; +export {EntitySearch} from './entity-search'; +export {EntitySelector} from './entity-selector'; +export {EntitySelectorPopup} from './entity-selector-popup'; +export {EventEmitSelect} from './event-emit-select'; +export {ExpandToggle} from './expand-toggle'; +export {GlobalSearch} from './global-search'; +export {HeaderMobileToggle} from './header-mobile-toggle'; +export {ImageManager} from './image-manager'; +export {ImagePicker} from './image-picker'; +export {ListSortControl} from './list-sort-control'; +export {MarkdownEditor} from './markdown-editor'; +export {NewUserPassword} from './new-user-password'; +export {Notification} from './notification'; +export {OptionalInput} from './optional-input'; +export {PageComments} from './page-comments'; +export {PageDisplay} from './page-display'; +export {PageEditor} from './page-editor'; +export {PagePicker} from './page-picker'; +export {PermissionsTable} from './permissions-table'; +export {Pointer} from './pointer'; +export {Popup} from './popup'; +export {SettingAppColorScheme} from './setting-app-color-scheme'; +export {SettingColorPicker} from './setting-color-picker'; +export {SettingHomepageControl} from './setting-homepage-control'; +export {ShelfSort} from './shelf-sort'; +export {Shortcuts} from './shortcuts'; +export {ShortcutInput} from './shortcut-input'; +export {SortableList} from './sortable-list'; +export {SubmitOnChange} from './submit-on-change'; +export {Tabs} from './tabs'; +export {TagManager} from './tag-manager'; +export {TemplateManager} from './template-manager'; +export {ToggleSwitch} from './toggle-switch'; +export {TriLayout} from './tri-layout'; +export {UserSelect} from './user-select'; +export {WebhookEvents} from './webhook-events'; +export {WysiwygEditor} from './wysiwyg-editor'; diff --git a/resources/js/components/list-sort-control.js b/resources/js/components/list-sort-control.js index b8d4de73a..4b38fc1e5 100644 --- a/resources/js/components/list-sort-control.js +++ b/resources/js/components/list-sort-control.js @@ -2,7 +2,7 @@ * ListSortControl * Manages the logic for the control which provides list sorting options. */ -import {Component} from "./component"; +import {Component} from './component'; export class ListSortControl extends Component { @@ -45,4 +45,4 @@ export class ListSortControl extends Component { this.form.submit(); } -} \ No newline at end of file +} diff --git a/resources/js/components/markdown-editor.js b/resources/js/components/markdown-editor.js index 9d687c83c..fa06807a5 100644 --- a/resources/js/components/markdown-editor.js +++ b/resources/js/components/markdown-editor.js @@ -1,5 +1,5 @@ -import {Component} from "./component"; -import {init as initEditor} from "../markdown/editor"; +import {Component} from './component'; +import {init as initEditor} from '../markdown/editor'; export class MarkdownEditor extends Component { @@ -16,7 +16,7 @@ export class MarkdownEditor extends Component { this.divider = this.$refs.divider; this.displayWrap = this.$refs.displayWrap; - const settingContainer = this.$refs.settingContainer; + const {settingContainer} = this.$refs; const settingInputs = settingContainer.querySelectorAll('input[type="checkbox"]'); this.editor = null; @@ -49,10 +49,9 @@ export class MarkdownEditor extends Component { } setupListeners() { - // Button actions this.elem.addEventListener('click', event => { - let button = event.target.closest('button[data-action]'); + const button = event.target.closest('button[data-action]'); if (button === null) return; const action = button.getAttribute('data-action'); @@ -83,15 +82,15 @@ export class MarkdownEditor extends Component { } handleDividerDrag() { - this.divider.addEventListener('pointerdown', event => { + this.divider.addEventListener('pointerdown', () => { const wrapRect = this.elem.getBoundingClientRect(); - const moveListener = (event) => { + const moveListener = event => { const xRel = event.pageX - wrapRect.left; const xPct = Math.min(Math.max(20, Math.floor((xRel / wrapRect.width) * 100)), 80); - this.displayWrap.style.flexBasis = `${100-xPct}%`; + this.displayWrap.style.flexBasis = `${100 - xPct}%`; this.editor.settings.set('editorWidth', xPct); }; - const upListener = (event) => { + const upListener = () => { window.removeEventListener('pointermove', moveListener); window.removeEventListener('pointerup', upListener); this.display.style.pointerEvents = null; @@ -105,7 +104,7 @@ export class MarkdownEditor extends Component { }); const widthSetting = this.editor.settings.get('editorWidth'); if (widthSetting) { - this.displayWrap.style.flexBasis = `${100-widthSetting}%`; + this.displayWrap.style.flexBasis = `${100 - widthSetting}%`; } } diff --git a/resources/js/components/new-user-password.js b/resources/js/components/new-user-password.js index a4ed4d15b..e294f8e97 100644 --- a/resources/js/components/new-user-password.js +++ b/resources/js/components/new-user-password.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class NewUserPassword extends Component { @@ -23,4 +23,4 @@ export class NewUserPassword extends Component { this.inputContainer.style.display = inviting ? 'none' : 'block'; } -} \ No newline at end of file +} diff --git a/resources/js/components/notification.js b/resources/js/components/notification.js index 8a0876241..8a1ef922a 100644 --- a/resources/js/components/notification.js +++ b/resources/js/components/notification.js @@ -1,13 +1,13 @@ -import {Component} from "./component"; +import {Component} from './component'; -export class Notification extends Component { +export class Notification extends Component { setup() { this.container = this.$el; this.type = this.$opts.type; this.textElem = this.container.querySelector('span'); this.autoHide = this.$opts.autoHide === 'true'; - this.initialShow = this.$opts.show === 'true' + this.initialShow = this.$opts.show === 'true'; this.container.style.display = 'grid'; window.$events.listen(this.type, text => { @@ -47,4 +47,4 @@ export class Notification extends Component { this.container.removeEventListener('transitionend', this.hideCleanup); } -} \ No newline at end of file +} diff --git a/resources/js/components/optional-input.js b/resources/js/components/optional-input.js index cc429c991..64cee12cd 100644 --- a/resources/js/components/optional-input.js +++ b/resources/js/components/optional-input.js @@ -1,7 +1,8 @@ -import {onSelect} from "../services/dom"; -import {Component} from "./component"; +import {onSelect} from '../services/dom'; +import {Component} from './component'; export class OptionalInput extends Component { + setup() { this.removeButton = this.$refs.remove; this.showButton = this.$refs.show; @@ -24,4 +25,4 @@ export class OptionalInput extends Component { }); } -} \ No newline at end of file +} diff --git a/resources/js/components/page-comments.js b/resources/js/components/page-comments.js index 726531e95..0ac9d0572 100644 --- a/resources/js/components/page-comments.js +++ b/resources/js/components/page-comments.js @@ -1,6 +1,6 @@ -import {scrollAndHighlightElement} from "../services/util"; -import {Component} from "./component"; -import {htmlToDom} from "../services/dom"; +import {scrollAndHighlightElement} from '../services/util'; +import {Component} from './component'; +import {htmlToDom} from '../services/dom'; export class PageComments extends Component { @@ -36,11 +36,11 @@ export class PageComments extends Component { } handleAction(event) { - let actionElem = event.target.closest('[action]'); + const actionElem = event.target.closest('[action]'); if (event.target.matches('a[href^="#"]')) { const id = event.target.href.split('#')[1]; - scrollAndHighlightElement(document.querySelector('#' + id)); + scrollAndHighlightElement(document.querySelector(`#${id}`)); } if (actionElem === null) return; @@ -68,24 +68,24 @@ export class PageComments extends Component { if (this.editingComment) this.closeUpdateForm(); commentElem.querySelector('[comment-content]').style.display = 'none'; commentElem.querySelector('[comment-edit-container]').style.display = 'block'; - let textArea = commentElem.querySelector('[comment-edit-container] textarea'); - let lineCount = textArea.value.split('\n').length; - textArea.style.height = ((lineCount * 20) + 40) + 'px'; + const textArea = commentElem.querySelector('[comment-edit-container] textarea'); + const lineCount = textArea.value.split('\n').length; + textArea.style.height = `${(lineCount * 20) + 40}px`; this.editingComment = commentElem; } updateComment(event) { - let form = event.target; + const form = event.target; event.preventDefault(); - let text = form.querySelector('textarea').value; - let reqData = { - text: text, + const text = form.querySelector('textarea').value; + const reqData = { + text, parent_id: this.parentId || null, }; this.showLoading(form); - let commentId = this.editingComment.getAttribute('comment'); + const commentId = this.editingComment.getAttribute('comment'); window.$http.put(`/comment/${commentId}`, reqData).then(resp => { - let newComment = document.createElement('div'); + const newComment = document.createElement('div'); newComment.innerHTML = resp.data; this.editingComment.innerHTML = newComment.children[0].innerHTML; window.$events.success(this.updatedText); @@ -98,9 +98,9 @@ export class PageComments extends Component { } deleteComment(commentElem) { - let id = commentElem.getAttribute('comment'); + const id = commentElem.getAttribute('comment'); this.showLoading(commentElem.querySelector('[comment-content]')); - window.$http.delete(`/comment/${id}`).then(resp => { + window.$http.delete(`/comment/${id}`).then(() => { commentElem.parentNode.removeChild(commentElem); window.$events.success(this.deletedText); this.updateCount(); @@ -111,9 +111,9 @@ export class PageComments extends Component { saveComment(event) { event.preventDefault(); event.stopPropagation(); - let text = this.formInput.value; - let reqData = { - text: text, + const text = this.formInput.value; + const reqData = { + text, parent_id: this.parentId || null, }; this.showLoading(this.form); @@ -131,7 +131,7 @@ export class PageComments extends Component { } updateCount() { - let count = this.container.children.length; + const count = this.container.children.length; this.elem.querySelector('[comments-title]').textContent = window.trans_plural(this.countText, count, {count}); } @@ -148,14 +148,14 @@ export class PageComments extends Component { this.formContainer.parentNode.style.display = 'block'; this.addButtonContainer.style.display = 'none'; this.formInput.focus(); - this.formInput.scrollIntoView({behavior: "smooth"}); + this.formInput.scrollIntoView({behavior: 'smooth'}); } hideForm() { this.formContainer.style.display = 'none'; this.formContainer.parentNode.style.display = 'none'; if (this.getCommentCount() > 0) { - this.elem.appendChild(this.addButtonContainer) + this.elem.appendChild(this.addButtonContainer); } else { this.commentCountBar.appendChild(this.addButtonContainer); } @@ -182,7 +182,7 @@ export class PageComments extends Component { showLoading(formElem) { const groups = formElem.querySelectorAll('.form-group'); - for (let group of groups) { + for (const group of groups) { group.style.display = 'none'; } formElem.querySelector('.form-group.loading').style.display = 'block'; @@ -190,10 +190,10 @@ export class PageComments extends Component { hideLoading(formElem) { const groups = formElem.querySelectorAll('.form-group'); - for (let group of groups) { + for (const group of groups) { group.style.display = 'block'; } formElem.querySelector('.form-group.loading').style.display = 'none'; } -} \ No newline at end of file +} diff --git a/resources/js/components/page-display.js b/resources/js/components/page-display.js index c06c3310d..eb7df5fb6 100644 --- a/resources/js/components/page-display.js +++ b/resources/js/components/page-display.js @@ -1,6 +1,33 @@ -import * as DOM from "../services/dom"; -import {scrollAndHighlightElement} from "../services/util"; -import {Component} from "./component"; +import * as DOM from '../services/dom'; +import {scrollAndHighlightElement} from '../services/util'; +import {Component} from './component'; + +function toggleAnchorHighlighting(elementId, shouldHighlight) { + DOM.forEach(`a[href="#${elementId}"]`, anchor => { + anchor.closest('li').classList.toggle('current-heading', shouldHighlight); + }); +} + +function headingVisibilityChange(entries) { + for (const entry of entries) { + const isVisible = (entry.intersectionRatio === 1); + toggleAnchorHighlighting(entry.target.id, isVisible); + } +} + +function addNavObserver(headings) { + // Setup the intersection observer. + const intersectOpts = { + rootMargin: '0px 0px 0px 0px', + threshold: 1.0, + }; + const pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts); + + // observe each heading + for (const heading of headings) { + pageNavObserver.observe(heading); + } +} export class PageDisplay extends Component { @@ -26,7 +53,7 @@ export class PageDisplay extends Component { window.$components.first('tri-layout').showContent(); const contentId = child.getAttribute('href').substr(1); this.goToText(contentId); - window.history.pushState(null, null, '#' + contentId); + window.history.pushState(null, null, `#${contentId}`); }); } } @@ -58,33 +85,6 @@ export class PageDisplay extends Component { if (headings.length > 0 && pageNav !== null) { addNavObserver(headings); } - - function addNavObserver(headings) { - // Setup the intersection observer. - const intersectOpts = { - rootMargin: '0px 0px 0px 0px', - threshold: 1.0 - }; - const pageNavObserver = new IntersectionObserver(headingVisibilityChange, intersectOpts); - - // observe each heading - for (const heading of headings) { - pageNavObserver.observe(heading); - } - } - - function headingVisibilityChange(entries, observer) { - for (const entry of entries) { - const isVisible = (entry.intersectionRatio === 1); - toggleAnchorHighlighting(entry.target.id, isVisible); - } - } - - function toggleAnchorHighlighting(elementId, shouldHighlight) { - DOM.forEach('a[href="#' + elementId + '"]', anchor => { - anchor.closest('li').classList.toggle('current-heading', shouldHighlight); - }); - } } setupDetailsCodeBlockRefresh() { @@ -96,4 +96,5 @@ export class PageDisplay extends Component { const details = [...this.container.querySelectorAll('details')]; details.forEach(detail => detail.addEventListener('toggle', onToggle)); } -} \ No newline at end of file + +} diff --git a/resources/js/components/page-editor.js b/resources/js/components/page-editor.js index e2b92ff68..e7f4c0ba9 100644 --- a/resources/js/components/page-editor.js +++ b/resources/js/components/page-editor.js @@ -1,9 +1,10 @@ -import * as Dates from "../services/dates"; -import {onSelect} from "../services/dom"; -import {debounce} from "../services/util"; -import {Component} from "./component"; +import * as Dates from '../services/dates'; +import {onSelect} from '../services/dom'; +import {debounce} from '../services/util'; +import {Component} from './component'; export class PageEditor extends Component { + setup() { // Options this.draftsEnabled = this.$opts.draftsEnabled === 'true'; @@ -58,7 +59,9 @@ export class PageEditor extends Component { window.$events.listen('editor-save-page', this.savePage.bind(this)); // Listen to content changes from the editor - const onContentChange = () => this.autoSave.pendingChange = true; + const onContentChange = () => { + this.autoSave.pendingChange = true; + }; window.$events.listen('editor-html-change', onContentChange); window.$events.listen('editor-markdown-change', onContentChange); @@ -79,7 +82,8 @@ export class PageEditor extends Component { setInitialFocus() { if (this.hasDefaultTitle) { - return this.titleElem.select(); + this.titleElem.select(); + return; } window.setTimeout(() => { @@ -93,12 +97,12 @@ export class PageEditor extends Component { runAutoSave() { // Stop if manually saved recently to prevent bombarding the server - const savedRecently = (Date.now() - this.autoSave.last < (this.autoSave.frequency)/2); + const savedRecently = (Date.now() - this.autoSave.last < (this.autoSave.frequency) / 2); if (savedRecently || !this.autoSave.pendingChange) { return; } - this.saveDraft() + this.saveDraft(); } savePage() { @@ -132,7 +136,9 @@ export class PageEditor extends Component { try { const saveKey = `draft-save-fail-${(new Date()).toISOString()}`; window.localStorage.setItem(saveKey, JSON.stringify(data)); - } catch (err) {} + } catch (lsErr) { + console.error(lsErr); + } window.$events.emit('error', this.autosaveFailText); } @@ -153,7 +159,8 @@ export class PageEditor extends Component { try { response = await window.$http.get(`/ajax/page/${this.pageId}`); } catch (e) { - return console.error(e); + console.error(e); + return; } if (this.autoSave.interval) { @@ -172,7 +179,6 @@ export class PageEditor extends Component { this.startAutoSave(); }, 1000); window.$events.emit('success', this.draftDiscardedText); - } updateChangelogDisplay() { @@ -180,7 +186,7 @@ export class PageEditor extends Component { if (summary.length === 0) { summary = this.setChangelogText; } else if (summary.length > 16) { - summary = summary.slice(0, 16) + '...'; + summary = `${summary.slice(0, 16)}...`; } this.changelogDisplay.innerText = summary; } @@ -193,7 +199,7 @@ export class PageEditor extends Component { event.preventDefault(); const link = event.target.closest('a').href; - /** @var {ConfirmDialog} **/ + /** @var {ConfirmDialog} * */ const dialog = window.$components.firstOnElement(this.switchDialogContainer, 'confirm-dialog'); const [saved, confirmed] = await Promise.all([this.saveDraft(), dialog.show()]); diff --git a/resources/js/components/page-picker.js b/resources/js/components/page-picker.js index fba0a0a43..130972fdd 100644 --- a/resources/js/components/page-picker.js +++ b/resources/js/components/page-picker.js @@ -1,4 +1,8 @@ -import {Component} from "./component"; +import {Component} from './component'; + +function toggleElem(elem, show) { + elem.style.display = show ? null : 'none'; +} export class PagePicker extends Component { @@ -18,13 +22,13 @@ export class PagePicker extends Component { this.selectButton.addEventListener('click', this.showPopup.bind(this)); this.display.parentElement.addEventListener('click', this.showPopup.bind(this)); - this.resetButton.addEventListener('click', event => { + this.resetButton.addEventListener('click', () => { this.setValue('', ''); }); } showPopup() { - /** @type {EntitySelectorPopup} **/ + /** @type {EntitySelectorPopup} * */ const selectorPopup = window.$components.first('entity-selector-popup'); selectorPopup.show(entity => { this.setValue(entity.id, entity.name); @@ -44,7 +48,7 @@ export class PagePicker extends Component { toggleElem(this.defaultDisplay, !hasValue); toggleElem(this.display, hasValue); if (hasValue) { - let id = this.getAssetIdFromVal(); + const id = this.getAssetIdFromVal(); this.display.textContent = `#${id}, ${name}`; this.display.href = window.baseUrl(`/link/${id}`); } @@ -55,7 +59,3 @@ export class PagePicker extends Component { } } - -function toggleElem(elem, show) { - elem.style.display = show ? null : 'none'; -} \ No newline at end of file diff --git a/resources/js/components/permissions-table.js b/resources/js/components/permissions-table.js index 58ead1d57..800403c61 100644 --- a/resources/js/components/permissions-table.js +++ b/resources/js/components/permissions-table.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class PermissionsTable extends Component { @@ -41,7 +41,7 @@ export class PermissionsTable extends Component { const tableRows = this.container.querySelectorAll(this.rowSelector); const inputsToToggle = []; - for (let row of tableRows) { + for (const row of tableRows) { const targetCell = row.children[colIndex]; if (targetCell) { inputsToToggle.push(...targetCell.querySelectorAll('input[type=checkbox]')); @@ -57,10 +57,10 @@ export class PermissionsTable extends Component { toggleAllInputs(inputsToToggle) { const currentState = inputsToToggle.length > 0 ? inputsToToggle[0].checked : false; - for (let checkbox of inputsToToggle) { + for (const checkbox of inputsToToggle) { checkbox.checked = !currentState; checkbox.dispatchEvent(new Event('change')); } } -} \ No newline at end of file +} diff --git a/resources/js/components/pointer.js b/resources/js/components/pointer.js index a60525cb4..e2e2ceca7 100644 --- a/resources/js/components/pointer.js +++ b/resources/js/components/pointer.js @@ -1,7 +1,6 @@ -import * as DOM from "../services/dom"; -import {Component} from "./component"; -import {copyTextToClipboard} from "../services/clipboard"; - +import * as DOM from '../services/dom'; +import {Component} from './component'; +import {copyTextToClipboard} from '../services/clipboard'; export class Pointer extends Component { @@ -22,7 +21,7 @@ export class Pointer extends Component { setupListeners() { // Copy on copy button click - this.button.addEventListener('click', event => { + this.button.addEventListener('click', () => { copyTextToClipboard(this.input.value); }); @@ -47,7 +46,7 @@ export class Pointer extends Component { }); // Hide pointer when clicking away - DOM.onEvents(document.body, ['click', 'focus'], event => { + DOM.onEvents(document.body, ['click', 'focus'], () => { if (!this.showing || this.isSelection) return; this.hidePointer(); }); @@ -113,7 +112,7 @@ export class Pointer extends Component { updateForTarget(element) { let inputText = this.pointerModeLink ? window.baseUrl(`/link/${this.pageId}#${this.pointerSectionId}`) : `{{@${this.pageId}#${this.pointerSectionId}}}`; if (this.pointerModeLink && !inputText.startsWith('http')) { - inputText = window.location.protocol + "//" + window.location.host + inputText; + inputText = `${window.location.protocol}//${window.location.host}${inputText}`; } this.input.value = inputText; @@ -121,7 +120,7 @@ export class Pointer extends Component { // Update anchor if present const editAnchor = this.container.querySelector('#pointer-edit'); if (editAnchor && element) { - const editHref = editAnchor.dataset.editHref; + const {editHref} = editAnchor.dataset; const elementId = element.id; // get the first 50 characters. @@ -129,4 +128,5 @@ export class Pointer extends Component { editAnchor.href = `${editHref}?content-id=${elementId}&content-text=${encodeURIComponent(queryContent)}`; } } -} \ No newline at end of file + +} diff --git a/resources/js/components/popup.js b/resources/js/components/popup.js index 4c20876f8..662736548 100644 --- a/resources/js/components/popup.js +++ b/resources/js/components/popup.js @@ -1,6 +1,6 @@ -import {fadeIn, fadeOut} from "../services/animations"; -import {onSelect} from "../services/dom"; -import {Component} from "./component"; +import {fadeIn, fadeOut} from '../services/animations'; +import {onSelect} from '../services/dom'; +import {Component} from './component'; /** * Popup window that will contain other content. @@ -26,11 +26,11 @@ export class Popup extends Component { this.container.addEventListener('click', event => { if (event.target === this.container && lastMouseDownTarget === this.container) { - return this.hide(); + this.hide(); } }); - onSelect(this.hideButtons, e => this.hide()); + onSelect(this.hideButtons, () => this.hide()); } hide(onComplete = null) { @@ -47,7 +47,7 @@ export class Popup extends Component { show(onComplete = null, onHide = null) { fadeIn(this.container, 120, onComplete); - this.onkeyup = (event) => { + this.onkeyup = event => { if (event.key === 'Escape') { this.hide(); } @@ -56,4 +56,4 @@ export class Popup extends Component { this.onHide = onHide; } -} \ No newline at end of file +} diff --git a/resources/js/components/setting-app-color-scheme.js b/resources/js/components/setting-app-color-scheme.js index 71b14badc..add2d0cda 100644 --- a/resources/js/components/setting-app-color-scheme.js +++ b/resources/js/components/setting-app-color-scheme.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class SettingAppColorScheme extends Component { @@ -14,7 +14,7 @@ export class SettingAppColorScheme extends Component { this.handleModeChange(newMode); }); - const onInputChange = (event) => { + const onInputChange = event => { this.updateAppColorsFromInputs(); if (event.target.name.startsWith('setting-app-color')) { @@ -44,7 +44,7 @@ export class SettingAppColorScheme extends Component { cssId = 'primary'; } - const varName = '--color-' + cssId; + const varName = `--color-${cssId}`; document.body.style.setProperty(varName, input.value); } } @@ -57,9 +57,8 @@ export class SettingAppColorScheme extends Component { const lightName = input.name.replace('-color', '-color-light'); const hexVal = input.value; const rgb = this.hexToRgb(hexVal); - const rgbLightVal = 'rgba('+ [rgb.r, rgb.g, rgb.b, '0.15'].join(',') +')'; + const rgbLightVal = `rgba(${[rgb.r, rgb.g, rgb.b, '0.15'].join(',')})`; - console.log(input.name, lightName, hexVal, rgbLightVal) const lightColorInput = this.container.querySelector(`input[name="${lightName}"][type="hidden"]`); lightColorInput.value = rgbLightVal; } @@ -75,7 +74,7 @@ export class SettingAppColorScheme extends Component { return { r: result ? parseInt(result[1], 16) : 0, g: result ? parseInt(result[2], 16) : 0, - b: result ? parseInt(result[3], 16) : 0 + b: result ? parseInt(result[3], 16) : 0, }; } diff --git a/resources/js/components/setting-color-picker.js b/resources/js/components/setting-color-picker.js index bfb2c93ce..bc47b96c0 100644 --- a/resources/js/components/setting-color-picker.js +++ b/resources/js/components/setting-color-picker.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class SettingColorPicker extends Component { @@ -17,4 +17,5 @@ export class SettingColorPicker extends Component { this.colorInput.value = value; this.colorInput.dispatchEvent(new Event('change', {bubbles: true})); } -} \ No newline at end of file + +} diff --git a/resources/js/components/setting-homepage-control.js b/resources/js/components/setting-homepage-control.js index 992be9f82..6563ced0d 100644 --- a/resources/js/components/setting-homepage-control.js +++ b/resources/js/components/setting-homepage-control.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class SettingHomepageControl extends Component { @@ -14,4 +14,5 @@ export class SettingHomepageControl extends Component { const showPagePicker = this.typeControl.value === 'page'; this.pagePickerContainer.style.display = (showPagePicker ? 'block' : 'none'); } -} \ No newline at end of file + +} diff --git a/resources/js/components/shelf-sort.js b/resources/js/components/shelf-sort.js index e4aefc591..01ca11a33 100644 --- a/resources/js/components/shelf-sort.js +++ b/resources/js/components/shelf-sort.js @@ -1,17 +1,17 @@ -import Sortable from "sortablejs"; -import {Component} from "./component"; +import Sortable from 'sortablejs'; +import {Component} from './component'; /** * @type {Object} */ const itemActions = { - move_up(item, shelfBooksList, allBooksList) { + move_up(item) { const list = item.parentNode; const index = Array.from(list.children).indexOf(item); const newIndex = Math.max(index - 1, 0); list.insertBefore(item, list.children[newIndex] || null); }, - move_down(item, shelfBooksList, allBooksList) { + move_down(item) { const list = item.parentNode; const index = Array.from(list.children).indexOf(item); const newIndex = Math.min(index + 2, list.children.length); @@ -20,7 +20,7 @@ const itemActions = { remove(item, shelfBooksList, allBooksList) { allBooksList.appendChild(item); }, - add(item, shelfBooksList, allBooksList) { + add(item, shelfBooksList) { shelfBooksList.appendChild(item); }, }; @@ -62,11 +62,11 @@ export class ShelfSort extends Component { } }); - this.bookSearchInput.addEventListener('input', event => { + this.bookSearchInput.addEventListener('input', () => { this.filterBooksByName(this.bookSearchInput.value); }); - this.sortButtonContainer.addEventListener('click' , event => { + this.sortButtonContainer.addEventListener('click', event => { const button = event.target.closest('button[data-sort]'); if (button) { this.sortShelfBooks(button.dataset.sort); @@ -78,11 +78,10 @@ export class ShelfSort extends Component { * @param {String} filterVal */ filterBooksByName(filterVal) { - // Set height on first search, if not already set, to prevent the distraction // of the list height jumping around if (!this.allBookList.style.height) { - this.allBookList.style.height = this.allBookList.getBoundingClientRect().height + 'px'; + this.allBookList.style.height = `${this.allBookList.getBoundingClientRect().height}px`; } const books = this.allBookList.children; @@ -100,7 +99,7 @@ export class ShelfSort extends Component { */ sortItemActionClick(sortItemAction) { const sortItem = sortItemAction.closest('.scroll-box-item'); - const action = sortItemAction.dataset.action; + const {action} = sortItemAction.dataset; const actionFunction = itemActions[action]; actionFunction(sortItem, this.shelfBookList, this.allBookList); @@ -122,10 +121,10 @@ export class ShelfSort extends Component { const bProp = bookB.dataset[sortProperty].toLowerCase(); if (reverse) { - return aProp < bProp ? (aProp === bProp ? 0 : 1) : -1; + return bProp.localeCompare(aProp); } - return aProp < bProp ? (aProp === bProp ? 0 : -1) : 1; + return aProp.localeCompare(bProp); }); for (const book of books) { @@ -136,4 +135,4 @@ export class ShelfSort extends Component { this.onChange(); } -} \ No newline at end of file +} diff --git a/resources/js/components/shortcut-input.js b/resources/js/components/shortcut-input.js index 2a2aaa225..17e05fc8d 100644 --- a/resources/js/components/shortcut-input.js +++ b/resources/js/components/shortcut-input.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; /** * Keys to ignore when recording shortcuts. @@ -18,16 +18,16 @@ export class ShortcutInput extends Component { this.listenerRecordKey = this.listenerRecordKey.bind(this); this.input.addEventListener('focus', () => { - this.startListeningForInput(); + this.startListeningForInput(); }); this.input.addEventListener('blur', () => { this.stopListeningForInput(); - }) + }); } startListeningForInput() { - this.input.addEventListener('keydown', this.listenerRecordKey) + this.input.addEventListener('keydown', this.listenerRecordKey); } /** @@ -51,4 +51,4 @@ export class ShortcutInput extends Component { this.input.removeEventListener('keydown', this.listenerRecordKey); } -} \ No newline at end of file +} diff --git a/resources/js/components/shortcuts.js b/resources/js/components/shortcuts.js index a87213b2e..8e927e34c 100644 --- a/resources/js/components/shortcuts.js +++ b/resources/js/components/shortcuts.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; function reverseMap(map) { const reversed = {}; @@ -8,7 +8,6 @@ function reverseMap(map) { return reversed; } - export class Shortcuts extends Component { setup() { @@ -25,7 +24,6 @@ export class Shortcuts extends Component { setupListeners() { window.addEventListener('keydown', event => { - if (event.target.closest('input, select, textarea')) { return; } @@ -35,7 +33,8 @@ export class Shortcuts extends Component { window.addEventListener('keydown', event => { if (event.key === '?') { - this.hintsShowing ? this.hideHints() : this.showHints(); + const action = this.hintsShowing ? this.hideHints : this.showHints; + action(); } }); } @@ -44,7 +43,6 @@ export class Shortcuts extends Component { * @param {KeyboardEvent} event */ handleShortcutPress(event) { - const keys = [ event.ctrlKey ? 'Ctrl' : '', event.metaKey ? 'Cmd' : '', @@ -90,7 +88,7 @@ export class Shortcuts extends Component { return true; } - console.error(`Shortcut attempted to be ran for element type that does not have handling setup`, el); + console.error('Shortcut attempted to be ran for element type that does not have handling setup', el); return false; } @@ -135,10 +133,10 @@ export class Shortcuts extends Component { const linkage = document.createElement('div'); linkage.classList.add('shortcut-linkage'); - linkage.style.left = targetBounds.x + 'px'; - linkage.style.top = targetBounds.y + 'px'; - linkage.style.width = targetBounds.width + 'px'; - linkage.style.height = targetBounds.height + 'px'; + linkage.style.left = `${targetBounds.x}px`; + linkage.style.top = `${targetBounds.y}px`; + linkage.style.width = `${targetBounds.width}px`; + linkage.style.height = `${targetBounds.height}px`; wrapper.append(label, linkage); @@ -159,4 +157,5 @@ export class Shortcuts extends Component { this.hintsShowing = false; } -} \ No newline at end of file + +} diff --git a/resources/js/components/sortable-list.js b/resources/js/components/sortable-list.js index bbbd92088..7b0c4f243 100644 --- a/resources/js/components/sortable-list.js +++ b/resources/js/components/sortable-list.js @@ -1,5 +1,5 @@ -import Sortable from "sortablejs"; -import {Component} from "./component"; +import Sortable from 'sortablejs'; +import {Component} from './component'; /** * SortableList @@ -9,6 +9,7 @@ import {Component} from "./component"; * the data to set on the data-transfer. */ export class SortableList extends Component { + setup() { this.container = this.$el; this.handleSelector = this.$opts.handleSelector; @@ -33,4 +34,5 @@ export class SortableList extends Component { dragoverBubble: false, }); } -} \ No newline at end of file + +} diff --git a/resources/js/components/submit-on-change.js b/resources/js/components/submit-on-change.js index da4ac6996..52faa1d10 100644 --- a/resources/js/components/submit-on-change.js +++ b/resources/js/components/submit-on-change.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; /** * Submit on change @@ -9,8 +9,7 @@ export class SubmitOnChange extends Component { setup() { this.filter = this.$opts.filter; - this.$el.addEventListener('change', (event) => { - + this.$el.addEventListener('change', event => { if (this.filter && !event.target.matches(this.filter)) { return; } @@ -22,4 +21,4 @@ export class SubmitOnChange extends Component { }); } -} \ No newline at end of file +} diff --git a/resources/js/components/tabs.js b/resources/js/components/tabs.js index 8d22e3e9b..560dc6273 100644 --- a/resources/js/components/tabs.js +++ b/resources/js/components/tabs.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; /** * Tabs @@ -46,4 +46,4 @@ export class Tabs extends Component { this.$emit('change', {showing: sectionId}); } -} \ No newline at end of file +} diff --git a/resources/js/components/tag-manager.js b/resources/js/components/tag-manager.js index 24d340553..1172e7775 100644 --- a/resources/js/components/tag-manager.js +++ b/resources/js/components/tag-manager.js @@ -1,6 +1,7 @@ -import {Component} from "./component"; +import {Component} from './component'; export class TagManager extends Component { + setup() { this.addRemoveComponentEl = this.$refs.addRemove; this.container = this.$el; @@ -11,8 +12,7 @@ export class TagManager extends Component { setupListeners() { this.container.addEventListener('input', event => { - - /** @var {AddRemoveRows} **/ + /** @var {AddRemoveRows} * */ const addRemoveComponent = window.$components.firstOnElement(this.addRemoveComponentEl, 'add-remove-rows'); if (!this.hasEmptyRows() && event.target.value) { addRemoveComponent.add(); @@ -22,9 +22,8 @@ export class TagManager extends Component { hasEmptyRows() { const rows = this.container.querySelectorAll(this.rowSelector); - const firstEmpty = [...rows].find(row => { - return [...row.querySelectorAll('input')].filter(input => input.value).length === 0; - }); + const firstEmpty = [...rows].find(row => [...row.querySelectorAll('input')].filter(input => input.value).length === 0); return firstEmpty !== undefined; } -} \ No newline at end of file + +} diff --git a/resources/js/components/template-manager.js b/resources/js/components/template-manager.js index 774336471..56ec876d4 100644 --- a/resources/js/components/template-manager.js +++ b/resources/js/components/template-manager.js @@ -1,5 +1,5 @@ -import * as DOM from "../services/dom"; -import {Component} from "./component"; +import * as DOM from '../services/dom'; +import {Component} from './component'; export class TemplateManager extends Component { @@ -36,10 +36,10 @@ export class TemplateManager extends Component { }); // Search submit button press - this.searchButton.addEventListener('click', event => this.performSearch()); + this.searchButton.addEventListener('click', () => this.performSearch()); // Search cancel button press - this.searchCancel.addEventListener('click', event => { + this.searchCancel.addEventListener('click', () => { this.searchInput.value = ''; this.performSearch(); }); @@ -66,7 +66,7 @@ export class TemplateManager extends Component { async insertTemplate(templateId, action = 'replace') { const resp = await window.$http.get(`/templates/${templateId}`); - const eventName = 'editor::' + action; + const eventName = `editor::${action}`; window.$events.emit(eventName, resp.data); } @@ -79,10 +79,11 @@ export class TemplateManager extends Component { async performSearch() { const searchTerm = this.searchInput.value; - const resp = await window.$http.get(`/templates`, { - search: searchTerm + const resp = await window.$http.get('/templates', { + search: searchTerm, }); this.searchCancel.style.display = searchTerm ? 'block' : 'none'; this.list.innerHTML = resp.data; } -} \ No newline at end of file + +} diff --git a/resources/js/components/toggle-switch.js b/resources/js/components/toggle-switch.js index b749eb541..e8209eb27 100644 --- a/resources/js/components/toggle-switch.js +++ b/resources/js/components/toggle-switch.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class ToggleSwitch extends Component { @@ -18,4 +18,4 @@ export class ToggleSwitch extends Component { this.input.dispatchEvent(changeEvent); } -} \ No newline at end of file +} diff --git a/resources/js/components/tri-layout.js b/resources/js/components/tri-layout.js index ead2ac3d0..8ccefb06c 100644 --- a/resources/js/components/tri-layout.js +++ b/resources/js/components/tri-layout.js @@ -1,4 +1,4 @@ -import {Component} from "./component"; +import {Component} from './component'; export class TriLayout extends Component { @@ -9,8 +9,8 @@ export class TriLayout extends Component { this.lastLayoutType = 'none'; this.onDestroy = null; this.scrollCache = { - 'content': 0, - 'info': 0, + content: 0, + info: 0, }; this.lastTabShown = 'content'; @@ -19,15 +19,15 @@ export class TriLayout extends Component { // Watch layout changes this.updateLayout(); - window.addEventListener('resize', event => { + window.addEventListener('resize', () => { this.updateLayout(); }, {passive: true}); } updateLayout() { let newLayout = 'tablet'; - if (window.innerWidth <= 1000) newLayout = 'mobile'; - if (window.innerWidth >= 1400) newLayout = 'desktop'; + if (window.innerWidth <= 1000) newLayout = 'mobile'; + if (window.innerWidth >= 1400) newLayout = 'desktop'; if (newLayout === this.lastLayoutType) return; if (this.onDestroy) { @@ -53,20 +53,19 @@ export class TriLayout extends Component { for (const tab of this.tabs) { tab.removeEventListener('click', this.mobileTabClick); } - } + }; } setupDesktop() { // } - /** * Action to run when the mobile info toggle bar is clicked/tapped * @param event */ mobileTabClick(event) { - const tab = event.target.dataset.tab; + const {tab} = event.target.dataset; this.showTab(tab); } @@ -109,4 +108,4 @@ export class TriLayout extends Component { this.lastTabShown = tabName; } -} \ No newline at end of file +} diff --git a/resources/js/components/user-select.js b/resources/js/components/user-select.js index d4d88a633..e6adc3c23 100644 --- a/resources/js/components/user-select.js +++ b/resources/js/components/user-select.js @@ -1,5 +1,5 @@ -import {onChildEvent} from "../services/dom"; -import {Component} from "./component"; +import {onChildEvent} from '../services/dom'; +import {Component} from './component'; export class UserSelect extends Component { @@ -20,9 +20,9 @@ export class UserSelect extends Component { } hide() { - /** @var {Dropdown} **/ + /** @var {Dropdown} * */ const dropdown = window.$components.firstOnElement(this.container, 'dropdown'); dropdown.hide(); } -} \ No newline at end of file +} diff --git a/resources/js/components/webhook-events.js b/resources/js/components/webhook-events.js index ad8c59ac2..68661972d 100644 --- a/resources/js/components/webhook-events.js +++ b/resources/js/components/webhook-events.js @@ -2,7 +2,7 @@ * Webhook Events * Manages dynamic selection control in the webhook form interface. */ -import {Component} from "./component"; +import {Component} from './component'; export class WebhookEvents extends Component { @@ -27,4 +27,4 @@ export class WebhookEvents extends Component { } } -} \ No newline at end of file +} diff --git a/resources/js/components/wysiwyg-editor.js b/resources/js/components/wysiwyg-editor.js index 96731a0d9..21db207e6 100644 --- a/resources/js/components/wysiwyg-editor.js +++ b/resources/js/components/wysiwyg-editor.js @@ -1,5 +1,5 @@ -import {build as buildEditorConfig} from "../wysiwyg/config"; -import {Component} from "./component"; +import {build as buildEditorConfig} from '../wysiwyg/config'; +import {Component} from './component'; export class WysiwygEditor extends Component { @@ -45,8 +45,8 @@ export class WysiwygEditor extends Component { */ getContent() { return { - html: this.editor.getContent() + html: this.editor.getContent(), }; } -} \ No newline at end of file +} diff --git a/resources/js/markdown/actions.js b/resources/js/markdown/actions.js index a73cddd30..514bff87d 100644 --- a/resources/js/markdown/actions.js +++ b/resources/js/markdown/actions.js @@ -1,6 +1,7 @@ -import DrawIO from "../services/drawio"; +import * as DrawIO from '../services/drawio'; export class Actions { + /** * @param {MarkdownEditor} editor */ @@ -29,13 +30,13 @@ export class Actions { } showImageInsert() { - /** @type {ImageManager} **/ + /** @type {ImageManager} * */ const imageManager = window.$components.first('image-manager'); imageManager.show(image => { const imageUrl = image.thumbs.display || image.url; const selectedText = this.#getSelectionText(); - const newText = "[![" + (selectedText || image.name) + "](" + imageUrl + ")](" + image.url + ")"; + const newText = `[![${selectedText || image.name}](${imageUrl})](${image.url})`; this.#replaceSelection(newText, newText.length); }, 'gallery'); } @@ -49,12 +50,12 @@ export class Actions { const selectedText = this.#getSelectionText(); const newText = `[${selectedText}]()`; const cursorPosDiff = (selectedText === '') ? -3 : -1; - this.#replaceSelection(newText, newText.length+cursorPosDiff); + this.#replaceSelection(newText, newText.length + cursorPosDiff); } showImageManager() { const selectionRange = this.#getSelectionRange(); - /** @type {ImageManager} **/ + /** @type {ImageManager} * */ const imageManager = window.$components.first('image-manager'); imageManager.show(image => { this.#insertDrawing(image, selectionRange); @@ -65,7 +66,7 @@ export class Actions { showLinkSelector() { const selectionRange = this.#getSelectionRange(); - /** @type {EntitySelectorPopup} **/ + /** @type {EntitySelectorPopup} * */ const selector = window.$components.first('entity-selector-popup'); selector.show(entity => { const selectedText = this.#getSelectionText(selectionRange) || entity.name; @@ -81,16 +82,13 @@ export class Actions { const selectionRange = this.#getSelectionRange(); - DrawIO.show(url,() => { - return Promise.resolve(''); - }, (pngData) => { - + DrawIO.show(url, () => Promise.resolve(''), pngData => { const data = { image: pngData, uploaded_to: Number(this.editor.config.pageId), }; - window.$http.post("/images/drawio", data).then(resp => { + window.$http.post('/images/drawio', data).then(resp => { this.#insertDrawing(resp.data, selectionRange); DrawIO.close(); }).catch(err => { @@ -106,7 +104,7 @@ export class Actions { // Show draw.io if enabled and handle save. editDrawing(imgContainer) { - const drawioUrl = this.editor.config.drawioUrl; + const {drawioUrl} = this.editor.config; if (!drawioUrl) { return; } @@ -114,16 +112,13 @@ export class Actions { const selectionRange = this.#getSelectionRange(); const drawingId = imgContainer.getAttribute('drawio-diagram'); - DrawIO.show(drawioUrl, () => { - return DrawIO.load(drawingId); - }, (pngData) => { - + DrawIO.show(drawioUrl, () => DrawIO.load(drawingId), pngData => { const data = { image: pngData, uploaded_to: Number(this.editor.config.pageId), }; - window.$http.post("/images/drawio", data).then(resp => { + window.$http.post('/images/drawio', data).then(resp => { const newText = `
    `; const newContent = this.#getText().split('\n').map(line => { if (line.indexOf(`drawio-diagram="${drawingId}"`) !== -1) { @@ -145,12 +140,12 @@ export class Actions { } else { window.$events.emit('error', this.editor.config.text.imageUploadError); } - console.log(error); + console.error(error); } // Make the editor full screen fullScreen() { - const container = this.editor.config.container; + const {container} = this.editor.config; const alreadyFullscreen = container.classList.contains('fullscreen'); container.classList.toggle('fullscreen', !alreadyFullscreen); document.body.classList.toggle('markdown-fullscreen', !alreadyFullscreen); @@ -170,7 +165,7 @@ export class Actions { scrollToLine = lineCount; break; } - lineCount++; + lineCount += 1; } if (scrollToLine === -1) { @@ -204,7 +199,7 @@ export class Actions { content = this.#cleanTextForEditor(content); const selectionRange = this.#getSelectionRange(); const selectFrom = selectionRange.from + content.length + 1; - this.#dispatchChange(0, 0, content + '\n', selectFrom); + this.#dispatchChange(0, 0, `${content}\n`, selectFrom); this.focus(); } @@ -214,7 +209,7 @@ export class Actions { */ appendContent(content) { content = this.#cleanTextForEditor(content); - this.#dispatchChange(this.editor.cm.state.doc.length, '\n' + content); + this.#dispatchChange(this.editor.cm.state.doc.length, `\n${content}`); this.focus(); } @@ -223,7 +218,7 @@ export class Actions { * @param {String} content */ replaceContent(content) { - this.#setText(content) + this.#setText(content); } /** @@ -250,7 +245,7 @@ export class Actions { if (alreadySymbol) { newLineContent = lineContent.replace(lineStart, newStart).trim(); } else if (newStart !== '') { - newLineContent = newStart + ' ' + lineContent; + newLineContent = `${newStart} ${lineContent}`; } const selectFrom = selectionRange.from + (newLineContent.length - lineContent.length); @@ -263,22 +258,31 @@ export class Actions { * @param {String} end */ wrapSelection(start, end) { - const selectionRange = this.#getSelectionRange(); - const selectionText = this.#getSelectionText(selectionRange); - if (!selectionText) return this.#wrapLine(start, end); + const selectRange = this.#getSelectionRange(); + const selectionText = this.#getSelectionText(selectRange); + if (!selectionText) { + this.#wrapLine(start, end); + return; + } let newSelectionText = selectionText; let newRange; if (selectionText.startsWith(start) && selectionText.endsWith(end)) { newSelectionText = selectionText.slice(start.length, selectionText.length - end.length); - newRange = selectionRange.extend(selectionRange.from, selectionRange.to - (start.length + end.length)); + newRange = selectRange.extend(selectRange.from, selectRange.to - (start.length + end.length)); } else { newSelectionText = `${start}${selectionText}${end}`; - newRange = selectionRange.extend(selectionRange.from, selectionRange.to + (start.length + end.length)); + newRange = selectRange.extend(selectRange.from, selectRange.to + (start.length + end.length)); } - this.#dispatchChange(selectionRange.from, selectionRange.to, newSelectionText, newRange.anchor, newRange.head); + this.#dispatchChange( + selectRange.from, + selectRange.to, + newSelectionText, + newRange.anchor, + newRange.head, + ); } replaceLineStartForOrderedList() { @@ -290,7 +294,7 @@ export class Actions { const number = (Number(listMatch[2]) || 0) + 1; const whiteSpace = listMatch[1] || ''; - const listMark = listMatch[3] || '.' + const listMark = listMatch[3] || '.'; const prefix = `${whiteSpace}${number}${listMark}`; return this.replaceLineStart(prefix); @@ -319,7 +323,13 @@ export class Actions { const newFormat = formats[newFormatIndex]; const newContent = line.text.replace(matches[0], matches[0].replace(format, newFormat)); const lineDiff = newContent.length - line.text.length; - this.#dispatchChange(line.from, line.to, newContent, selectionRange.anchor + lineDiff, selectionRange.head + lineDiff); + this.#dispatchChange( + line.from, + line.to, + newContent, + selectionRange.anchor + lineDiff, + selectionRange.head + lineDiff, + ); } } @@ -373,7 +383,7 @@ export class Actions { * @param {File} file * @param {?Number} position */ - async uploadImage(file, position= null) { + async uploadImage(file, position = null) { if (file === null || file.type.indexOf('image') !== 0) return; let ext = 'png'; @@ -382,17 +392,17 @@ export class Actions { } if (file.name) { - let fileNameMatches = file.name.match(/\.(.+)$/); + const fileNameMatches = file.name.match(/\.(.+)$/); if (fileNameMatches.length > 1) ext = fileNameMatches[1]; } // Insert image into markdown - const id = "image-" + Math.random().toString(16).slice(2); + const id = `image-${Math.random().toString(16).slice(2)}`; const placeholderImage = window.baseUrl(`/loading.gif#upload${id}`); const placeHolderText = `![](${placeholderImage})`; this.#dispatchChange(position, position, placeHolderText, position); - const remoteFilename = "image-" + Date.now() + "." + ext; + const remoteFilename = `image-${Date.now()}.${ext}`; const formData = new FormData(); formData.append('file', file, remoteFilename); formData.append('uploaded_to', this.editor.config.pageId); @@ -404,7 +414,7 @@ export class Actions { } catch (err) { window.$events.emit('error', this.editor.config.text.imageUploadError); this.#findAndReplaceContent(placeHolderText, ''); - console.log(err); + console.error(err); } } @@ -437,7 +447,8 @@ export class Actions { */ #replaceSelection(newContent, cursorOffset = 0, selectionRange = null) { selectionRange = selectionRange || this.editor.cm.state.selection.main; - this.#dispatchChange(selectionRange.from, selectionRange.to, newContent, selectionRange.from + cursorOffset); + const selectFrom = selectionRange.from + cursorOffset; + this.#dispatchChange(selectionRange.from, selectionRange.to, newContent, selectFrom); this.focus(); } @@ -466,7 +477,7 @@ export class Actions { * @return {String} */ #cleanTextForEditor(text) { - return text.replace(/\r\n|\r/g, "\n"); + return text.replace(/\r\n|\r/g, '\n'); } /** @@ -511,10 +522,13 @@ export class Actions { * @param {?Number} selectTo */ #dispatchChange(from, to = null, text = null, selectFrom = null, selectTo = null) { - const tr = {changes: {from, to: to, insert: text}}; + const tr = {changes: {from, to, insert: text}}; if (selectFrom) { tr.selection = {anchor: selectFrom}; + if (selectTo) { + tr.selection.head = selectTo; + } } this.editor.cm.dispatch(tr); @@ -533,4 +547,5 @@ export class Actions { scrollIntoView, }); } -} \ No newline at end of file + +} diff --git a/resources/js/markdown/codemirror.js b/resources/js/markdown/codemirror.js index 55ea485e3..9d54c19d7 100644 --- a/resources/js/markdown/codemirror.js +++ b/resources/js/markdown/codemirror.js @@ -1,6 +1,6 @@ -import {provideKeyBindings} from "./shortcuts"; -import {debounce} from "../services/util"; -import Clipboard from "../services/clipboard"; +import {provideKeyBindings} from './shortcuts'; +import {debounce} from '../services/util'; +import {Clipboard} from '../services/clipboard'; /** * Initiate the codemirror instance for the markdown editor. @@ -21,13 +21,15 @@ export async function init(editor) { const onScrollDebounced = debounce(editor.actions.syncDisplayPosition.bind(editor.actions), 100, false); let syncActive = editor.settings.get('scrollSync'); - editor.settings.onChange('scrollSync', val => syncActive = val); + editor.settings.onChange('scrollSync', val => { + syncActive = val; + }); const domEventHandlers = { // Handle scroll to sync display view - scroll: (event) => syncActive && onScrollDebounced(event), + scroll: event => syncActive && onScrollDebounced(event), // Handle image & content drag n drop - drop: (event) => { + drop: event => { const templateId = event.dataTransfer.getData('bookstack/template'); if (templateId) { event.preventDefault(); @@ -43,7 +45,7 @@ export async function init(editor) { } }, // Handle image paste - paste: (event) => { + paste: event => { const clipboard = new Clipboard(event.clipboardData || event.dataTransfer); // Don't handle the event ourselves if no items exist of contains table-looking data @@ -55,8 +57,8 @@ export async function init(editor) { for (const image of images) { editor.actions.uploadImage(image); } - } - } + }, + }; const cm = Code.markdownEditor( editor.config.inputEl, @@ -70,4 +72,4 @@ export async function init(editor) { window.mdEditorView = cm; return cm; -} \ No newline at end of file +} diff --git a/resources/js/markdown/common-events.js b/resources/js/markdown/common-events.js index 3afd03dd5..c3d803f70 100644 --- a/resources/js/markdown/common-events.js +++ b/resources/js/markdown/common-events.js @@ -6,23 +6,22 @@ function getContentToInsert({html, markdown}) { * @param {MarkdownEditor} editor */ export function listen(editor) { - - window.$events.listen('editor::replace', (eventContent) => { + window.$events.listen('editor::replace', eventContent => { const markdown = getContentToInsert(eventContent); editor.actions.replaceContent(markdown); }); - window.$events.listen('editor::append', (eventContent) => { + window.$events.listen('editor::append', eventContent => { const markdown = getContentToInsert(eventContent); editor.actions.appendContent(markdown); }); - window.$events.listen('editor::prepend', (eventContent) => { + window.$events.listen('editor::prepend', eventContent => { const markdown = getContentToInsert(eventContent); editor.actions.prependContent(markdown); }); - window.$events.listen('editor::insert', (eventContent) => { + window.$events.listen('editor::insert', eventContent => { const markdown = getContentToInsert(eventContent); editor.actions.insertContent(markdown); }); @@ -30,4 +29,4 @@ export function listen(editor) { window.$events.listen('editor::focus', () => { editor.actions.focus(); }); -} \ No newline at end of file +} diff --git a/resources/js/markdown/display.js b/resources/js/markdown/display.js index 2c78da189..487df1cab 100644 --- a/resources/js/markdown/display.js +++ b/resources/js/markdown/display.js @@ -1,4 +1,4 @@ -import {patchDomFromHtmlString} from "../services/vdom"; +import {patchDomFromHtmlString} from '../services/vdom'; export class Display { @@ -81,7 +81,7 @@ export class Display { * @param {String} html */ patchWithHtml(html) { - const body = this.doc.body; + const {body} = this.doc; if (body.children.length === 0) { const wrap = document.createElement('div'); @@ -102,8 +102,8 @@ export class Display { const elems = this.doc.body?.children[0]?.children; if (elems && elems.length <= index) return; - const topElem = (index === -1) ? elems[elems.length-1] : elems[index]; - topElem.scrollIntoView({ block: 'start', inline: 'nearest', behavior: 'smooth'}); + const topElem = (index === -1) ? elems[elems.length - 1] : elems[index]; + topElem.scrollIntoView({block: 'start', inline: 'nearest', behavior: 'smooth'}); } -} \ No newline at end of file +} diff --git a/resources/js/markdown/editor.js b/resources/js/markdown/editor.js index cb5bf7d1a..46c35c850 100644 --- a/resources/js/markdown/editor.js +++ b/resources/js/markdown/editor.js @@ -1,10 +1,9 @@ -import {Markdown} from "./markdown"; -import {Display} from "./display"; -import {Actions} from "./actions"; -import {Settings} from "./settings"; -import {listen} from "./common-events"; -import {init as initCodemirror} from "./codemirror"; - +import {Markdown} from './markdown'; +import {Display} from './display'; +import {Actions} from './actions'; +import {Settings} from './settings'; +import {listen} from './common-events'; +import {init as initCodemirror} from './codemirror'; /** * Initiate a new markdown editor instance. @@ -12,7 +11,6 @@ import {init as initCodemirror} from "./codemirror"; * @returns {Promise} */ export async function init(config) { - /** * @type {MarkdownEditor} */ @@ -31,7 +29,6 @@ export async function init(config) { return editor; } - /** * @typedef MarkdownEditorConfig * @property {String} pageId @@ -51,4 +48,4 @@ export async function init(config) { * @property {Actions} actions * @property {EditorView} cm * @property {Settings} settings - */ \ No newline at end of file + */ diff --git a/resources/js/markdown/markdown.js b/resources/js/markdown/markdown.js index ef3a02872..e63184acc 100644 --- a/resources/js/markdown/markdown.js +++ b/resources/js/markdown/markdown.js @@ -1,4 +1,4 @@ -import MarkdownIt from "markdown-it"; +import MarkdownIt from 'markdown-it'; import mdTasksLists from 'markdown-it-task-lists'; export class Markdown { @@ -24,7 +24,5 @@ export class Markdown { render(markdown) { return this.renderer.render(markdown); } + } - - - diff --git a/resources/js/markdown/settings.js b/resources/js/markdown/settings.js index 62aab82e9..b843aaa8a 100644 --- a/resources/js/markdown/settings.js +++ b/resources/js/markdown/settings.js @@ -21,7 +21,7 @@ export class Settings { listenToInputChanges(inputs) { for (const input of inputs) { - input.addEventListener('change', event => { + input.addEventListener('change', () => { const name = input.getAttribute('name').replace('md-', ''); this.set(name, input.checked); }); @@ -59,4 +59,5 @@ export class Settings { listeners.push(callback); this.changeListeners[key] = listeners; } -} \ No newline at end of file + +} diff --git a/resources/js/markdown/shortcuts.js b/resources/js/markdown/shortcuts.js index c4a86e544..543e6dcdd 100644 --- a/resources/js/markdown/shortcuts.js +++ b/resources/js/markdown/shortcuts.js @@ -7,35 +7,35 @@ function provide(editor) { const shortcuts = {}; // Insert Image shortcut - shortcuts['Shift-Mod-i'] = cm => editor.actions.insertImage(); + shortcuts['Shift-Mod-i'] = () => editor.actions.insertImage(); // Save draft - shortcuts['Mod-s'] = cm => window.$events.emit('editor-save-draft'); + shortcuts['Mod-s'] = () => window.$events.emit('editor-save-draft'); // Save page - shortcuts['Mod-Enter'] = cm => window.$events.emit('editor-save-page'); + shortcuts['Mod-Enter'] = () => window.$events.emit('editor-save-page'); // Show link selector - shortcuts['Shift-Mod-k'] = cm => editor.actions.showLinkSelector(); + shortcuts['Shift-Mod-k'] = () => editor.actions.showLinkSelector(); // Insert Link - shortcuts['Mod-k'] = cm => editor.actions.insertLink(); + shortcuts['Mod-k'] = () => editor.actions.insertLink(); // FormatShortcuts - shortcuts['Mod-1'] = cm => editor.actions.replaceLineStart('##'); - shortcuts['Mod-2'] = cm => editor.actions.replaceLineStart('###'); - shortcuts['Mod-3'] = cm => editor.actions.replaceLineStart('####'); - shortcuts['Mod-4'] = cm => editor.actions.replaceLineStart('#####'); - shortcuts['Mod-5'] = cm => editor.actions.replaceLineStart(''); - shortcuts['Mod-d'] = cm => editor.actions.replaceLineStart(''); - shortcuts['Mod-6'] = cm => editor.actions.replaceLineStart('>'); - shortcuts['Mod-q'] = cm => editor.actions.replaceLineStart('>'); - shortcuts['Mod-7'] = cm => editor.actions.wrapSelection('\n```\n', '\n```'); - shortcuts['Mod-8'] = cm => editor.actions.wrapSelection('`', '`'); - shortcuts['Shift-Mod-e'] = cm => editor.actions.wrapSelection('`', '`'); - shortcuts['Mod-9'] = cm => editor.actions.cycleCalloutTypeAtSelection(); - shortcuts['Mod-p'] = cm => editor.actions.replaceLineStart('-') - shortcuts['Mod-o'] = cm => editor.actions.replaceLineStartForOrderedList() + shortcuts['Mod-1'] = () => editor.actions.replaceLineStart('##'); + shortcuts['Mod-2'] = () => editor.actions.replaceLineStart('###'); + shortcuts['Mod-3'] = () => editor.actions.replaceLineStart('####'); + shortcuts['Mod-4'] = () => editor.actions.replaceLineStart('#####'); + shortcuts['Mod-5'] = () => editor.actions.replaceLineStart(''); + shortcuts['Mod-d'] = () => editor.actions.replaceLineStart(''); + shortcuts['Mod-6'] = () => editor.actions.replaceLineStart('>'); + shortcuts['Mod-q'] = () => editor.actions.replaceLineStart('>'); + shortcuts['Mod-7'] = () => editor.actions.wrapSelection('\n```\n', '\n```'); + shortcuts['Mod-8'] = () => editor.actions.wrapSelection('`', '`'); + shortcuts['Shift-Mod-e'] = () => editor.actions.wrapSelection('`', '`'); + shortcuts['Mod-9'] = () => editor.actions.cycleCalloutTypeAtSelection(); + shortcuts['Mod-p'] = () => editor.actions.replaceLineStart('-'); + shortcuts['Mod-o'] = () => editor.actions.replaceLineStartForOrderedList(); return shortcuts; } @@ -46,14 +46,12 @@ function provide(editor) { * @return {{key: String, run: function, preventDefault: boolean}[]} */ export function provideKeyBindings(editor) { - const shortcuts= provide(editor); + const shortcuts = provide(editor); const keyBindings = []; - const wrapAction = (action) => { - return () => { - action(); - return true; - }; + const wrapAction = action => () => { + action(); + return true; }; for (const [shortcut, action] of Object.entries(shortcuts)) { @@ -61,4 +59,4 @@ export function provideKeyBindings(editor) { } return keyBindings; -} \ No newline at end of file +} diff --git a/resources/js/services/animations.js b/resources/js/services/animations.js index 12b8077cf..bc983c807 100644 --- a/resources/js/services/animations.js +++ b/resources/js/services/animations.js @@ -5,6 +5,53 @@ */ const animateStylesCleanupMap = new WeakMap(); +/** + * Animate the css styles of an element using FLIP animation techniques. + * Styles must be an object where the keys are style properties, camelcase, and the values + * are an array of two items in the format [initialValue, finalValue] + * @param {Element} element + * @param {Object} styles + * @param {Number} animTime + * @param {Function} onComplete + */ +function animateStyles(element, styles, animTime = 400, onComplete = null) { + const styleNames = Object.keys(styles); + for (const style of styleNames) { + element.style[style] = styles[style][0]; + } + + const cleanup = () => { + for (const style of styleNames) { + element.style[style] = null; + } + element.style.transition = null; + element.removeEventListener('transitionend', cleanup); + animateStylesCleanupMap.delete(element); + if (onComplete) onComplete(); + }; + + setTimeout(() => { + element.style.transition = `all ease-in-out ${animTime}ms`; + for (const style of styleNames) { + element.style[style] = styles[style][1]; + } + + element.addEventListener('transitionend', cleanup); + animateStylesCleanupMap.set(element, cleanup); + }, 15); +} + +/** + * Run the active cleanup action for the given element. + * @param {Element} element + */ +function cleanupExistingElementAnimation(element) { + if (animateStylesCleanupMap.has(element)) { + const oldCleanup = animateStylesCleanupMap.get(element); + oldCleanup(); + } +} + /** * Fade in the given element. * @param {Element} element @@ -15,7 +62,7 @@ export function fadeIn(element, animTime = 400, onComplete = null) { cleanupExistingElementAnimation(element); element.style.display = 'block'; animateStyles(element, { - opacity: ['0', '1'] + opacity: ['0', '1'], }, animTime, () => { if (onComplete) onComplete(); }); @@ -30,7 +77,7 @@ export function fadeIn(element, animTime = 400, onComplete = null) { export function fadeOut(element, animTime = 400, onComplete = null) { cleanupExistingElementAnimation(element); animateStyles(element, { - opacity: ['1', '0'] + opacity: ['1', '0'], }, animTime, () => { element.style.display = 'none'; if (onComplete) onComplete(); @@ -113,50 +160,3 @@ export function transitionHeight(element, animTime = 400) { animateStyles(element, animStyles, animTime); }; } - -/** - * Animate the css styles of an element using FLIP animation techniques. - * Styles must be an object where the keys are style properties, camelcase, and the values - * are an array of two items in the format [initialValue, finalValue] - * @param {Element} element - * @param {Object} styles - * @param {Number} animTime - * @param {Function} onComplete - */ -function animateStyles(element, styles, animTime = 400, onComplete = null) { - const styleNames = Object.keys(styles); - for (let style of styleNames) { - element.style[style] = styles[style][0]; - } - - const cleanup = () => { - for (let style of styleNames) { - element.style[style] = null; - } - element.style.transition = null; - element.removeEventListener('transitionend', cleanup); - animateStylesCleanupMap.delete(element); - if (onComplete) onComplete(); - }; - - setTimeout(() => { - element.style.transition = `all ease-in-out ${animTime}ms`; - for (let style of styleNames) { - element.style[style] = styles[style][1]; - } - - element.addEventListener('transitionend', cleanup); - animateStylesCleanupMap.set(element, cleanup); - }, 15); -} - -/** - * Run the active cleanup action for the given element. - * @param {Element} element - */ -function cleanupExistingElementAnimation(element) { - if (animateStylesCleanupMap.has(element)) { - const oldCleanup = animateStylesCleanupMap.get(element); - oldCleanup(); - } -} \ No newline at end of file diff --git a/resources/js/services/clipboard.js b/resources/js/services/clipboard.js index c0b0fbfab..02db29be0 100644 --- a/resources/js/services/clipboard.js +++ b/resources/js/services/clipboard.js @@ -1,4 +1,3 @@ - export class Clipboard { /** @@ -21,7 +20,7 @@ export class Clipboard { * @return {boolean} */ containsTabularData() { - const rtfData = this.data.getData( 'text/rtf'); + const rtfData = this.data.getData('text/rtf'); return rtfData && rtfData.includes('\\trowd'); } @@ -30,8 +29,8 @@ export class Clipboard { * @return {Array} */ getImages() { - const types = this.data.types; - const files = this.data.files; + const {types} = this.data; + const {files} = this.data; const images = []; for (const type of types) { @@ -49,6 +48,7 @@ export class Clipboard { return images; } + } export async function copyTextToClipboard(text) { @@ -58,13 +58,11 @@ export async function copyTextToClipboard(text) { } // Backup option where we can't use the navigator.clipboard API - const tempInput = document.createElement("textarea"); - tempInput.style = "position: absolute; left: -1000px; top: -1000px;"; + const tempInput = document.createElement('textarea'); + tempInput.style = 'position: absolute; left: -1000px; top: -1000px;'; tempInput.value = text; document.body.appendChild(tempInput); tempInput.select(); - document.execCommand("copy"); + document.execCommand('copy'); document.body.removeChild(tempInput); } - -export default Clipboard; \ No newline at end of file diff --git a/resources/js/services/components.js b/resources/js/services/components.js index d1503db4d..beb0ce92f 100644 --- a/resources/js/services/components.js +++ b/resources/js/services/components.js @@ -1,4 +1,4 @@ -import {kebabToCamel, camelToKebab} from "./text"; +import {kebabToCamel, camelToKebab} from './text'; /** * A mapping of active components keyed by name, with values being arrays of component @@ -19,44 +19,6 @@ const componentModelMap = {}; */ const elementComponentMap = new WeakMap(); -/** - * Initialize a component instance on the given dom element. - * @param {String} name - * @param {Element} element - */ -function initComponent(name, element) { - /** @type {Function|undefined} **/ - const componentModel = componentModelMap[name]; - if (componentModel === undefined) return; - - // Create our component instance - /** @type {Component} **/ - let instance; - try { - instance = new componentModel(); - instance.$name = name; - instance.$el = element; - const allRefs = parseRefs(name, element); - instance.$refs = allRefs.refs; - instance.$manyRefs = allRefs.manyRefs; - instance.$opts = parseOpts(name, element); - instance.setup(); - } catch (e) { - console.error('Failed to create component', e, name, element); - } - - // Add to global listing - if (typeof components[name] === "undefined") { - components[name] = []; - } - components[name].push(instance); - - // Add to element mapping - const elComponents = elementComponentMap.get(element) || {}; - elComponents[name] = instance; - elementComponentMap.set(element, elComponents); -} - /** * Parse out the element references within the given element * for the given component name. @@ -67,7 +29,7 @@ function parseRefs(name, element) { const refs = {}; const manyRefs = {}; - const prefix = `${name}@` + const prefix = `${name}@`; const selector = `[refs*="${prefix}"]`; const refElems = [...element.querySelectorAll(selector)]; if (element.matches(selector)) { @@ -93,13 +55,13 @@ function parseRefs(name, element) { /** * Parse out the element component options. - * @param {String} name + * @param {String} componentName * @param {Element} element * @return {Object} */ -function parseOpts(name, element) { +function parseOpts(componentName, element) { const opts = {}; - const prefix = `option:${name}:`; + const prefix = `option:${componentName}:`; for (const {name, value} of element.attributes) { if (name.startsWith(prefix)) { const optName = name.replace(prefix, ''); @@ -109,12 +71,50 @@ function parseOpts(name, element) { return opts; } +/** + * Initialize a component instance on the given dom element. + * @param {String} name + * @param {Element} element + */ +function initComponent(name, element) { + /** @type {Function|undefined} * */ + const ComponentModel = componentModelMap[name]; + if (ComponentModel === undefined) return; + + // Create our component instance + /** @type {Component} * */ + let instance; + try { + instance = new ComponentModel(); + instance.$name = name; + instance.$el = element; + const allRefs = parseRefs(name, element); + instance.$refs = allRefs.refs; + instance.$manyRefs = allRefs.manyRefs; + instance.$opts = parseOpts(name, element); + instance.setup(); + } catch (e) { + console.error('Failed to create component', e, name, element); + } + + // Add to global listing + if (typeof components[name] === 'undefined') { + components[name] = []; + } + components[name].push(instance); + + // Add to element mapping + const elComponents = elementComponentMap.get(element) || {}; + elComponents[name] = instance; + elementComponentMap.set(element, elComponents); +} + /** * Initialize all components found within the given element. * @param {Element|Document} parentElement */ export function init(parentElement = document) { - const componentElems = parentElement.querySelectorAll(`[component],[components]`); + const componentElems = parentElement.querySelectorAll('[component],[components]'); for (const el of componentElems) { const componentNames = `${el.getAttribute('component') || ''} ${(el.getAttribute('components'))}`.toLowerCase().split(' ').filter(Boolean); @@ -162,4 +162,4 @@ export function get(name) { export function firstOnElement(element, name) { const elComponents = elementComponentMap.get(element) || {}; return elComponents[name] || null; -} \ No newline at end of file +} diff --git a/resources/js/services/dates.js b/resources/js/services/dates.js index 119d8fa60..2e6b34aee 100644 --- a/resources/js/services/dates.js +++ b/resources/js/services/dates.js @@ -1,24 +1,23 @@ - export function getCurrentDay() { - let date = new Date(); - let month = date.getMonth() + 1; - let day = date.getDate(); + const date = new Date(); + const month = date.getMonth() + 1; + const day = date.getDate(); - return `${date.getFullYear()}-${(month>9?'':'0') + month}-${(day>9?'':'0') + day}`; + return `${date.getFullYear()}-${(month > 9 ? '' : '0') + month}-${(day > 9 ? '' : '0') + day}`; } export function utcTimeStampToLocalTime(timestamp) { - let date = new Date(timestamp * 1000); - let hours = date.getHours(); - let mins = date.getMinutes(); - return `${(hours>9?'':'0') + hours}:${(mins>9?'':'0') + mins}`; + const date = new Date(timestamp * 1000); + const hours = date.getHours(); + const mins = date.getMinutes(); + return `${(hours > 9 ? '' : '0') + hours}:${(mins > 9 ? '' : '0') + mins}`; } export function formatDateTime(date) { - let month = date.getMonth() + 1; - let day = date.getDate(); - let hours = date.getHours(); - let mins = date.getMinutes(); + const month = date.getMonth() + 1; + const day = date.getDate(); + const hours = date.getHours(); + const mins = date.getMinutes(); - return `${date.getFullYear()}-${(month>9?'':'0') + month}-${(day>9?'':'0') + day} ${(hours>9?'':'0') + hours}:${(mins>9?'':'0') + mins}`; -} \ No newline at end of file + return `${date.getFullYear()}-${(month > 9 ? '' : '0') + month}-${(day > 9 ? '' : '0') + day} ${(hours > 9 ? '' : '0') + hours}:${(mins > 9 ? '' : '0') + mins}`; +} diff --git a/resources/js/services/dom.js b/resources/js/services/dom.js index 882d5228d..17f5a803a 100644 --- a/resources/js/services/dom.js +++ b/resources/js/services/dom.js @@ -5,7 +5,7 @@ */ export function forEach(selector, callback) { const elements = document.querySelectorAll(selector); - for (let element of elements) { + for (const element of elements) { callback(element); } } @@ -17,7 +17,7 @@ export function forEach(selector, callback) { * @param {Function} callback */ export function onEvents(listenerElement, events, callback) { - for (let eventName of events) { + for (const eventName of events) { listenerElement.addEventListener(eventName, callback); } } @@ -35,7 +35,7 @@ export function onSelect(elements, callback) { for (const listenerElement of elements) { listenerElement.addEventListener('click', callback); - listenerElement.addEventListener('keydown', (event) => { + listenerElement.addEventListener('keydown', event => { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault(); callback(event); @@ -58,7 +58,7 @@ export function onEnterPress(elements, callback) { if (event.key === 'Enter') { callback(event); } - } + }; elements.forEach(e => e.addEventListener('keypress', listener)); } @@ -73,7 +73,7 @@ export function onEnterPress(elements, callback) { * @param {Function} callback */ export function onChildEvent(listenerElement, childSelector, eventName, callback) { - listenerElement.addEventListener(eventName, function(event) { + listenerElement.addEventListener(eventName, event => { const matchingChild = event.target.closest(childSelector); if (matchingChild) { callback.call(matchingChild, event, matchingChild); @@ -91,7 +91,7 @@ export function onChildEvent(listenerElement, childSelector, eventName, callback export function findText(selector, text) { const elements = document.querySelectorAll(selector); text = text.toLowerCase(); - for (let element of elements) { + for (const element of elements) { if (element.textContent.toLowerCase().includes(text)) { return element; } @@ -105,7 +105,7 @@ export function findText(selector, text) { * @param {Element} element */ export function showLoading(element) { - element.innerHTML = `
    `; + element.innerHTML = '
    '; } /** @@ -130,4 +130,4 @@ export function htmlToDom(html) { wrap.innerHTML = html; window.$components.init(wrap); return wrap.children[0]; -} \ No newline at end of file +} diff --git a/resources/js/services/drawio.js b/resources/js/services/drawio.js index 67ac4cc0e..c773a780d 100644 --- a/resources/js/services/drawio.js +++ b/resources/js/services/drawio.js @@ -1,29 +1,41 @@ let iFrame = null; let lastApprovedOrigin; -let onInit, onSave; +let onInit; let + onSave; -/** - * Show the draw.io editor. - * @param {String} drawioUrl - * @param {Function} onInitCallback - Must return a promise with the xml to load for the editor. - * @param {Function} onSaveCallback - Is called with the drawing data on save. - */ -function show(drawioUrl, onInitCallback, onSaveCallback) { - onInit = onInitCallback; - onSave = onSaveCallback; - - iFrame = document.createElement('iframe'); - iFrame.setAttribute('frameborder', '0'); - window.addEventListener('message', drawReceive); - iFrame.setAttribute('src', drawioUrl); - iFrame.setAttribute('class', 'fullscreen'); - iFrame.style.backgroundColor = '#FFFFFF'; - document.body.appendChild(iFrame); - lastApprovedOrigin = (new URL(drawioUrl)).origin; +function drawPostMessage(data) { + iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin); } -function close() { - drawEventClose(); +function drawEventExport(message) { + if (onSave) { + onSave(message.data); + } +} + +function drawEventSave(message) { + drawPostMessage({ + action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing', + }); +} + +function drawEventInit() { + if (!onInit) return; + onInit().then(xml => { + drawPostMessage({action: 'load', autosave: 1, xml}); + }); +} + +function drawEventConfigure() { + const config = {}; + window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config}); + drawPostMessage({action: 'configure', config}); +} + +function drawEventClose() { + // eslint-disable-next-line no-use-before-define + window.removeEventListener('message', drawReceive); + if (iFrame) document.body.removeChild(iFrame); } /** @@ -48,53 +60,45 @@ function drawReceive(event) { } } -function drawEventExport(message) { - if (onSave) { - onSave(message.data); - } +/** + * Show the draw.io editor. + * @param {String} drawioUrl + * @param {Function} onInitCallback - Must return a promise with the xml to load for the editor. + * @param {Function} onSaveCallback - Is called with the drawing data on save. + */ +export function show(drawioUrl, onInitCallback, onSaveCallback) { + onInit = onInitCallback; + onSave = onSaveCallback; + + iFrame = document.createElement('iframe'); + iFrame.setAttribute('frameborder', '0'); + window.addEventListener('message', drawReceive); + iFrame.setAttribute('src', drawioUrl); + iFrame.setAttribute('class', 'fullscreen'); + iFrame.style.backgroundColor = '#FFFFFF'; + document.body.appendChild(iFrame); + lastApprovedOrigin = (new URL(drawioUrl)).origin; } -function drawEventSave(message) { - drawPostMessage({action: 'export', format: 'xmlpng', xml: message.xml, spin: 'Updating drawing'}); -} - -function drawEventInit() { - if (!onInit) return; - onInit().then(xml => { - drawPostMessage({action: 'load', autosave: 1, xml: xml}); - }); -} - -function drawEventConfigure() { - const config = {}; - window.$events.emitPublic(iFrame, 'editor-drawio::configure', {config}); - drawPostMessage({action: 'configure', config}); -} - -function drawEventClose() { - window.removeEventListener('message', drawReceive); - if (iFrame) document.body.removeChild(iFrame); -} - -function drawPostMessage(data) { - iFrame.contentWindow.postMessage(JSON.stringify(data), lastApprovedOrigin); -} - -async function upload(imageData, pageUploadedToId) { - let data = { +export async function upload(imageData, pageUploadedToId) { + const data = { image: imageData, uploaded_to: pageUploadedToId, }; - const resp = await window.$http.post(window.baseUrl(`/images/drawio`), data); + const resp = await window.$http.post(window.baseUrl('/images/drawio'), data); return resp.data; } +export function close() { + drawEventClose(); +} + /** * Load an existing image, by fetching it as Base64 from the system. * @param drawingId * @returns {Promise} */ -async function load(drawingId) { +export async function load(drawingId) { try { const resp = await window.$http.get(window.baseUrl(`/images/drawio/base64/${drawingId}`)); return `data:image/png;base64,${resp.data.content}`; @@ -106,5 +110,3 @@ async function load(drawingId) { throw error; } } - -export default {show, close, upload, load}; \ No newline at end of file diff --git a/resources/js/services/events.js b/resources/js/services/events.js index d2dacfa7b..761305793 100644 --- a/resources/js/services/events.js +++ b/resources/js/services/events.js @@ -6,13 +6,12 @@ const stack = []; * @param {String} eventName * @param {*} eventData */ -function emit(eventName, eventData) { +export function emit(eventName, eventData) { stack.push({name: eventName, data: eventData}); - if (typeof listeners[eventName] === 'undefined') return this; - let eventsToStart = listeners[eventName]; - for (let i = 0; i < eventsToStart.length; i++) { - let event = eventsToStart[i]; - event(eventData); + + const listenersToRun = listeners[eventName] || []; + for (const listener of listenersToRun) { + listener(eventData); } } @@ -22,7 +21,7 @@ function emit(eventName, eventData) { * @param {Function} callback * @returns {Events} */ -function listen(eventName, callback) { +export function listen(eventName, callback) { if (typeof listeners[eventName] === 'undefined') listeners[eventName] = []; listeners[eventName].push(callback); } @@ -34,43 +33,49 @@ function listen(eventName, callback) { * @param {String} eventName * @param {Object} eventData */ -function emitPublic(targetElement, eventName, eventData) { +export function emitPublic(targetElement, eventName, eventData) { const event = new CustomEvent(eventName, { detail: eventData, - bubbles: true + bubbles: true, }); targetElement.dispatchEvent(event); } /** - * Notify of standard server-provided validation errors. - * @param {Object} error + * Emit a success event with the provided message. + * @param {String} message */ -function showValidationErrors(error) { - if (!error.status) return; - if (error.status === 422 && error.data) { - const message = Object.values(error.data).flat().join('\n'); - emit('error', message); +export function success(message) { + emit('success', message); +} + +/** + * Emit an error event with the provided message. + * @param {String} message + */ +export function error(message) { + emit('error', message); +} + +/** + * Notify of standard server-provided validation errors. + * @param {Object} responseErr + */ +export function showValidationErrors(responseErr) { + if (!responseErr.status) return; + if (responseErr.status === 422 && responseErr.data) { + const message = Object.values(responseErr.data).flat().join('\n'); + error(message); } } /** * Notify standard server-provided error messages. - * @param {Object} error + * @param {Object} responseErr */ -function showResponseError(error) { - if (!error.status) return; - if (error.status >= 400 && error.data && error.data.message) { - emit('error', error.data.message); +export function showResponseError(responseErr) { + if (!responseErr.status) return; + if (responseErr.status >= 400 && responseErr.data && responseErr.data.message) { + error(responseErr.data.message); } } - -export default { - emit, - emitPublic, - listen, - success: (msg) => emit('success', msg), - error: (msg) => emit('error', msg), - showValidationErrors, - showResponseError, -} \ No newline at end of file diff --git a/resources/js/services/http.js b/resources/js/services/http.js index 211ea0c92..d0d33e317 100644 --- a/resources/js/services/http.js +++ b/resources/js/services/http.js @@ -1,56 +1,100 @@ +/** + * @typedef FormattedResponse + * @property {Headers} headers + * @property {Response} original + * @property {Object|String} data + * @property {Boolean} redirected + * @property {Number} status + * @property {string} statusText + * @property {string} url + */ /** - * Perform a HTTP GET request. - * Can easily pass query parameters as the second parameter. - * @param {String} url - * @param {Object} params - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} + * Get the content from a fetch response. + * Checks the content-type header to determine the format. + * @param {Response} response + * @returns {Promise} */ -async function get(url, params = {}) { - return request(url, { - method: 'GET', - params, - }); +async function getResponseContent(response) { + if (response.status === 204) { + return null; + } + + const responseContentType = response.headers.get('Content-Type') || ''; + const subType = responseContentType.split(';')[0].split('/').pop(); + + if (subType === 'javascript' || subType === 'json') { + return response.json(); + } + + return response.text(); +} + +export class HttpError extends Error { + + constructor(response, content) { + super(response.statusText); + this.data = content; + this.headers = response.headers; + this.redirected = response.redirected; + this.status = response.status; + this.statusText = response.statusText; + this.url = response.url; + this.original = response; + } + } /** - * Perform a HTTP POST request. + * Create a new HTTP request, setting the required CSRF information + * to communicate with the back-end. Parses & formats the response. * @param {String} url - * @param {Object} data - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} + * @param {Object} options + * @returns {Promise} */ -async function post(url, data = null) { - return dataRequest('POST', url, data); -} +async function request(url, options = {}) { + let requestUrl = url; -/** - * Perform a HTTP PUT request. - * @param {String} url - * @param {Object} data - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} - */ -async function put(url, data = null) { - return dataRequest('PUT', url, data); -} + if (!requestUrl.startsWith('http')) { + requestUrl = window.baseUrl(requestUrl); + } -/** - * Perform a HTTP PATCH request. - * @param {String} url - * @param {Object} data - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} - */ -async function patch(url, data = null) { - return dataRequest('PATCH', url, data); -} + if (options.params) { + const urlObj = new URL(requestUrl); + for (const paramName of Object.keys(options.params)) { + const value = options.params[paramName]; + if (typeof value !== 'undefined' && value !== null) { + urlObj.searchParams.set(paramName, value); + } + } + requestUrl = urlObj.toString(); + } -/** - * Perform a HTTP DELETE request. - * @param {String} url - * @param {Object} data - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} - */ -async function performDelete(url, data = null) { - return dataRequest('DELETE', url, data); + const csrfToken = document.querySelector('meta[name=token]').getAttribute('content'); + const requestOptions = {...options, credentials: 'same-origin'}; + requestOptions.headers = { + ...requestOptions.headers || {}, + baseURL: window.baseUrl(''), + 'X-CSRF-TOKEN': csrfToken, + }; + + const response = await fetch(requestUrl, requestOptions); + const content = await getResponseContent(response); + const returnData = { + data: content, + headers: response.headers, + redirected: response.redirected, + status: response.status, + statusText: response.statusText, + url: response.url, + original: response, + }; + + if (!response.ok) { + throw new HttpError(response, content); + } + + return returnData; } /** @@ -59,11 +103,11 @@ async function performDelete(url, data = null) { * @param {String} method * @param {String} url * @param {Object} data - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} + * @returns {Promise} */ async function dataRequest(method, url, data = null) { const options = { - method: method, + method, body: data, }; @@ -84,99 +128,61 @@ async function dataRequest(method, url, data = null) { options.method = 'post'; } - return request(url, options) + return request(url, options); } /** - * Create a new HTTP request, setting the required CSRF information - * to communicate with the back-end. Parses & formats the response. + * Perform a HTTP GET request. + * Can easily pass query parameters as the second parameter. * @param {String} url - * @param {Object} options - * @returns {Promise<{headers: Headers, original: Response, data: (Object|String), redirected: boolean, statusText: string, url: string, status: number}>} + * @param {Object} params + * @returns {Promise} */ -async function request(url, options = {}) { - if (!url.startsWith('http')) { - url = window.baseUrl(url); - } - - if (options.params) { - const urlObj = new URL(url); - for (let paramName of Object.keys(options.params)) { - const value = options.params[paramName]; - if (typeof value !== 'undefined' && value !== null) { - urlObj.searchParams.set(paramName, value); - } - } - url = urlObj.toString(); - } - - const csrfToken = document.querySelector('meta[name=token]').getAttribute('content'); - options = Object.assign({}, options, { - 'credentials': 'same-origin', +export async function get(url, params = {}) { + return request(url, { + method: 'GET', + params, }); - options.headers = Object.assign({}, options.headers || {}, { - 'baseURL': window.baseUrl(''), - 'X-CSRF-TOKEN': csrfToken, - }); - - const response = await fetch(url, options); - const content = await getResponseContent(response); - const returnData = { - data: content, - headers: response.headers, - redirected: response.redirected, - status: response.status, - statusText: response.statusText, - url: response.url, - original: response, - }; - - if (!response.ok) { - throw new HttpError(response, content); - } - - return returnData; } /** - * Get the content from a fetch response. - * Checks the content-type header to determine the format. - * @param {Response} response - * @returns {Promise} + * Perform a HTTP POST request. + * @param {String} url + * @param {Object} data + * @returns {Promise} */ -async function getResponseContent(response) { - if (response.status === 204) { - return null; - } - - const responseContentType = response.headers.get('Content-Type') || ''; - const subType = responseContentType.split(';')[0].split('/').pop(); - - if (subType === 'javascript' || subType === 'json') { - return await response.json(); - } - - return await response.text(); +export async function post(url, data = null) { + return dataRequest('POST', url, data); } -class HttpError extends Error { - constructor(response, content) { - super(response.statusText); - this.data = content; - this.headers = response.headers; - this.redirected = response.redirected; - this.status = response.status; - this.statusText = response.statusText; - this.url = response.url; - this.original = response; - } +/** + * Perform a HTTP PUT request. + * @param {String} url + * @param {Object} data + * @returns {Promise} + */ +export async function put(url, data = null) { + return dataRequest('PUT', url, data); } -export default { - get: get, - post: post, - put: put, - patch: patch, - delete: performDelete, - HttpError: HttpError, -}; \ No newline at end of file +/** + * Perform a HTTP PATCH request. + * @param {String} url + * @param {Object} data + * @returns {Promise} + */ +export async function patch(url, data = null) { + return dataRequest('PATCH', url, data); +} + +/** + * Perform a HTTP DELETE request. + * @param {String} url + * @param {Object} data + * @returns {Promise} + */ +async function performDelete(url, data = null) { + return dataRequest('DELETE', url, data); +} + +export {performDelete as delete}; diff --git a/resources/js/services/keyboard-navigation.js b/resources/js/services/keyboard-navigation.js index 0f866ceaa..34111bb2d 100644 --- a/resources/js/services/keyboard-navigation.js +++ b/resources/js/services/keyboard-navigation.js @@ -57,7 +57,6 @@ export class KeyboardNavigationHandler { * @param {KeyboardEvent} event */ #keydownHandler(event) { - // Ignore certain key events in inputs to allow text editing. if (event.target.matches('input') && (event.key === 'ArrowRight' || event.key === 'ArrowLeft')) { return; @@ -72,7 +71,7 @@ export class KeyboardNavigationHandler { } else if (event.key === 'Escape') { if (this.onEscape) { this.onEscape(event); - } else if (document.activeElement) { + } else if (document.activeElement) { document.activeElement.blur(); } } else if (event.key === 'Enter' && this.onEnter) { @@ -88,8 +87,9 @@ export class KeyboardNavigationHandler { const focusable = []; const selector = '[tabindex]:not([tabindex="-1"]),[href],button:not([tabindex="-1"],[disabled]),input:not([type=hidden])'; for (const container of this.containers) { - focusable.push(...container.querySelectorAll(selector)) + focusable.push(...container.querySelectorAll(selector)); } return focusable; } -} \ No newline at end of file + +} diff --git a/resources/js/services/text.js b/resources/js/services/text.js index ea82f993e..d5e6fa798 100644 --- a/resources/js/services/text.js +++ b/resources/js/services/text.js @@ -4,7 +4,7 @@ * @returns {string} */ export function kebabToCamel(kebab) { - const ucFirst = (word) => word.slice(0,1).toUpperCase() + word.slice(1); + const ucFirst = word => word.slice(0, 1).toUpperCase() + word.slice(1); const words = kebab.split('-'); return words[0] + words.slice(1).map(ucFirst).join(''); } @@ -15,5 +15,5 @@ export function kebabToCamel(kebab) { * @returns {String} */ export function camelToKebab(camelStr) { - return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase()); -} \ No newline at end of file + return camelStr.replace(/[A-Z]/g, (str, offset) => (offset > 0 ? '-' : '') + str.toLowerCase()); +} diff --git a/resources/js/services/translations.js b/resources/js/services/translations.js index 62bb51f56..e562a9152 100644 --- a/resources/js/services/translations.js +++ b/resources/js/services/translations.js @@ -5,11 +5,7 @@ */ class Translator { - /** - * Create an instance, Passing in the required translations - * @param translations - */ - constructor(translations) { + constructor() { this.store = new Map(); this.parseTranslations(); } @@ -19,7 +15,7 @@ class Translator { */ parseTranslations() { const translationMetaTags = document.querySelectorAll('meta[name="translation"]'); - for (let tag of translationMetaTags) { + for (const tag of translationMetaTags) { const key = tag.getAttribute('key'); const value = tag.getAttribute('value'); this.store.set(key, value); @@ -27,7 +23,7 @@ class Translator { } /** - * Get a translation, Same format as laravel's 'trans' helper + * Get a translation, Same format as Laravel's 'trans' helper * @param key * @param replacements * @returns {*} @@ -38,8 +34,8 @@ class Translator { } /** - * Get pluralised text, Dependant on the given count. - * Same format at laravel's 'trans_choice' helper. + * Get pluralised text, Dependent on the given count. + * Same format at Laravel's 'trans_choice' helper. * @param key * @param count * @param replacements @@ -52,7 +48,7 @@ class Translator { /** * Parse the given translation and find the correct plural option - * to use. Similar format at laravel's 'trans_choice' helper. + * to use. Similar format at Laravel's 'trans_choice' helper. * @param {String} translation * @param {Number} count * @param {Object} replacements @@ -64,7 +60,7 @@ class Translator { const rangeRegex = /^\[([0-9]+),([0-9*]+)]/; let result = null; - for (let t of splitText) { + for (const t of splitText) { // Parse exact matches const exactMatches = t.match(exactCountRegex); if (exactMatches !== null && Number(exactMatches[1]) === count) { @@ -117,14 +113,17 @@ class Translator { */ performReplacements(string, replacements) { if (!replacements) return string; - const replaceMatches = string.match(/:([\S]+)/g); + const replaceMatches = string.match(/:(\S+)/g); if (replaceMatches === null) return string; + let updatedString = string; + replaceMatches.forEach(match => { const key = match.substring(1); if (typeof replacements[key] === 'undefined') return; - string = string.replace(match, replacements[key]); + updatedString = updatedString.replace(match, replacements[key]); }); - return string; + + return updatedString; } } diff --git a/resources/js/services/util.js b/resources/js/services/util.js index 238f8b1d8..dd97d81aa 100644 --- a/resources/js/services/util.js +++ b/resources/js/services/util.js @@ -1,5 +1,3 @@ - - /** * Returns a function, that, as long as it continues to be invoked, will not * be triggered. The function will be called after it stops being called for @@ -13,9 +11,9 @@ */ export function debounce(func, wait, immediate) { let timeout; - return function() { - const context = this, args = arguments; - const later = function() { + return function debouncedWrapper(...args) { + const context = this; + const later = function debouncedTimeout() { timeout = null; if (!immediate) func.apply(context, args); }; @@ -24,7 +22,7 @@ export function debounce(func, wait, immediate) { timeout = setTimeout(later, wait); if (callNow) func.apply(context, args); }; -}; +} /** * Scroll and highlight an element. @@ -55,11 +53,11 @@ export function scrollAndHighlightElement(element) { */ export function escapeHtml(unsafe) { return unsafe - .replace(/&/g, "&") - .replace(//g, ">") - .replace(/"/g, """) - .replace(/'/g, "'"); + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); } /** @@ -68,6 +66,7 @@ export function escapeHtml(unsafe) { * @returns {string} */ export function uniqueId() { - const S4 = () => (((1+Math.random())*0x10000)|0).toString(16).substring(1); - return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4()); -} \ No newline at end of file + // eslint-disable-next-line no-bitwise + const S4 = () => (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1); + return (`${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}`); +} diff --git a/resources/js/services/vdom.js b/resources/js/services/vdom.js index 89a809084..2095cd2ca 100644 --- a/resources/js/services/vdom.js +++ b/resources/js/services/vdom.js @@ -1,8 +1,8 @@ import { init, attributesModule, - toVNode -} from "snabbdom"; + toVNode, +} from 'snabbdom'; let patcher; @@ -12,7 +12,6 @@ let patcher; function getPatcher() { if (patcher) return patcher; - patcher = init([ attributesModule, ]); @@ -28,4 +27,4 @@ export function patchDomFromHtmlString(domTarget, html) { const contentDom = document.createElement('div'); contentDom.innerHTML = html; getPatcher()(toVNode(domTarget), toVNode(contentDom)); -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/common-events.js b/resources/js/wysiwyg/common-events.js index a25debac1..d0a5acdc2 100644 --- a/resources/js/wysiwyg/common-events.js +++ b/resources/js/wysiwyg/common-events.js @@ -2,7 +2,6 @@ * @param {Editor} editor */ export function listen(editor) { - // Replace editor content window.$events.listen('editor::replace', ({html}) => { editor.setContent(html); @@ -31,4 +30,4 @@ export function listen(editor) { editor.focus(); } }); -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/config.js b/resources/js/wysiwyg/config.js index 85c1919d4..a0e7156ee 100644 --- a/resources/js/wysiwyg/config.js +++ b/resources/js/wysiwyg/config.js @@ -1,32 +1,35 @@ -import {register as registerShortcuts} from "./shortcuts"; -import {listen as listenForCommonEvents} from "./common-events"; -import {scrollToQueryString} from "./scrolling"; -import {listenForDragAndPaste} from "./drop-paste-handling"; -import {getPrimaryToolbar, registerAdditionalToolbars} from "./toolbars"; -import {registerCustomIcons} from "./icons"; +import {register as registerShortcuts} from './shortcuts'; +import {listen as listenForCommonEvents} from './common-events'; +import {scrollToQueryString} from './scrolling'; +import {listenForDragAndPaste} from './drop-paste-handling'; +import {getPrimaryToolbar, registerAdditionalToolbars} from './toolbars'; +import {registerCustomIcons} from './icons'; -import {getPlugin as getCodeeditorPlugin} from "./plugin-codeeditor"; -import {getPlugin as getDrawioPlugin} from "./plugin-drawio"; -import {getPlugin as getCustomhrPlugin} from "./plugins-customhr"; -import {getPlugin as getImagemanagerPlugin} from "./plugins-imagemanager"; -import {getPlugin as getAboutPlugin} from "./plugins-about"; -import {getPlugin as getDetailsPlugin} from "./plugins-details"; -import {getPlugin as getTasklistPlugin} from "./plugins-tasklist"; +import {getPlugin as getCodeeditorPlugin} from './plugin-codeeditor'; +import {getPlugin as getDrawioPlugin} from './plugin-drawio'; +import {getPlugin as getCustomhrPlugin} from './plugins-customhr'; +import {getPlugin as getImagemanagerPlugin} from './plugins-imagemanager'; +import {getPlugin as getAboutPlugin} from './plugins-about'; +import {getPlugin as getDetailsPlugin} from './plugins-details'; +import {getPlugin as getTasklistPlugin} from './plugins-tasklist'; -const style_formats = [ - {title: "Large Header", format: "h2", preview: 'color: blue;'}, - {title: "Medium Header", format: "h3"}, - {title: "Small Header", format: "h4"}, - {title: "Tiny Header", format: "h5"}, - {title: "Paragraph", format: "p", exact: true, classes: ''}, - {title: "Blockquote", format: "blockquote"}, +const styleFormats = [ + {title: 'Large Header', format: 'h2', preview: 'color: blue;'}, + {title: 'Medium Header', format: 'h3'}, + {title: 'Small Header', format: 'h4'}, + {title: 'Tiny Header', format: 'h5'}, { - title: "Callouts", items: [ - {title: "Information", format: 'calloutinfo'}, - {title: "Success", format: 'calloutsuccess'}, - {title: "Warning", format: 'calloutwarning'}, - {title: "Danger", format: 'calloutdanger'} - ] + title: 'Paragraph', format: 'p', exact: true, classes: '', + }, + {title: 'Blockquote', format: 'blockquote'}, + { + title: 'Callouts', + items: [ + {title: 'Information', format: 'calloutinfo'}, + {title: 'Success', format: 'calloutsuccess'}, + {title: 'Warning', format: 'calloutwarning'}, + {title: 'Danger', format: 'calloutdanger'}, + ], }, ]; @@ -37,10 +40,10 @@ const formats = { calloutsuccess: {block: 'p', exact: true, attributes: {class: 'callout success'}}, calloutinfo: {block: 'p', exact: true, attributes: {class: 'callout info'}}, calloutwarning: {block: 'p', exact: true, attributes: {class: 'callout warning'}}, - calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}} + calloutdanger: {block: 'p', exact: true, attributes: {class: 'callout danger'}}, }; -const color_map = [ +const colorMap = [ '#BFEDD2', '', '#FBEEB8', '', '#F8CAC6', '', @@ -66,14 +69,13 @@ const color_map = [ '#34495E', '', '#000000', '', - '#ffffff', '' + '#ffffff', '', ]; -function file_picker_callback(callback, value, meta) { - +function filePickerCallback(callback, value, meta) { // field_name, url, type, win if (meta.filetype === 'file') { - /** @type {EntitySelectorPopup} **/ + /** @type {EntitySelectorPopup} * */ const selector = window.$components.first('entity-selector-popup'); selector.show(entity => { callback(entity.link, { @@ -85,13 +87,12 @@ function file_picker_callback(callback, value, meta) { if (meta.filetype === 'image') { // Show image manager - /** @type {ImageManager} **/ + /** @type {ImageManager} * */ const imageManager = window.$components.first('image-manager'); - imageManager.show(function (image) { + imageManager.show(image => { callback(image.url, {alt: image.name}); }, 'gallery'); } - } /** @@ -100,30 +101,30 @@ function file_picker_callback(callback, value, meta) { */ function gatherPlugins(options) { const plugins = [ - "image", - "table", - "link", - "autolink", - "fullscreen", - "code", - "customhr", - "autosave", - "lists", - "codeeditor", - "media", - "imagemanager", - "about", - "details", - "tasklist", + 'image', + 'table', + 'link', + 'autolink', + 'fullscreen', + 'code', + 'customhr', + 'autosave', + 'lists', + 'codeeditor', + 'media', + 'imagemanager', + 'about', + 'details', + 'tasklist', options.textDirection === 'rtl' ? 'directionality' : '', ]; - window.tinymce.PluginManager.add('codeeditor', getCodeeditorPlugin(options)); - window.tinymce.PluginManager.add('customhr', getCustomhrPlugin(options)); - window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin(options)); - window.tinymce.PluginManager.add('about', getAboutPlugin(options)); - window.tinymce.PluginManager.add('details', getDetailsPlugin(options)); - window.tinymce.PluginManager.add('tasklist', getTasklistPlugin(options)); + window.tinymce.PluginManager.add('codeeditor', getCodeeditorPlugin()); + window.tinymce.PluginManager.add('customhr', getCustomhrPlugin()); + window.tinymce.PluginManager.add('imagemanager', getImagemanagerPlugin()); + window.tinymce.PluginManager.add('about', getAboutPlugin()); + window.tinymce.PluginManager.add('details', getDetailsPlugin()); + window.tinymce.PluginManager.add('tasklist', getTasklistPlugin()); if (options.drawioUrl) { window.tinymce.PluginManager.add('drawio', getDrawioPlugin(options)); @@ -137,11 +138,11 @@ function gatherPlugins(options) { * Fetch custom HTML head content from the parent page head into the editor. */ function fetchCustomHeadContent() { - const headContentLines = document.head.innerHTML.split("\n"); + const headContentLines = document.head.innerHTML.split('\n'); const startLineIndex = headContentLines.findIndex(line => line.trim() === ''); const endLineIndex = headContentLines.findIndex(line => line.trim() === ''); if (startLineIndex === -1 || endLineIndex === -1) { - return '' + return ''; } return headContentLines.slice(startLineIndex + 1, endLineIndex).join('\n'); } @@ -152,10 +153,10 @@ function fetchCustomHeadContent() { * @param {Editor} editor */ function setupBrFilter(editor) { - editor.serializer.addNodeFilter('br', function(nodes) { + editor.serializer.addNodeFilter('br', nodes => { for (const node of nodes) { if (node.parent && node.parent.name === 'code') { - const newline = tinymce.html.Node.create('#text'); + const newline = window.tinymce.html.Node.create('#text'); newline.value = '\n'; node.replace(newline); } @@ -168,7 +169,14 @@ function setupBrFilter(editor) { * @return {function(Editor)} */ function getSetupCallback(options) { - return function(editor) { + return function setupCallback(editor) { + function editorChange() { + if (options.darkMode) { + editor.contentDocument.documentElement.classList.add('dark-mode'); + } + window.$events.emit('editor-html-change', ''); + } + editor.on('ExecCommand change input NodeChange ObjectResized', editorChange); listenForCommonEvents(editor); listenForDragAndPaste(editor, options); @@ -184,13 +192,6 @@ function getSetupCallback(options) { setupBrFilter(editor); }); - function editorChange() { - if (options.darkMode) { - editor.contentDocument.documentElement.classList.add('dark-mode'); - } - window.$events.emit('editor-html-change', ''); - } - // Custom handler hook window.$events.emitPublic(options.containerElement, 'editor-tinymce::setup', {editor}); @@ -200,9 +201,9 @@ function getSetupCallback(options) { icon: 'sourcecode', onAction() { editor.execCommand('mceToggleFormat', false, 'code'); - } - }) - } + }, + }); + }; } /** @@ -229,7 +230,6 @@ body { * @return {Object} */ export function build(options) { - // Set language window.tinymce.addI18n(options.language, options.translationMap); @@ -241,7 +241,7 @@ export function build(options) { width: '100%', height: '100%', selector: '#html-editor', - cache_suffix: '?version=' + version, + cache_suffix: `?version=${version}`, content_css: [ window.baseUrl('/dist/styles.css'), ], @@ -263,18 +263,18 @@ export function build(options) { automatic_uploads: false, custom_elements: 'doc-root,code-block', valid_children: [ - "-div[p|h1|h2|h3|h4|h5|h6|blockquote|code-block]", - "+div[pre|img]", - "-doc-root[doc-root|#text]", - "-li[details]", - "+code-block[pre]", - "+doc-root[p|h1|h2|h3|h4|h5|h6|blockquote|code-block|div]" + '-div[p|h1|h2|h3|h4|h5|h6|blockquote|code-block]', + '+div[pre|img]', + '-doc-root[doc-root|#text]', + '-li[details]', + '+code-block[pre]', + '+doc-root[p|h1|h2|h3|h4|h5|h6|blockquote|code-block|div]', ].join(','), plugins: gatherPlugins(options), contextmenu: false, toolbar: getPrimaryToolbar(options), content_style: getContentStyle(options), - style_formats, + style_formats: styleFormats, style_formats_merge: false, media_alt_source: false, media_poster: false, @@ -282,10 +282,10 @@ export function build(options) { table_style_by_css: true, table_use_colgroups: true, file_picker_types: 'file image', - color_map, - file_picker_callback, + color_map: colorMap, + file_picker_callback: filePickerCallback, paste_preprocess(plugin, args) { - const content = args.content; + const {content} = args; if (content.indexOf(' { editor.dom.remove(id); window.$events.emit('error', options.translations.imageUploadErrorText); - console.log(err); + console.error(err); }); }, 10); } } -/** - * Upload an image file to the server - * @param {File} file - * @param {int} pageId - */ -async function uploadImageFile(file, pageId) { - if (file === null || file.type.indexOf('image') !== 0) { - throw new Error(`Not an image file`); - } - - const remoteFilename = file.name || `image-${Date.now()}.png`; - const formData = new FormData(); - formData.append('file', file, remoteFilename); - formData.append('uploaded_to', pageId); - - const resp = await window.$http.post(window.baseUrl('/images/gallery'), formData); - return resp.data; -} - /** * @param {Editor} editor - * @param {WysiwygConfigOptions} options */ -function dragStart(editor, options) { - let node = editor.selection.getNode(); +function dragStart(editor) { + const node = editor.selection.getNode(); if (node.nodeName === 'IMG') { wrap = editor.dom.getParent(node, '.mceTemp'); @@ -96,8 +94,12 @@ function dragStart(editor, options) { * @param {DragEvent} event */ function drop(editor, options, event) { - let dom = editor.dom, - rng = tinymce.dom.RangeUtils.getCaretRangeFromPoint(event.clientX, event.clientY, editor.getDoc()); + const {dom} = editor; + const rng = window.tinymce.dom.RangeUtils.getCaretRangeFromPoint( + event.clientX, + event.clientY, + editor.getDoc(), + ); // Template insertion const templateId = event.dataTransfer && event.dataTransfer.getData('bookstack/template'); @@ -105,7 +107,7 @@ function drop(editor, options, event) { event.preventDefault(); window.$http.get(`/templates/${templateId}`).then(resp => { editor.selection.setRng(rng); - editor.undoManager.transact(function () { + editor.undoManager.transact(() => { editor.execCommand('mceInsertContent', false, resp.data.html); }); }); @@ -117,7 +119,7 @@ function drop(editor, options, event) { } else if (wrap) { event.preventDefault(); - editor.undoManager.transact(function () { + editor.undoManager.transact(() => { editor.selection.setRng(rng); editor.selection.setNode(wrap); dom.remove(wrap); @@ -127,7 +129,7 @@ function drop(editor, options, event) { // Handle contenteditable section drop if (!event.isDefaultPrevented() && draggedContentEditable) { event.preventDefault(); - editor.undoManager.transact(function () { + editor.undoManager.transact(() => { const selectedNode = editor.selection.getNode(); const range = editor.selection.getRng(); const selectedNodeRoot = selectedNode.closest('body > *'); @@ -152,7 +154,7 @@ function drop(editor, options, event) { * @param {WysiwygConfigOptions} options */ export function listenForDragAndPaste(editor, options) { - editor.on('dragstart', () => dragStart(editor, options)); - editor.on('drop', event => drop(editor, options, event)); + editor.on('dragstart', () => dragStart(editor)); + editor.on('drop', event => drop(editor, options, event)); editor.on('paste', event => paste(editor, options, event)); -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/icons.js b/resources/js/wysiwyg/icons.js index 2c2457fe1..3045df227 100644 --- a/resources/js/wysiwyg/icons.js +++ b/resources/js/wysiwyg/icons.js @@ -5,17 +5,15 @@ const icons = { 'table-insert-column-before': '', 'table-insert-row-above': '', 'table-insert-row-after': '', - 'table': '', + table: '', 'table-delete-table': '', }; - /** * @param {Editor} editor */ export function registerCustomIcons(editor) { - for (const [name, svg] of Object.entries(icons)) { editor.ui.registry.addIcon(name, svg); } -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugin-codeeditor.js b/resources/js/wysiwyg/plugin-codeeditor.js index 9e681486d..fa3804ea8 100644 --- a/resources/js/wysiwyg/plugin-codeeditor.js +++ b/resources/js/wysiwyg/plugin-codeeditor.js @@ -10,8 +10,8 @@ function elemIsCodeBlock(elem) { */ function showPopup(editor, code, language, callback) { window.$components.first('code-editor').open(code, language, (newCode, newLang) => { - callback(newCode, newLang) - editor.focus() + callback(newCode, newLang); + editor.focus(); }); } @@ -59,7 +59,7 @@ function defineCodeBlockCustomElement(editor) { } getLanguage() { - const getLanguageFromClassList = (classes) => { + const getLanguageFromClassList = classes => { const langClasses = classes.split(' ').filter(cssClass => cssClass.startsWith('language-')); return (langClasses[0] || '').replace('language-', ''); }; @@ -114,12 +114,14 @@ function defineCodeBlockCustomElement(editor) { this.style.height = `${height}px`; const container = this.shadowRoot.querySelector('.CodeMirrorContainer'); - const renderEditor = (Code) => { + const renderEditor = Code => { this.editor = Code.wysiwygView(container, this.shadowRoot, content, this.getLanguage()); - setTimeout(() => this.style.height = null, 12); + setTimeout(() => { + this.style.height = null; + }, 12); }; - window.importVersioned('code').then((Code) => { + window.importVersioned('code').then(Code => { const timeout = (Date.now() - connectedTime < 20) ? 20 : 0; setTimeout(() => renderEditor(Code), timeout); }); @@ -135,26 +137,24 @@ function defineCodeBlockCustomElement(editor) { } } } + } win.customElements.define('code-block', CodeBlockElement); } - /** * @param {Editor} editor - * @param {String} url */ -function register(editor, url) { - - editor.ui.registry.addIcon('codeblock', '') +function register(editor) { + editor.ui.registry.addIcon('codeblock', ''); editor.ui.registry.addButton('codeeditor', { tooltip: 'Insert code block', icon: 'codeblock', onAction() { editor.execCommand('codeeditor'); - } + }, }); editor.ui.registry.addButton('editcodeeditor', { @@ -162,7 +162,7 @@ function register(editor, url) { icon: 'edit-block', onAction() { editor.execCommand('codeeditor'); - } + }, }); editor.addCommand('codeeditor', () => { @@ -184,17 +184,17 @@ function register(editor, url) { } }); - editor.on('dblclick', event => { - let selectedNode = editor.selection.getNode(); + editor.on('dblclick', () => { + const selectedNode = editor.selection.getNode(); if (elemIsCodeBlock(selectedNode)) { showPopupForCodeBlock(editor, selectedNode); } }); editor.on('PreInit', () => { - editor.parser.addNodeFilter('pre', function(elms) { + editor.parser.addNodeFilter('pre', elms => { for (const el of elms) { - const wrapper = tinymce.html.Node.create('code-block', { + const wrapper = window.tinymce.html.Node.create('code-block', { contenteditable: 'false', }); @@ -207,13 +207,13 @@ function register(editor, url) { } }); - editor.parser.addNodeFilter('code-block', function(elms) { + editor.parser.addNodeFilter('code-block', elms => { for (const el of elms) { el.attr('contenteditable', 'false'); } }); - editor.serializer.addNodeFilter('code-block', function(elms) { + editor.serializer.addNodeFilter('code-block', elms => { for (const el of elms) { el.unwrap(); } @@ -221,12 +221,12 @@ function register(editor, url) { }); editor.ui.registry.addContextToolbar('codeeditor', { - predicate: function (node) { + predicate(node) { return node.nodeName.toLowerCase() === 'code-block'; }, items: 'editcodeeditor', position: 'node', - scope: 'node' + scope: 'node', }); editor.on('PreInit', () => { @@ -235,9 +235,8 @@ function register(editor, url) { } /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugin-drawio.js b/resources/js/wysiwyg/plugin-drawio.js index 9f4a065ad..7b1750786 100644 --- a/resources/js/wysiwyg/plugin-drawio.js +++ b/resources/js/wysiwyg/plugin-drawio.js @@ -1,4 +1,4 @@ -import DrawIO from "../services/drawio"; +import * as DrawIO from '../services/drawio'; let pageEditor = null; let currentNode = null; @@ -16,12 +16,12 @@ function showDrawingManager(mceEditor, selectedNode = null) { pageEditor = mceEditor; currentNode = selectedNode; - /** @type {ImageManager} **/ + /** @type {ImageManager} * */ const imageManager = window.$components.first('image-manager'); - imageManager.show(function (image) { + imageManager.show(image => { if (selectedNode) { const imgElem = selectedNode.querySelector('img'); - pageEditor.undoManager.transact(function () { + pageEditor.undoManager.transact(() => { pageEditor.dom.setAttrib(imgElem, 'src', image.url); pageEditor.dom.setAttrib(selectedNode, 'drawio-diagram', image.id); }); @@ -32,32 +32,26 @@ function showDrawingManager(mceEditor, selectedNode = null) { }, 'drawio'); } -function showDrawingEditor(mceEditor, selectedNode = null) { - pageEditor = mceEditor; - currentNode = selectedNode; - DrawIO.show(options.drawioUrl, drawingInit, updateContent); -} - async function updateContent(pngData) { - const id = "image-" + Math.random().toString(16).slice(2); + const id = `image-${Math.random().toString(16).slice(2)}`; const loadingImage = window.baseUrl('/loading.gif'); - const handleUploadError = (error) => { + const handleUploadError = error => { if (error.status === 413) { window.$events.emit('error', options.translations.serverUploadLimitText); } else { window.$events.emit('error', options.translations.imageUploadErrorText); } - console.log(error); + console.error(error); }; // Handle updating an existing image if (currentNode) { DrawIO.close(); - let imgElem = currentNode.querySelector('img'); + const imgElem = currentNode.querySelector('img'); try { const img = await DrawIO.upload(pngData, options.pageId); - pageEditor.undoManager.transact(function () { + pageEditor.undoManager.transact(() => { pageEditor.dom.setAttrib(imgElem, 'src', img.url); pageEditor.dom.setAttrib(currentNode, 'drawio-diagram', img.id); }); @@ -72,7 +66,7 @@ async function updateContent(pngData) { DrawIO.close(); try { const img = await DrawIO.upload(pngData, options.pageId); - pageEditor.undoManager.transact(function () { + pageEditor.undoManager.transact(() => { pageEditor.dom.setAttrib(id, 'src', img.url); pageEditor.dom.get(id).parentNode.setAttribute('drawio-diagram', img.id); }); @@ -83,7 +77,6 @@ async function updateContent(pngData) { }, 5); } - function drawingInit() { if (!currentNode) { return Promise.resolve(''); @@ -93,6 +86,66 @@ function drawingInit() { return DrawIO.load(drawingId); } +function showDrawingEditor(mceEditor, selectedNode = null) { + pageEditor = mceEditor; + currentNode = selectedNode; + DrawIO.show(options.drawioUrl, drawingInit, updateContent); +} + +/** + * @param {Editor} editor + */ +function register(editor) { + editor.addCommand('drawio', () => { + const selectedNode = editor.selection.getNode(); + showDrawingEditor(editor, isDrawing(selectedNode) ? selectedNode : null); + }); + + editor.ui.registry.addIcon('diagram', ``); + + editor.ui.registry.addSplitButton('drawio', { + tooltip: 'Insert/edit drawing', + icon: 'diagram', + onAction() { + editor.execCommand('drawio'); + // Hack to de-focus the tinymce editor toolbar + window.document.body.dispatchEvent(new Event('mousedown', {bubbles: true})); + }, + fetch(callback) { + callback([ + { + type: 'choiceitem', + text: 'Drawing manager', + value: 'drawing-manager', + }, + ]); + }, + onItemAction(api, value) { + if (value === 'drawing-manager') { + const selectedNode = editor.selection.getNode(); + showDrawingManager(editor, isDrawing(selectedNode) ? selectedNode : null); + } + }, + }); + + editor.on('dblclick', () => { + const selectedNode = editor.selection.getNode(); + if (!isDrawing(selectedNode)) return; + showDrawingEditor(editor, selectedNode); + }); + + editor.on('SetContent', () => { + const drawings = editor.dom.select('body > div[drawio-diagram]'); + if (!drawings.length) return; + + editor.undoManager.transact(() => { + for (const drawing of drawings) { + drawing.setAttribute('contenteditable', 'false'); + } + }); + }); +} + /** * * @param {WysiwygConfigOptions} providedOptions @@ -100,56 +153,5 @@ function drawingInit() { */ export function getPlugin(providedOptions) { options = providedOptions; - return function(editor, url) { - - editor.addCommand('drawio', () => { - const selectedNode = editor.selection.getNode(); - showDrawingEditor(editor, isDrawing(selectedNode) ? selectedNode : null); - }); - - editor.ui.registry.addIcon('diagram', ``) - - editor.ui.registry.addSplitButton('drawio', { - tooltip: 'Insert/edit drawing', - icon: 'diagram', - onAction() { - editor.execCommand('drawio'); - // Hack to de-focus the tinymce editor toolbar - window.document.body.dispatchEvent(new Event('mousedown', {bubbles: true})); - }, - fetch(callback) { - callback([ - { - type: 'choiceitem', - text: 'Drawing manager', - value: 'drawing-manager', - } - ]); - }, - onItemAction(api, value) { - if (value === 'drawing-manager') { - const selectedNode = editor.selection.getNode(); - showDrawingManager(editor, isDrawing(selectedNode) ? selectedNode : null); - } - } - }); - - editor.on('dblclick', event => { - let selectedNode = editor.selection.getNode(); - if (!isDrawing(selectedNode)) return; - showDrawingEditor(editor, selectedNode); - }); - - editor.on('SetContent', function () { - const drawings = editor.dom.select('body > div[drawio-diagram]'); - if (!drawings.length) return; - - editor.undoManager.transact(function () { - for (const drawing of drawings) { - drawing.setAttribute('contenteditable', 'false'); - } - }); - }); - - }; -} \ No newline at end of file + return register; +} diff --git a/resources/js/wysiwyg/plugins-about.js b/resources/js/wysiwyg/plugins-about.js index 1585de72d..096b4f968 100644 --- a/resources/js/wysiwyg/plugins-about.js +++ b/resources/js/wysiwyg/plugins-about.js @@ -1,9 +1,7 @@ /** * @param {Editor} editor - * @param {String} url */ -function register(editor, url) { - +function register(editor) { const aboutDialog = { title: 'About the WYSIWYG Editor', url: window.baseUrl('/help/wysiwyg'), @@ -13,17 +11,14 @@ function register(editor, url) { icon: 'help', tooltip: 'About the editor', onAction() { - tinymce.activeEditor.windowManager.openUrl(aboutDialog); - } + window.tinymce.activeEditor.windowManager.openUrl(aboutDialog); + }, }); - } - /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugins-customhr.js b/resources/js/wysiwyg/plugins-customhr.js index df1984d4e..f5da947f2 100644 --- a/resources/js/wysiwyg/plugins-customhr.js +++ b/resources/js/wysiwyg/plugins-customhr.js @@ -1,12 +1,11 @@ /** * @param {Editor} editor - * @param {String} url */ -function register(editor, url) { - editor.addCommand('InsertHorizontalRule', function () { - let hrElem = document.createElement('hr'); - let cNode = editor.selection.getNode(); - let parentNode = cNode.parentNode; +function register(editor) { + editor.addCommand('InsertHorizontalRule', () => { + const hrElem = document.createElement('hr'); + const cNode = editor.selection.getNode(); + const {parentNode} = cNode; parentNode.insertBefore(hrElem, cNode); }); @@ -15,15 +14,13 @@ function register(editor, url) { tooltip: 'Insert horizontal line', onAction() { editor.execCommand('InsertHorizontalRule'); - } + }, }); } - /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugins-details.js b/resources/js/wysiwyg/plugins-details.js index 44a0a35ab..c4a6d927d 100644 --- a/resources/js/wysiwyg/plugins-details.js +++ b/resources/js/wysiwyg/plugins-details.js @@ -1,102 +1,4 @@ -/** - * @param {Editor} editor - * @param {String} url - */ -import {blockElementTypes} from "./util"; - -function register(editor, url) { - - editor.ui.registry.addIcon('details', ''); - editor.ui.registry.addIcon('togglefold', ''); - editor.ui.registry.addIcon('togglelabel', ''); - - editor.ui.registry.addButton('details', { - icon: 'details', - tooltip: 'Insert collapsible block', - onAction() { - editor.execCommand('InsertDetailsBlock'); - } - }); - - editor.ui.registry.addButton('removedetails', { - icon: 'table-delete-table', - tooltip: 'Unwrap', - onAction() { - unwrapDetailsInSelection(editor) - } - }); - - editor.ui.registry.addButton('editdetials', { - icon: 'togglelabel', - tooltip: 'Edit label', - onAction() { - showDetailLabelEditWindow(editor); - } - }); - - editor.on('dblclick', event => { - if (!getSelectedDetailsBlock(editor) || event.target.closest('doc-root')) return; - showDetailLabelEditWindow(editor); - }); - - editor.ui.registry.addButton('toggledetails', { - icon: 'togglefold', - tooltip: 'Toggle open/closed', - onAction() { - const details = getSelectedDetailsBlock(editor); - details.toggleAttribute('open'); - editor.focus(); - } - }); - - editor.addCommand('InsertDetailsBlock', function () { - let content = editor.selection.getContent({format: 'html'}); - const details = document.createElement('details'); - const summary = document.createElement('summary'); - const id = 'details-' + Date.now(); - details.setAttribute('data-id', id) - details.appendChild(summary); - - if (!content) { - content = '


    '; - } - - details.innerHTML += content; - editor.insertContent(details.outerHTML); - editor.focus(); - - const domDetails = editor.dom.select(`[data-id="${id}"]`)[0] || null; - if (domDetails) { - const firstChild = domDetails.querySelector('doc-root > *'); - if (firstChild) { - firstChild.focus(); - } - domDetails.removeAttribute('data-id'); - } - }); - - editor.ui.registry.addContextToolbar('details', { - predicate: function (node) { - return node.nodeName.toLowerCase() === 'details'; - }, - items: 'editdetials toggledetails removedetails', - position: 'node', - scope: 'node' - }); - - editor.on('PreInit', () => { - setupElementFilters(editor); - }); -} - -/** - * @param {Editor} editor - */ -function showDetailLabelEditWindow(editor) { - const details = getSelectedDetailsBlock(editor); - const dialog = editor.windowManager.open(detailsDialog(editor)); - dialog.setData({summary: getSummaryTextFromDetails(details)}); -} +import {blockElementTypes} from './util'; /** * @param {Editor} editor @@ -105,15 +7,18 @@ function getSelectedDetailsBlock(editor) { return editor.selection.getNode().closest('details'); } -/** - * @param {Element} element - */ -function getSummaryTextFromDetails(element) { - const summary = element.querySelector('summary'); - if (!summary) { - return ''; - } - return summary.textContent; +function setSummary(editor, summaryContent) { + const details = getSelectedDetailsBlock(editor); + if (!details) return; + + editor.undoManager.transact(() => { + let summary = details.querySelector('summary'); + if (!summary) { + summary = document.createElement('summary'); + details.prepend(summary); + } + summary.textContent = summaryContent; + }); } /** @@ -135,34 +40,40 @@ function detailsDialog(editor) { buttons: [ { type: 'cancel', - text: 'Cancel' + text: 'Cancel', }, { type: 'submit', text: 'Save', primary: true, - } + }, ], onSubmit(api) { const {summary} = api.getData(); setSummary(editor, summary); api.close(); - } - } + }, + }; } -function setSummary(editor, summaryContent) { - const details = getSelectedDetailsBlock(editor); - if (!details) return; +/** + * @param {Element} element + */ +function getSummaryTextFromDetails(element) { + const summary = element.querySelector('summary'); + if (!summary) { + return ''; + } + return summary.textContent; +} - editor.undoManager.transact(() => { - let summary = details.querySelector('summary'); - if (!summary) { - summary = document.createElement('summary'); - details.prepend(summary); - } - summary.textContent = summaryContent; - }); +/** + * @param {Editor} editor + */ +function showDetailLabelEditWindow(editor) { + const details = getSelectedDetailsBlock(editor); + const dialog = editor.windowManager.open(detailsDialog(editor)); + dialog.setData({summary: getSummaryTextFromDetails(details)}); } /** @@ -187,59 +98,6 @@ function unwrapDetailsInSelection(editor) { editor.selection.moveToBookmark(selectionBm); } -/** - * @param {Editor} editor - */ -function setupElementFilters(editor) { - editor.parser.addNodeFilter('details', function(elms) { - for (const el of elms) { - ensureDetailsWrappedInEditable(el); - } - }); - - editor.serializer.addNodeFilter('details', function(elms) { - for (const el of elms) { - unwrapDetailsEditable(el); - el.attr('open', null); - } - }); - - editor.serializer.addNodeFilter('doc-root', function(elms) { - for (const el of elms) { - el.unwrap(); - } - }); -} - -/** - * @param {tinymce.html.Node} detailsEl - */ -function ensureDetailsWrappedInEditable(detailsEl) { - unwrapDetailsEditable(detailsEl); - - detailsEl.attr('contenteditable', 'false'); - const rootWrap = tinymce.html.Node.create('doc-root', {contenteditable: 'true'}); - let previousBlockWrap = null; - - for (const child of detailsEl.children()) { - if (child.name === 'summary') continue; - const isBlock = blockElementTypes.includes(child.name); - - if (!isBlock) { - if (!previousBlockWrap) { - previousBlockWrap = tinymce.html.Node.create('p'); - rootWrap.append(previousBlockWrap); - } - previousBlockWrap.append(child); - } else { - rootWrap.append(child); - previousBlockWrap = null; - } - } - - detailsEl.append(rootWrap); -} - /** * @param {tinymce.html.Node} detailsEl */ @@ -258,11 +116,149 @@ function unwrapDetailsEditable(detailsEl) { } } +/** + * @param {tinymce.html.Node} detailsEl + */ +function ensureDetailsWrappedInEditable(detailsEl) { + unwrapDetailsEditable(detailsEl); + + detailsEl.attr('contenteditable', 'false'); + const rootWrap = window.tinymce.html.Node.create('doc-root', {contenteditable: 'true'}); + let previousBlockWrap = null; + + for (const child of detailsEl.children()) { + if (child.name === 'summary') continue; + const isBlock = blockElementTypes.includes(child.name); + + if (!isBlock) { + if (!previousBlockWrap) { + previousBlockWrap = window.tinymce.html.Node.create('p'); + rootWrap.append(previousBlockWrap); + } + previousBlockWrap.append(child); + } else { + rootWrap.append(child); + previousBlockWrap = null; + } + } + + detailsEl.append(rootWrap); +} + +/** + * @param {Editor} editor + */ +function setupElementFilters(editor) { + editor.parser.addNodeFilter('details', elms => { + for (const el of elms) { + ensureDetailsWrappedInEditable(el); + } + }); + + editor.serializer.addNodeFilter('details', elms => { + for (const el of elms) { + unwrapDetailsEditable(el); + el.attr('open', null); + } + }); + + editor.serializer.addNodeFilter('doc-root', elms => { + for (const el of elms) { + el.unwrap(); + } + }); +} + +/** + * @param {Editor} editor + */ +function register(editor) { + editor.ui.registry.addIcon('details', ''); + editor.ui.registry.addIcon('togglefold', ''); + editor.ui.registry.addIcon('togglelabel', ''); + + editor.ui.registry.addButton('details', { + icon: 'details', + tooltip: 'Insert collapsible block', + onAction() { + editor.execCommand('InsertDetailsBlock'); + }, + }); + + editor.ui.registry.addButton('removedetails', { + icon: 'table-delete-table', + tooltip: 'Unwrap', + onAction() { + unwrapDetailsInSelection(editor); + }, + }); + + editor.ui.registry.addButton('editdetials', { + icon: 'togglelabel', + tooltip: 'Edit label', + onAction() { + showDetailLabelEditWindow(editor); + }, + }); + + editor.on('dblclick', event => { + if (!getSelectedDetailsBlock(editor) || event.target.closest('doc-root')) return; + showDetailLabelEditWindow(editor); + }); + + editor.ui.registry.addButton('toggledetails', { + icon: 'togglefold', + tooltip: 'Toggle open/closed', + onAction() { + const details = getSelectedDetailsBlock(editor); + details.toggleAttribute('open'); + editor.focus(); + }, + }); + + editor.addCommand('InsertDetailsBlock', () => { + let content = editor.selection.getContent({format: 'html'}); + const details = document.createElement('details'); + const summary = document.createElement('summary'); + const id = `details-${Date.now()}`; + details.setAttribute('data-id', id); + details.appendChild(summary); + + if (!content) { + content = '


    '; + } + + details.innerHTML += content; + editor.insertContent(details.outerHTML); + editor.focus(); + + const domDetails = editor.dom.select(`[data-id="${id}"]`)[0] || null; + if (domDetails) { + const firstChild = domDetails.querySelector('doc-root > *'); + if (firstChild) { + firstChild.focus(); + } + domDetails.removeAttribute('data-id'); + } + }); + + editor.ui.registry.addContextToolbar('details', { + predicate(node) { + return node.nodeName.toLowerCase() === 'details'; + }, + items: 'editdetials toggledetails removedetails', + position: 'node', + scope: 'node', + }); + + editor.on('PreInit', () => { + setupElementFilters(editor); + }); +} /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugins-imagemanager.js b/resources/js/wysiwyg/plugins-imagemanager.js index 6969a50e2..37b5bfafd 100644 --- a/resources/js/wysiwyg/plugins-imagemanager.js +++ b/resources/js/wysiwyg/plugins-imagemanager.js @@ -1,32 +1,29 @@ /** * @param {Editor} editor - * @param {String} url */ -function register(editor, url) { +function register(editor) { // Custom Image picker button editor.ui.registry.addButton('imagemanager-insert', { title: 'Insert image', icon: 'image', tooltip: 'Insert image', onAction() { - /** @type {ImageManager} **/ + /** @type {ImageManager} * */ const imageManager = window.$components.first('image-manager'); - imageManager.show(function (image) { + imageManager.show(image => { const imageUrl = image.thumbs.display || image.url; let html = ``; html += `${image.name}`; html += ''; editor.execCommand('mceInsertContent', false, html); }, 'gallery'); - } + }, }); } - /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugins-stub.js b/resources/js/wysiwyg/plugins-stub.js index d220ac02d..38725a180 100644 --- a/resources/js/wysiwyg/plugins-stub.js +++ b/resources/js/wysiwyg/plugins-stub.js @@ -6,11 +6,10 @@ function register(editor, url) { } - /** * @param {WysiwygConfigOptions} options * @return {register} */ export function getPlugin(options) { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/plugins-tasklist.js b/resources/js/wysiwyg/plugins-tasklist.js index 4afbfa8e6..191f83649 100644 --- a/resources/js/wysiwyg/plugins-tasklist.js +++ b/resources/js/wysiwyg/plugins-tasklist.js @@ -1,97 +1,3 @@ -/** - * @param {Editor} editor - * @param {String} url - */ -function register(editor, url) { - - // Tasklist UI buttons - editor.ui.registry.addIcon('tasklist', ''); - editor.ui.registry.addToggleButton('tasklist', { - tooltip: 'Task list', - icon: 'tasklist', - active: false, - onAction(api) { - if (api.isActive()) { - editor.execCommand('RemoveList'); - } else { - editor.execCommand('InsertUnorderedList', null, { - 'list-item-attributes': { - class: 'task-list-item', - }, - 'list-style-type': 'tasklist', - }); - } - }, - onSetup(api) { - editor.on('NodeChange', event => { - const parentListEl = event.parents.find(el => el.nodeName === 'LI'); - const inList = parentListEl && parentListEl.classList.contains('task-list-item'); - api.setActive(Boolean(inList)); - }); - } - }); - - // Tweak existing bullet list button active state to not be active - // when we're in a task list. - const existingBullListButton = editor.ui.registry.getAll().buttons.bullist; - existingBullListButton.onSetup = function(api) { - editor.on('NodeChange', event => { - const parentList = event.parents.find(el => el.nodeName === 'LI'); - const inTaskList = parentList && parentList.classList.contains('task-list-item'); - const inUlList = parentList && parentList.parentNode.nodeName === 'UL'; - api.setActive(Boolean(inUlList && !inTaskList)); - }); - }; - existingBullListButton.onAction = function() { - // Cheeky hack to prevent list toggle action treating tasklists as normal - // unordered lists which would unwrap the list on toggle from tasklist to bullet list. - // Instead we quickly jump through an ordered list first if we're within a tasklist. - if (elementWithinTaskList(editor.selection.getNode())) { - editor.execCommand('InsertOrderedList', null, { - 'list-item-attributes': {class: null} - }); - } - - editor.execCommand('InsertUnorderedList', null, { - 'list-item-attributes': {class: null} - }); - }; - // Tweak existing number list to not allow classes on child items - const existingNumListButton = editor.ui.registry.getAll().buttons.numlist; - existingNumListButton.onAction = function() { - editor.execCommand('InsertOrderedList', null, { - 'list-item-attributes': {class: null} - }); - }; - - // Setup filters on pre-init - editor.on('PreInit', () => { - editor.parser.addNodeFilter('li', function(nodes) { - for (const node of nodes) { - if (node.attributes.map.class === 'task-list-item') { - parseTaskListNode(node); - } - } - }); - editor.serializer.addNodeFilter('li', function(nodes) { - for (const node of nodes) { - if (node.attributes.map.class === 'task-list-item') { - serializeTaskListNode(node); - } - } - }); - }); - - // Handle checkbox click in editor - editor.on('click', function(event) { - const clickedEl = event.target; - if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) { - handleTaskListItemClick(event, clickedEl, editor); - event.preventDefault(); - } - }); -} - /** * @param {Element} element * @return {boolean} @@ -109,16 +15,16 @@ function elementWithinTaskList(element) { function handleTaskListItemClick(event, clickedEl, editor) { const bounds = clickedEl.getBoundingClientRect(); const withinBounds = event.clientX <= bounds.right - && event.clientX >= bounds.left - && event.clientY >= bounds.top - && event.clientY <= bounds.bottom; + && event.clientX >= bounds.left + && event.clientY >= bounds.top + && event.clientY <= bounds.bottom; // Outside of the task list item bounds mean we're probably clicking the pseudo-element. if (!withinBounds) { editor.undoManager.transact(() => { if (clickedEl.hasAttribute('checked')) { clickedEl.removeAttribute('checked'); - } else { + } else { clickedEl.setAttribute('checked', 'checked'); } }); @@ -157,15 +63,111 @@ function serializeTaskListNode(node) { } // Create & insert checkbox input element - const checkbox = tinymce.html.Node.create('input', inputAttrs); + const checkbox = window.tinymce.html.Node.create('input', inputAttrs); checkbox.shortEnded = true; - node.firstChild ? node.insert(checkbox, node.firstChild, true) : node.append(checkbox); + + if (node.firstChild) { + node.insert(checkbox, node.firstChild, true); + } else { + node.append(checkbox); + } +} + +/** + * @param {Editor} editor + */ +function register(editor) { + // Tasklist UI buttons + editor.ui.registry.addIcon('tasklist', ''); + editor.ui.registry.addToggleButton('tasklist', { + tooltip: 'Task list', + icon: 'tasklist', + active: false, + onAction(api) { + if (api.isActive()) { + editor.execCommand('RemoveList'); + } else { + editor.execCommand('InsertUnorderedList', null, { + 'list-item-attributes': { + class: 'task-list-item', + }, + 'list-style-type': 'tasklist', + }); + } + }, + onSetup(api) { + editor.on('NodeChange', event => { + const parentListEl = event.parents.find(el => el.nodeName === 'LI'); + const inList = parentListEl && parentListEl.classList.contains('task-list-item'); + api.setActive(Boolean(inList)); + }); + }, + }); + + // Tweak existing bullet list button active state to not be active + // when we're in a task list. + const existingBullListButton = editor.ui.registry.getAll().buttons.bullist; + existingBullListButton.onSetup = function customBullListOnSetup(api) { + editor.on('NodeChange', event => { + const parentList = event.parents.find(el => el.nodeName === 'LI'); + const inTaskList = parentList && parentList.classList.contains('task-list-item'); + const inUlList = parentList && parentList.parentNode.nodeName === 'UL'; + api.setActive(Boolean(inUlList && !inTaskList)); + }); + }; + existingBullListButton.onAction = function customBullListOnAction() { + // Cheeky hack to prevent list toggle action treating tasklists as normal + // unordered lists which would unwrap the list on toggle from tasklist to bullet list. + // Instead we quickly jump through an ordered list first if we're within a tasklist. + if (elementWithinTaskList(editor.selection.getNode())) { + editor.execCommand('InsertOrderedList', null, { + 'list-item-attributes': {class: null}, + }); + } + + editor.execCommand('InsertUnorderedList', null, { + 'list-item-attributes': {class: null}, + }); + }; + // Tweak existing number list to not allow classes on child items + const existingNumListButton = editor.ui.registry.getAll().buttons.numlist; + existingNumListButton.onAction = function customNumListButtonOnAction() { + editor.execCommand('InsertOrderedList', null, { + 'list-item-attributes': {class: null}, + }); + }; + + // Setup filters on pre-init + editor.on('PreInit', () => { + editor.parser.addNodeFilter('li', nodes => { + for (const node of nodes) { + if (node.attributes.map.class === 'task-list-item') { + parseTaskListNode(node); + } + } + }); + editor.serializer.addNodeFilter('li', nodes => { + for (const node of nodes) { + if (node.attributes.map.class === 'task-list-item') { + serializeTaskListNode(node); + } + } + }); + }); + + // Handle checkbox click in editor + editor.on('click', event => { + const clickedEl = event.target; + if (clickedEl.nodeName === 'LI' && clickedEl.classList.contains('task-list-item')) { + handleTaskListItemClick(event, clickedEl, editor); + event.preventDefault(); + } + }); } /** - * @param {WysiwygConfigOptions} options * @return {register} */ -export function getPlugin(options) { +export function getPlugin() { return register; -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/scrolling.js b/resources/js/wysiwyg/scrolling.js index f14ef4c64..92f8f1583 100644 --- a/resources/js/wysiwyg/scrolling.js +++ b/resources/js/wysiwyg/scrolling.js @@ -1,16 +1,3 @@ -/** - * Scroll to a section dictated by the current URL query string, if present. - * Used when directly editing a specific section of the page. - * @param {Editor} editor - */ -export function scrollToQueryString(editor) { - const queryParams = (new URL(window.location)).searchParams; - const scrollId = queryParams.get('content-id'); - if (scrollId) { - scrollToText(editor, scrollId); - } -} - /** * @param {Editor} editor * @param {String} scrollId @@ -26,4 +13,17 @@ function scrollToText(editor, scrollId) { editor.selection.select(element, true); editor.selection.collapse(false); editor.focus(); -} \ No newline at end of file +} + +/** + * Scroll to a section dictated by the current URL query string, if present. + * Used when directly editing a specific section of the page. + * @param {Editor} editor + */ +export function scrollToQueryString(editor) { + const queryParams = (new URL(window.location)).searchParams; + const scrollId = queryParams.get('content-id'); + if (scrollId) { + scrollToText(editor, scrollId); + } +} diff --git a/resources/js/wysiwyg/shortcuts.js b/resources/js/wysiwyg/shortcuts.js index ef364ddad..1c20df9c5 100644 --- a/resources/js/wysiwyg/shortcuts.js +++ b/resources/js/wysiwyg/shortcuts.js @@ -4,7 +4,7 @@ export function register(editor) { // Headers for (let i = 1; i < 5; i++) { - editor.shortcuts.add('meta+' + i, '', ['FormatBlock', false, 'h' + (i+1)]); + editor.shortcuts.add(`meta+${i}`, '', ['FormatBlock', false, `h${i + 1}`]); } // Other block shortcuts @@ -30,24 +30,25 @@ export function register(editor) { }); // Loop through callout styles - editor.shortcuts.add('meta+9', '', function() { + editor.shortcuts.add('meta+9', '', () => { const selectedNode = editor.selection.getNode(); const callout = selectedNode ? selectedNode.closest('.callout') : null; const formats = ['info', 'success', 'warning', 'danger']; - const currentFormatIndex = formats.findIndex(format => callout && callout.classList.contains(format)); + const currentFormatIndex = formats.findIndex(format => { + return callout && callout.classList.contains(format); + }); const newFormatIndex = (currentFormatIndex + 1) % formats.length; const newFormat = formats[newFormatIndex]; - editor.formatter.apply('callout' + newFormat); + editor.formatter.apply(`callout${newFormat}`); }); // Link selector shortcut - editor.shortcuts.add('meta+shift+K', '', function() { - /** @var {EntitySelectorPopup} **/ + editor.shortcuts.add('meta+shift+K', '', () => { + /** @var {EntitySelectorPopup} * */ const selectorPopup = window.$components.first('entity-selector-popup'); - selectorPopup.show(function(entity) { - + selectorPopup.show(entity => { if (editor.selection.isCollapsed()) { editor.insertContent(editor.dom.createHTML('a', {href: entity.link}, editor.dom.encode(entity.name))); } else { @@ -56,6 +57,6 @@ export function register(editor) { editor.selection.collapse(false); editor.focus(); - }) + }); }); -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/toolbars.js b/resources/js/wysiwyg/toolbars.js index 9debb08b5..4663ad132 100644 --- a/resources/js/wysiwyg/toolbars.js +++ b/resources/js/wysiwyg/toolbars.js @@ -13,7 +13,7 @@ export function getPrimaryToolbar(options) { 'bullist numlist listoverflow', textDirPlugins, 'link table imagemanager-insert insertoverflow', - 'code about fullscreen' + 'code about fullscreen', ]; return toolbar.filter(row => Boolean(row)).join(' | '); @@ -26,17 +26,17 @@ function registerPrimaryToolbarGroups(editor) { editor.ui.registry.addGroupToolbarButton('formatoverflow', { icon: 'more-drawer', tooltip: 'More', - items: 'strikethrough superscript subscript inlinecode removeformat' + items: 'strikethrough superscript subscript inlinecode removeformat', }); editor.ui.registry.addGroupToolbarButton('listoverflow', { icon: 'more-drawer', tooltip: 'More', - items: 'tasklist outdent indent' + items: 'tasklist outdent indent', }); editor.ui.registry.addGroupToolbarButton('insertoverflow', { icon: 'more-drawer', tooltip: 'More', - items: 'customhr codeeditor drawio media details' + items: 'customhr codeeditor drawio media details', }); } @@ -50,7 +50,7 @@ function registerLinkContextToolbar(editor) { }, position: 'node', scope: 'node', - items: 'link unlink openlink' + items: 'link unlink openlink', }); } @@ -64,16 +64,15 @@ function registerImageContextToolbar(editor) { }, position: 'node', scope: 'node', - items: 'image' + items: 'image', }); } /** * @param {Editor} editor - * @param {WysiwygConfigOptions} options */ -export function registerAdditionalToolbars(editor, options) { +export function registerAdditionalToolbars(editor) { registerPrimaryToolbarGroups(editor); registerLinkContextToolbar(editor); registerImageContextToolbar(editor); -} \ No newline at end of file +} diff --git a/resources/js/wysiwyg/util.js b/resources/js/wysiwyg/util.js index 1f63b6529..68b6aabfc 100644 --- a/resources/js/wysiwyg/util.js +++ b/resources/js/wysiwyg/util.js @@ -1,5 +1,3 @@ - - export const blockElementTypes = [ 'p', 'h1', @@ -15,5 +13,5 @@ export const blockElementTypes = [ 'details', 'ul', 'ol', - 'table' -]; \ No newline at end of file + 'table', +];