diff --git a/package-lock.json b/package-lock.json index 7f603811..4c6a7e07 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "ladnodavaytebezroflov-frontend", - "version": "0.2.1", + "version": "0.2.2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "ladnodavaytebezroflov-frontend", - "version": "0.2.1", + "version": "0.2.2", "license": "GPL-3.0-or-later", "dependencies": { "body-parser": "^1.19.0", @@ -26,6 +26,7 @@ "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.3.0", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "^2.4.4", "node-sass": "^6.0.1", @@ -1818,6 +1819,56 @@ "node": ">=6" } }, + "node_modules/@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/@types/cssnano": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/cssnano/-/cssnano-4.0.1.tgz", + "integrity": "sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q==", + "dev": true, + "dependencies": { + "postcss": "5 - 7" + } + }, + "node_modules/@types/cssnano/node_modules/picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "node_modules/@types/cssnano/node_modules/postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "dependencies": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + } + }, + "node_modules/@types/cssnano/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@types/eslint": { "version": "7.28.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz", @@ -2188,6 +2239,45 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, "node_modules/ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -2197,6 +2287,12 @@ "ajv": "^6.9.1" } }, + "node_modules/alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, "node_modules/amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -2985,6 +3081,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "dependencies": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001278", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001278.tgz", @@ -3279,6 +3387,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "node_modules/colord": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "dev": true + }, "node_modules/colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", @@ -3547,6 +3661,21 @@ "node": ">=8" } }, + "node_modules/css-declaration-sorter": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz", + "integrity": "sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA==", + "dev": true, + "dependencies": { + "timsort": "^0.3.0" + }, + "engines": { + "node": ">= 10" + }, + "peerDependencies": { + "postcss": "^8.0.9" + } + }, "node_modules/css-loader": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.5.1.tgz", @@ -3588,6 +3717,104 @@ "node": ">=10" } }, + "node_modules/css-minimizer-webpack-plugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.0.tgz", + "integrity": "sha512-+SU5aHgGZkk2kxKsq/BZXnYee2cjHIiFARF2gGaG6gIFtLJ87330GeafqhxAemwi/WgQ40v0OQ7pBVljKAMoXg==", + "dev": true, + "dependencies": { + "@types/cssnano": "^4.0.1", + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "clean-css": { + "optional": true + }, + "csso": { + "optional": true + }, + "esbuild": { + "optional": true + } + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/css-minimizer-webpack-plugin/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/css-select": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", @@ -3604,6 +3831,28 @@ "url": "https://github.com/sponsors/fb55" } }, + "node_modules/css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "dependencies": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/css-tree/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/css-what": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", @@ -3628,6 +3877,95 @@ "node": ">=4" } }, + "node_modules/cssnano": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.13.tgz", + "integrity": "sha512-cAmLruIF28a7vKIOieXCTrllaLwbouxV1PPi8Z4M+XloXbmeooWAu4KhJgASo4vQUwbs2pqDgAlnZ1ZKJZKtuw==", + "dev": true, + "dependencies": { + "cssnano-preset-default": "^5.1.9", + "is-resolvable": "^1.1.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/cssnano" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-preset-default": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz", + "integrity": "sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA==", + "dev": true, + "dependencies": { + "css-declaration-sorter": "^6.0.3", + "cssnano-utils": "^2.0.1", + "postcss-calc": "^8.0.0", + "postcss-colormin": "^5.2.2", + "postcss-convert-values": "^5.0.2", + "postcss-discard-comments": "^5.0.1", + "postcss-discard-duplicates": "^5.0.1", + "postcss-discard-empty": "^5.0.1", + "postcss-discard-overridden": "^5.0.1", + "postcss-merge-longhand": "^5.0.4", + "postcss-merge-rules": "^5.0.3", + "postcss-minify-font-values": "^5.0.1", + "postcss-minify-gradients": "^5.0.3", + "postcss-minify-params": "^5.0.2", + "postcss-minify-selectors": "^5.1.0", + "postcss-normalize-charset": "^5.0.1", + "postcss-normalize-display-values": "^5.0.1", + "postcss-normalize-positions": "^5.0.1", + "postcss-normalize-repeat-style": "^5.0.1", + "postcss-normalize-string": "^5.0.1", + "postcss-normalize-timing-functions": "^5.0.1", + "postcss-normalize-unicode": "^5.0.1", + "postcss-normalize-url": "^5.0.4", + "postcss-normalize-whitespace": "^5.0.1", + "postcss-ordered-values": "^5.0.2", + "postcss-reduce-initial": "^5.0.2", + "postcss-reduce-transforms": "^5.0.1", + "postcss-svgo": "^5.0.3", + "postcss-unique-selectors": "^5.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/cssnano-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", + "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "dependencies": { + "css-tree": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -6205,6 +6543,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -6509,6 +6853,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lilconfig": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, "node_modules/lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -6574,6 +6927,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "node_modules/lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -6585,6 +6944,12 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -6736,6 +7101,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -7976,68 +8347,459 @@ "url": "https://opencollective.com/postcss/" } }, - "node_modules/postcss-modules-extract-imports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", - "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "node_modules/postcss-calc": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", + "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", "dev": true, - "engines": { - "node": "^10 || ^12 || >= 14" + "dependencies": { + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.2.2" } }, - "node_modules/postcss-modules-local-by-default": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", - "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "node_modules/postcss-colormin": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.2.tgz", + "integrity": "sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g==", "dev": true, "dependencies": { - "icss-utils": "^5.0.0", - "postcss-selector-parser": "^6.0.2", - "postcss-value-parser": "^4.1.0" + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^10 || ^12 || >=14.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.2.15" } }, - "node_modules/postcss-modules-scope": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", - "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "node_modules/postcss-convert-values": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz", + "integrity": "sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg==", "dev": true, "dependencies": { - "postcss-selector-parser": "^6.0.4" + "postcss-value-parser": "^4.1.0" }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^10 || ^12 || >=14.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.2.15" } }, - "node_modules/postcss-modules-values": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", - "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "node_modules/postcss-discard-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", + "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", "dev": true, - "dependencies": { - "icss-utils": "^5.0.0" - }, "engines": { - "node": "^10 || ^12 || >= 14" + "node": "^10 || ^12 || >=14.0" }, "peerDependencies": { - "postcss": "^8.1.0" + "postcss": "^8.2.15" } }, - "node_modules/postcss-selector-parser": { - "version": "6.0.6", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "node_modules/postcss-discard-duplicates": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", + "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-empty": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", + "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-discard-overridden": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", + "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-longhand": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz", + "integrity": "sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.1.0", + "stylehacks": "^5.0.1" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-merge-rules": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz", + "integrity": "sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^2.0.1", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-font-values": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", + "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-gradients": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz", + "integrity": "sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q==", + "dev": true, + "dependencies": { + "colord": "^2.9.1", + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-params": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz", + "integrity": "sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg==", + "dev": true, + "dependencies": { + "alphanum-sort": "^1.0.2", + "browserslist": "^4.16.6", + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-minify-selectors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", + "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", + "dev": true, + "dependencies": { + "alphanum-sort": "^1.0.2", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "dependencies": { + "icss-utils": "^5.0.0" + }, + "engines": { + "node": "^10 || ^12 || >= 14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-normalize-charset": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", + "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", + "dev": true, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-display-values": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", + "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", + "dev": true, + "dependencies": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-positions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", + "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-repeat-style": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", + "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", + "dev": true, + "dependencies": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-string": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", + "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-timing-functions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", + "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", + "dev": true, + "dependencies": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-unicode": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", + "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.0", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz", + "integrity": "sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg==", + "dev": true, + "dependencies": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-normalize-url/node_modules/normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/postcss-normalize-whitespace": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", + "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-ordered-values": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz", + "integrity": "sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ==", + "dev": true, + "dependencies": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-initial": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz", + "integrity": "sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-reduce-transforms": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", + "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", + "dev": true, + "dependencies": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", "dev": true, "dependencies": { @@ -8048,10 +8810,42 @@ "node": ">=4" } }, + "node_modules/postcss-svgo": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.3.tgz", + "integrity": "sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.1.0", + "svgo": "^2.7.0" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, + "node_modules/postcss-unique-selectors": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz", + "integrity": "sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA==", + "dev": true, + "dependencies": { + "alphanum-sort": "^1.0.2", + "postcss-selector-parser": "^6.0.5" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, "node_modules/postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "node_modules/prelude-ls": { @@ -9338,6 +10132,12 @@ "node": ">=0.10.0" } }, + "node_modules/stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "node_modules/statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -9465,6 +10265,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stylehacks": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", + "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", + "dev": true, + "dependencies": { + "browserslist": "^4.16.0", + "postcss-selector-parser": "^6.0.4" + }, + "engines": { + "node": "^10 || ^12 || >=14.0" + }, + "peerDependencies": { + "postcss": "^8.2.15" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9477,6 +10293,36 @@ "node": ">=4" } }, + "node_modules/svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "dependencies": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "bin": { + "svgo": "bin/svgo" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/svgo/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -9664,6 +10510,12 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, "node_modules/tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -11280,6 +12132,15 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", @@ -12673,6 +13534,45 @@ "defer-to-connect": "^1.0.1" } }, + "@trysound/sax": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", + "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==", + "dev": true + }, + "@types/cssnano": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/cssnano/-/cssnano-4.0.1.tgz", + "integrity": "sha512-hGOroxRTBkYl5gSBRJOffhV4+io+Y2bFX1VP7LgKEVHJt/LPPJaWUIuDAz74Vlp7l7hCDZfaDi7iPxwNwuVA4Q==", + "dev": true, + "requires": { + "postcss": "5 - 7" + }, + "dependencies": { + "picocolors": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-0.2.1.tgz", + "integrity": "sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==", + "dev": true + }, + "postcss": { + "version": "7.0.39", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.39.tgz", + "integrity": "sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==", + "dev": true, + "requires": { + "picocolors": "^0.2.1", + "source-map": "^0.6.1" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "@types/eslint": { "version": "7.28.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.28.2.tgz", @@ -13010,6 +13910,35 @@ "uri-js": "^4.2.2" } }, + "ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "requires": { + "ajv": "^8.0.0" + }, + "dependencies": { + "ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + } + } + }, "ajv-keywords": { "version": "3.5.2", "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", @@ -13017,6 +13946,12 @@ "dev": true, "requires": {} }, + "alphanum-sort": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz", + "integrity": "sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=", + "dev": true + }, "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", @@ -13616,6 +14551,18 @@ "quick-lru": "^4.0.1" } }, + "caniuse-api": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", + "integrity": "sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", + "lodash.memoize": "^4.1.2", + "lodash.uniq": "^4.5.0" + } + }, "caniuse-lite": { "version": "1.0.30001278", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001278.tgz", @@ -13841,6 +14788,12 @@ "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", "dev": true }, + "colord": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.2.tgz", + "integrity": "sha512-Uqbg+J445nc1TKn4FoDPS6ZZqAvEDnwrH42yo8B40JSOgSLxMZ/gt3h4nmCtPLQeXhjJJkqBx7SCY35WnIixaQ==", + "dev": true + }, "colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", @@ -14052,6 +15005,15 @@ "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", "dev": true }, + "css-declaration-sorter": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz", + "integrity": "sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA==", + "dev": true, + "requires": { + "timsort": "^0.3.0" + } + }, "css-loader": { "version": "6.5.1", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.5.1.tgz", @@ -14079,6 +15041,68 @@ } } }, + "css-minimizer-webpack-plugin": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/css-minimizer-webpack-plugin/-/css-minimizer-webpack-plugin-3.3.0.tgz", + "integrity": "sha512-+SU5aHgGZkk2kxKsq/BZXnYee2cjHIiFARF2gGaG6gIFtLJ87330GeafqhxAemwi/WgQ40v0OQ7pBVljKAMoXg==", + "dev": true, + "requires": { + "@types/cssnano": "^4.0.1", + "cssnano": "^5.0.6", + "jest-worker": "^27.0.2", + "postcss": "^8.3.5", + "schema-utils": "^4.0.0", + "serialize-javascript": "^6.0.0", + "source-map": "^0.6.1" + }, + "dependencies": { + "ajv": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.8.2.tgz", + "integrity": "sha512-x9VuX+R/jcFj1DHo/fCp99esgGDWiHENrKxaCENuCxpoMCmAt/COCGVDwA7kleEpEzJjDnvh3yGoOuLu0Dtllw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + } + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-select": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/css-select/-/css-select-4.1.3.tgz", @@ -14092,6 +15116,24 @@ "nth-check": "^2.0.0" } }, + "css-tree": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz", + "integrity": "sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==", + "dev": true, + "requires": { + "mdn-data": "2.0.14", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, "css-what": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-5.1.0.tgz", @@ -14104,6 +15146,71 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, + "cssnano": { + "version": "5.0.13", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-5.0.13.tgz", + "integrity": "sha512-cAmLruIF28a7vKIOieXCTrllaLwbouxV1PPi8Z4M+XloXbmeooWAu4KhJgASo4vQUwbs2pqDgAlnZ1ZKJZKtuw==", + "dev": true, + "requires": { + "cssnano-preset-default": "^5.1.9", + "is-resolvable": "^1.1.0", + "lilconfig": "^2.0.3", + "yaml": "^1.10.2" + } + }, + "cssnano-preset-default": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.9.tgz", + "integrity": "sha512-RhkEucqlQ+OxEi14K1p8gdXcMQy1mSpo7P1oC44oRls7BYIj8p+cht4IFBFV3W4iOjTP8EUB33XV1fX9KhDzyA==", + "dev": true, + "requires": { + "css-declaration-sorter": "^6.0.3", + "cssnano-utils": "^2.0.1", + "postcss-calc": "^8.0.0", + "postcss-colormin": "^5.2.2", + "postcss-convert-values": "^5.0.2", + "postcss-discard-comments": "^5.0.1", + "postcss-discard-duplicates": "^5.0.1", + "postcss-discard-empty": "^5.0.1", + "postcss-discard-overridden": "^5.0.1", + "postcss-merge-longhand": "^5.0.4", + "postcss-merge-rules": "^5.0.3", + "postcss-minify-font-values": "^5.0.1", + "postcss-minify-gradients": "^5.0.3", + "postcss-minify-params": "^5.0.2", + "postcss-minify-selectors": "^5.1.0", + "postcss-normalize-charset": "^5.0.1", + "postcss-normalize-display-values": "^5.0.1", + "postcss-normalize-positions": "^5.0.1", + "postcss-normalize-repeat-style": "^5.0.1", + "postcss-normalize-string": "^5.0.1", + "postcss-normalize-timing-functions": "^5.0.1", + "postcss-normalize-unicode": "^5.0.1", + "postcss-normalize-url": "^5.0.4", + "postcss-normalize-whitespace": "^5.0.1", + "postcss-ordered-values": "^5.0.2", + "postcss-reduce-initial": "^5.0.2", + "postcss-reduce-transforms": "^5.0.1", + "postcss-svgo": "^5.0.3", + "postcss-unique-selectors": "^5.0.2" + } + }, + "cssnano-utils": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz", + "integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==", + "dev": true, + "requires": {} + }, + "csso": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz", + "integrity": "sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==", + "dev": true, + "requires": { + "css-tree": "^1.1.2" + } + }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", @@ -16032,6 +17139,12 @@ "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", "dev": true }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, "is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", @@ -16270,6 +17383,12 @@ "type-check": "~0.4.0" } }, + "lilconfig": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz", + "integrity": "sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==", + "dev": true + }, "lines-and-columns": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", @@ -16325,6 +17444,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.memoize": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=", + "dev": true + }, "lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -16336,6 +17461,12 @@ "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, + "lodash.uniq": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", + "integrity": "sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=", + "dev": true + }, "log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", @@ -16444,6 +17575,12 @@ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==", "dev": true }, + "mdn-data": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.14.tgz", + "integrity": "sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==", + "dev": true + }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -17363,6 +18500,129 @@ "source-map-js": "^0.6.2" } }, + "postcss-calc": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz", + "integrity": "sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.0.2" + } + }, + "postcss-colormin": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.2.tgz", + "integrity": "sha512-tSEe3NpqWARUTidDlF0LntPkdlhXqfDFuA1yslqpvvGAfpZ7oBaw+/QXd935NKm2U9p4PED0HDZlzmMk7fVC6g==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "colord": "^2.9.1", + "postcss-value-parser": "^4.2.0" + } + }, + "postcss-convert-values": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz", + "integrity": "sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-discard-comments": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz", + "integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==", + "dev": true, + "requires": {} + }, + "postcss-discard-duplicates": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz", + "integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==", + "dev": true, + "requires": {} + }, + "postcss-discard-empty": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz", + "integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==", + "dev": true, + "requires": {} + }, + "postcss-discard-overridden": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz", + "integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==", + "dev": true, + "requires": {} + }, + "postcss-merge-longhand": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz", + "integrity": "sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0", + "stylehacks": "^5.0.1" + } + }, + "postcss-merge-rules": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz", + "integrity": "sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0", + "cssnano-utils": "^2.0.1", + "postcss-selector-parser": "^6.0.5" + } + }, + "postcss-minify-font-values": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz", + "integrity": "sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-minify-gradients": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz", + "integrity": "sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q==", + "dev": true, + "requires": { + "colord": "^2.9.1", + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-minify-params": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz", + "integrity": "sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.2", + "browserslist": "^4.16.6", + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-minify-selectors": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz", + "integrity": "sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.2", + "postcss-selector-parser": "^6.0.5" + } + }, "postcss-modules-extract-imports": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", @@ -17399,6 +18659,128 @@ "icss-utils": "^5.0.0" } }, + "postcss-normalize-charset": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz", + "integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==", + "dev": true, + "requires": {} + }, + "postcss-normalize-display-values": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz", + "integrity": "sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==", + "dev": true, + "requires": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-normalize-positions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz", + "integrity": "sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-normalize-repeat-style": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz", + "integrity": "sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==", + "dev": true, + "requires": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-normalize-string": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz", + "integrity": "sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-normalize-timing-functions": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz", + "integrity": "sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==", + "dev": true, + "requires": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-normalize-unicode": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz", + "integrity": "sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==", + "dev": true, + "requires": { + "browserslist": "^4.16.0", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-normalize-url": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.4.tgz", + "integrity": "sha512-cNj3RzK2pgQQyNp7dzq0dqpUpQ/wYtdDZM3DepPmFjCmYIfceuD9VIAcOdvrNetjIU65g1B4uwdP/Krf6AFdXg==", + "dev": true, + "requires": { + "normalize-url": "^6.0.1", + "postcss-value-parser": "^4.2.0" + }, + "dependencies": { + "normalize-url": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", + "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "dev": true + } + } + }, + "postcss-normalize-whitespace": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz", + "integrity": "sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-ordered-values": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz", + "integrity": "sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ==", + "dev": true, + "requires": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-reduce-initial": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.2.tgz", + "integrity": "sha512-v/kbAAQ+S1V5v9TJvbGkV98V2ERPdU6XvMcKMjqAlYiJ2NtsHGlKYLPjWWcXlaTKNxooId7BGxeraK8qXvzKtw==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "caniuse-api": "^3.0.0" + } + }, + "postcss-reduce-transforms": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz", + "integrity": "sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==", + "dev": true, + "requires": { + "cssnano-utils": "^2.0.1", + "postcss-value-parser": "^4.1.0" + } + }, "postcss-selector-parser": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", @@ -17409,10 +18791,30 @@ "util-deprecate": "^1.0.2" } }, + "postcss-svgo": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.3.tgz", + "integrity": "sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.1.0", + "svgo": "^2.7.0" + } + }, + "postcss-unique-selectors": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz", + "integrity": "sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA==", + "dev": true, + "requires": { + "alphanum-sort": "^1.0.2", + "postcss-selector-parser": "^6.0.5" + } + }, "postcss-value-parser": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", - "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, "prelude-ls": { @@ -18440,6 +19842,12 @@ "tweetnacl": "~0.14.0" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "statuses": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", @@ -18536,6 +19944,16 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" }, + "stylehacks": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz", + "integrity": "sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==", + "dev": true, + "requires": { + "browserslist": "^4.16.0", + "postcss-selector-parser": "^6.0.4" + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -18545,6 +19963,29 @@ "has-flag": "^3.0.0" } }, + "svgo": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz", + "integrity": "sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==", + "dev": true, + "requires": { + "@trysound/sax": "0.2.0", + "commander": "^7.2.0", + "css-select": "^4.1.3", + "css-tree": "^1.1.3", + "csso": "^4.2.0", + "picocolors": "^1.0.0", + "stable": "^0.1.8" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, "tapable": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", @@ -18671,6 +20112,12 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -19938,6 +21385,12 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, "yargs": { "version": "13.3.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", diff --git a/package.json b/package.json index 59eeaed3..9660ec7c 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "lint:fix": "npx eslint . --ext .js --fix", "test": "echo \"Error: no test specified\" && exit 1", "build-dev": "npx webpack", - "server-dev-express": "PORT=3000 node server/server.js", + "server-dev-express": "PORT=3001 node server/server.js", "server-dev": "npx webpack serve", "start-dev": "npm run server-dev", "build": "NODE_ENV='production' npx webpack", @@ -46,6 +46,7 @@ "clean-webpack-plugin": "^4.0.0", "copy-webpack-plugin": "^9.0.1", "css-loader": "^6.5.1", + "css-minimizer-webpack-plugin": "^3.3.0", "html-webpack-plugin": "^5.5.0", "mini-css-extract-plugin": "^2.4.4", "node-sass": "^6.0.1", diff --git a/public/assets/no-internet-icon.webp b/public/assets/no-internet-icon.webp new file mode 100644 index 00000000..b9cf3169 Binary files /dev/null and b/public/assets/no-internet-icon.webp differ diff --git a/server/server.js b/server/server.js index d33450db..dc651fb4 100644 --- a/server/server.js +++ b/server/server.js @@ -13,13 +13,13 @@ const port = process.env.PORT || 80; app.use(morgan('dev')); /* Время кэширования ответов, в сек */ -const cacheTime = 256 * 24 * 60 * 60; +// const cacheTime = 256 * 24 * 60 * 60; /* middleware для кэширования бандлов */ -const cacheMW = (req, res, next) => { +/* const cacheMW = (req, res, next) => { res.set('Cache-control', `public, max-age=${cacheTime}`); next(); }; -app.use([/\/bundle\.[A-Za-z0-9]*\.js/, /\/style\.[A-Za-z0-9]*\.css/], cacheMW); +app.use([/\/bundle\.[A-Za-z0-9]*\.js/, /\/style\.[A-Za-z0-9]*\.css/], cacheMW); */ /* Директория со статикой */ const distFolder = path.resolve(__dirname, '..', 'dist'); diff --git a/src/actions/serviceworker.js b/src/actions/serviceworker.js new file mode 100644 index 00000000..8d38bf4e --- /dev/null +++ b/src/actions/serviceworker.js @@ -0,0 +1,66 @@ +'use strict'; + +// Modules +import Dispatcher from '../modules/Dispatcher/Dispatcher.js'; + +/** + * Константа, содержащая в себе типы действий для собыйтий от sw. + */ +export const ServiceWorkerTypes = { + HALF_OFFLINE: 'half-offline', + FULL_OFFLINE: 'full-offline', + CLOSE_OFFLINE_MSG: 'close-offline', + SHOW_OFFLINE_MSG: 'show-offline', + HIDE_OFFLINE_MSG: 'hide-offline', +}; + +/** + * Класс, содержащий в себе действия для работы с тегами. + */ +export const serviceWorkerActions = { + + /** + * Отображает предупреждение об offline работе + */ + responseFromCache() { + Dispatcher.dispatch({ + actionName: ServiceWorkerTypes.HALF_OFFLINE, + }); + }, + + /** + * Отображает offline страницу + */ + fullOffline() { + Dispatcher.dispatch({ + actionName: ServiceWorkerTypes.FULL_OFFLINE, + }); + }, + + /** + * Сворачивает предупреждение об offline работе + */ + onCloseOfflineMessage() { + Dispatcher.dispatch({ + actionName: ServiceWorkerTypes.CLOSE_OFFLINE_MSG, + }); + }, + + /** + * Разворачивает offline сообшение + */ + onOpenOfflineMessage() { + Dispatcher.dispatch({ + actionName: ServiceWorkerTypes.SHOW_OFFLINE_MSG, + }); + }, + + /** + * Скрывает предупреждение об offline работе + */ + hideOfflineMessage() { + Dispatcher.dispatch({ + actionName: ServiceWorkerTypes.HIDE_OFFLINE_MSG, + }); + }, +}; diff --git a/src/components/OfflineMessage/OfflineMessage.hbs b/src/components/OfflineMessage/OfflineMessage.hbs new file mode 100644 index 00000000..3f22875c --- /dev/null +++ b/src/components/OfflineMessage/OfflineMessage.hbs @@ -0,0 +1,18 @@ +{{#with offline}} + {{# if visible }} +
+
+ {{# if open }} +
Похоже у вас отсутствует соединиение с Интернетом
Часть функций недоступна.
+
+ {{/if}} +
+ {{/if}} +{{/with}} diff --git a/src/components/OfflineMessage/OfflineMessage.js b/src/components/OfflineMessage/OfflineMessage.js new file mode 100644 index 00000000..7751256b --- /dev/null +++ b/src/components/OfflineMessage/OfflineMessage.js @@ -0,0 +1,97 @@ +// Базовый компонент +import BaseComponent from '../BaseComponent.js'; +// Стили + +import './OfflineMessage.scss'; +import UserStore from '../../stores/UserStore/UserStore.js'; + +// Шаблон +import template from './OfflineMessage.hbs'; + +// Action +import {serviceWorkerActions} from '../../actions/serviceworker.js'; + + +/** + * Класс, реализующий компонент OfflineMessage. + */ +export default class OfflineMessage extends BaseComponent { + /** + * Конструктор, создающий класс компонента OfflineMessage. + * @param {Object} context контекст отрисовки шаблона + */ + constructor(context) { + super(context, template); + + this._onRefresh = this._onRefresh.bind(this); + UserStore.addListener(this._onRefresh); + + this._elements = {}; + this._bindCallBacks(); + } + + /** + * Метод, вызывающийся по умолчанию при обновлении компонента. + */ + _onRefresh() { + this.context = UserStore.getContext(); + } + + /** + * Метод биндит callback'и к this + * @private + */ + _bindCallBacks() { + this._onClose = this._onClose.bind(this); + this._onOpen = this._onOpen.bind(this); + } + + /** + * Метод сохраняет элементы DOM связанные с navbar + * @private + */ + _registerElements() { + this._elements = { + wrapper: document.getElementById('offlineMessageShowId'), + close: document.getElementById('offlineMessageCloseId'), + }; + } + + /** + * Метод, добавляющий обработчики событий для компонента. + */ + addEventListeners() { + this._registerElements(); + this._elements.wrapper?.addEventListener('click', this._onOpen); + this._elements.close?.addEventListener('click', this._onClose); + } + + /** + * Метод, удаляющий обработчики событий для компонента. + */ + removeEventListeners() { + this._elements.wrapper?.removeEventListener('click', this._onOpen); + this._elements.close?.removeEventListener('click', this._onClose); + } + + /** + * CallBack на закрытие сообщения + * @param {Event} event объект события + * @private + */ + _onClose(event) { + event.preventDefault(); + serviceWorkerActions.onCloseOfflineMessage(); + } + + + /** + * CallBack на открытие сообщения + * @param {Event} event объект события + * @private + */ + _onOpen(event) { + event.preventDefault(); + serviceWorkerActions.onOpenOfflineMessage(); + } +} diff --git a/src/components/OfflineMessage/OfflineMessage.scss b/src/components/OfflineMessage/OfflineMessage.scss new file mode 100644 index 00000000..e1eb3d11 --- /dev/null +++ b/src/components/OfflineMessage/OfflineMessage.scss @@ -0,0 +1,32 @@ +@import '../../styles/scss/Constants'; + +.offline { + &__wrapper { + position: fixed; + z-index: 2; + left: 20px; + bottom: 20px; + background-color: $offline-background; + border: 2px solid $offline-border; + border-radius: 20px; + + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + column-gap: 5px; + padding: 5px; + + &_clickable { + cursor: pointer; + } + + &_non-clickable { + cursor: pointer; + } + } + + + &__content { + } +} diff --git a/src/constants/constants.js b/src/constants/constants.js index 96a0f24e..ed6aee81 100644 --- a/src/constants/constants.js +++ b/src/constants/constants.js @@ -18,6 +18,7 @@ export const Urls = { CardPath: '/invite/card/', Card: '/invite/card/', }, + Offline: '/offline', }; /** @@ -29,19 +30,18 @@ export const Html = { }; /** - * Константа, содержащая в себе параметры самого себя. + * Константа, содержащая в себе параметры URL */ -export const SelfAddress = { - Url: FRONTEND_ADDRESS, - Port: FRONTEND_PORT, -}; - -/** - * Константа, содержащая в себе параметры бэкенда. - */ -export const BackendAddress = { - Url: BACKEND_ADDRESS, - Port: BACKEND_PORT, +export const HTTP = { + SelfAddress: { + Url: FRONTEND_ADDRESS, + Port: FRONTEND_PORT, + }, + BackendAddress: { + Url: BACKEND_ADDRESS, + Port: BACKEND_PORT, + }, + Scheme: SCHEME, }; /** @@ -119,3 +119,19 @@ export const CheckLists = { export const SettingStoreConstants = { MobileNavWidth: 500, }; + +export const ServiceWorker = { + CacheUrls: { + HTML_URL: '/index.html', + NO_INTERNET_IMG_URL: '/assets/no-internet-icon.webp', + }, + API_PREFIX: '/api', + STATIC_CACHE_NAME: `static-cache-${APP_VERSION}`, + API_CACHE_NAME: `api-cache-${APP_VERSION}`, + SW_HEADER: 'X-Is-From-Service-Worker', + Messages: { + OFFLINE_FROM_CACHE: 'offline-cache', // Приложение работает в offline + OFFLINE_NO_CACHE: 'offline-no-cache', // Приложение offline и дальше не может работать + ONLINE: 'online', // Приложение online + }, +}; diff --git a/src/index.js b/src/index.js index a74a61c0..bcc00e1d 100644 --- a/src/index.js +++ b/src/index.js @@ -1,5 +1,4 @@ 'use strict'; - import {userActions} from './actions/user.js'; // Stores @@ -22,16 +21,9 @@ import BoardsView from './views/BoardsView/BoardsView.js'; import BoardView from './views/BoardView/BoardView.js'; import ProfileView from './views/ProfileView/ProfileView.js'; import {settingsActions} from './actions/settings'; - -if ('serviceWorker' in navigator && !DEBUG) { - window.addEventListener('load', () => { - navigator.serviceWorker.register('/sw.js').then((registration) => { - console.log('SW registered with scope ', registration.scope); - }).catch((registrationError) => { - console.log('SW registration failed: ', registrationError); - }); - }); -} +import ServiceWorkerClient from './modules/ServiceWorkerClient/ServiceWorkerClient'; +import OfflineView from './views/OfflineView/OfflineView.js'; +import NotFoundView from './views/NotFoundView/NotFoundView'; if (UserStore.getContext('isAuthorized') === undefined) { userActions.fetchUser(); @@ -44,6 +36,7 @@ window.addEventListener('DOMContentLoaded', async () => { const boardsView = new BoardsView(root); const boardView = new BoardView(root); + Router.registerNotFound(new NotFoundView(root)); Router.register(Urls.Root, boardsView); Router.register(Urls.Boards, boardsView); Router.register(Urls.Invite.Board, boardView); @@ -52,6 +45,7 @@ window.addEventListener('DOMContentLoaded', async () => { Router.register(Urls.Login, new LoginView(root)); Router.register(Urls.Board, boardView); Router.register(Urls.Profile, new ProfileView(root)); + Router.register(Urls.Offline, new OfflineView(root)); UserStore.addListener(() => { if (UserStore.getContext('isAuthorized') === undefined) { @@ -81,3 +75,16 @@ window.addEventListener('resize', (() => { } }; })(), false); + + +if ('serviceWorker' in navigator) { + // eslint-disable-next-line no-unused-vars + const serviceWorkerClient = new ServiceWorkerClient(navigator.serviceWorker); + window.addEventListener('load', async () => { + try { + await navigator.serviceWorker.register(`/${SW_FILE_NAME}`); + } catch (error) { + console.log(`Ошибка при регистрации SW: ${error}`); + } + }); +} diff --git a/src/index_template.html b/src/index_template.html index 31f3236f..be321561 100644 --- a/src/index_template.html +++ b/src/index_template.html @@ -43,11 +43,12 @@

Ошибка :с

diff --git a/src/modules/Network/Network.js b/src/modules/Network/Network.js index 6b8d97f2..69db73b4 100644 --- a/src/modules/Network/Network.js +++ b/src/modules/Network/Network.js @@ -1,4 +1,4 @@ -import {SelfAddress, BackendAddress} from '../../constants/constants.js'; +import {HTTP} from '../../constants/constants.js'; /** * Класс, реализующий работу с сетью. @@ -8,10 +8,9 @@ class Network { * Конструктор, инициализирующий BackendUrl и порт бэкенд-сервера. */ constructor() { - this.SelfUrl = SelfAddress.Url; - this.SelfPort = SelfAddress.Port; - - this.BackendUrl = BackendAddress.Url; + this.FrontendHost = + `${HTTP.Scheme}://${HTTP.SelfAddress.Url}${DEBUG ? `:${HTTP.SelfAddress.Port}` : ''}`; + this.BackendHost = `${HTTP.Scheme}://${HTTP.BackendAddress.Url}:${HTTP.BackendAddress.Port}`; this._endpoints = { sessions: 'api/sessions', @@ -36,7 +35,7 @@ class Network { mode: 'cors', credentials: 'include', headers: { - Origin: `https://${this.SelfUrl}:${this.SelfPort}`, + Origin: this.FrontendHost, }, }; } @@ -66,7 +65,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.sessions}`, + `${this.BackendHost}/${this._endpoints.sessions}`, options); } @@ -83,7 +82,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.profile}/${data.userName}`, + `${this.BackendHost}/${this._endpoints.profile}/${data.userName}`, options); } @@ -101,7 +100,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.profile}`, + `${this.BackendHost}/${this._endpoints.profile}`, options); } @@ -119,7 +118,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/` + + `${this.BackendHost}/` + `${this._endpoints.profile}/${data.login}`, options); } @@ -136,7 +135,7 @@ class Network { body: data, }; return this.httpRequest( - `https://${this.BackendUrl}/` + + `${this.BackendHost}/` + `${this._endpoints.profile}/${login}/upload`, options); } @@ -155,7 +154,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.sessions}`, + `${this.BackendHost}/${this._endpoints.sessions}`, options); } @@ -168,7 +167,7 @@ class Network { method: 'get', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}`, + `${this.BackendHost}/${this._endpoints.board}`, options); } @@ -185,7 +184,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}/${bid}`, + `${this.BackendHost}/${this._endpoints.board}/${bid}`, options); } @@ -203,7 +202,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}`, + `${this.BackendHost}/${this._endpoints.card}`, options); } @@ -222,7 +221,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}/${cid}`, + `${this.BackendHost}/${this._endpoints.card}/${cid}`, options); } @@ -239,7 +238,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}/${cid}`, + `${this.BackendHost}/${this._endpoints.card}/${cid}`, options); } /** @@ -256,7 +255,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.cardlist}`, + `${this.BackendHost}/${this._endpoints.cardlist}`, options); } @@ -275,7 +274,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.cardlist}/${clid}`, + `${this.BackendHost}/${this._endpoints.cardlist}/${clid}`, options); } @@ -292,7 +291,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.cardlist}/${clid}`, + `${this.BackendHost}/${this._endpoints.cardlist}/${clid}`, options); } @@ -306,7 +305,7 @@ class Network { method: 'post', body: JSON.stringify(data), }; - return this.httpRequest(`https://${this.BackendUrl}/api/boards`, + return this.httpRequest(`${this.BackendHost}/api/boards`, options); } @@ -325,8 +324,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}/${bid}`, - options); + `${this.BackendHost}/${this._endpoints.board}/${bid}`, options); } /** @@ -342,8 +340,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}/${bid}`, - options); + `${this.BackendHost}/${this._endpoints.board}/${bid}`, options); } /** @@ -358,7 +355,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.sessions}`, + `${this.BackendHost}/${this._endpoints.sessions}`, options); } @@ -372,7 +369,7 @@ class Network { method: 'get', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.usersearch.card}` + + `${this.BackendHost}/${this._endpoints.usersearch.card}` + `/${cid}/${searchString}`, options); } @@ -387,7 +384,7 @@ class Network { method: 'get', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.usersearch.board}` + + `${this.BackendHost}/${this._endpoints.usersearch.board}` + `/${bid}/${searchString}`, options); } @@ -402,7 +399,7 @@ class Network { method: 'get', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.usersearch.team}` + + `${this.BackendHost}/${this._endpoints.usersearch.team}` + `/${tid}/${searchString}`, options); } @@ -420,7 +417,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.team}/${tid}` + + `${this.BackendHost}/${this._endpoints.team}/${tid}` + `/toggleuser/${uid}`, options); } @@ -437,7 +434,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}/${bid}` + + `${this.BackendHost}/${this._endpoints.board}/${bid}` + `/toggleuser/${uid}`, options); } @@ -454,7 +451,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}/${cid}` + + `${this.BackendHost}/${this._endpoints.card}/${cid}` + `/toggleuser/${uid}`, options); } /** @@ -467,7 +464,7 @@ class Network { method: 'post', body: JSON.stringify(data), }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.checklists}`, options); } @@ -480,7 +477,7 @@ class Network { const options = { method: 'delete', }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.checklists}/${chlid}`, options); } @@ -495,7 +492,7 @@ class Network { method: 'put', body: JSON.stringify(data), }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.checklists}/${chlid}`, options); } @@ -509,7 +506,7 @@ class Network { method: 'post', body: JSON.stringify(data), }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.checklistsItems}`, options); } @@ -524,7 +521,7 @@ class Network { method: 'put', body: JSON.stringify(data), }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.checklistsItems}/${chliid}`, options); } @@ -537,7 +534,7 @@ class Network { const options = { method: 'delete', }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.checklistsItems}/${chliid}`, options); } @@ -553,7 +550,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.comments}`, + `${this.BackendHost}/${this._endpoints.comments}`, options); } @@ -572,7 +569,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.comments}/${data.cmid}`, + `${this.BackendHost}/${this._endpoints.comments}/${data.cmid}`, options); } @@ -589,7 +586,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.comments}/${data.cmid}`, + `${this.BackendHost}/${this._endpoints.comments}/${data.cmid}`, options); } @@ -606,7 +603,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}/${cid}` + + `${this.BackendHost}/${this._endpoints.card}/${cid}` + `/toggletag/${tgid}`, options); } @@ -619,7 +616,7 @@ class Network { const options = { method: 'delete', }; - return this.httpRequest(`https://${this.BackendUrl}` + + return this.httpRequest(`${this.BackendHost}` + `/${this._endpoints.tags}/${tgid}`, options); } @@ -635,7 +632,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.tags}`, + `${this.BackendHost}/${this._endpoints.tags}`, options); } @@ -651,7 +648,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.tags}/${tgid}`, + `${this.BackendHost}/${this._endpoints.tags}/${tgid}`, options); } @@ -665,7 +662,7 @@ class Network { method: 'put', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}/access/` + + `${this.BackendHost}/${this._endpoints.board}/access/` + `${accessPath}`, options); } @@ -679,7 +676,7 @@ class Network { method: 'put', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.board}/${bid}/access`, + `${this.BackendHost}/${this._endpoints.board}/${bid}/access`, options); } @@ -693,7 +690,7 @@ class Network { method: 'put', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}/access/tocard/` + + `${this.BackendHost}/${this._endpoints.card}/access/tocard/` + `${accessPath}`, options); } @@ -707,7 +704,7 @@ class Network { method: 'put', }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.card}/access/${cid}`, + `${this.BackendHost}/${this._endpoints.card}/access/${cid}`, options); } @@ -723,7 +720,7 @@ class Network { body: data, }; return this.httpRequest( - `https://${this.BackendUrl}/` + + `${this.BackendHost}/` + `${this._endpoints.attachments}/${cid}`, options); } @@ -741,7 +738,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.attachments}/${atid}`, + `${this.BackendHost}/${this._endpoints.attachments}/${atid}`, options); } @@ -756,7 +753,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.team}`, options); + `${this.BackendHost}/${this._endpoints.team}`, options); } /** @@ -772,7 +769,7 @@ class Network { }, }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.team}/${tid}`, + `${this.BackendHost}/${this._endpoints.team}/${tid}`, options); } @@ -791,7 +788,7 @@ class Network { body: JSON.stringify(data), }; return this.httpRequest( - `https://${this.BackendUrl}/${this._endpoints.team}/${tid}`, + `${this.BackendHost}/${this._endpoints.team}/${tid}`, options); } } diff --git a/src/modules/Router/Router.js b/src/modules/Router/Router.js index 5932f857..4bc5dea9 100644 --- a/src/modules/Router/Router.js +++ b/src/modules/Router/Router.js @@ -1,7 +1,5 @@ import {Html, Urls} from '../../constants/constants.js'; -import NotFoundView from '../../views/NotFoundView/NotFoundView.js'; - import BaseView from '../../views/BaseView.js'; /** @@ -70,7 +68,6 @@ class Router { * (по умолчанию: false) */ go(url, replaceState = false) { - console.log(url); const {urlData, view} = this.processURL(url) || {}; if (!urlData || !view) { this.go(Urls.NotFound, true); @@ -117,12 +114,13 @@ class Router { /** * Регестрирует view по умолчанию для неизвестных url + * @param {NotFoundView} notFoundView объект view 404 */ - registerNotFound() { + registerNotFound(notFoundView) { if (!this.isTemplateValid(Urls.NotFound)) { throw new Error(`Шаблон ${Urls.NotFound} для NotFoundview не валидный`); } - this.register(Urls.NotFound, new NotFoundView(document.getElementById(Html.Root))); + this.register(Urls.NotFound, notFoundView); } /** diff --git a/src/modules/ServiceWorkerClient/ServiceWorkerClient.js b/src/modules/ServiceWorkerClient/ServiceWorkerClient.js new file mode 100644 index 00000000..1d8adb31 --- /dev/null +++ b/src/modules/ServiceWorkerClient/ServiceWorkerClient.js @@ -0,0 +1,61 @@ +/** + * Клиент для общения общения с SW + */ +import {ServiceWorker} from '../../constants/constants'; +import {serviceWorkerActions} from '../../actions/serviceworker'; + +/** + * Класс клиента для взаимодействия с SW + */ +export default class ServiceWorkerClient { + /** + * Конструирует клиента для общения общения с SW + * @param {ServiceWorkerContainer} sw - объект sw container + */ + constructor(sw) { + this.sw = sw; + navigator.serviceWorker.addEventListener('message', (event) => { + if (!event.data) { + return; + } + switch (event.data.messageType) { + case ServiceWorker.Messages.OFFLINE_FROM_CACHE: + this._onResponseFromCache(); + break; + + case ServiceWorker.Messages.OFFLINE_NO_CACHE: + this._onFullOffline(); + break; + + case ServiceWorker.Messages.ONLINE: + this._onOnline(); + break; + } + }); + } + + /** + * При получении ответа из кэша (отсутствует интернет) + * @todo для разных запросов (передавать из SW) - разные actions + * @private + */ + _onResponseFromCache() { + serviceWorkerActions.responseFromCache(); + } + + /** + * При остутствии интернета и кэша на запросы + * @private + */ + _onFullOffline() { + serviceWorkerActions.fullOffline(); + } + + /** + * При получении запроса из сети + * @private + */ + _onOnline() { + serviceWorkerActions.hideOfflineMessage(); + } +} diff --git a/src/stores/BoardStore/BoardStore.js b/src/stores/BoardStore/BoardStore.js index 6d2888fc..39decb56 100644 --- a/src/stores/BoardStore/BoardStore.js +++ b/src/stores/BoardStore/BoardStore.js @@ -18,9 +18,8 @@ import Validator from '../../modules/Validator/Validator'; import { BoardStoreConstants, CheckLists, - ConstantMessages, + ConstantMessages, HTTP, HttpStatusCodes, - SelfAddress, Urls, } from '../../constants/constants.js'; @@ -471,7 +470,8 @@ class BoardStore extends BaseStore { */ _setCardInvite(accessPath) { this._storage.get('add-card-member-popup').inviteLink = - `http://${SelfAddress.Url}` + Urls.Invite.CardPath + accessPath; + `${HTTP.Scheme}://${HTTP.SelfAddress.Url}${DEBUG ? `:${HTTP.SelfAddress.Port}` : ''}` + + Urls.Invite.CardPath + accessPath; } /** @@ -481,7 +481,8 @@ class BoardStore extends BaseStore { */ _setBoardInvite(accessPath) { this._storage.get('add-board-member-popup').inviteLink = - `http://${SelfAddress.Url}` + Urls.Invite.BoardPath + accessPath; + `${HTTP.Scheme}://${HTTP.SelfAddress.Url}${DEBUG ? `:${HTTP.SelfAddress.Port}` : ''}` + + Urls.Invite.BoardPath + accessPath; } /** @@ -598,6 +599,9 @@ class BoardStore extends BaseStore { * @private */ async _updateTitleAndDescription(data) { + if (SettingsStore.isOffline()) { + return; + } this._storage.get('setting-popup').errors = null; const validator = new Validator(); @@ -703,6 +707,9 @@ class BoardStore extends BaseStore { * @private */ async _createCardList(data) { + if (SettingsStore.isOffline()) { + return; + } this._storage.get('cardlist-popup').errors = null; const validator = new Validator(); @@ -794,6 +801,9 @@ class BoardStore extends BaseStore { * @private */ async _updateCardList(data) { + if (SettingsStore.isOffline()) { + return; + } this._storage.get('cardlist-popup').errors = null; const validator = new Validator(); @@ -945,6 +955,9 @@ class BoardStore extends BaseStore { * @private */ async _createCard(data) { + if (SettingsStore.isOffline()) { + return; + } this._storage.get('card-popup').errors = null; const validator = new Validator(); @@ -1069,6 +1082,9 @@ class BoardStore extends BaseStore { * @private */ async _updateDeadlineCheck(data) { + if (SettingsStore.isOffline()) { + return; + } const card = this._getCardById( data.clid, data.cid, @@ -1119,6 +1135,9 @@ class BoardStore extends BaseStore { * @private */ async _updateCard(data) { + if (SettingsStore.isOffline()) { + return; + } this._storage.get('card-popup').errors = null; const validator = new Validator(); @@ -1307,6 +1326,9 @@ class BoardStore extends BaseStore { * @private */ async _createCheckList() { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('card-popup'); context.errors = null; @@ -1355,6 +1377,9 @@ class BoardStore extends BaseStore { * @private */ async _saveCheckList(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('card-popup'); context.errors = null; const checkList = this._getCheckListById(data.chlid); @@ -1429,6 +1454,9 @@ class BoardStore extends BaseStore { * @private */ async _createCheckListItem(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('card-popup'); context.errors = null; const checkList = this._getCheckListById(data.chlid); @@ -1478,6 +1506,9 @@ class BoardStore extends BaseStore { * @private */ async _saveCheckListItem(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('card-popup'); context.errors = null; const item = this._getCheckListItemById(data.chlid, data.chliid); @@ -1556,6 +1587,9 @@ class BoardStore extends BaseStore { * @private */ async _toggleCheckListItem(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('card-popup'); context.errors = null; context.selectInvite = false; @@ -1673,6 +1707,9 @@ class BoardStore extends BaseStore { * @private */ async _toggleCardAssigneeInSearchList(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('add-card-member-popup'); context.errors = null; context.selectInvite = false; @@ -1769,6 +1806,9 @@ class BoardStore extends BaseStore { * @private */ async _toggleBoardMemberInSearchList(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('add-board-member-popup'); context.selectInvite = false; context.errors = null; @@ -1872,6 +1912,9 @@ class BoardStore extends BaseStore { * @private */ async _createCardComment(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('card-popup'); const comment = { @@ -1936,6 +1979,9 @@ class BoardStore extends BaseStore { * @private */ async _updateCardComment(data) { + if (SettingsStore.isOffline()) { + return; + } let payload; try { @@ -2028,6 +2074,9 @@ class BoardStore extends BaseStore { * @private */ async _uploadAttachment(data) { + if (SettingsStore.isOffline()) { + return; + } const cardContext = this._storage.get('card-popup'); cardContext.errors = null; if (data.file.size > BoardStoreConstants.MaxAttachmentSize) { @@ -2065,6 +2114,9 @@ class BoardStore extends BaseStore { * @private */ async _deleteAttachment(data) { + if (SettingsStore.isOffline()) { + return; + } const cardContext = this._storage.get('card-popup'); let payload; @@ -2333,6 +2385,9 @@ class BoardStore extends BaseStore { * @param {Object} data данные c названием тега */ async _createTag(data) { + if (SettingsStore.isOffline()) { + return; + } const contextTagPopUp = this._storage.get('tag-popup'); const contextTagListPopUp = this._storage.get('tags-list-popup'); @@ -2431,6 +2486,9 @@ class BoardStore extends BaseStore { * Обновляет тег */ async _updateTag() { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('tag-popup'); context.errors = null; @@ -2483,6 +2541,9 @@ class BoardStore extends BaseStore { * @param {Object} data данные */ async _toggleTag(data) { + if (SettingsStore.isOffline()) { + return; + } const context = this._storage.get('tags-list-popup'); const currentCard = this._getCardById(this._storage.get('card-popup').clid, this._storage.get('card-popup').cid); diff --git a/src/stores/BoardsStore/BoardsStore.js b/src/stores/BoardsStore/BoardsStore.js index 38405767..c7b0691a 100644 --- a/src/stores/BoardsStore/BoardsStore.js +++ b/src/stores/BoardsStore/BoardsStore.js @@ -183,6 +183,9 @@ class BoardsStore extends BaseStore { * @private */ async _create(data) { + if (SettingsStore.isOffline()) { + return; + } const validator = new Validator(); const validatorStatus = validator.validateBoardTitle(data.name); @@ -429,6 +432,9 @@ class BoardsStore extends BaseStore { * @private */ async _submitAddTeamPopUp(data) { + if (SettingsStore.isOffline()) { + return; + } const validator = new Validator(); const validatorStatus = validator.validateTeamTitle(data.team_name); this._storage.get('team-popup').errors = validatorStatus; diff --git a/src/stores/SettingsStore/SettingsStore.js b/src/stores/SettingsStore/SettingsStore.js index f5df03a3..d8ba0750 100644 --- a/src/stores/SettingsStore/SettingsStore.js +++ b/src/stores/SettingsStore/SettingsStore.js @@ -1,7 +1,9 @@ import BaseStore from '../BaseStore.js'; +import Router from '../../modules/Router/Router.js'; // Actions import {SettingsActionTypes} from '../../actions/settings.js'; +import {ServiceWorkerTypes} from '../../actions/serviceworker.js'; import {userActions} from '../../actions/user.js'; // Modules @@ -9,7 +11,12 @@ import Network from '../../modules/Network/Network.js'; import Validator from '../../modules/Validator/Validator.js'; // Constants -import {ConstantMessages, HttpStatusCodes, SettingStoreConstants} from '../../constants/constants.js'; +import { + ConstantMessages, + HttpStatusCodes, + SettingStoreConstants, + Urls, +} from '../../constants/constants.js'; /** * Класс, реализующий хранилище настроек. @@ -44,6 +51,20 @@ class SettingsStore extends BaseStore { prevWidth: window.innerWidth, isMobile: window.innerWidth < SettingStoreConstants.MobileNavWidth, }); + this._storage.set('offline', { + visible: false, + open: false, + half: false, + full: false, + }); + } + + /** + * Нахоится ли приложение offline + * @return {Boolean} + */ + isOffline() { + return this._storage.get('offline').offline; } /** @@ -76,6 +97,31 @@ class SettingsStore extends BaseStore { this._windowResized(action.data); break; + case ServiceWorkerTypes.HALF_OFFLINE: + this._responseFromCache(); + this._emitChange(); + break; + + case ServiceWorkerTypes.FULL_OFFLINE: + this._fullOffline(); + this._emitChange(); + break; + + case ServiceWorkerTypes.SHOW_OFFLINE_MSG: + this._openOfflineMessage(); + this._emitChange(); + break; + + case ServiceWorkerTypes.CLOSE_OFFLINE_MSG: + this._closeOfflineMessage(); + this._emitChange(); + break; + + case ServiceWorkerTypes.HIDE_OFFLINE_MSG: + this._hideOfflineMessage(); + this._emitChange(); + break; + default: return; } @@ -121,6 +167,9 @@ class SettingsStore extends BaseStore { * @param {FormData} data данные запроса. */ async _put(data) { + if (this.isOffline()) { + return; + } const formdata = data; this._storage.set('login', data.login); @@ -186,6 +235,9 @@ class SettingsStore extends BaseStore { * @param {Object} data данные запроса. */ async _uploadAvatar(data) { + if (this.isOffline()) { + return; + } const validator = new Validator(); if (data.avatar instanceof File) { @@ -335,6 +387,53 @@ class SettingsStore extends BaseStore { this._emitChange(); } } + + /** + * Отображает предупреждение об offline работе + */ + _responseFromCache() { + const context = this._storage.get('offline'); + context.offline = true; + context.visible = true; + } + + /** + * Отображает offline страницу + */ + _fullOffline() { + const context = this._storage.get('offline'); + context.offline = true; + context.visible = true; + Router.go(Urls.Offline, true); + } + + /** + * Раскрыть offline сообщение + * @private + */ + _openOfflineMessage() { + const context = this._storage.get('offline'); + context.open = true; + } + + /** + * Свернуть offline сообщение + * @private + */ + _closeOfflineMessage() { + const context = this._storage.get('offline'); + context.open = false; + } + + /** + * Скрыть полностью offline сообщение + * @private + */ + _hideOfflineMessage() { + const context = this._storage.get('offline'); + context.offline = false; + context.visible = false; + } } export default new SettingsStore(); diff --git a/src/stores/UserStore/UserStore.js b/src/stores/UserStore/UserStore.js index 0b7e351a..8f7393d4 100644 --- a/src/stores/UserStore/UserStore.js +++ b/src/stores/UserStore/UserStore.js @@ -9,7 +9,8 @@ import Validator from '../../modules/Validator/Validator.js'; // Constants import {ConstantMessages, HttpStatusCodes} from '../../constants/constants.js'; -import {settingsActions} from '../../actions/settings'; +import {settingsActions} from '../../actions/settings.js'; +import SettingsStore from '../SettingsStore/SettingsStore.js'; /** * Класс, реализующий хранилище пользователя @@ -110,6 +111,9 @@ class UserStore extends BaseStore { * @param {Object} data данные для входа */ async _register(data) { + if (SettingsStore.isOffline()) { + return; + } this._storage.set('userRegisterData', data); this._validate(data, 'userRegisterData'); @@ -202,6 +206,9 @@ class UserStore extends BaseStore { * Метод, реализующий реакцию на выход. */ async _logout() { + if (SettingsStore.isOffline()) { + return; + } let response; try { diff --git a/src/styles/scss/Common.scss b/src/styles/scss/Common.scss index 469892b7..18250f4d 100644 --- a/src/styles/scss/Common.scss +++ b/src/styles/scss/Common.scss @@ -258,6 +258,20 @@ div#root { content: 'cancel'; } } + + &-warning { + @extend .material-icons; + cursor: pointer; + opacity: 0.8; + + &:hover { + opacity: 0.8; + } + + &::before { + content: 'warning'; + } + } } .horizontal-line { @@ -326,8 +340,8 @@ div#root { } &-file { - @extend .material-icon; - + @extend .material-icons; + opacity: 0.3; &::before { content: 'description'; } diff --git a/src/styles/scss/Constants.scss b/src/styles/scss/Constants.scss index a0bf2be1..9ae6de6d 100644 --- a/src/styles/scss/Constants.scss +++ b/src/styles/scss/Constants.scss @@ -33,3 +33,6 @@ $mobile-threshold: 500px; $popup-min-width: 190px; $tags-list-tag-height: 32px; + +$offline-background: #FFBF00; +$offline-border: #FF9C19; diff --git a/src/styles/scss/PopUp.scss b/src/styles/scss/PopUp.scss index 5a318921..06134ba1 100644 --- a/src/styles/scss/PopUp.scss +++ b/src/styles/scss/PopUp.scss @@ -52,7 +52,7 @@ } &_invite { - max-width: 450px; + max-width: 550px; } &_tag { diff --git a/src/sw.js b/src/sw.js index 9126fe8e..02df1562 100644 --- a/src/sw.js +++ b/src/sw.js @@ -1,68 +1,127 @@ -import {CacheFirst, StaleWhileRevalidate, NetworkOnly} from 'workbox-strategies'; -import {ExpirationPlugin} from 'workbox-expiration'; -import {precacheAndRoute} from 'workbox-precaching'; -import {registerRoute, setCatchHandler} from 'workbox-routing'; +import {ServiceWorker} from './constants/constants'; -precacheAndRoute(self.__WB_MANIFEST); - -const CACHE_NAME = 'general'; -const FALLBACK_HTML_URL = '/offline.html'; +const STATIC_FILES_URL = (self.__WB_MANIFEST || []).map((pair) => { + return pair.url; +}); self.addEventListener('install', async (event) => { - event.waitUntil( - caches.open(CACHE_NAME) - .then((cache) => cache.add(FALLBACK_HTML_URL)), + await caches.open(ServiceWorker.API_CACHE_NAME); + const cache = await caches.open(ServiceWorker.STATIC_CACHE_NAME); + await cache.addAll(STATIC_FILES_URL); +}); + +self.addEventListener('activate', async (event) => { + /* Удалим устаревшии версии кэша: */ + const cacheNames = await caches.keys(); + await Promise.all( + cacheNames + .filter((name) => name !== ServiceWorker.STATIC_CACHE_NAME) + .filter((name) => name !== ServiceWorker.API_CACHE_NAME) + .map((name) => caches.delete(name)), ); }); -registerRoute( - /\.css$/, - new CacheFirst({ - cacheName: 'css-cache', - }), -); +self.addEventListener('fetch', (event) => { + const {request} = event; + const url = new URL(request.url); -registerRoute( - /\.js/, - new NetworkOnly({ - cacheName: 'js-cache', - plugins: [ - {fetchDidFail: () => caches.match(FALLBACK_HTML_URL)}, - ], - }), -); + if (url.pathname.startsWith('/api')) { // Запрос на API + event.respondWith(networkFirst(request, event.clientId)); + } else if (event.request.mode === 'navigate') { // Переход по URL в адресной строке + event.respondWith(cacheFirst(ServiceWorker.CacheUrls.HTML_URL)); + } else { // Запрос за статикой + event.respondWith(cacheFirst(request)); + } +}); -registerRoute( - /\.(?:woff2|eot|svg|ttf|woff|otf)$/, - new CacheFirst({ - cacheName: 'font-cache', - }), -); +/** + * CacheFirst - кэширование + * @param {Request | String} request объект запроса или URL строка + * @return {Promise, number>>} фыв + */ +async function cacheFirst(request) { + try { + const cached = await caches.match(request); + if (cached) { + return cached; + } + const response = await fetch(request); + const cache = await caches.open(ServiceWorker.STATIC_CACHE_NAME); + await cache.put(request, response.clone()); + return response; + } catch (error) { + if (request.url.endsWith('webp')) { + return await caches.match(ServiceWorker.CacheUrls.NO_INTERNET_IMG_URL); + } + } +} -registerRoute( - /\.webp/, - new StaleWhileRevalidate({ - cacheName: 'image-cache', - plugins: [ - new ExpirationPlugin({ - maxEntries: 50, - maxAgeSeconds: 7 * 24 * 60 * 60, // 7 дней - }), - {fetchDidFail: () => caches.match(FALLBACK_HTML_URL)}, - ], - }), -); +/** + * NetworkFirst - кэширование запросов на API + * @param {Request | String} request объект запроса или URL строка + * @param {Number} clientId id клиента + * @return {Promise} + */ +async function networkFirst(request, clientId) { + if (request.method !== 'GET') { + try { + return await fetch(request); + } catch (error) { + return undefined; + } + } + + const cache = await caches.open(ServiceWorker.API_CACHE_NAME); + try { + const response = await fetch(request); + const responseCopy = response.clone(); + await sendMessage(clientId, + ServiceWorker.Messages.ONLINE, + request.url); + + /* Добавим служебный заголовок, позволяющий определить что запрос был кэширован в SW*/ + const headers = new Headers(responseCopy.headers); + headers.set('X-Is-From-Service-Worker', 'true'); -self.addEventListener('message', (event) => { - if (event.data && event.data.type === 'SKIP_WAITING') { - self.skipWaiting(); + const responseBytes = await responseCopy.blob(); + const cachedResponse = new Response(responseBytes, { + status: responseCopy.status, + statusText: responseCopy.statusText, + headers: headers, + }); + await cache.put(request, cachedResponse); + + return response; + } catch (error) { + const cachedResponse = await cache.match(request); + if (!cachedResponse) { + await sendMessage(clientId, + ServiceWorker.Messages.OFFLINE_NO_CACHE, + request.url); + return undefined; + } + await sendMessage(clientId, + ServiceWorker.Messages.OFFLINE_FROM_CACHE, + request.url); + return cachedResponse; } -}); +} -setCatchHandler(async ({event}) => { - // TODO fallback'и на image, fonts - switch (event.request.destination) { - default: - return caches.match(FALLBACK_HTML_URL); +/** + * Отправляет сообщение в приложение + * @param {Number} clientId id клиента + * @param {String} messageType сообщение + * @param {String} url url связанный с сообщением + * @return {Promise} + */ +async function sendMessage(clientId, messageType, url) { + const client = await clients.get(clientId); + if (!client) { + return; } -}); + client.postMessage({ + messageType, + clientId, + url, + }); +} diff --git a/src/views/BaseView.js b/src/views/BaseView.js index c648940f..fb47e34e 100644 --- a/src/views/BaseView.js +++ b/src/views/BaseView.js @@ -4,6 +4,7 @@ import BaseComponent from '../components/BaseComponent.js'; // Компоненты по умолчанию import NavbarComponent from '../components/Navbar/Navbar.js'; import FooterComponent from '../components/Footer/Footer.js'; +import OfflineMessage from '../components/OfflineMessage/OfflineMessage.js'; /** @@ -22,6 +23,7 @@ export default class BaseView extends BaseComponent { this.addComponent('Navbar', new NavbarComponent(context)); this.addComponent('Footer', new FooterComponent(context)); + this.addComponent('OfflineMessage', new OfflineMessage(context)); this._isActive = false; } diff --git a/src/views/BoardView/BoardView.hbs b/src/views/BoardView/BoardView.hbs index 64ff1adc..cc4acdae 100644 --- a/src/views/BoardView/BoardView.hbs +++ b/src/views/BoardView/BoardView.hbs @@ -34,4 +34,6 @@ {{{TagsListPopUp}}} {{{TagPopUp}}} +{{{OfflineMessage}}} + {{{Footer}}} diff --git a/src/views/BoardsView/BoardsView.hbs b/src/views/BoardsView/BoardsView.hbs index 995ed381..a2fe1656 100644 --- a/src/views/BoardsView/BoardsView.hbs +++ b/src/views/BoardsView/BoardsView.hbs @@ -51,4 +51,6 @@ {{{TeamPopUp}}} {{{DeleteTeam}}} +{{{OfflineMessage}}} + {{{Footer}}} diff --git a/src/views/LoginView/LoginView.hbs b/src/views/LoginView/LoginView.hbs index 6347b7be..026313ad 100644 --- a/src/views/LoginView/LoginView.hbs +++ b/src/views/LoginView/LoginView.hbs @@ -33,4 +33,6 @@
+{{{OfflineMessage}}} + {{{Footer}}} diff --git a/src/views/LoginView/LoginView.js b/src/views/LoginView/LoginView.js index 6d18dd6d..068cff96 100644 --- a/src/views/LoginView/LoginView.js +++ b/src/views/LoginView/LoginView.js @@ -29,6 +29,7 @@ export default class LoginView extends BaseView { ...UserStore.getContext(), ['avatar', SettingsStore.getContext('avatar')], ['navbar', SettingsStore.getContext('navbar')], + ['offline', SettingsStore.getContext('offline')], ]); super(context, template, parent); @@ -52,6 +53,7 @@ export default class LoginView extends BaseView { ...UserStore.getContext(), ['avatar', SettingsStore.getContext('avatar')], ['navbar', SettingsStore.getContext('navbar')], + ['offline', SettingsStore.getContext('offline')], ])); this.render(); this._isActive = true; @@ -66,6 +68,7 @@ export default class LoginView extends BaseView { ...UserStore.getContext(), ['avatar', SettingsStore.getContext('avatar')], ['navbar', SettingsStore.getContext('navbar')], + ['offline', SettingsStore.getContext('offline')], ])); if (!this._isActive) { diff --git a/src/views/NotFoundView/NotFoundView.hbs b/src/views/NotFoundView/NotFoundView.hbs index 9db567a0..4e6d24c7 100644 --- a/src/views/NotFoundView/NotFoundView.hbs +++ b/src/views/NotFoundView/NotFoundView.hbs @@ -5,4 +5,6 @@ На главную +{{{OfflineMessage}}} + {{{Footer}}} diff --git a/src/views/OfflineView/OfflineView.hbs b/src/views/OfflineView/OfflineView.hbs new file mode 100644 index 00000000..b8cdb035 --- /dev/null +++ b/src/views/OfflineView/OfflineView.hbs @@ -0,0 +1,10 @@ +{{{Navbar}}} + +
+

Вы находитесь offline, эта страница не доступна.
Проверьте ваше соединение и обновите страницу ;]


+ На главную +
+ +{{{OfflineMessage}}} + +{{{Footer}}} diff --git a/src/views/OfflineView/OfflineView.js b/src/views/OfflineView/OfflineView.js new file mode 100644 index 00000000..b4723a94 --- /dev/null +++ b/src/views/OfflineView/OfflineView.js @@ -0,0 +1,64 @@ +import BaseView from '../BaseView.js'; + +import UserStore from '../../stores/UserStore/UserStore.js'; +import SettingsStore from '../../stores/SettingsStore/SettingsStore.js'; + +// Шаблон +import template from './OfflineView.hbs'; + +/** + * Класс, реализующий страницу "не найдено". + */ +export default class OfflineView extends BaseView { + /** + * @constructor + * @param {Element} parent элемент, в который будет происходить отрисовка + */ + constructor(parent) { + const context = new Map([ + ...UserStore.getContext(), + ['avatar', SettingsStore.getContext('avatar')], + ['navbar', SettingsStore.getContext('navbar')], + ['offline', SettingsStore.getContext('offline')], + ]); + super(context, template, parent); + + this._onRefresh = this._onRefresh.bind(this); + UserStore.addListener(this._onRefresh); + SettingsStore.addListener(this._onRefresh); + } + + /** + * Метод, отрисовывающий offline + */ + render() { + super.render(); + } + + /** + * Метод, вызывающийся по умолчанию при открытии страницы. + */ + _onShow() { + this._setContext(new Map([ + ...UserStore.getContext(), + ['avatar', SettingsStore.getContext('avatar')], + ['navbar', SettingsStore.getContext('navbar')], + ['offline', SettingsStore.getContext('offline')], + ])); + + this.render(); + this._isActive = true; + } + + /** + * Метод, вызывающийся по умолчанию при обновлении страницы. + */ + _onRefresh() { + this._setContext(new Map([ + ...UserStore.getContext(), + ['avatar', SettingsStore.getContext('avatar')], + ['navbar', SettingsStore.getContext('navbar')], + ['offline', SettingsStore.getContext('offline')], + ])); + } +} diff --git a/src/views/ProfileView/ProfileView.hbs b/src/views/ProfileView/ProfileView.hbs index c186eedc..e7bf850c 100644 --- a/src/views/ProfileView/ProfileView.hbs +++ b/src/views/ProfileView/ProfileView.hbs @@ -71,4 +71,6 @@ +{{{OfflineMessage}}} + {{{Footer}}} diff --git a/src/views/RegisterView/RegisterView.hbs b/src/views/RegisterView/RegisterView.hbs index 292d8c8e..2772b302 100644 --- a/src/views/RegisterView/RegisterView.hbs +++ b/src/views/RegisterView/RegisterView.hbs @@ -52,4 +52,6 @@ +{{{OfflineMessage}}} + {{{Footer}}} diff --git a/webpack.config.js b/webpack.config.js index f5227e68..658ad339 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,17 +5,37 @@ const CopyPlugin = require('copy-webpack-plugin'); const {InjectManifest} = require('workbox-webpack-plugin'); const {CleanWebpackPlugin} = require('clean-webpack-plugin'); const {DefinePlugin} = require('webpack'); +const CssMinimizerPlugin = require('css-minimizer-webpack-plugin'); +const packageJSON = require('./package.json'); + +const crypto = require('crypto'); +const fs = require('fs'); + +/** + * Высчитывает hash от содержимого файла + * @param {String} filePath путь до файла + * @return {String} + */ +function getFileHash(filePath) { + const fileBuffer = fs.readFileSync(filePath); + const hashSum = crypto.createHash('sha256'); + hashSum.update(fileBuffer); + return hashSum.digest('hex'); +} + const DEPLOY_DIR = 'dist'; const SERVER_IP = 'brrrello.ru'; const confConst = { LOCAL_HOST: 'localhost', BACKEND_RELEASE: process.env.BACKEND_RELEASE || SERVER_IP, - BACKEND_PORT: process.env.BACKEND_PORT || 8000, + BACKEND_PORT_RELEASE: 443, + BACKEND_PORT_DEBUG: 8000, FRONTEND_RELEASE: process.env.FRONTEND_RELEASE || SERVER_IP, - FRONTEND_PORT: process.env.FRONTEND_PORT || 3001, + FRONTEND_PORT: 3001, DEBUG: process.env.NODE_ENV !== 'production', - PROTOCOL: process.env.PROTOCOL || 'https', + HTTP: 'http', + HTTPS: 'https', }; const confDefs = { @@ -23,12 +43,15 @@ const confDefs = { confConst.LOCAL_HOST : confConst.FRONTEND_RELEASE), FRONTEND_PORT: confConst.FRONTEND_PORT, BACKEND_ADDRESS: JSON.stringify(confConst.DEBUG ? confConst.LOCAL_HOST : confConst.BACKEND_RELEASE), - BACKEND_PORT: confConst.BACKEND_PORT, + BACKEND_PORT: confConst.DEBUG ? confConst.BACKEND_PORT_DEBUG : confConst.BACKEND_PORT_RELEASE, DEBUG: confConst.DEBUG, + SCHEME: JSON.stringify(confConst.DEBUG ? confConst.HTTP : confConst.HTTPS), + APP_VERSION: JSON.stringify(packageJSON.version), + SW_FILE_NAME: JSON.stringify(`sw.${confConst.DEBUG ? '' : getFileHash('./src/sw.js')}.js`), }; const devServer = { - port: JSON.parse(confDefs.FRONTEND_PORT), + port: confDefs.FRONTEND_PORT, hot: false, static: [DEPLOY_DIR], historyApiFallback: true, // Для работы роута на 404 @@ -36,6 +59,7 @@ const devServer = { const config = { entry: './src/index.js', + output: { publicPath: '/', path: path.resolve(__dirname, DEPLOY_DIR), @@ -78,12 +102,21 @@ const config = { }, ], }, + optimization: { + minimizer: [ + new CssMinimizerPlugin({ + parallel: true, + }), + ], + minimize: true, + }, plugins: [ new CleanWebpackPlugin(), new MiniCssExtractPlugin({filename: `style${confConst.DEBUG ? '' : '.[contenthash]'}.css`}), new HtmlWebpackPlugin({ - backend: `${confConst.PROTOCOL}://${JSON.parse(confDefs.BACKEND_ADDRESS)}` + - `:${JSON.parse(confDefs.BACKEND_PORT)}`, + backend: confDefs.DEBUG ? + `${JSON.parse(confDefs.SCHEME)}://${JSON.parse(confDefs.BACKEND_ADDRESS)}` + + `:${confDefs.BACKEND_PORT}` : null, scriptLoading: 'module', filename: 'index.html', template: 'src/index_template.html', @@ -97,20 +130,17 @@ const config = { }, ], }), + new InjectManifest({ + swSrc: './src/sw.js', + swDest: JSON.parse(confDefs.SW_FILE_NAME), + exclude: [ + /\.m?js$/, + ], + }), ], mode: confConst.DEBUG ? 'development' : 'production', devtool: confConst.DEBUG ? 'source-map' : undefined, devServer: confConst.DEBUG ? devServer : devServer, }; -if (!confConst.DEBUG) { - config.plugins.push(new InjectManifest({ - swSrc: './src/sw.js', - swDest: 'sw.js', - exclude: [ - /\.m?js$/, - ], - })); -} - module.exports = config;