diff --git a/babel.config.js b/babel.config.js index 644fe06a6..890d314d2 100644 --- a/babel.config.js +++ b/babel.config.js @@ -3,9 +3,10 @@ * https://babeljs.io/docs/en/config-files#project-wide-configuration * * We use this file to apply babel configuration to packages in `node_modules` - * for testing with jest. */ -const lodashOptimizations = ["use-lodash-es", "lodash"]; + +const lodashOptimizations = + process.env.WEBPACK === "true" ? ["use-lodash-es", "lodash"] : []; module.exports = { env: { @@ -23,6 +24,7 @@ module.exports = { ], "@babel/react", ], + plugins: ["dynamic-import-node"], }, }, }; diff --git a/package-lock.json b/package-lock.json index 5c7c34da1..cabb710ed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1747,6 +1747,45 @@ "through2": "^2.0.3" } }, + "@intervolga/optimize-cssnano-plugin": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@intervolga/optimize-cssnano-plugin/-/optimize-cssnano-plugin-1.0.6.tgz", + "integrity": "sha512-zN69TnSr0viRSU6cEDIcuPcP67QcpQ6uHACg58FiN9PDrU6SLyGW3MR4tiISbYxy1kDWAVPwD+XwQTWE5cigAA==", + "dev": true, + "requires": { + "cssnano": "^4.0.0", + "cssnano-preset-default": "^4.0.0", + "postcss": "^7.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "@mdx-js/loader": { "version": "0.13.1-1", "resolved": "https://registry.npmjs.org/@mdx-js/loader/-/loader-0.13.1-1.tgz", @@ -2318,9 +2357,9 @@ } }, "@types/linkify-it": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-2.0.3.tgz", - "integrity": "sha512-WRj1rJPkj3fyDME63xUkWvcWzq0XjpiqjJGNCH4Y6Fbiv2fVMDCqeIA6ch/UUG3VYrfGq1VNFLsz6Sat6FjsPw==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-2.0.4.tgz", + "integrity": "sha512-9o5piu3tP6DwqT+Cyf7S3BitsTc6Cl0pCPKUhIE5hzQbtueiBXdtBipTLLvaGfT11/8XHRmsagu4YfBesTaiCA==", "dev": true }, "@types/linkifyjs": { @@ -2484,6 +2523,12 @@ "@types/react": "*" } }, + "@types/q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.1.tgz", + "integrity": "sha512-eqz8c/0kwNi/OEHQfvIuJVLTst3in0e7uTKeuY+WL/zfKn0xVujOTp42bS/vUUokhK5P2BppLd9JXMOMHcgbjA==", + "dev": true + }, "@types/range-parser": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.2.tgz", @@ -2545,6 +2590,15 @@ "@types/react": "*" } }, + "@types/react-transition-group": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-2.0.14.tgz", + "integrity": "sha512-pa7qB0/mkhwWMBFoXhX8BcntK8G4eQl4sIfSrJCxnivTYRQWjOWf2ClR9bWdm0EUFBDHzMbKYS+QYfDtBzkY4w==", + "dev": true, + "requires": { + "@types/react": "*" + } + }, "@types/recompose": { "version": "0.26.5", "resolved": "https://registry.npmjs.org/@types/recompose/-/recompose-0.26.5.tgz", @@ -4044,6 +4098,15 @@ "babel-runtime": "^6.22.0" } }, + "babel-plugin-dynamic-import-node": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.2.0.tgz", + "integrity": "sha512-fP899ELUnTaBcIzmrW7nniyqqdYWrWuJUyPWHxFa/c7r7hS6KC8FscNfLlBNIoPSc55kYMGEEKjPjJGCLbE1qA==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, "babel-plugin-emotion": { "version": "9.2.6", "resolved": "https://registry.npmjs.org/babel-plugin-emotion/-/babel-plugin-emotion-9.2.6.tgz", @@ -6407,6 +6470,24 @@ "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", "dev": true }, + "caller-callsite": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", + "dev": true, + "requires": { + "callsites": "^2.0.0" + } + }, + "caller-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", + "dev": true, + "requires": { + "caller-callsite": "^2.0.0" + } + }, "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", @@ -6446,35 +6527,44 @@ "dev": true }, "caniuse-api": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-1.6.1.tgz", - "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=", + "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": "^1.3.6", - "caniuse-db": "^1.0.30000529", + "browserslist": "^4.0.0", + "caniuse-lite": "^1.0.0", "lodash.memoize": "^4.1.2", "lodash.uniq": "^4.5.0" }, "dependencies": { "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", "dev": true, "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" + }, + "dependencies": { + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + } } + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", + "dev": true } } }, - "caniuse-db": { - "version": "1.0.30000859", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000859.tgz", - "integrity": "sha1-boE6F1fxmpPLNnX2tQN6yoC+oGI=", - "dev": true - }, "caniuse-lite": { "version": "1.0.30000859", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000859.tgz", @@ -6695,30 +6785,6 @@ "safe-buffer": "^5.0.1" } }, - "clap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/clap/-/clap-1.2.3.tgz", - "integrity": "sha512-4CoL/A3hf90V3VIEjeuhSvlGFEHKzOz+Wfc2IVZc+FaUgU0ZQafJTP49fvnULipOPcAfqhyI2duwQyns6xqjYA==", - "dev": true, - "requires": { - "chalk": "^1.1.3" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - } - } - }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", @@ -6939,12 +7005,6 @@ } } }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, "clone-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", @@ -6979,11 +7039,13 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "coa": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.4.tgz", - "integrity": "sha1-qe8VNmDWqGqL3sAomlxoTSF0Mv0=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/coa/-/coa-2.0.2.tgz", + "integrity": "sha512-q5/jG+YQnSy4nRTV4F7lPepBJZ8qBNJJDBuJdoejDyLXgmL7IEo+Le2JDZudFTFt7mrCqIRaSjws4ygRCTCAXA==", "dev": true, "requires": { + "@types/q": "^1.5.1", + "chalk": "^2.4.1", "q": "^1.1.2" } }, @@ -7030,14 +7092,13 @@ } }, "color": { - "version": "0.11.4", - "resolved": "https://registry.npmjs.org/color/-/color-0.11.4.tgz", - "integrity": "sha1-bXtcdPtl6EHNSHkq0e1eB7kE12Q=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.0.tgz", + "integrity": "sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg==", "dev": true, "requires": { - "clone": "^1.0.2", - "color-convert": "^1.3.0", - "color-string": "^0.3.0" + "color-convert": "^1.9.1", + "color-string": "^1.5.2" } }, "color-convert": { @@ -7056,12 +7117,13 @@ "dev": true }, "color-string": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-0.3.0.tgz", - "integrity": "sha1-J9RvtnAlxcL6JZk7+/V55HhBuZE=", + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", + "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", "dev": true, "requires": { - "color-name": "^1.0.0" + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" } }, "color-support": { @@ -7070,17 +7132,6 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, - "colormin": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colormin/-/colormin-1.1.2.tgz", - "integrity": "sha1-6i90IKcrlogaOKrlnsEkpvcpgTM=", - "dev": true, - "requires": { - "color": "^0.11.0", - "css-color-names": "0.0.4", - "has": "^1.0.1" - } - }, "colornames": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz", @@ -7089,7 +7140,7 @@ }, "colors": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", + "resolved": "http://registry.npmjs.org/colors/-/colors-1.1.2.tgz", "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", "dev": true }, @@ -7858,26 +7909,137 @@ } } }, + "css-blank-pseudo": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/css-blank-pseudo/-/css-blank-pseudo-0.1.4.tgz", + "integrity": "sha512-LHz35Hr83dnFeipc7oqFDmsjHdljj3TQtxGGiNWSOsTLIAubSm4TEz8qCaKFpk7idaQ1GfWscF4E6mgpBysA1w==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "css-color-names": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz", "integrity": "sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=", "dev": true }, + "css-declaration-sorter": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz", + "integrity": "sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==", + "dev": true, + "requires": { + "postcss": "^7.0.1", + "timsort": "^0.3.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "css-has-pseudo": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/css-has-pseudo/-/css-has-pseudo-0.10.0.tgz", + "integrity": "sha512-Z8hnfsZu4o/kt+AuFzeGpLVhFOGO9mluyHBaA2bA8aCGTwah5sT3WV/fTHH8UNZUytOIImuGPrl/prlb4oX4qQ==", + "dev": true, + "requires": { + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "css-loader": { - "version": "0.28.11", - "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-0.28.11.tgz", - "integrity": "sha512-wovHgjAx8ZIMGSL8pTys7edA1ClmzxHeY6n/d97gg5odgsxEgKjULPR0viqyC+FWMCL9sfqoC/QCUBo62tLvPg==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-1.0.1.tgz", + "integrity": "sha512-+ZHAZm/yqvJ2kDtPne3uX0C+Vr3Zn5jFn2N4HywtS5ujwvsVkyg0VArEXpl3BgczDA8anieki1FIzhchX4yrDw==", "dev": true, "requires": { "babel-code-frame": "^6.26.0", "css-selector-tokenizer": "^0.7.0", - "cssnano": "^3.10.0", "icss-utils": "^2.1.0", "loader-utils": "^1.0.2", - "lodash.camelcase": "^4.3.0", - "object-assign": "^4.1.1", - "postcss": "^5.0.6", + "lodash": "^4.17.11", + "postcss": "^6.0.23", "postcss-modules-extract-imports": "^1.2.0", "postcss-modules-local-by-default": "^1.2.0", "postcss-modules-scope": "^1.1.0", @@ -7886,53 +8048,11 @@ "source-list-map": "^2.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "lodash": { + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } } } }, @@ -8014,6 +8134,43 @@ } } }, + "css-prefers-color-scheme": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/css-prefers-color-scheme/-/css-prefers-color-scheme-3.1.1.tgz", + "integrity": "sha512-MTu6+tMs9S3EUqzmqLXEcgNRbNkkD/TGFvowpeoWJn5Vfq7FMgsmRQs9X5NXAURiOBmOxm/lLjsDNXDE6k9bhg==", + "dev": true, + "requires": { + "postcss": "^7.0.5" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, "css-select": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", @@ -8025,6 +8182,12 @@ "nth-check": "~1.0.1" } }, + "css-select-base-adapter": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", + "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==", + "dev": true + }, "css-selector-tokenizer": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/css-selector-tokenizer/-/css-selector-tokenizer-0.7.0.tgz", @@ -8070,15 +8233,37 @@ } } }, + "css-tree": { + "version": "1.0.0-alpha.28", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.28.tgz", + "integrity": "sha512-joNNW1gCp3qFFzj4St6zk+Wh/NBv0vM5YbEreZk0SD4S23S+1xBKb6cLDg2uj4P4k/GUMlIm6cKIDqIG+vdt0w==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + }, + "css-unit-converter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.1.tgz", + "integrity": "sha1-2bkoGtz9jO2TW9urqDeGiX9k6ZY=", + "dev": true + }, + "css-url-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/css-url-regex/-/css-url-regex-1.1.0.tgz", + "integrity": "sha1-g4NCMMyfdMRX3lnuvRVD/uuDt+w=", + "dev": true + }, "css-what": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.0.tgz", "integrity": "sha1-lGfQMsOM+u+58teVASUwYvh/ob0=" }, "cssdb": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-3.1.0.tgz", - "integrity": "sha512-9+ASPG9O+d876YfZJjStXszOGn3EEOp5scMeCwZb/+TwTZfadQFshz/Vsh1vGET9HzaXvsfV+1O0ShFb52DddA==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/cssdb/-/cssdb-4.3.0.tgz", + "integrity": "sha512-VHPES/+c9s+I0ryNj+PXvp84nz+ms843z/efpaEINwP/QfGsINL3gpLp5qjapzDNzNzbXxur8uxKxSXImrg4ag==", "dev": true }, "cssesc": { @@ -8088,127 +8273,207 @@ "dev": true }, "cssnano": { - "version": "3.10.0", - "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-3.10.0.tgz", - "integrity": "sha1-Tzj2zqK5sX+gFJDyPx3GjqZcHDg=", + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/cssnano/-/cssnano-4.1.7.tgz", + "integrity": "sha512-AiXL90l+MDuQmRNyypG2P7ux7K4XklxYzNNUd5HXZCNcH8/N9bHPcpN97v8tXgRVeFL/Ed8iP8mVmAAu0ZpT7A==", "dev": true, "requires": { - "autoprefixer": "^6.3.1", - "decamelize": "^1.1.2", - "defined": "^1.0.0", - "has": "^1.0.1", - "object-assign": "^4.0.1", - "postcss": "^5.0.14", - "postcss-calc": "^5.2.0", - "postcss-colormin": "^2.1.8", - "postcss-convert-values": "^2.3.4", - "postcss-discard-comments": "^2.0.4", - "postcss-discard-duplicates": "^2.0.1", - "postcss-discard-empty": "^2.0.1", - "postcss-discard-overridden": "^0.1.1", - "postcss-discard-unused": "^2.2.1", - "postcss-filter-plugins": "^2.0.0", - "postcss-merge-idents": "^2.1.5", - "postcss-merge-longhand": "^2.0.1", - "postcss-merge-rules": "^2.0.3", - "postcss-minify-font-values": "^1.0.2", - "postcss-minify-gradients": "^1.0.1", - "postcss-minify-params": "^1.0.4", - "postcss-minify-selectors": "^2.0.4", - "postcss-normalize-charset": "^1.1.0", - "postcss-normalize-url": "^3.0.7", - "postcss-ordered-values": "^2.1.0", - "postcss-reduce-idents": "^2.2.2", - "postcss-reduce-initial": "^1.0.0", - "postcss-reduce-transforms": "^1.0.3", - "postcss-svgo": "^2.1.1", - "postcss-unique-selectors": "^2.0.2", - "postcss-value-parser": "^3.2.3", - "postcss-zindex": "^2.0.1" + "cosmiconfig": "^5.0.0", + "cssnano-preset-default": "^4.0.5", + "is-resolvable": "^1.0.0", + "postcss": "^7.0.0" }, "dependencies": { - "autoprefixer": { - "version": "6.7.7", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-6.7.7.tgz", - "integrity": "sha1-Hb0cg1ZY41zj+ZhAmdsAWFx4IBQ=", + "cosmiconfig": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.0.7.tgz", + "integrity": "sha512-PcLqxTKiDmNT6pSpy4N6KtuPwb53W+2tzNvwOZw0WH9N6O0vLIBq0x8aj8Oj75ere4YcGi48bDFCL+3fRJdlNA==", "dev": true, "requires": { - "browserslist": "^1.7.6", - "caniuse-db": "^1.0.30000634", - "normalize-range": "^0.1.2", - "num2fraction": "^1.2.2", - "postcss": "^5.2.16", - "postcss-value-parser": "^3.2.3" + "import-fresh": "^2.0.0", + "is-directory": "^0.3.1", + "js-yaml": "^3.9.0", + "parse-json": "^4.0.0" } }, - "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "dev": true, "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" } }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, - "csso": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/csso/-/csso-2.3.2.tgz", - "integrity": "sha1-3dUsWHAz9J6Utx/FVWnyUuj/X4U=", + "cssnano-preset-default": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.5.tgz", + "integrity": "sha512-f1uhya0ZAjPYtDD58QkBB0R+uYdzHPei7cDxJyQQIHt5acdhyGXaSXl2nDLzWHLwGFbZcHxQtkJS8mmNwnxTvw==", "dev": true, "requires": { - "clap": "^1.0.9", - "source-map": "^0.5.3" + "css-declaration-sorter": "^4.0.1", + "cssnano-util-raw-cache": "^4.0.1", + "postcss": "^7.0.0", + "postcss-calc": "^7.0.0", + "postcss-colormin": "^4.0.2", + "postcss-convert-values": "^4.0.1", + "postcss-discard-comments": "^4.0.1", + "postcss-discard-duplicates": "^4.0.2", + "postcss-discard-empty": "^4.0.1", + "postcss-discard-overridden": "^4.0.1", + "postcss-merge-longhand": "^4.0.9", + "postcss-merge-rules": "^4.0.2", + "postcss-minify-font-values": "^4.0.2", + "postcss-minify-gradients": "^4.0.1", + "postcss-minify-params": "^4.0.1", + "postcss-minify-selectors": "^4.0.1", + "postcss-normalize-charset": "^4.0.1", + "postcss-normalize-display-values": "^4.0.1", + "postcss-normalize-positions": "^4.0.1", + "postcss-normalize-repeat-style": "^4.0.1", + "postcss-normalize-string": "^4.0.1", + "postcss-normalize-timing-functions": "^4.0.1", + "postcss-normalize-unicode": "^4.0.1", + "postcss-normalize-url": "^4.0.1", + "postcss-normalize-whitespace": "^4.0.1", + "postcss-ordered-values": "^4.1.1", + "postcss-reduce-initial": "^4.0.2", + "postcss-reduce-transforms": "^4.0.1", + "postcss-svgo": "^4.0.1", + "postcss-unique-selectors": "^4.0.1" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-util-get-arguments": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz", + "integrity": "sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=", + "dev": true + }, + "cssnano-util-get-match": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz", + "integrity": "sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=", + "dev": true + }, + "cssnano-util-raw-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz", + "integrity": "sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==", + "dev": true, + "requires": { + "postcss": "^7.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "cssnano-util-same-parent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz", + "integrity": "sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==", + "dev": true + }, + "csso": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", + "integrity": "sha512-vrqULLffYU1Q2tLdJvaCYbONStnfkfimRxXNaGjxMldI0C7JPBC4rB1RyjhfdZ4m1frm8pM9uRPKH3d2knZ8gg==", + "dev": true, + "requires": { + "css-tree": "1.0.0-alpha.29" + }, + "dependencies": { + "css-tree": { + "version": "1.0.0-alpha.29", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.29.tgz", + "integrity": "sha512-sRNb1XydwkW9IOci6iB2xmy8IGCj6r/fr+JWitvJ2JxQRPzN3T4AGGVWCMlVmVwM1gtgALJRmGIlWv5ppnGGkg==", + "dev": true, + "requires": { + "mdn-data": "~1.1.0", + "source-map": "^0.5.3" + } + } } }, "cssom": { @@ -8542,12 +8807,6 @@ } } }, - "defined": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", - "dev": true - }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -10256,9 +10515,9 @@ } }, "eslint-plugin-prettier": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz", - "integrity": "sha512-floiaI4F7hRkTrFe8V2ItOK97QYrX75DjmdzmVITZoAP6Cn06oEDPQRsO6MlHEP/u2SxI3xQ52Kpjw6j5WGfeQ==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.7.0.tgz", + "integrity": "sha512-CStQYJgALoQBw3FsBzH0VOVDRnJ/ZimUlpLm226U8qgqYJfPOY/CPK6wyRInMxh73HSKg5wyRwdS4BVYYHwokA==", "dev": true, "requires": { "fast-diff": "^1.1.1", @@ -10737,9 +10996,9 @@ "dev": true }, "fast-diff": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", - "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", "dev": true }, "fast-glob": { @@ -12998,6 +13257,12 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=" }, + "hex-color-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz", + "integrity": "sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==", + "dev": true + }, "history": { "version": "4.7.2", "resolved": "https://registry.npmjs.org/history/-/history-4.7.2.tgz", @@ -13086,10 +13351,22 @@ "wbuf": "^1.1.0" } }, + "hsl-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz", + "integrity": "sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=", + "dev": true + }, + "hsla-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz", + "integrity": "sha1-wc56MWjIxmFAM6S194d/OyJfnDg=", + "dev": true + }, "html-comment-regex": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.1.tgz", - "integrity": "sha1-ZouTd26q5V696POtRkswekljYl4=", + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/html-comment-regex/-/html-comment-regex-1.1.2.tgz", + "integrity": "sha512-P+M65QY2JQ5Y0G9KKdlDpo0zK+/OHptU5AaBwUfAIDJZk1MYf32Frm84EcOytfJE0t5JvkAnKlmjsXDnWzCJmQ==", "dev": true }, "html-encoding-sniffer": { @@ -13555,6 +13832,24 @@ "import-from": "^2.1.0" } }, + "import-fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", + "dev": true, + "requires": { + "caller-path": "^2.0.0", + "resolve-from": "^3.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", + "dev": true + } + } + }, "import-from": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-from/-/import-from-2.1.0.tgz", @@ -13815,6 +14110,20 @@ "ci-info": "^1.0.0" } }, + "is-color-stop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz", + "integrity": "sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=", + "dev": true, + "requires": { + "css-color-names": "^0.0.4", + "hex-color-regex": "^1.1.0", + "hsl-regex": "^1.0.0", + "hsla-regex": "^1.0.0", + "rgb-regex": "^1.0.1", + "rgba-regex": "^1.0.0" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -14130,6 +14439,12 @@ "is-absolute-url": "^2.0.0" } }, + "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-retry-allowed": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", @@ -14161,9 +14476,9 @@ "dev": true }, "is-svg": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-2.1.0.tgz", - "integrity": "sha1-z2EJDaDZ77yrhyLeum8DIgjbsOk=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-svg/-/is-svg-3.0.0.tgz", + "integrity": "sha512-gi4iHK53LR2ujhLVVj+37Ykh9GLqYHX6JOVXbLAucaG/Cqw9xwdFOjDM2qeifLs1sF1npXXFvDu0r5HNgCMrzQ==", "dev": true, "requires": { "html-comment-regex": "^1.1.0" @@ -16326,10 +16641,16 @@ "resolved": "https://registry.npmjs.org/limiter/-/limiter-1.1.3.tgz", "integrity": "sha512-zrycnIMsLw/3ZxTbW7HCez56rcFGecWTx5OZNplzcXUUmJLmoYArC6qdJzmAN5BWiNXGcpjhF9RQ1HSv5zebEw==" }, + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", + "dev": true + }, "linkify-it": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.0.3.tgz", - "integrity": "sha1-2UpGSPmxwXnWT6lykSaL22zpQ08=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-2.1.0.tgz", + "integrity": "sha512-4REs8/062kV2DSHxNfq5183zrqXMl7WP0WzABH9IeJI+NLm429FgE1PDecltYfnOoFDFlZGh2T8PfZn0r+GTRg==", "requires": { "uc.micro": "^1.0.1" } @@ -16821,12 +17142,6 @@ "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" }, - "lodash.camelcase": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", - "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=", - "dev": true - }, "lodash.clone": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", @@ -17278,12 +17593,6 @@ "integrity": "sha1-mnHEh0chjrylHlGmbaaCA4zct78=", "dev": true }, - "math-expression-evaluator": { - "version": "1.2.17", - "resolved": "https://registry.npmjs.org/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz", - "integrity": "sha1-3oGf282E3M2PrlnGrreWFbnSZqw=", - "dev": true - }, "math-random": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", @@ -17343,6 +17652,12 @@ "integrity": "sha1-XEVch4yTVfDB5/PotxnPWDaRrPs=", "dev": true }, + "mdn-data": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-1.1.4.tgz", + "integrity": "sha512-FSYbp3lyKjyj3E7fMl6rYvUdX0FBXaluGqlFoYESWQlyUTq8R+wp0rkFxoYFqZlHCvsUXGjyJmLQSnXToYhOSA==", + "dev": true + }, "mdurl": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", @@ -17589,14 +17904,27 @@ } }, "mini-css-extract-plugin": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.4.1.tgz", - "integrity": "sha512-XWuB3G61Rtasq/gLe7cp5cuozehE6hN+E4sxCamRR/WDiHTg+f7ZIAS024r8UJQffY+e2gGELXQZgQoFDfNDCg==", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/mini-css-extract-plugin/-/mini-css-extract-plugin-0.5.0.tgz", + "integrity": "sha512-IuaLjruM0vMKhUUT51fQdQzBYTX49dLj8w68ALEAe2A4iYNpIC4eMac67mt3NzycvjOlf07/kYxJDc0RTl1Wqw==", "dev": true, "requires": { - "@webpack-contrib/schema-utils": "^1.0.0-beta.0", "loader-utils": "^1.1.0", + "schema-utils": "^1.0.0", "webpack-sources": "^1.1.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } } }, "minimalistic-assert": { @@ -18179,28 +18507,10 @@ "dev": true }, "normalize-url": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-1.9.1.tgz", - "integrity": "sha1-LMDWazHqIwNkWENuNiDYWVTGbDw=", - "dev": true, - "requires": { - "object-assign": "^4.0.1", - "prepend-http": "^1.0.0", - "query-string": "^4.1.0", - "sort-keys": "^1.0.0" - }, - "dependencies": { - "query-string": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-4.3.4.tgz", - "integrity": "sha1-u7aTucqRXCMlFbIosaArYJBD2+s=", - "dev": true, - "requires": { - "object-assign": "^4.1.0", - "strict-uri-encode": "^1.0.0" - } - } - } + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz", + "integrity": "sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==", + "dev": true }, "now-and-later": { "version": "2.0.0", @@ -19253,294 +19563,425 @@ } }, "postcss-advanced-variables": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/postcss-advanced-variables/-/postcss-advanced-variables-2.3.3.tgz", - "integrity": "sha512-X7nwaP4yDVu3ZWsftQVuVcd/+thKsXTeQ2zQL9ivtgdpXu/ERlSGiOA8D7O/b0jnYj6oO4WpfvOCw7cOnGYEow==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-advanced-variables/-/postcss-advanced-variables-3.0.0.tgz", + "integrity": "sha512-e5tcG0+l2qaqV65+qQCAW91vX4+mYbh2m5tdBYrzOhYjFgtNmtehmZWotvzWTGbtgbP11tgGirEYy2P7m6HceQ==", "dev": true, "requires": { - "@csstools/sass-import-resolve": "^1", - "postcss": "^6" + "@csstools/sass-import-resolve": "^1.0.0", + "postcss": "^7.0.6" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-attribute-case-insensitive": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-3.0.1.tgz", - "integrity": "sha512-X/viSS9YrAoDnRa2R4sElsAmW+scOWeVW11FjWQN8m+FW1YY0jdIA9fuBSSF1pKsJTYXJXGJ1oAjFHl8cqcmKw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-attribute-case-insensitive/-/postcss-attribute-case-insensitive-4.0.0.tgz", + "integrity": "sha512-K/zqdg0/UgUgC8qR0lDuxYzmowPpnvrrNC5YuoqzhHMubR9AuhsPlpVu3jjkLHgDAzR+ohD/m7//iGnN9WxbzQ==", "dev": true, "requires": { - "postcss": "^6.0.23", - "postcss-selector-parser": "^4.0.0" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "cssesc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", - "integrity": "sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA==", - "dev": true - }, - "postcss-selector-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-4.0.0.tgz", - "integrity": "sha512-5h+MvEjnzu1qy6MabjuoPatsGAjjDV9B24e7Cktjl+ClNtjVjmvAXjOFQr1u7RlWULKNGYaYVE4s+DIIQ4bOGA==", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "cssesc": "^1.0.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, "postcss-calc": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-5.3.1.tgz", - "integrity": "sha1-d7rnypKK2FcW4v2kLyYb98HWW14=", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.1.tgz", + "integrity": "sha512-oXqx0m6tb4N3JGdmeMSc/i91KppbYsFZKdH0xMOqK8V1rJlzrKlTdokz8ozUXLVejydRN6u2IddxpcijRj2FqQ==", "dev": true, "requires": { - "postcss": "^5.0.2", - "postcss-message-helpers": "^2.0.0", - "reduce-css-calc": "^1.2.6" + "css-unit-converter": "^1.1.1", + "postcss": "^7.0.5", + "postcss-selector-parser": "^5.0.0-rc.4", + "postcss-value-parser": "^3.3.1" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } + "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 }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-color-functional-notation": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-1.0.2.tgz", - "integrity": "sha512-FxkEr+s/KCrcrTxUhHcDMKGZmnLjUKK7pl2gDjnEoAJaVcbThdDWLhuASu02qdA3Ys7np/BwJgwc72JrURTvJQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-2.0.1.tgz", + "integrity": "sha512-ZBARCypjEDofW4P6IdPVTLhDNXPRn8T2s1zHbZidW6rPaaZvcnCS2soYFIQJrMZSxiePJ2XIYTlcb2ztr/eT2g==", "dev": true, "requires": { - "postcss": "^6.0.23", - "postcss-values-parser": "^1.5.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-color-gray": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-color-gray/-/postcss-color-gray-5.0.0.tgz", + "integrity": "sha512-q6BuRnAGKM/ZRpfDascZlIZPjvwsRye7UDNalqVz3s7GDxMtqPY6+Q871liNxsonUw8oC61OG+PSaysYpl1bnw==", + "dev": true, + "requires": { + "@csstools/convert-colors": "^1.4.0", + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-color-hex-alpha": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-3.0.0.tgz", - "integrity": "sha1-HlPmyKyyN5Vej9CLfs2xuLgwn5U=", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-hex-alpha/-/postcss-color-hex-alpha-5.0.2.tgz", + "integrity": "sha512-8bIOzQMGdZVifoBQUJdw+yIY00omBd2EwkJXepQo9cjp1UOHHHoeRDeSzTP6vakEpaRc6GAIOfvcQR7jBYaG5Q==", "dev": true, "requires": { - "color": "^1.0.3", - "postcss": "^6.0.1", - "postcss-message-helpers": "^2.0.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" }, "dependencies": { - "color": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/color/-/color-1.0.3.tgz", - "integrity": "sha1-5I6DLYXxTvaU+0aIEcLVz+cptV0=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "color-convert": "^1.8.2", - "color-string": "^1.4.0" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "color-string": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.2.tgz", - "integrity": "sha1-JuRYFLw8mny9Z1FkikFDRRSnc6k=", + "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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "has-flag": "^3.0.0" } } } }, "postcss-color-mod-function": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-2.4.2.tgz", - "integrity": "sha512-j9RM33ybsEJUvIc22Y5I4ucvSJVHMliiW0I34JDLV0gVdvCo7/Y+zW6QMBANj+M4VZJLmyGz2mafIK4Tb5GVyg==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/postcss-color-mod-function/-/postcss-color-mod-function-3.0.3.tgz", + "integrity": "sha512-YP4VG+xufxaVtzV6ZmhEtc+/aTXH3d0JLpnYfxqTvwZPbJhWqp8bSY3nfNzNRFLgB4XSaBA82OE4VjOOKpCdVQ==", "dev": true, "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^6.0.19", - "postcss-values-parser": "^1.3.2" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-color-rebeccapurple": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-3.1.0.tgz", - "integrity": "sha512-212hJUk9uSsbwO5ECqVjmh/iLsmiVL1xy9ce9TVf+X3cK/ZlUIlaMdoxje/YpsL9cmUH3I7io+/G2LyWx5rg1g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-color-rebeccapurple/-/postcss-color-rebeccapurple-4.0.1.tgz", + "integrity": "sha512-aAe3OhkS6qJXBbqzvZth2Au4V3KieR5sRQ4ptb2b2O8wgvB3SJBsdG+jsn2BZbbwekDG8nTfcCNKcSfe/lEy8g==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-colormin": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-2.2.2.tgz", - "integrity": "sha1-ZjFBfV8OkJo9fsJrJMio0eT5bks=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.2.tgz", + "integrity": "sha512-1QJc2coIehnVFsz0otges8kQLsryi4lo19WD+U5xCWvXd0uw/Z+KKYnbiNDCnO9GP+PvErPHCG0jNvWTngk9Rw==", "dev": true, "requires": { - "colormin": "^1.0.5", - "postcss": "^5.0.13", - "postcss-value-parser": "^3.2.3" + "browserslist": "^4.0.0", + "color": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", "dev": true }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-convert-values": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-2.6.1.tgz", - "integrity": "sha1-u9hZPFwf0uPRwyK7kl3K6Nrk1i0=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz", + "integrity": "sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==", "dev": true, "requires": { - "postcss": "^5.0.11", - "postcss-value-parser": "^3.1.2" + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-css-variables": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.9.0.tgz", - "integrity": "sha512-pSNj57bdBaFnyDzt96SZOidMMWY7Q5OySQdKsTxaxehYO6EtNC5SAKIwjah/poh5vUmNthkrlOd03bP+HNGuCA==", + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/postcss-css-variables/-/postcss-css-variables-0.11.0.tgz", + "integrity": "sha512-pjqWnJSy8zoentAhRIph/DiOX0EZmT/dpmVbpdSrCSdkdqstl2ViBlAfIIuHvHI+baTV8Gd+WzsVFjDZqVn4dg==", "dev": true, "requires": { "escape-string-regexp": "^1.0.3", @@ -19549,462 +19990,489 @@ } }, "postcss-custom-media": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-6.0.0.tgz", - "integrity": "sha1-vlMnhBEOyylQRPtTlaGABushpzc=", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-7.0.7.tgz", + "integrity": "sha512-bWPCdZKdH60wKOTG4HKEgxWnZVjAIVNOJDvi3lkuTa90xo/K0YHa2ZnlKLC5e2qF8qCcMQXt0yzQITBp8d0OFA==", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "^7.0.5" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-custom-properties": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-7.0.0.tgz", - "integrity": "sha512-dl/CNaM6z2RBa0vZZqsV6Hunj4HD6Spu7FcAkiVp5B2tgm6xReKKYzI7x7QNx3wTMBNj5v+ylfVcQGMW4xdkHw==", + "version": "8.0.9", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-8.0.9.tgz", + "integrity": "sha512-/Lbn5GP2JkKhgUO2elMs4NnbUJcvHX4AaF5nuJDaNkd2chYW1KA5qtOGGgdkBEWcXtKSQfHXzT7C6grEVyb13w==", "dev": true, "requires": { - "balanced-match": "^1.0.0", - "postcss": "^6.0.18" + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-custom-selectors": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-4.0.1.tgz", - "integrity": "sha1-eBOC+UxS5yfvXKR3bqKt9JphE4I=", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-5.1.2.tgz", + "integrity": "sha512-DSGDhqinCqXqlS4R7KGxL1OSycd1lydugJ1ky4iRXPHdBRiozyMHrdu0H3o7qNOCiZwySZTUI5MV0T8QhCLu+w==", "dev": true, "requires": { - "postcss": "^6.0.1", - "postcss-selector-matches": "^3.0.0" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-dir-pseudo-class": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-4.0.0.tgz", - "integrity": "sha512-ZAeXMIyZukHHeDt5IFchWB+okPzasb8YnpkXIgTiJl4216X1IplMrODjihZIBDXNE2RdJRBCAOx8uGzCnGSxTA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/postcss-dir-pseudo-class/-/postcss-dir-pseudo-class-5.0.0.tgz", + "integrity": "sha512-3pm4oq8HYWMZePJY+5ANriPs3P07q+LW6FAdTlkFH2XqDdP4HeeJYMOzn0HYLhRSjBO3fhiqSwwU9xEULSrPgw==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-selector-parser": "^4.0.0" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "cssesc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", - "integrity": "sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA==", - "dev": true - }, - "postcss-selector-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-4.0.0.tgz", - "integrity": "sha512-5h+MvEjnzu1qy6MabjuoPatsGAjjDV9B24e7Cktjl+ClNtjVjmvAXjOFQr1u7RlWULKNGYaYVE4s+DIIQ4bOGA==", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "cssesc": "^1.0.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, "postcss-discard-comments": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-2.0.4.tgz", - "integrity": "sha1-vv6J+v1bPazlzM5Rt2uBUUvgDj0=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.1.tgz", + "integrity": "sha512-Ay+rZu1Sz6g8IdzRjUgG2NafSNpp2MSMOQUb+9kkzzzP+kh07fP0yNbhtFejURnyVXSX3FYy2nVNW1QTnNjgBQ==", "dev": true, "requires": { - "postcss": "^5.0.14" + "postcss": "^7.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-discard-duplicates": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-2.1.0.tgz", - "integrity": "sha1-uavye4isGIFYpesSq8riAmO5GTI=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz", + "integrity": "sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==", "dev": true, "requires": { - "postcss": "^5.0.4" + "postcss": "^7.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-discard-empty": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-2.1.0.tgz", - "integrity": "sha1-0rS9nVztXr2Nyt52QMfXzX9PkrU=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz", + "integrity": "sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==", "dev": true, "requires": { - "postcss": "^5.0.14" + "postcss": "^7.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-discard-overridden": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-0.1.1.tgz", - "integrity": "sha1-ix6vVU9ob7KIzYdMVWZ7CqNmjVg=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz", + "integrity": "sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==", "dev": true, "requires": { - "postcss": "^5.0.16" + "postcss": "^7.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, - "postcss-discard-unused": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-discard-unused/-/postcss-discard-unused-2.2.3.tgz", - "integrity": "sha1-vOMLLMWR/8Y0Mitfs0ZLbZNPRDM=", + "postcss-double-position-gradients": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/postcss-double-position-gradients/-/postcss-double-position-gradients-1.0.0.tgz", + "integrity": "sha512-G+nV8EnQq25fOI8CH/B6krEohGWnF5+3A6H/+JEpOncu5dCnkS1QQ6+ct3Jkaepw1NGVqqOZH6lqrm244mCftA==", "dev": true, "requires": { - "postcss": "^5.0.14", - "uniqs": "^2.0.0" + "postcss": "^7.0.5", + "postcss-values-parser": "^2.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-env-function": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-1.0.0.tgz", - "integrity": "sha512-UVkdbVCRAEr79XkS6uxMRWIHYrFNuhXmjw6gxyesCBXzzHIvYOoz5UKTWM39xX3j9vGO5waVzxq/VzEiZgsM0g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/postcss-env-function/-/postcss-env-function-2.0.2.tgz", + "integrity": "sha512-rwac4BuZlITeUbiBq60h/xbLzXY43qOsIErngWa4l7Mt+RaSkT7QBjXVGTcBHupykkblHMDrBFh30zchYPaOUw==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" - } - }, - "postcss-filter-plugins": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/postcss-filter-plugins/-/postcss-filter-plugins-2.0.3.tgz", - "integrity": "sha512-T53GVFsdinJhgwm7rg1BzbeBRomOg9y5MBVhGcsV0CxurUdVj1UlPdKtn7aqYA/c/QVkzKMjq2bSV5dKG5+AwQ==", - "dev": true, - "requires": { - "postcss": "^5.0.4" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-flexbugs-fixes": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-3.3.1.tgz", - "integrity": "sha512-9y9kDDf2F9EjKX6x9ueNa5GARvsUbXw4ezH8vXItXHwKzljbu8awP7t5dCaabKYm18Vs1lo5bKQcnc0HkISt+w==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-flexbugs-fixes/-/postcss-flexbugs-fixes-4.1.0.tgz", + "integrity": "sha512-jr1LHxQvStNNAHlgco6PzY308zvLklh7SJVYuWUwyUQncofaAlD2l+P/gxKHOdqWKe7xJSkVLFF/2Tp+JqMSZA==", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "^7.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-focus-visible": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-3.0.0.tgz", - "integrity": "sha512-6i3HsOrWNelxBYPh/HWAXF9lPwCFAfFVlUTZqsLRXYMPhcXH1eXdItozRBvT9l5pYF4ddJJbgk4JOp0au0QToA==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-focus-visible/-/postcss-focus-visible-4.0.0.tgz", + "integrity": "sha512-Z5CkWBw0+idJHSV6+Bgf2peDOFf/x4o+vX/pwcNYrWpXFrSfTkQ3JQ1ojrq9yS+upnAlNRHeg8uEwFTgorjI8g==", "dev": true, "requires": { - "postcss": "^6.0" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-focus-within": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-2.0.0.tgz", - "integrity": "sha512-LTbT/dxZ3FahpOv1XZMyRLNnBk5QWVU4HL/p82iFkzoPNVhNQazaYIujHXTOAKea5clgjbj6GdFj7mU7qzy1kQ==", - "dev": true, - "requires": { - "postcss": "^6.0.21" - } - }, - "postcss-font-family-system-ui": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-family-system-ui/-/postcss-font-family-system-ui-3.0.0.tgz", - "integrity": "sha512-58G/hTxMSSKlIRpcPUjlyo6hV2MEzvcVO2m4L/T7Bb2fJTG4DYYfQjQeRvuimKQh1V1sOzCIz99g+H2aFNtlQw==", + "resolved": "https://registry.npmjs.org/postcss-focus-within/-/postcss-focus-within-3.0.0.tgz", + "integrity": "sha512-W0APui8jQeBKbCGZudW37EeMCjDeVxKgiYfIIEo8Bdh5SpB9sxds/Iq8SEuzS0Q4YFOlG7EPFulbbxujpkrV2w==", "dev": true, "requires": { - "postcss": "^6.0" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-font-magician": { @@ -20020,31 +20488,115 @@ } }, "postcss-font-variant": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-3.0.0.tgz", - "integrity": "sha1-CMzIj2BQuoLtjvLMdsDGprQfGD4=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-font-variant/-/postcss-font-variant-4.0.0.tgz", + "integrity": "sha512-M8BFYKOvCrI2aITzDad7kWuXXTm0YhGdP9Q8HanmN4EF1Hmcgs1KK5rSHylt/lUJe8yLxiSwWAHdScoEiIxztg==", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-gap-properties": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-1.0.0.tgz", - "integrity": "sha512-snL2k0Nie72J0uCsKgfO2Sd5rs3Wlhsk+k9uVzyMaeBH9gouNPPY7tZ4bopRJmBISbZEUtvF8Gchat6nOFQHdg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-gap-properties/-/postcss-gap-properties-2.0.0.tgz", + "integrity": "sha512-QZSqDaMgXCHuHTEzMsS2KfVDOq7ZFiknSpkrPJY6jmxbugUPTuSzs/vuE5I3zv0WAS+3vhrlqhijiprnuQfzmg==", "dev": true, "requires": { - "postcss": "^6.0.22" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-image-set-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-2.0.0.tgz", - "integrity": "sha512-2bpCIj2wFhIhpyM1G/ZZgBJM8K/VKbEcQ0SX5MI9MTGv4giPMnuXMa/sX3bHXHhi1PL4v2WRfqD1+w5T9EhFqg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/postcss-image-set-function/-/postcss-image-set-function-3.0.1.tgz", + "integrity": "sha512-oPTcFFip5LZy8Y/whto91L9xdRHCWEMs3e1MdJxhgt4jy2WYXfhkng59fH5qLXSCPN8k4n94p1Czrfe5IOkKUw==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-import": { @@ -20060,24 +20612,80 @@ } }, "postcss-initial": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-2.0.0.tgz", - "integrity": "sha1-cnFfczbgu3k1HZnuZcSiU6hEG6Q=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-initial/-/postcss-initial-3.0.0.tgz", + "integrity": "sha512-WzrqZ5nG9R9fUtrA+we92R4jhVvEB32IIRTzfIG/PLL8UV4CvbF1ugTEHEFX6vWxl41Xt5RTCJPEZkuWzrOM+Q==", "dev": true, "requires": { "lodash.template": "^4.2.4", - "postcss": "^6.0.1" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-lab-function": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-1.0.1.tgz", - "integrity": "sha512-kxaHXTRjlBLDpJUMsrGM0GnVgBrMqQz1HfaXdv3VNpz2EwwUhrU9uVfqv/NAtiWt6ZkD7IK2MUlncRoj024lIA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-2.0.1.tgz", + "integrity": "sha512-whLy1IeZKY+3fYdqQFuDBf8Auw+qFuVnChWjmxm/UhHWqNHZx+B99EwxTvGYmUBqe3Fjxs4L1BoZTJmPu6usVg==", "dev": true, "requires": { "@csstools/convert-colors": "^1.4.0", - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-load-config": { @@ -20115,483 +20723,444 @@ } }, "postcss-loader": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-2.1.6.tgz", - "integrity": "sha512-hgiWSc13xVQAq25cVw80CH0l49ZKlAnU1hKPOdRrNj89bokRr/bZF2nT+hebPPF9c9xs8c3gw3Fr2nxtmXYnNg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-3.0.0.tgz", + "integrity": "sha512-cLWoDEY5OwHcAjDnkyRQzAXfs2jrKjXpO/HQFcc5b5u/r7aa471wdmChmwfnv7x2u840iat/wi0lQ5nbRgSkUA==", "dev": true, "requires": { "loader-utils": "^1.1.0", - "postcss": "^6.0.0", + "postcss": "^7.0.0", "postcss-load-config": "^2.0.0", - "schema-utils": "^0.4.0" + "schema-utils": "^1.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-logical": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-1.1.1.tgz", - "integrity": "sha512-ZJgyLJlp3uPKae9+6sJKFjD06UZzb/m3M1LPeHsaBMvvyatcNWwCfOZVIq00fJdxUqa9QeuQO6RZElKmRdWMEg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-logical/-/postcss-logical-3.0.0.tgz", + "integrity": "sha512-1SUKdJc2vuMOmeItqGuNaC+N8MzBWFWEkAnRnLpFYj1tGGa7NqyVBujfRtgNa2gXR+6RkGUiB2O5Vmh7E2RmiA==", "dev": true, "requires": { - "postcss": "^6.0.20" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-media-minmax": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-3.0.0.tgz", - "integrity": "sha1-Z1JWA3pD70C8Twdgv9BtTcadSNI=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-media-minmax/-/postcss-media-minmax-4.0.0.tgz", + "integrity": "sha512-fo9moya6qyxsjbFAYl97qKO9gyre3qvbMnkOZeZwlsW6XYFsvs2DMGDlchVLfAd8LHPZDxivu/+qW2SMQeTHBw==", "dev": true, "requires": { - "postcss": "^6.0.1" - } - }, - "postcss-merge-idents": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/postcss-merge-idents/-/postcss-merge-idents-2.1.7.tgz", - "integrity": "sha1-TFUwMTwI4dWzu/PSu8dH4njuonA=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.10", - "postcss-value-parser": "^3.1.1" + "postcss": "^7.0.2" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-merge-longhand": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-2.0.2.tgz", - "integrity": "sha1-I9kM0Sewp3mUkVMyc5A0oaTz1lg=", + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.9.tgz", + "integrity": "sha512-UVMXrXF5K/kIwUbK/crPFCytpWbNX2Q3dZSc8+nQUgfOHrCT4+MHncpdxVphUlQeZxlLXUJbDyXc5NBhTnS2tA==", "dev": true, "requires": { - "postcss": "^5.0.4" + "css-color-names": "0.0.4", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "stylehacks": "^4.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-merge-rules": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-2.1.2.tgz", - "integrity": "sha1-0d9d+qexrMO+VT8OnhDofGG19yE=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.2.tgz", + "integrity": "sha512-UiuXwCCJtQy9tAIxsnurfF0mrNHKc4NnNx6NxqmzNNjXpQwLSukUxELHTRF0Rg1pAmcoKLih8PwvZbiordchag==", "dev": true, "requires": { - "browserslist": "^1.5.2", - "caniuse-api": "^1.5.2", - "postcss": "^5.0.4", - "postcss-selector-parser": "^2.2.2", + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "cssnano-util-same-parent": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0", "vendors": "^1.0.0" }, "dependencies": { "browserslist": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-1.7.7.tgz", - "integrity": "sha1-C9dnBCWL6CmyOYu1Dkti0aFmsLk=", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", "dev": true, "requires": { - "caniuse-db": "^1.0.30000639", - "electron-to-chromium": "^1.2.7" + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" } }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", "dev": true }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", "dev": true, "requires": { - "has-flag": "^1.0.0" + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, - "postcss-message-helpers": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-message-helpers/-/postcss-message-helpers-2.0.0.tgz", - "integrity": "sha1-pPL0+rbk/gAvCu0ABHjN9S+bpg4=", - "dev": true - }, "postcss-minify-font-values": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-1.0.5.tgz", - "integrity": "sha1-S1jttWZB66fIR0qzUmyv17vey2k=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz", + "integrity": "sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==", "dev": true, "requires": { - "object-assign": "^4.0.1", - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-minify-gradients": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-1.0.5.tgz", - "integrity": "sha1-Xb2hE3NwP4PPtKPqOIHY11/15uE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.1.tgz", + "integrity": "sha512-pySEW3E6Ly5mHm18rekbWiAjVi/Wj8KKt2vwSfVFAWdW6wOIekgqxKxLU7vJfb107o3FDNPkaYFCxGAJBFyogA==", "dev": true, "requires": { - "postcss": "^5.0.12", - "postcss-value-parser": "^3.3.0" + "cssnano-util-get-arguments": "^4.0.0", + "is-color-stop": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-minify-params": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-1.2.2.tgz", - "integrity": "sha1-rSzgcTc7lDs9kwo/pZo1jCjW8fM=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.1.tgz", + "integrity": "sha512-h4W0FEMEzBLxpxIVelRtMheskOKKp52ND6rJv+nBS33G1twu2tCyurYj/YtgU76+UDCvWeNs0hs8HFAWE2OUFg==", "dev": true, "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.2", - "postcss-value-parser": "^3.0.2", + "alphanum-sort": "^1.0.0", + "browserslist": "^4.0.0", + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", "uniqs": "^2.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", "dev": true }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-minify-selectors": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-2.1.1.tgz", - "integrity": "sha1-ssapjAByz5G5MtGkllCBFDEXNb8=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.1.tgz", + "integrity": "sha512-8+plQkomve3G+CodLCgbhAKrb5lekAnLYuL1d7Nz+/7RANpBEVdgBkPNwljfSKvZ9xkkZTZITd04KP+zeJTJqg==", "dev": true, "requires": { - "alphanum-sort": "^1.0.2", - "has": "^1.0.1", - "postcss": "^5.0.14", - "postcss-selector-parser": "^2.0.0" + "alphanum-sort": "^1.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-modules-extract-imports": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.0.tgz", - "integrity": "sha1-ZhQOzs447wa/DT41XWm/WdFB6oU=", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-1.2.1.tgz", + "integrity": "sha512-6jt9XZwUhwmRUhb/CkyJY020PYaPJsCyt3UjbaWo6XEbH/94Hmv6MP7fG2C5NDU/BcHzyGYxNtHvM+LTf9HrYw==", "dev": true, "requires": { "postcss": "^6.0.1" @@ -20628,244 +21197,603 @@ } }, "postcss-nested": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-3.0.0.tgz", - "integrity": "sha512-1xxmLHSfubuUi6xZZ0zLsNoiKfk3BWQj6fkNMaBJC529wKKLcdeCxXt6KJmDLva+trNyQNwEaE/ZWMA7cve1fA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-4.1.1.tgz", + "integrity": "sha512-3+V8+g+i9zUQ/AADNtBj3DVVvSOhRCV7W8Kzn9n4ViWJtSQrSdtIJnxZaupfdTrnhCkY86sAsuKVxBCuyfJDeA==", "dev": true, "requires": { - "postcss": "^6.0.14", - "postcss-selector-parser": "^3.1.1" + "postcss": "^7.0.6", + "postcss-selector-parser": "^5.0.0-rc.4" }, "dependencies": { - "postcss-selector-parser": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", - "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "dot-prop": "^4.1.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" } } } }, "postcss-nesting": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-6.0.0.tgz", - "integrity": "sha512-Yoglsy6eZbDCbRIXoYSmnIt9ao4xyg07iFwVBd7WyIkDzMSeRxIqUk8xEAdkeJQ7eGfWo6RufrTU7FSUjZ22fg==", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/postcss-nesting/-/postcss-nesting-7.0.0.tgz", + "integrity": "sha512-WSsbVd5Ampi3Y0nk/SKr5+K34n52PqMqEfswu6RtU4r7wA8vSD+gM8/D9qq4aJkHImwn1+9iEFTbjoWsQeqtaQ==", "dev": true, "requires": { - "postcss": "^6.0.22" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-normalize-charset": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-1.1.1.tgz", - "integrity": "sha1-757nEhLX/nWceO0WL2HtYrXLk/E=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz", + "integrity": "sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==", "dev": true, "requires": { - "postcss": "^5.0.5" + "postcss": "^7.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-display-values": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.1.tgz", + "integrity": "sha512-R5mC4vaDdvsrku96yXP7zak+O3Mm9Y8IslUobk7IMP+u/g+lXvcN4jngmHY5zeJnrQvE13dfAg5ViU05ZFDwdg==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-positions": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.1.tgz", + "integrity": "sha512-GNoOaLRBM0gvH+ZRb2vKCIujzz4aclli64MBwDuYGU2EY53LwiP7MxOZGE46UGtotrSnmarPPZ69l2S/uxdaWA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-repeat-style": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.1.tgz", + "integrity": "sha512-fFHPGIjBUyUiswY2rd9rsFcC0t3oRta4wxE1h3lpwfQZwFeFjXFSiDtdJ7APCmHQOnUZnqYBADNRPKPwFAONgA==", + "dev": true, + "requires": { + "cssnano-util-get-arguments": "^4.0.0", + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-string": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.1.tgz", + "integrity": "sha512-IJoexFTkAvAq5UZVxWXAGE0yLoNN/012v7TQh5nDo6imZJl2Fwgbhy3J2qnIoaDBrtUP0H7JrXlX1jjn2YcvCQ==", + "dev": true, + "requires": { + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-timing-functions": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.1.tgz", + "integrity": "sha512-1nOtk7ze36+63ONWD8RCaRDYsnzorrj+Q6fxkQV+mlY5+471Qx9kspqv0O/qQNMeApg8KNrRf496zHwJ3tBZ7w==", + "dev": true, + "requires": { + "cssnano-util-get-match": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-unicode": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz", + "integrity": "sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" + } + }, + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", "dev": true }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-normalize-url": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-3.0.8.tgz", - "integrity": "sha1-EI90s/L82viRov+j6kWSJ5/HgiI=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz", + "integrity": "sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==", "dev": true, "requires": { "is-absolute-url": "^2.0.0", - "normalize-url": "^1.4.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3" + "normalize-url": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "has-flag": "^3.0.0" + } + } + } + }, + "postcss-normalize-whitespace": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.1.tgz", + "integrity": "sha512-U8MBODMB2L+nStzOk6VvWWjZgi5kQNShCyjRhMT3s+W9Jw93yIjOnrEkKYD3Ul7ChWbEcjDWmXq0qOL9MIAnAw==", + "dev": true, + "requires": { + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-ordered-values": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-2.2.3.tgz", - "integrity": "sha1-7sbCpntsQSqNsgQud/6NpD+VwR0=", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.1.tgz", + "integrity": "sha512-PeJiLgJWPzkVF8JuKSBcylaU+hDJ/TX3zqAMIjlghgn1JBi6QwQaDZoDIlqWRcCAI8SxKrt3FCPSRmOgKRB97Q==", "dev": true, "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.1" + "cssnano-util-get-arguments": "^4.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-overflow-shorthand": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-1.0.1.tgz", - "integrity": "sha512-QeJk23W8dLP2DrWYSKTwfFfh4Tcy5Msr58vuuxCPcCijX/07jva0OGNKtUH9vZ6NnXB2WEsnfIIg5M0ScPEWeQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-overflow-shorthand/-/postcss-overflow-shorthand-2.0.0.tgz", + "integrity": "sha512-aK0fHc9CBNx8jbzMYhshZcEv8LtYnBIRYQD5i7w/K/wS9c2+0NSR6B3OVMu5y0hBHYLcMGjfU+dmWYNKH0I85g==", "dev": true, "requires": { - "postcss": "^6.0.22" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-page-break": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-1.0.0.tgz", - "integrity": "sha512-FgjJ7q/cQFbfQFdmm15XDu+DjNb6Tcn7LYm+o1CxyHV5p6pCm0jkRhuU+PF6FaMrSTfy5nF8nuWhwOtUQyWiYA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-page-break/-/postcss-page-break-2.0.0.tgz", + "integrity": "sha512-tkpTSrLpfLfD9HvgOlJuigLuk39wVTbbd8RKcy8/ugV2bNBUW3xU+AIqyxhDrQr1VUj1RmyJrBn1YWrqUm9zAQ==", "dev": true, "requires": { - "postcss": "^6.0.16" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-place": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-3.0.1.tgz", - "integrity": "sha512-6Cg0z39zBU4FOS85Z6+Us+GCIW7UqKdOGH/9j26LwMzZ3L909wG7NP3BF+L12AEeIL5XfI8Q5SWu9Or3nJTS8g==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-place/-/postcss-place-4.0.1.tgz", + "integrity": "sha512-Zb6byCSLkgRKLODj/5mQugyuj9bvAAw9LqJJjgwz5cYryGeXfFZfSXoP1UfveccFmeq0b/2xxwcTEVScnqGxBg==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-values-parser": "^1.5.0" + "postcss": "^7.0.2", + "postcss-values-parser": "^2.0.0" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-prepend-imports": { @@ -20928,438 +21856,469 @@ } }, "postcss-preset-env": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-5.2.2.tgz", - "integrity": "sha512-ehhyyssfRLv6N5zysA09lTgL1KgGOt9RMuXEOrB8CV+aH6Ky1fz1OMmEv9YK+JQ6okRIeQTktOZ+STfTZj2ckw==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-6.5.0.tgz", + "integrity": "sha512-RdsIrYJd9p9AouQoJ8dFP5ksBJEIegA4q4WzJDih8nevz3cZyIP/q1Eaw3pTVpUAu3n7Y32YmvAW3X07mSRGkw==", "dev": true, "requires": { - "autoprefixer": "^8.6.5", - "browserslist": "^3.2.8", - "caniuse-lite": "^1.0.30000865", - "cssdb": "^3.1.0", - "postcss": "^6.0.23", - "postcss-attribute-case-insensitive": "^3.0.1", - "postcss-color-functional-notation": "^1.0.2", - "postcss-color-hex-alpha": "^3.0.0", - "postcss-color-mod-function": "^2.4.2", - "postcss-color-rebeccapurple": "^3.1.0", - "postcss-custom-media": "^6.0.0", - "postcss-custom-properties": "^7.0.0", - "postcss-custom-selectors": "^4.0.1", - "postcss-dir-pseudo-class": "^4.0.0", - "postcss-env-function": "^1.0.0", - "postcss-focus-visible": "^3.0.0", - "postcss-focus-within": "^2.0.0", - "postcss-font-family-system-ui": "^3.0.0", - "postcss-font-variant": "^3.0.0", - "postcss-gap-properties": "^1.0.0", - "postcss-image-set-function": "^2.0.0", - "postcss-initial": "^2.0.0", - "postcss-lab-function": "^1.0.1", - "postcss-logical": "^1.1.1", - "postcss-media-minmax": "^3.0.0", - "postcss-nesting": "^6.0.0", - "postcss-overflow-shorthand": "^1.0.1", - "postcss-page-break": "^1.0.0", - "postcss-place": "^3.0.1", - "postcss-pseudo-class-any-link": "^5.0.0", - "postcss-replace-overflow-wrap": "^2.0.0", - "postcss-selector-matches": "^3.0.1", - "postcss-selector-not": "^3.0.1" + "autoprefixer": "^9.4.2", + "browserslist": "^4.3.5", + "caniuse-lite": "^1.0.30000918", + "css-blank-pseudo": "^0.1.4", + "css-has-pseudo": "^0.10.0", + "css-prefers-color-scheme": "^3.1.1", + "cssdb": "^4.3.0", + "postcss": "^7.0.6", + "postcss-attribute-case-insensitive": "^4.0.0", + "postcss-color-functional-notation": "^2.0.1", + "postcss-color-gray": "^5.0.0", + "postcss-color-hex-alpha": "^5.0.2", + "postcss-color-mod-function": "^3.0.3", + "postcss-color-rebeccapurple": "^4.0.1", + "postcss-custom-media": "^7.0.7", + "postcss-custom-properties": "^8.0.9", + "postcss-custom-selectors": "^5.1.2", + "postcss-dir-pseudo-class": "^5.0.0", + "postcss-double-position-gradients": "^1.0.0", + "postcss-env-function": "^2.0.2", + "postcss-focus-visible": "^4.0.0", + "postcss-focus-within": "^3.0.0", + "postcss-font-variant": "^4.0.0", + "postcss-gap-properties": "^2.0.0", + "postcss-image-set-function": "^3.0.1", + "postcss-initial": "^3.0.0", + "postcss-lab-function": "^2.0.1", + "postcss-logical": "^3.0.0", + "postcss-media-minmax": "^4.0.0", + "postcss-nesting": "^7.0.0", + "postcss-overflow-shorthand": "^2.0.0", + "postcss-page-break": "^2.0.0", + "postcss-place": "^4.0.1", + "postcss-pseudo-class-any-link": "^6.0.0", + "postcss-replace-overflow-wrap": "^3.0.0", + "postcss-selector-matches": "^4.0.0", + "postcss-selector-not": "^4.0.0" }, "dependencies": { + "autoprefixer": { + "version": "9.4.3", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-9.4.3.tgz", + "integrity": "sha512-/XSnzDepRkAU//xLcXA/lUWxpsBuw0WiriAHOqnxkuCtzLhaz+fL4it4gp20BQ8n5SyLzK/FOc7A0+u/rti2FQ==", + "dev": true, + "requires": { + "browserslist": "^4.3.6", + "caniuse-lite": "^1.0.30000921", + "normalize-range": "^0.1.2", + "num2fraction": "^1.2.2", + "postcss": "^7.0.6", + "postcss-value-parser": "^3.3.1" + } + }, + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" + } + }, "caniuse-lite": { - "version": "1.0.30000865", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000865.tgz", - "integrity": "sha512-vs79o1mOSKRGv/1pSkp4EXgl4ZviWeYReXw60XfacPU64uQWZwJT6vZNmxRF9O+6zu71sJwMxLK5JXxbzuVrLw==", + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", "dev": true + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", + "dev": true + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "postcss-value-parser": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", + "dev": true + }, + "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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "postcss-pseudo-class-any-link": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-5.0.0.tgz", - "integrity": "sha512-rA5grdRhLLMMI654eOZVuKGr4fUBQNGSH0K+7j5839CiBA/IvNt/Ewt7aIrkGZPySKI3nqzs34HefO8U0Fxahg==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/postcss-pseudo-class-any-link/-/postcss-pseudo-class-any-link-6.0.0.tgz", + "integrity": "sha512-lgXW9sYJdLqtmw23otOzrtbDXofUdfYzNm4PIpNE322/swES3VU9XlXHeJS46zT2onFO7V1QFdD4Q9LiZj8mew==", "dev": true, "requires": { - "postcss": "^6.0.22", - "postcss-selector-parser": "^4.0.0" + "postcss": "^7.0.2", + "postcss-selector-parser": "^5.0.0-rc.3" }, "dependencies": { - "cssesc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-1.0.1.tgz", - "integrity": "sha512-S2hzrpWvE6G/rW7i7IxJfWBYn27QWfOIncUW++8Rbo1VB5zsJDSVPcnI+Q8z7rhxT6/yZeLOCja4cZnghJrNGA==", - "dev": true - }, - "postcss-selector-parser": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-4.0.0.tgz", - "integrity": "sha512-5h+MvEjnzu1qy6MabjuoPatsGAjjDV9B24e7Cktjl+ClNtjVjmvAXjOFQr1u7RlWULKNGYaYVE4s+DIIQ4bOGA==", - "dev": true, - "requires": { - "cssesc": "^1.0.1", - "indexes-of": "^1.0.1", - "uniq": "^1.0.1" - } - } - } - }, - "postcss-reduce-idents": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/postcss-reduce-idents/-/postcss-reduce-idents-2.4.0.tgz", - "integrity": "sha1-wsbSDMlYKE9qv75j92Cb9AkFmtM=", - "dev": true, - "requires": { - "postcss": "^5.0.4", - "postcss-value-parser": "^3.0.2" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-reduce-initial": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-1.0.1.tgz", - "integrity": "sha1-aPgGlfBF0IJjqHmtJA343WT2ROo=", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.2.tgz", + "integrity": "sha512-epUiC39NonKUKG+P3eAOKKZtm5OtAtQJL7Ye0CBN1f+UQTHzqotudp+hki7zxXm7tT0ZAKDMBj1uihpPjP25ug==", "dev": true, "requires": { - "postcss": "^5.0.4" + "browserslist": "^4.0.0", + "caniuse-api": "^3.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", "dev": true }, "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-reduce-transforms": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-1.0.4.tgz", - "integrity": "sha1-/3b02CEkN7McKYpC0uFEQCV3GuE=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.1.tgz", + "integrity": "sha512-sZVr3QlGs0pjh6JAIe6DzWvBaqYw05V1t3d9Tp+VnFRT5j+rsqoWsysh/iSD7YNsULjq9IAylCznIwVd5oU/zA==", "dev": true, "requires": { - "has": "^1.0.1", - "postcss": "^5.0.8", - "postcss-value-parser": "^3.0.1" + "cssnano-util-get-match": "^4.0.0", + "has": "^1.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-replace-overflow-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-2.0.0.tgz", - "integrity": "sha1-eU22+qVPjbEAhUOSqTr0V2i04ls=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-replace-overflow-wrap/-/postcss-replace-overflow-wrap-3.0.0.tgz", + "integrity": "sha512-2T5hcEHArDT6X9+9dVSPQdo7QHzG4XKclFT8rU5TzJPDN7RIRTbO9c4drUISOVemLj03aezStHCR2AIcr8XLpw==", "dev": true, "requires": { - "postcss": "^6.0.1" + "postcss": "^7.0.2" + }, + "dependencies": { + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "postcss-selector-matches": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-3.0.1.tgz", - "integrity": "sha1-5WNAEeE5UIgYYbvdWMLQER/8lqs=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-matches/-/postcss-selector-matches-4.0.0.tgz", + "integrity": "sha512-LgsHwQR/EsRYSqlwdGzeaPKVT0Ml7LAT6E75T8W8xLJY62CE4S/l03BWIt3jT8Taq22kXP08s2SfTSzaraoPww==", "dev": true, "requires": { - "balanced-match": "^0.4.2", - "postcss": "^6.0.1" + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" }, "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "postcss-selector-not": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-3.0.1.tgz", - "integrity": "sha1-Lk2y8JZTNsAefOx9tsYN/3ZzNdk=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-selector-not/-/postcss-selector-not-4.0.0.tgz", + "integrity": "sha512-W+bkBZRhqJaYN8XAnbbZPLWMvZD1wKTu0UxtFKdhtGjWYmxhkUneoeOhRJKdAE5V7ZTlnbHfCR+6bNwK9e1dTQ==", "dev": true, "requires": { - "balanced-match": "^0.4.2", - "postcss": "^6.0.1" + "balanced-match": "^1.0.0", + "postcss": "^7.0.2" }, "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, "postcss-selector-parser": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-2.2.3.tgz", - "integrity": "sha1-+UN3iGBsPJrO4W/+jYsWKX8nu5A=", + "version": "5.0.0-rc.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-5.0.0-rc.4.tgz", + "integrity": "sha512-0XvfYuShrKlTk1ooUrVzMCFQRcypsdEIsGqh5IxC5rdtBi4/M/tDAJeSONwC2MTqEFsmPZYAV7Dd4X8rgAfV0A==", "dev": true, "requires": { - "flatten": "^1.0.2", + "cssesc": "^2.0.0", "indexes-of": "^1.0.1", "uniq": "^1.0.1" + }, + "dependencies": { + "cssesc": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-2.0.0.tgz", + "integrity": "sha512-MsCAG1z9lPdoO/IUMLSBWBSVxVtJ1395VGIQ+Fc2gNdkQ1hNDnQdw3YhA71WJCBW1vdwA0cAnk/DnW6bqoEUYg==", + "dev": true + } } }, "postcss-svgo": { - "version": "2.1.6", - "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-2.1.6.tgz", - "integrity": "sha1-tt8YqmE7Zm4TPwittSGcJoSsEI0=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.1.tgz", + "integrity": "sha512-YD5uIk5NDRySy0hcI+ZJHwqemv2WiqqzDgtvgMzO8EGSkK5aONyX8HMVFRFJSdO8wUWTuisUFn/d7yRRbBr5Qw==", "dev": true, "requires": { - "is-svg": "^2.0.0", - "postcss": "^5.0.14", - "postcss-value-parser": "^3.2.3", - "svgo": "^0.7.0" + "is-svg": "^3.0.0", + "postcss": "^7.0.0", + "postcss-value-parser": "^3.0.0", + "svgo": "^1.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } }, "postcss-unique-selectors": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-2.0.2.tgz", - "integrity": "sha1-mB1X0p3csz57Hf4f1DuGSfkzyh0=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz", + "integrity": "sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==", "dev": true, "requires": { - "alphanum-sort": "^1.0.1", - "postcss": "^5.0.4", + "alphanum-sort": "^1.0.0", + "postcss": "^7.0.0", "uniqs": "^2.0.0" }, "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" } }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", + "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 }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^3.0.0" } } } @@ -21371,9 +22330,9 @@ "dev": true }, "postcss-values-parser": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-1.5.0.tgz", - "integrity": "sha512-3M3p+2gMp0AH3da530TlX8kiO1nxdTnc3C6vr8dMxRLIlh8UYkz0/wcwptSXjhtx2Fr0TySI7a+BHDQ8NL7LaQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/postcss-values-parser/-/postcss-values-parser-2.0.0.tgz", + "integrity": "sha512-cyRdkgbRRefu91ByAlJow4y9w/hnBmmWgLpWmlFQ2bpIy2eKrqowt3VeYcaHQ08otVXmC9V2JtYW1Z/RpvYR8A==", "dev": true, "requires": { "flatten": "^1.0.2", @@ -21381,67 +22340,6 @@ "uniq": "^1.0.1" } }, - "postcss-zindex": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/postcss-zindex/-/postcss-zindex-2.2.0.tgz", - "integrity": "sha1-0hCd3AVbka9n/EyzsCWUZjnSryI=", - "dev": true, - "requires": { - "has": "^1.0.1", - "postcss": "^5.0.4", - "uniqs": "^2.0.0" - }, - "dependencies": { - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true - }, - "postcss": { - "version": "5.2.18", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-5.2.18.tgz", - "integrity": "sha512-zrUjRRe1bpXKsX1qAJNJjqZViErVuyEkMTRrwu4ud4sbTtIBRmtaYDrHmcGgmrbsW3MHfmtIf+vJumgQn+PrXg==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "js-base64": "^2.1.9", - "source-map": "^0.5.6", - "supports-color": "^3.2.3" - } - }, - "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, - "requires": { - "has-flag": "^1.0.0" - } - } - } - }, "postinstall-build": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/postinstall-build/-/postinstall-build-5.0.1.tgz", @@ -22492,6 +23390,29 @@ "integrity": "sha512-MKucv9nU65BOPqIrClAFxqvpGCC4RdRpqp0P1YIb7C3yT6TQVdcoOlr0k4TDHvLQhbkwd3nbTxiDQMa3iDlZxg==", "dev": true }, + "react-transition-group": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.5.0.tgz", + "integrity": "sha512-qYB3JBF+9Y4sE4/Mg/9O6WFpdoYjeeYqx0AFb64PTazVy8RPMiE3A47CG9QmM4WJ/mzDiZYslV+Uly6O1Erlgw==", + "dev": true, + "requires": { + "dom-helpers": "^3.3.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2", + "react-lifecycles-compat": "^3.0.4" + }, + "dependencies": { + "loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "requires": { + "js-tokens": "^3.0.0 || ^4.0.0" + } + } + } + }, "react-with-state-props": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/react-with-state-props/-/react-with-state-props-2.0.4.tgz", @@ -22650,42 +23571,6 @@ "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=" }, - "reduce-css-calc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz", - "integrity": "sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2", - "math-expression-evaluator": "^1.2.14", - "reduce-function-call": "^1.0.1" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, - "reduce-function-call": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/reduce-function-call/-/reduce-function-call-1.0.2.tgz", - "integrity": "sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=", - "dev": true, - "requires": { - "balanced-match": "^0.4.2" - }, - "dependencies": { - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - } - } - }, "redux": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/redux/-/redux-4.0.0.tgz", @@ -23526,6 +24411,18 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, + "rgb-regex": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz", + "integrity": "sha1-wODWiC3w4jviVKR16O3UGRX+rrE=", + "dev": true + }, + "rgba-regex": { + "version": "1.0.0", + "resolved": "http://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz", + "integrity": "sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=", + "dev": true + }, "rimraf": { "version": "2.4.5", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz", @@ -24186,15 +25083,6 @@ "url-parse": "^1.1.8" } }, - "sort-keys": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", - "integrity": "sha1-RBttTTRnmPG05J6JIK37oOVD+a0=", - "dev": true, - "requires": { - "is-plain-obj": "^1.0.0" - } - }, "source-list-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", @@ -24392,6 +25280,12 @@ "safe-buffer": "^5.1.1" } }, + "stable": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/stable/-/stable-0.1.8.tgz", + "integrity": "sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==", + "dev": true + }, "stack-trace": { "version": "0.0.10", "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", @@ -24655,13 +25549,99 @@ "integrity": "sha1-yMPn/db7S7OjKjt1LltePjgJPr0=" }, "style-loader": { - "version": "0.21.0", - "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.21.0.tgz", - "integrity": "sha512-T+UNsAcl3Yg+BsPKs1vd22Fr8sVT+CJMtzqc6LEw9bbJZb43lm9GoeIfUcDEefBSWC0BhYbcdupV1GtI4DGzxg==", + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-0.23.1.tgz", + "integrity": "sha512-XK+uv9kWwhZMZ1y7mysB+zoihsEj4wneFWAS5qoiLwzW0WzSqMrrsIy+a3zkQJq0ipFtBpX5W3MqyRIBF/WFGg==", "dev": true, "requires": { "loader-utils": "^1.1.0", - "schema-utils": "^0.4.5" + "schema-utils": "^1.0.0" + }, + "dependencies": { + "schema-utils": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", + "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", + "dev": true, + "requires": { + "ajv": "^6.1.0", + "ajv-errors": "^1.0.0", + "ajv-keywords": "^3.1.0" + } + } + } + }, + "stylehacks": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.1.tgz", + "integrity": "sha512-TK5zEPeD9NyC1uPIdjikzsgWxdQQN/ry1X3d1iOz1UkYDCmcr928gWD1KHgyC27F50UnE0xCTrBOO1l6KR8M4w==", + "dev": true, + "requires": { + "browserslist": "^4.0.0", + "postcss": "^7.0.0", + "postcss-selector-parser": "^3.0.0" + }, + "dependencies": { + "browserslist": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.3.6.tgz", + "integrity": "sha512-kMGKs4BTzRWviZ8yru18xBpx+CyHG9eqgRbj9XbE3IMgtczf4aiA0Y1YCpVdvUieKGZ03kolSPXqTcscBCb9qw==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30000921", + "electron-to-chromium": "^1.3.92", + "node-releases": "^1.1.1" + } + }, + "caniuse-lite": { + "version": "1.0.30000921", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000921.tgz", + "integrity": "sha512-Bu09ciy0lMWLgpYC77I0YGuI8eFRBPPzaSOYJK1jTI64txCphYCqnWbxJYjHABYVt/TYX/p3jNjLBR87u1Bfpw==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.3.93", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.93.tgz", + "integrity": "sha512-H+tt+fedI+C5zl4yGtzdk17e6mOYAyawolXuAojWBULIbqOz9VR7h0cG5YcivQ1yCXrpw+Rjk6cBw47HlYtnKg==", + "dev": true + }, + "postcss": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-7.0.7.tgz", + "integrity": "sha512-HThWSJEPkupqew2fnuQMEI2YcTj/8gMV3n80cMdJsKxfIh5tHf7nM5JigNX6LxVMqo6zkgQNAI88hyFvBk41Pg==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "source-map": "^0.6.1", + "supports-color": "^5.5.0" + } + }, + "postcss-selector-parser": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.1.tgz", + "integrity": "sha1-T4dfSvsMllc9XPTXQBGu4lCn6GU=", + "dev": true, + "requires": { + "dot-prop": "^4.1.1", + "indexes-of": "^1.0.1", + "uniq": "^1.0.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 + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "stylis": { @@ -24748,34 +25728,62 @@ } }, "svgo": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/svgo/-/svgo-0.7.2.tgz", - "integrity": "sha1-n1dyQTlSE1xv779Ar+ak+qiLS7U=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.1.1.tgz", + "integrity": "sha512-GBkJbnTuFpM4jFbiERHDWhZc/S/kpHToqmZag3aEBjPYK44JAN2QBjvrGIxLOoCyMZjuFQIfTO2eJd8uwLY/9g==", "dev": true, "requires": { - "coa": "~1.0.1", + "coa": "~2.0.1", "colors": "~1.1.2", - "csso": "~2.3.1", - "js-yaml": "~3.7.0", + "css-select": "^2.0.0", + "css-select-base-adapter": "~0.1.0", + "css-tree": "1.0.0-alpha.28", + "css-url-regex": "^1.1.0", + "csso": "^3.5.0", + "js-yaml": "^3.12.0", "mkdirp": "~0.5.1", - "sax": "~1.2.1", - "whet.extend": "~0.9.9" + "object.values": "^1.0.4", + "sax": "~1.2.4", + "stable": "~0.1.6", + "unquote": "~1.1.1", + "util.promisify": "~1.0.0" }, "dependencies": { - "esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", - "dev": true - }, - "js-yaml": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.7.0.tgz", - "integrity": "sha1-XJZ93YN6m/3KXy3oQlOr6KHAO4A=", + "css-select": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.0.2.tgz", + "integrity": "sha512-dSpYaDVoWaELjvZ3mS6IKZM/y2PMPa/XYoEfYNZePL4U/XgyxZNroHEHReDx/d+VgXh9VbCTtFqLkFbmeqeaRQ==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^2.6.0" + "boolbase": "^1.0.0", + "css-what": "^2.1.2", + "domutils": "^1.7.0", + "nth-check": "^1.0.2" + } + }, + "css-what": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.2.tgz", + "integrity": "sha512-wan8dMWQ0GUeF7DGEPVjhHemVW/vy6xUYmFzRY8RYqgA0JtXC9rJmbScBjqSu6dg9q0lwPQy6ZAmJVr3PPTvqQ==", + "dev": true + }, + "domutils": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", + "integrity": "sha512-Lgd2XcJ/NjEw+7tFvfKxOzCYKZsdct5lczQ2ZaQY8Djz7pfAD3Gbp8ySJWtreII/vDlMVmxwa6pHmdxIYgttDg==", + "dev": true, + "requires": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dev": true, + "requires": { + "boolbase": "~1.0.0" } } } @@ -25295,6 +26303,12 @@ "next-tick": "1" } }, + "timsort": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/timsort/-/timsort-0.3.0.tgz", + "integrity": "sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=", + "dev": true + }, "tiny-emitter": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.0.2.tgz", @@ -25786,9 +26800,9 @@ } }, "tslint-config-prettier": { - "version": "1.14.0", - "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.14.0.tgz", - "integrity": "sha512-SomD+aLvAwoihMtyCfkhhWKt9wcpSY2ZpgDV6OuxLYi8+7uOwE2g03aa+jJLSmY0Ys8s3ZLM5Iwfuwu3giCSQQ==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/tslint-config-prettier/-/tslint-config-prettier-1.17.0.tgz", + "integrity": "sha512-NKWNkThwqE4Snn4Cm6SZB7lV5RMDDFsBwz6fWUkTxOKGjMx8ycOHnjIbhn7dZd5XmssW3CwqUjlANR6EhP9YQw==", "dev": true }, "tslint-loader": { @@ -25805,12 +26819,13 @@ } }, "tslint-plugin-prettier": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-1.3.0.tgz", - "integrity": "sha512-6UqeeV6EABp0RdQkW6eC1vwnAXcKMGJgPeJ5soXiKdSm2vv7c3dp+835CM8pjgx9l4uSa7tICm1Kli+SMsADDg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/tslint-plugin-prettier/-/tslint-plugin-prettier-2.0.1.tgz", + "integrity": "sha512-4FX9JIx/1rKHIPJNfMb+ooX1gPk5Vg3vNi7+dyFYpLO+O57F4g+b/fo1+W/G0SUOkBLHB/YKScxjX/P+7ZT/Tw==", "dev": true, "requires": { "eslint-plugin-prettier": "^2.2.0", + "lines-and-columns": "^1.1.6", "tslib": "^1.7.1" } }, @@ -26457,6 +27472,12 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" }, + "unquote": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unquote/-/unquote-1.1.1.tgz", + "integrity": "sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ=", + "dev": true + }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", @@ -27841,12 +28862,6 @@ "webidl-conversions": "^4.0.2" } }, - "whet.extend": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/whet.extend/-/whet.extend-0.9.9.tgz", - "integrity": "sha1-+HfVv2SMl+WqVC+twW1qJZucEaE=", - "dev": true - }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 724c84a20..7f7487861 100644 --- a/package.json +++ b/package.json @@ -80,7 +80,7 @@ "joi": "^13.4.0", "jsonwebtoken": "^8.3.0", "jwks-rsa": "^1.3.0", - "linkify-it": "^2.0.3", + "linkify-it": "^2.1.0", "linkifyjs": "^2.1.7", "lodash": "^4.17.10", "luxon": "^1.3.1", @@ -116,6 +116,7 @@ "@babel/preset-env": "^7.2.0", "@babel/preset-react": "^7.0.0", "@coralproject/rte": "^0.10.13", + "@intervolga/optimize-cssnano-plugin": "^1.0.6", "@types/bcryptjs": "^2.4.1", "@types/bull": "^3.3.16", "@types/bunyan": "^1.8.4", @@ -144,7 +145,7 @@ "@types/joi": "^13.0.8", "@types/jsdom": "^11.12.0", "@types/jsonwebtoken": "^7.2.7", - "@types/linkify-it": "^2.0.3", + "@types/linkify-it": "^2.0.4", "@types/linkifyjs": "^2.1.0", "@types/lodash": "^4.14.118", "@types/lodash-webpack-plugin": "^0.11.3", @@ -166,6 +167,7 @@ "@types/react-relay": "^1.3.9", "@types/react-responsive": "^3.0.1", "@types/react-test-renderer": "^16.0.1", + "@types/react-transition-group": "^2.0.14", "@types/recompose": "^0.26.5", "@types/relay-runtime": "^1.3.6", "@types/sane": "^2.0.0", @@ -182,6 +184,7 @@ "babel-core": "^7.0.0-bridge.0", "babel-jest": "^23.6.0", "babel-loader": "^8.0.4", + "babel-plugin-dynamic-import-node": "^2.2.0", "babel-plugin-lodash": "^3.3.4", "babel-plugin-module-resolver": "^3.1.1", "babel-plugin-relay": "^1.7.0", @@ -198,7 +201,7 @@ "compression-webpack-plugin": "^1.1.11", "copy-webpack-plugin": "^4.5.1", "cross-spawn": "^6.0.5", - "css-loader": "^0.28.11", + "css-loader": "^1.0.1", "del": "^3.0.0", "docz": "^0.5.8", "enzyme": "^3.7.0", @@ -230,17 +233,17 @@ "lodash-es": "^4.17.11", "lodash-webpack-plugin": "^0.11.5", "material-design-icons": "^3.0.1", - "mini-css-extract-plugin": "^0.4.1", + "mini-css-extract-plugin": "^0.5.0", "npm-run-all": "^4.1.3", - "postcss-advanced-variables": "^2.3.3", - "postcss-css-variables": "^0.9.0", - "postcss-flexbugs-fixes": "^3.3.1", + "postcss-advanced-variables": "^3.0.0", + "postcss-css-variables": "^0.11.0", + "postcss-flexbugs-fixes": "^4.1.0", "postcss-font-magician": "^2.2.1", "postcss-import": "^11.1.0", - "postcss-loader": "^2.1.6", - "postcss-nested": "^3.0.0", + "postcss-loader": "^3.0.0", + "postcss-nested": "^4.1.1", "postcss-prepend-imports": "^1.0.1", - "postcss-preset-env": "^5.2.1", + "postcss-preset-env": "^6.5.0", "prettier": "^1.13.7", "prop-types": "^15.6.2", "pstree.remy": "^1.1.0", @@ -257,6 +260,7 @@ "react-responsive": "^5.0.0", "react-test-renderer": "^16.5.2", "react-timeago": "^4.1.9", + "react-transition-group": "^2.5.0", "react-with-state-props": "^2.0.4", "recompose": "^0.27.1", "relay-compiler": "^1.7.0-rc.1", @@ -266,7 +270,7 @@ "sane": "^4.0.2", "simulant": "^0.2.2", "sinon": "^6.1.5", - "style-loader": "^0.21.0", + "style-loader": "^0.23.1", "terser-webpack-plugin": "^1.1.0", "timekeeper": "^2.1.2", "ts-jest": "^23.0.0", @@ -275,9 +279,9 @@ "tsconfig-paths": "^3.4.2", "tsconfig-paths-webpack-plugin": "^3.1.4", "tslint": "^5.11.0", - "tslint-config-prettier": "^1.14.0", + "tslint-config-prettier": "^1.17.0", "tslint-loader": "^3.6.0", - "tslint-plugin-prettier": "^1.3.0", + "tslint-plugin-prettier": "^2.0.1", "tslint-react": "^3.6.0", "typed-css-modules": "^0.3.4", "typeface-manuale": "0.0.54", diff --git a/scripts/build.ts b/scripts/build.ts index 58b453ce8..ace98d2f6 100644 --- a/scripts/build.ts +++ b/scripts/build.ts @@ -1,5 +1,11 @@ #!/usr/bin/env ts-node +import dotenv from "dotenv"; + +// Apply all the configuration provided in the .env file if it isn't already in +// the environment. +dotenv.config(); + import chalk from "chalk"; import fs from "fs-extra"; import FileSizeReporter from "react-dev-utils/FileSizeReporter"; @@ -8,8 +14,8 @@ import printBuildError from "react-dev-utils/printBuildError"; import webpack from "webpack"; import paths from "../config/paths"; +import config from "../src/core/build/config"; import createWebpackConfig from "../src/core/build/createWebpackConfig"; -import config, { createClientEnv } from "../src/core/common/config"; // tslint:disable: no-console @@ -97,9 +103,7 @@ measureFileSizesBeforeBuild(paths.appDistStatic) // Create the production build and print the deployment instructions. function build(previousFileSizes: any) { console.log("Creating an optimized production build..."); - const webpackConfig = createWebpackConfig({ - env: createClientEnv(config), - }); + const webpackConfig = createWebpackConfig(config); const compiler = webpack(webpackConfig); return new Promise((resolve, reject) => { compiler.run((err, stats) => { diff --git a/scripts/start.ts b/scripts/start.ts index c5a2a7355..cbcc58aa5 100644 --- a/scripts/start.ts +++ b/scripts/start.ts @@ -1,4 +1,9 @@ #!/usr/bin/env ts-node +import dotenv from "dotenv"; + +// Apply all the configuration provided in the .env file if it isn't already in +// the environment. +dotenv.config(); import chalk from "chalk"; import { @@ -10,8 +15,8 @@ import webpack from "webpack"; import WebpackDevServer from "webpack-dev-server"; import createDevServerConfig from "../config/webpackDevServer.config"; +import config from "../src/core/build/config"; import createWebpackConfig from "../src/core/build/createWebpackConfig"; -import config, { createClientEnv } from "../src/core/common/config"; // tslint:disable: no-console @@ -58,9 +63,7 @@ choosePort(HOST, PORT) const protocol = "http"; const appName = "Talk"; const urls = prepareUrls(protocol, HOST, port); - const webpackConfig = createWebpackConfig({ - env: createClientEnv(config), - }); + const webpackConfig = createWebpackConfig(config); // Create a webpack compiler that is configured with custom messages. const compiler = createCompiler(webpack, webpackConfig, appName, urls); // Serve webpack assets generated by the compiler over a web sever. diff --git a/src/core/build/config.ts b/src/core/build/config.ts new file mode 100644 index 000000000..7d7bf5619 --- /dev/null +++ b/src/core/build/config.ts @@ -0,0 +1,54 @@ +import convict from "convict"; + +const config = convict({ + env: { + doc: "The application environment.", + format: ["production", "development", "test"], + default: "development", + env: "NODE_ENV", + }, + port: { + doc: "The port the server is bound to", + format: "port", + default: 3000, + env: "PORT", + arg: "port", + }, + generateReport: { + doc: "Generate a report using webpack-bundle-analyzer", + format: Boolean, + default: false, + env: "WEBPACK_REPORT", + arg: "generateReport", + }, + disableSourcemaps: { + doc: "Disable sourcemaps generation", + format: Boolean, + default: false, + env: "WEBPACK_DISABLE_SOURCEMAPS", + arg: "disableSourceMaps", + }, + disableMinimize: { + doc: "Disable minimization in production", + format: Boolean, + default: false, + env: "WEBPACK_DISABLE_MINIMIZE", + arg: "disableMinimize", + }, + enableTreeShake: { + doc: "Enabled tree shaking in development", + format: Boolean, + default: false, + env: "WEBPACK_TREESHAKE", + arg: "enableTreeShake", + }, +}); + +export type Config = typeof config; + +export const createClientEnv = (c: Config) => ({ + NODE_ENV: c.get("env"), +}); + +// Setup the base configuration. +export default config; diff --git a/src/core/build/createWebpackConfig.ts b/src/core/build/createWebpackConfig.ts index 7f28fbd87..b53ac9611 100644 --- a/src/core/build/createWebpackConfig.ts +++ b/src/core/build/createWebpackConfig.ts @@ -1,3 +1,4 @@ +import OptimizeCssnanoPlugin from "@intervolga/optimize-cssnano-plugin"; import CaseSensitivePathsPlugin from "case-sensitive-paths-webpack-plugin"; import CompressionPlugin from "compression-webpack-plugin"; import HtmlWebpackPlugin, { Options } from "html-webpack-plugin"; @@ -13,6 +14,8 @@ import webpack, { Configuration, Plugin } from "webpack"; import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer"; import ManifestPlugin from "webpack-manifest-plugin"; +import { Config } from "./config"; +import { createClientEnv } from "./config"; import paths from "./paths"; import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin"; @@ -25,33 +28,34 @@ import PublicURIWebpackPlugin from "./plugins/PublicURIWebpackPlugin"; const filterPlugins = (plugins: Array): Plugin[] => plugins.filter(identity) as Plugin[]; -interface CreateWebpackConfig { - publicPath?: string; - publicURL?: string; - env?: Record; - disableSourcemaps?: boolean; +interface CreateWebpackOptions { appendPlugins?: any[]; } -export default function createWebpackConfig({ - publicPath = "/", - publicURL = "", - env = process.env as Record, - appendPlugins = [], - disableSourcemaps, -}: CreateWebpackConfig = {}): Configuration[] { +const publicPath = "/"; + +export default function createWebpackConfig( + config: Config, + { appendPlugins = [] }: CreateWebpackOptions = {} +): Configuration[] { + const env = createClientEnv(config); + const disableSourcemaps = config.get("disableSourcemaps"); + const generateReport = config.get("generateReport"); + + const isProduction = env.NODE_ENV === "production"; + const minimize = isProduction && !config.get("disableMinimize"); + const treeShake = config.get("enableTreeShake"); + const envStringified = { "process.env": Object.keys(env).reduce>( (result, key) => { - result[key] = JSON.stringify(env[key]); + result[key] = JSON.stringify((env as any)[key]); return result; }, {} ), }; - const isProduction = env.NODE_ENV === "production"; - /** * ifProduction will only include the nodes if we're in production mode. */ @@ -60,7 +64,7 @@ export default function createWebpackConfig({ : (...nodes: T[]) => []; const htmlWebpackConfig: Options = { - minify: isProduction && { + minify: minimize && { removeComments: true, collapseWhitespace: true, removeRedundantAttributes: true, @@ -109,6 +113,19 @@ export default function createWebpackConfig({ filename: "assets/css/[name].[hash].css", chunkFilename: "assets/css/[id].[hash].css", }), + new OptimizeCssnanoPlugin({ + sourceMap: true, + cssnanoOptions: { + preset: [ + "default", + { + discardComments: { + removeAll: true, + }, + }, + ], + }, + }), // Pre-compress all the assets as they will be served as is. new CompressionPlugin({}), ] @@ -128,9 +145,6 @@ export default function createWebpackConfig({ new WatchMissingNodeModulesPlugin(paths.appNodeModules), ]; - // If the WEBPACK_STATS environment variable is specified, output the stats! - const includeStats = Boolean(process.env.WEBPACK_STATS); - const baseConfig: Configuration = { // Set webpack mode. mode: isProduction ? "production" : "development", @@ -138,20 +152,15 @@ export default function createWebpackConfig({ concatenateModules: isProduction, providedExports: true, usedExports: true, - sideEffects: true, - // We also minimize during development but only - // limit it to dead code and unused code elemination - // to be sure nothing breaks later in the production build. - // - // If modules are written in a side-effects free way this - // should not happen. We strive to write side-effects free - // modules but no one is perfect ;-) - minimize: true, + // We can't use side effects because it disturbs css order + // https://github.com/webpack/webpack/issues/7094. + sideEffects: false, + minimize: minimize || treeShake, minimizer: [ // Minify the code. new TerserPlugin({ terserOptions: { - compress: isProduction + compress: minimize ? {} : { defaults: false, @@ -160,9 +169,9 @@ export default function createWebpackConfig({ side_effects: true, unused: true, }, - mangle: isProduction && {}, + mangle: minimize && {}, output: { - comments: !isProduction, + comments: !minimize, // Turned on because emoji and regex is not minified properly using default // https://github.com/facebookincubator/create-react-app/issues/2488 ascii_only: true, @@ -397,7 +406,6 @@ export default function createWebpackConfig({ modules: true, importLoaders: 1, localIdentName: "[name]-[local]-[hash:base64:5]", - minimize: isProduction, sourceMap: isProduction && !disableSourcemaps, }, }, @@ -438,6 +446,13 @@ export default function createWebpackConfig({ // Makes some environment variables available to the JS code, for example: // if (process.env.NODE_ENV === 'development') { ... }. See `./env.js`. new webpack.DefinePlugin(envStringified), + // If stats are enabled, output them! + generateReport + ? new BundleAnalyzerPlugin({ + analyzerMode: "static", + reportFilename: "report-assets.html", + }) + : null, ...additionalPlugins, ...appendPlugins, ], @@ -563,18 +578,17 @@ export default function createWebpackConfig({ new ManifestPlugin({ fileName: "asset-manifest.json", }), - // If stats are enabled, output them! - includeStats - ? new BundleAnalyzerPlugin({ - analyzerMode: "static", - reportFilename: "report-assets.html", - }) - : null, ]), }, /* Webpack config for our embed */ { ...baseConfig, + optimization: { + ...baseConfig.optimization, + // We can turn on sideEffects here as we don't use + // css here and don't run into: https://github.com/webpack/webpack/issues/7094 + sideEffects: true, + }, entry: [ /* Use minimal amount of polyfills (for IE) */ "intersection-observer", // also for Safari @@ -624,13 +638,6 @@ export default function createWebpackConfig({ new ManifestPlugin({ fileName: "embed-manifest.json", }), - // If stats are enabled, output them! - includeStats - ? new BundleAnalyzerPlugin({ - analyzerMode: "static", - reportFilename: "report-embed.html", - }) - : null, ]), }, ]; diff --git a/src/core/client/admin/components/App.css b/src/core/client/admin/components/App.css index d24beb684..3bddc8818 100644 --- a/src/core/client/admin/components/App.css +++ b/src/core/client/admin/components/App.css @@ -5,9 +5,5 @@ } } -.container { - max-width: 1280px; - margin: 0 auto; - padding: 0 var(--spacing-unit); - box-sizing: border-box; +.root { } diff --git a/src/core/client/admin/components/App.tsx b/src/core/client/admin/components/App.tsx index 0f9184263..1ab832afc 100644 --- a/src/core/client/admin/components/App.tsx +++ b/src/core/client/admin/components/App.tsx @@ -1,15 +1,28 @@ import React, { StatelessComponent } from "react"; -import styles from "./App.css"; -import AppBar from "./AppBar"; +import { Logo } from "talk-ui/components"; +import { AppBar, Begin, Divider, End } from "talk-ui/components/AppBar"; + +import SignOutButtonContainer from "../containers/SignOutButtonContainer"; +import DecisionHistoryButton from "./DecisionHistoryButton"; import Navigation from "./Navigation"; +import styles from "./App.css"; + const App: StatelessComponent = ({ children }) => ( -
- - +
+ + + + + + + + + + -
{children}
+ {children}
); diff --git a/src/core/client/admin/components/AppBar.css b/src/core/client/admin/components/AppBar.css deleted file mode 100644 index 8eb154452..000000000 --- a/src/core/client/admin/components/AppBar.css +++ /dev/null @@ -1,16 +0,0 @@ -.root { - background-color: #f5f5f5; - border-bottom: 1px solid var(--palette-grey-lighter); -} - -.container { - max-width: 1280px; - margin: 0 auto; - padding: 0 var(--spacing-unit) 0 calc(2 * var(--spacing-unit)); - height: calc(6 * var(--spacing-unit)); - box-sizing: border-box; -} - -.logo { - margin-right: calc(2.5 * var(--spacing-unit)); -} diff --git a/src/core/client/admin/components/AppBar.tsx b/src/core/client/admin/components/AppBar.tsx deleted file mode 100644 index af22aa902..000000000 --- a/src/core/client/admin/components/AppBar.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, { StatelessComponent } from "react"; - -import { Flex } from "talk-ui/components"; - -import styles from "./AppBar.css"; -import Logo from "./Logo"; - -const AppBar: StatelessComponent = ({ children }) => ( -
- - - {children} - -
-); - -export default AppBar; diff --git a/src/core/client/admin/components/DecisionHistoryButton.tsx b/src/core/client/admin/components/DecisionHistoryButton.tsx index c248cc6c3..f43611233 100644 --- a/src/core/client/admin/components/DecisionHistoryButton.tsx +++ b/src/core/client/admin/components/DecisionHistoryButton.tsx @@ -4,6 +4,7 @@ import { oncePerFrame } from "talk-common/utils"; import { BaseButton, ClickOutside, Icon, Popover } from "talk-ui/components"; import DecisionHistoryQuery from "../views/decisionHistory/queries/DecisionHistoryQuery"; + import styles from "./DecisionHistoryButton.css"; class DecisionHistoryButton extends React.Component { @@ -24,17 +25,17 @@ class DecisionHistoryButton extends React.Component { placement="bottom-end" description="A dialog showing a permalink to the comment" classes={{ popover: styles.popover }} - body={({ toggleVisibility }) => ( - - this.toggleVisibilityOncePerFrame(toggleVisibility) - } - > -
- -
-
- )} + body={({ toggleVisibility }) => { + const hide = () => + this.toggleVisibilityOncePerFrame(toggleVisibility); + return ( + +
+ +
+
+ ); + }} > {({ toggleVisibility, forwardRef, visible }) => ( = props => ( - - - - -); - -export default Logo; diff --git a/src/core/client/admin/components/MainLayout.css b/src/core/client/admin/components/MainLayout.css new file mode 100644 index 000000000..0ed70d627 --- /dev/null +++ b/src/core/client/admin/components/MainLayout.css @@ -0,0 +1,6 @@ +.root { + max-width: 1280px; + margin: 0 auto; + padding: 0 var(--spacing-unit); + box-sizing: border-box; +} diff --git a/src/core/client/admin/components/MainLayout.spec.tsx b/src/core/client/admin/components/MainLayout.spec.tsx new file mode 100644 index 000000000..0e54f040e --- /dev/null +++ b/src/core/client/admin/components/MainLayout.spec.tsx @@ -0,0 +1,14 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import MainLayout from "./MainLayout"; + +it("renders correctly", () => { + const props: PropTypesOf = { + children: "content", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/components/MainLayout.tsx b/src/core/client/admin/components/MainLayout.tsx new file mode 100644 index 000000000..22652d1ce --- /dev/null +++ b/src/core/client/admin/components/MainLayout.tsx @@ -0,0 +1,21 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import styles from "./MainLayout.css"; + +interface Props { + className?: string; + children?: React.ReactNode; +} + +const MainLayout: StatelessComponent = ({ + children, + className, + ...rest +}) => ( +
+ {children} +
+); + +export default MainLayout; diff --git a/src/core/client/admin/components/Navigation.css b/src/core/client/admin/components/Navigation.css deleted file mode 100644 index 661276f43..000000000 --- a/src/core/client/admin/components/Navigation.css +++ /dev/null @@ -1,33 +0,0 @@ -.root { - height: 100%; - width: 100%; -} - -.link { - color: var(--palette-text-secondary); - font-family: var(--font-family-sans-serif); - font-weight: var(--font-weight-bold); - font-size: calc(18rem / var(--rem-base)); - line-height: calc(20em / 18); - letter-spacing: calc(-0.1em / 18); - height: 100%; - display: inline-flex; - align-items: center; - padding: 0 calc(1.5 * var(--spacing-unit)); - text-transform: uppercase; - text-decoration: none; - &:hover { - cursor: pointer; - } -} - -.active { - background-color: var(--palette-brand); - text-decoration: none; - color: var(--palette-text-light); -} - -.historyIcon { - color: var(--palette-text-secondary); - padding: var(--spacing-unit) var(--spacing-unit); -} diff --git a/src/core/client/admin/components/Navigation.tsx b/src/core/client/admin/components/Navigation.tsx index 9f90c6a3e..b68df35f2 100644 --- a/src/core/client/admin/components/Navigation.tsx +++ b/src/core/client/admin/components/Navigation.tsx @@ -1,36 +1,28 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; -import { Flex } from "talk-ui/components"; +import { AppBarNavigation } from "talk-ui/components"; -import SignOutButtonContainer from "../containers/SignOutButtonContainer"; -import DecisionHistoryButton from "./DecisionHistoryButton"; -import styles from "./Navigation.css"; -import NavigationDivider from "./NavigationDivider"; import NavigationLink from "./NavigationLink"; +/* TODO: + + Community + + + Stories + + */ + const Navigation: StatelessComponent = () => ( - - - - Moderate - - - Community - - - Stories - - - Configure - - - - - - - - + + + Moderate + + + Configure + + ); export default Navigation; diff --git a/src/core/client/admin/components/NavigationDivider.css b/src/core/client/admin/components/NavigationDivider.css deleted file mode 100644 index db5d684a2..000000000 --- a/src/core/client/admin/components/NavigationDivider.css +++ /dev/null @@ -1,4 +0,0 @@ -.root { - height: 75%; - border-right: 1px solid var(--palette-divider); -} diff --git a/src/core/client/admin/components/NavigationDivider.tsx b/src/core/client/admin/components/NavigationDivider.tsx deleted file mode 100644 index f85a43c7c..000000000 --- a/src/core/client/admin/components/NavigationDivider.tsx +++ /dev/null @@ -1,9 +0,0 @@ -import React, { StatelessComponent } from "react"; - -import styles from "./NavigationDivider.css"; - -const NavigationDivider: StatelessComponent<{}> = () => ( -
-); - -export default NavigationDivider; diff --git a/src/core/client/admin/components/NavigationLink.css b/src/core/client/admin/components/NavigationLink.css deleted file mode 100644 index 9155c5f83..000000000 --- a/src/core/client/admin/components/NavigationLink.css +++ /dev/null @@ -1,23 +0,0 @@ -.root { - color: var(--palette-text-secondary); - font-family: var(--font-family-sans-serif); - font-weight: var(--font-weight-medium); - font-size: calc(18rem / var(--rem-base)); - line-height: calc(20em / 18); - letter-spacing: calc(-0.1em / 18); - height: 100%; - display: inline-flex; - align-items: center; - padding: 0 calc(1.5 * var(--spacing-unit)); - text-transform: uppercase; - text-decoration: none; - &:hover { - cursor: pointer; - } -} - -.active { - background-color: var(--palette-brand); - text-decoration: none; - color: var(--palette-text-light); -} diff --git a/src/core/client/admin/components/NavigationLink.tsx b/src/core/client/admin/components/NavigationLink.tsx index e91cb1d5c..c4fc26628 100644 --- a/src/core/client/admin/components/NavigationLink.tsx +++ b/src/core/client/admin/components/NavigationLink.tsx @@ -1,7 +1,7 @@ import { Link, LocationDescriptor } from "found"; import React, { StatelessComponent } from "react"; -import styles from "./NavigationLink.css"; +import { AppBarNavigationItem } from "talk-ui/components"; interface Props { children: React.ReactNode; @@ -9,7 +9,7 @@ interface Props { } const NavigationLink: StatelessComponent = props => ( - + {props.children} ); diff --git a/src/core/client/admin/components/__snapshots__/App.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/App.spec.tsx.snap index 49f6a0fc5..5f0d02dca 100644 --- a/src/core/client/admin/components/__snapshots__/App.spec.tsx.snap +++ b/src/core/client/admin/components/__snapshots__/App.spec.tsx.snap @@ -1,14 +1,27 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` -
- - - -
+ - child -
+ + + + + + + + + + + child
`; diff --git a/src/core/client/admin/components/__snapshots__/AppBar.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/AppBar.spec.tsx.snap deleted file mode 100644 index 1c6eaf2d5..000000000 --- a/src/core/client/admin/components/__snapshots__/AppBar.spec.tsx.snap +++ /dev/null @@ -1,16 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly 1`] = ` -
- - - child - -
-`; diff --git a/src/core/client/admin/components/__snapshots__/BrandIcon.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/BrandIcon.spec.tsx.snap deleted file mode 100644 index c4c3eaabd..000000000 --- a/src/core/client/admin/components/__snapshots__/BrandIcon.spec.tsx.snap +++ /dev/null @@ -1,46 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly 1`] = ` - - - - - - - - - - - logo mark2018 - - - - - -`; diff --git a/src/core/client/admin/components/__snapshots__/BrandName.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/BrandName.spec.tsx.snap deleted file mode 100644 index 12472cf0d..000000000 --- a/src/core/client/admin/components/__snapshots__/BrandName.spec.tsx.snap +++ /dev/null @@ -1,15 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly 1`] = ` - - - Talk - - -`; diff --git a/src/core/client/admin/components/__snapshots__/Logo.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/Logo.spec.tsx.snap deleted file mode 100644 index 0cef7c39a..000000000 --- a/src/core/client/admin/components/__snapshots__/Logo.spec.tsx.snap +++ /dev/null @@ -1,17 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly 1`] = ` - - - - -`; diff --git a/src/core/client/admin/components/__snapshots__/NavigationDivider.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/MainLayout.spec.tsx.snap similarity index 63% rename from src/core/client/admin/components/__snapshots__/NavigationDivider.spec.tsx.snap rename to src/core/client/admin/components/__snapshots__/MainLayout.spec.tsx.snap index 2c3bd27f6..23419dd69 100644 --- a/src/core/client/admin/components/__snapshots__/NavigationDivider.spec.tsx.snap +++ b/src/core/client/admin/components/__snapshots__/MainLayout.spec.tsx.snap @@ -2,6 +2,8 @@ exports[`renders correctly 1`] = `
+ className="MainLayout-root" +> + content +
`; diff --git a/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap index 2ae96297a..497f44bea 100644 --- a/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap +++ b/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap @@ -1,58 +1,24 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` - - + - - - Moderate - - - - - Community - - - - - Stories - - - - - Configure - - - - + + - - - - - + + Configure + + + `; diff --git a/src/core/client/admin/components/__snapshots__/NavigationLink.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/NavigationLink.spec.tsx.snap index 5a9a17e26..856a7af68 100644 --- a/src/core/client/admin/components/__snapshots__/NavigationLink.spec.tsx.snap +++ b/src/core/client/admin/components/__snapshots__/NavigationLink.spec.tsx.snap @@ -2,8 +2,8 @@ exports[`renders correctly 1`] = ` link diff --git a/src/core/client/admin/helpers/getQueueConnection.ts b/src/core/client/admin/helpers/getQueueConnection.ts new file mode 100644 index 000000000..d7711eaf7 --- /dev/null +++ b/src/core/client/admin/helpers/getQueueConnection.ts @@ -0,0 +1,23 @@ +import { ConnectionHandler, RecordSourceSelectorProxy } from "relay-runtime"; + +type Queue = "reported" | "pending" | "unmoderated" | "rejected"; + +export default function getQueueConnection( + queue: Queue, + store: RecordSourceSelectorProxy +) { + const root = store.getRoot(); + if (queue === "rejected") { + return ConnectionHandler.getConnection(root, "RejectedQueue_comments", { + filter: { status: "REJECTED" }, + }); + } + const queuesRecord = root.getLinkedRecord("moderationQueues")!; + if (!queuesRecord) { + return null; + } + return ConnectionHandler.getConnection( + queuesRecord.getLinkedRecord(queue), + "Queue_comments" + ); +} diff --git a/src/core/client/admin/helpers/index.ts b/src/core/client/admin/helpers/index.ts new file mode 100644 index 000000000..1555be6e2 --- /dev/null +++ b/src/core/client/admin/helpers/index.ts @@ -0,0 +1 @@ +export { default as getQueueConnection } from "./getQueueConnection"; diff --git a/src/core/client/admin/mutations/AcceptCommentMutation.ts b/src/core/client/admin/mutations/AcceptCommentMutation.ts new file mode 100644 index 000000000..b191eb0da --- /dev/null +++ b/src/core/client/admin/mutations/AcceptCommentMutation.ts @@ -0,0 +1,82 @@ +import { graphql } from "react-relay"; +import { ConnectionHandler, Environment } from "relay-runtime"; + +import { + commitMutationPromiseNormalized, + createMutationContainer, +} from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; + +import { AcceptCommentMutation as MutationTypes } from "talk-admin/__generated__/AcceptCommentMutation.graphql"; +import { getQueueConnection } from "talk-admin/helpers"; + +export type AcceptCommentInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; + +const mutation = graphql` + mutation AcceptCommentMutation($input: AcceptCommentInput!) { + acceptComment(input: $input) { + comment { + id + status + } + moderationQueues { + unmoderated { + count + } + reported { + count + } + pending { + count + } + } + clientMutationId + } + } +`; + +let clientMutationId = 0; + +function commit(environment: Environment, input: AcceptCommentInput) { + return commitMutationPromiseNormalized(environment, { + mutation, + variables: { + input: { + ...input, + clientMutationId: clientMutationId.toString(), + }, + }, + optimisticResponse: { + acceptComment: { + comment: { + id: input.commentID, + status: "ACCEPTED", + }, + clientMutationId: (clientMutationId++).toString(), + }, + } as any, // TODO: (cvle) generated types should contain one for the optimistic response. + updater: store => { + const connections = [ + getQueueConnection("reported", store), + getQueueConnection("pending", store), + getQueueConnection("unmoderated", store), + getQueueConnection("rejected", store), + ].filter(c => c); + connections.forEach(con => + ConnectionHandler.deleteNode(con, input.commentID) + ); + }, + }); +} + +export const withAcceptCommentMutation = createMutationContainer( + "acceptComment", + commit +); + +export type AcceptCommentMutation = ( + input: AcceptCommentInput +) => Promise; diff --git a/src/core/client/admin/mutations/CreateOIDCAuthIntegrationMutation.ts b/src/core/client/admin/mutations/CreateOIDCAuthIntegrationMutation.ts index 836cc1382..8664945b6 100644 --- a/src/core/client/admin/mutations/CreateOIDCAuthIntegrationMutation.ts +++ b/src/core/client/admin/mutations/CreateOIDCAuthIntegrationMutation.ts @@ -43,21 +43,6 @@ function commit( clientMutationId: (clientMutationId++).toString(), }, }, - updater: store => { - const record = store - .getRootField("createOIDCAuthIntegration")! - .getLinkedRecord("settings")! - .getLinkedRecord("auth")! - .getLinkedRecord("integrations"); - if (record) { - store - .getRoot() - .getLinkedRecord("settings")! - .getLinkedRecord("auth")! - .getLinkedRecord("integrations")! - .copyFieldsFrom(record); - } - }, }); } diff --git a/src/core/client/admin/mutations/RegenerateSSOKeyMutation.ts b/src/core/client/admin/mutations/RegenerateSSOKeyMutation.ts index 01b551459..9dba2624f 100644 --- a/src/core/client/admin/mutations/RegenerateSSOKeyMutation.ts +++ b/src/core/client/admin/mutations/RegenerateSSOKeyMutation.ts @@ -36,23 +36,6 @@ function commit(environment: Environment) { clientMutationId: (clientMutationId++).toString(), }, }, - updater: store => { - const record = store - .getRootField("regenerateSSOKey")! - .getLinkedRecord("settings")! - .getLinkedRecord("auth")! - .getLinkedRecord("integrations")! - .getLinkedRecord("sso"); - if (record) { - store - .getRoot() - .getLinkedRecord("settings")! - .getLinkedRecord("auth")! - .getLinkedRecord("integrations")! - .getLinkedRecord("sso")! - .copyFieldsFrom(record); - } - }, }); } diff --git a/src/core/client/admin/mutations/RejectCommentMutation.ts b/src/core/client/admin/mutations/RejectCommentMutation.ts new file mode 100644 index 000000000..b7d3f6a8a --- /dev/null +++ b/src/core/client/admin/mutations/RejectCommentMutation.ts @@ -0,0 +1,81 @@ +import { graphql } from "react-relay"; +import { ConnectionHandler, Environment } from "relay-runtime"; + +import { + commitMutationPromiseNormalized, + createMutationContainer, +} from "talk-framework/lib/relay"; +import { Omit } from "talk-framework/types"; + +import { RejectCommentMutation as MutationTypes } from "talk-admin/__generated__/RejectCommentMutation.graphql"; +import { getQueueConnection } from "talk-admin/helpers"; + +export type RejectCommentInput = Omit< + MutationTypes["variables"]["input"], + "clientMutationId" +>; + +const mutation = graphql` + mutation RejectCommentMutation($input: RejectCommentInput!) { + rejectComment(input: $input) { + comment { + id + status + } + moderationQueues { + unmoderated { + count + } + reported { + count + } + pending { + count + } + } + clientMutationId + } + } +`; + +let clientMutationId = 0; + +function commit(environment: Environment, input: RejectCommentInput) { + return commitMutationPromiseNormalized(environment, { + mutation, + variables: { + input: { + ...input, + clientMutationId: clientMutationId.toString(), + }, + }, + optimisticResponse: { + rejectComment: { + comment: { + id: input.commentID, + status: "REJECTED", + }, + clientMutationId: (clientMutationId++).toString(), + }, + } as any, // TODO: (cvle) generated types should contain one for the optimistic response. + updater: store => { + const connections = [ + getQueueConnection("reported", store), + getQueueConnection("pending", store), + getQueueConnection("unmoderated", store), + ].filter(c => c); + connections.forEach(con => + ConnectionHandler.deleteNode(con, input.commentID) + ); + }, + }); +} + +export const withRejectCommentMutation = createMutationContainer( + "rejectComment", + commit +); + +export type RejectCommentMutation = ( + input: RejectCommentInput +) => Promise; diff --git a/src/core/client/admin/mutations/UpdateSettingsMutation.ts b/src/core/client/admin/mutations/UpdateSettingsMutation.ts index 6491ffffc..5e52dd8f1 100644 --- a/src/core/client/admin/mutations/UpdateSettingsMutation.ts +++ b/src/core/client/admin/mutations/UpdateSettingsMutation.ts @@ -46,17 +46,6 @@ function commit(environment: Environment, input: UpdateSettingsInput) { clientMutationId: (clientMutationId++).toString(), }, }, - updater: store => { - const record = store - .getRootField("updateSettings")! - .getLinkedRecord("settings"); - if (record) { - store - .getRoot() - .getLinkedRecord("settings")! - .copyFieldsFrom(record); - } - }, }); } diff --git a/src/core/client/admin/mutations/index.ts b/src/core/client/admin/mutations/index.ts index 2836dec09..e57ebc2a0 100644 --- a/src/core/client/admin/mutations/index.ts +++ b/src/core/client/admin/mutations/index.ts @@ -3,6 +3,14 @@ export { withSetRedirectPathMutation, SetRedirectPathMutation, } from "./SetRedirectPathMutation"; +export { + withAcceptCommentMutation, + AcceptCommentMutation, +} from "./AcceptCommentMutation"; +export { + withRejectCommentMutation, + RejectCommentMutation, +} from "./RejectCommentMutation"; export { withUpdateSettingsMutation, UpdateSettingsMutation, diff --git a/src/core/client/admin/routeConfig.tsx b/src/core/client/admin/routeConfig.tsx index 62de716a4..aa7403617 100644 --- a/src/core/client/admin/routeConfig.tsx +++ b/src/core/client/admin/routeConfig.tsx @@ -5,11 +5,18 @@ import App from "./components/App"; import RedirectAppContainer from "./containers/RedirectAppContainer"; import RedirectLoginContainer from "./containers/RedirectLoginContainer"; import Community from "./routes/community/components/Community"; -import ConfigureMisc from "./routes/configure/components/Misc"; +import ConfigureModeration from "./routes/configure/components/Moderation"; import ConfigureContainer from "./routes/configure/containers/ConfigureContainer"; import ConfigureAuthContainer from "./routes/configure/sections/auth/containers/AuthContainer"; import Login from "./routes/login/components/Login"; -import Moderate from "./routes/moderate/components/Moderate"; +import ModerateContainer from "./routes/moderate/containers/ModerateContainer"; +import { + PendingQueueContainer, + ReportedQueueContainer, + UnmoderatedQueueContainer, +} from "./routes/moderate/containers/QueueContainer"; +import RejectedQueueContainer from "./routes/moderate/containers/RejectedQueueContainer"; +import SingleModerateContainer from "./routes/moderate/containers/SingleModerateContainer"; import Stories from "./routes/stories/components/Stories"; export default makeRouteConfig( @@ -17,13 +24,26 @@ export default makeRouteConfig( - + + + + + + + + - + + - diff --git a/src/core/client/admin/routes/community/components/Community.tsx b/src/core/client/admin/routes/community/components/Community.tsx index b0f09ee17..93e2cf2a4 100644 --- a/src/core/client/admin/routes/community/components/Community.tsx +++ b/src/core/client/admin/routes/community/components/Community.tsx @@ -1,10 +1,12 @@ import React, { StatelessComponent } from "react"; -import { HorizontalGutter, Typography } from "talk-ui/components"; + +import MainLayout from "talk-admin/components/MainLayout"; +import { Typography } from "talk-ui/components"; const Community: StatelessComponent = () => ( - + Community - + ); export default Community; diff --git a/src/core/client/admin/routes/community/components/__snapshots__/Community.spec.tsx.snap b/src/core/client/admin/routes/community/components/__snapshots__/Community.spec.tsx.snap index cf6dd0489..ee6c74d9f 100644 --- a/src/core/client/admin/routes/community/components/__snapshots__/Community.spec.tsx.snap +++ b/src/core/client/admin/routes/community/components/__snapshots__/Community.spec.tsx.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` - + Community - + `; diff --git a/src/core/client/admin/routes/configure/components/Configure.tsx b/src/core/client/admin/routes/configure/components/Configure.tsx index 262cfc264..a753540a8 100644 --- a/src/core/client/admin/routes/configure/components/Configure.tsx +++ b/src/core/client/admin/routes/configure/components/Configure.tsx @@ -3,7 +3,9 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; import { Form, FormSpy } from "react-final-form"; +import MainLayout from "talk-admin/components/MainLayout"; import { Button, CallOut, HorizontalGutter } from "talk-ui/components"; + import Layout from "./Layout"; import Main from "./Main"; import { Link, Navigation } from "./Navigation"; @@ -19,7 +21,7 @@ const Configure: StatelessComponent = ({ onChange, children, }) => ( -
+
{({ handleSubmit, submitting, pristine, form, submitError }) => ( @@ -28,10 +30,10 @@ const Configure: StatelessComponent = ({ + Moderation Auth - Misc @@ -67,7 +69,7 @@ const Configure: StatelessComponent = ({ )} -
+ ); export default Configure; diff --git a/src/core/client/admin/routes/configure/components/Misc.tsx b/src/core/client/admin/routes/configure/components/Misc.tsx deleted file mode 100644 index 69cf155a5..000000000 --- a/src/core/client/admin/routes/configure/components/Misc.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React, { StatelessComponent } from "react"; - -import { Typography } from "talk-ui/components"; - -import Header from "./Header"; - -const Misc: StatelessComponent = ({ children }) => ( -
-
Misc Integrations
- Other stuff -
-); - -export default Misc; diff --git a/src/core/client/admin/routes/configure/components/Misc.spec.tsx b/src/core/client/admin/routes/configure/components/Moderation.spec.tsx similarity index 60% rename from src/core/client/admin/routes/configure/components/Misc.spec.tsx rename to src/core/client/admin/routes/configure/components/Moderation.spec.tsx index 67ca2c8e6..c13273d4d 100644 --- a/src/core/client/admin/routes/configure/components/Misc.spec.tsx +++ b/src/core/client/admin/routes/configure/components/Moderation.spec.tsx @@ -3,12 +3,12 @@ import React from "react"; import { PropTypesOf } from "talk-framework/types"; -import Misc from "./Misc"; +import Moderation from "./Moderation"; it("renders correctly", () => { - const props: PropTypesOf = { + const props: PropTypesOf = { children: "child", }; - const wrapper = shallow(); + const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); diff --git a/src/core/client/admin/routes/configure/components/Moderation.tsx b/src/core/client/admin/routes/configure/components/Moderation.tsx new file mode 100644 index 000000000..13414b244 --- /dev/null +++ b/src/core/client/admin/routes/configure/components/Moderation.tsx @@ -0,0 +1,19 @@ +import React, { StatelessComponent } from "react"; + +import { Typography } from "talk-ui/components"; + +import Header from "./Header"; + +const Moderation: StatelessComponent = ({ children }) => ( +
+
Perspective Toxic Comment Filter
+ + Using the Perspective API, the Toxic Comment filter warns users when + comments exceed the predefined toxicity threshold. Toxic comments will not + be published and are placed in the Pending Queue for review by a + moderator. If approved by a moderator, the comment will be published. + +
+); + +export default Moderation; diff --git a/src/core/client/admin/routes/configure/components/Navigation/Link.css b/src/core/client/admin/routes/configure/components/Navigation/Link.css index 617d9e924..4b91bb27c 100644 --- a/src/core/client/admin/routes/configure/components/Navigation/Link.css +++ b/src/core/client/admin/routes/configure/components/Navigation/Link.css @@ -9,10 +9,10 @@ color: var(--palette-text-primary); font-family: var(--font-family-sans-serif); - font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-regular); font-size: calc(18rem / var(--rem-base)); line-height: calc(20em / 18); - letter-spacing: calc(-0.1em / 18); + letter-spacing: calc(0.2em / 18); &:hover { cursor: pointer; @@ -20,6 +20,7 @@ } .linkActive { + font-weight: var(--font-weight-bold); margin-left: 0px; border-left: calc(0.5 * var(--spacing-unit)) solid var(--palette-brand); padding-left: var(--spacing-unit); diff --git a/src/core/client/admin/routes/configure/components/__snapshots__/Configure.spec.tsx.snap b/src/core/client/admin/routes/configure/components/__snapshots__/Configure.spec.tsx.snap index d289ff2a4..a421e5338 100644 --- a/src/core/client/admin/routes/configure/components/__snapshots__/Configure.spec.tsx.snap +++ b/src/core/client/admin/routes/configure/components/__snapshots__/Configure.spec.tsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` -
-
+ `; diff --git a/src/core/client/admin/routes/configure/components/__snapshots__/Misc.spec.tsx.snap b/src/core/client/admin/routes/configure/components/__snapshots__/Misc.spec.tsx.snap deleted file mode 100644 index 0ed7b1c04..000000000 --- a/src/core/client/admin/routes/configure/components/__snapshots__/Misc.spec.tsx.snap +++ /dev/null @@ -1,12 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`renders correctly 1`] = ` -
-
- Misc Integrations -
- - Other stuff - -
-`; diff --git a/src/core/client/admin/routes/configure/components/__snapshots__/Moderation.spec.tsx.snap b/src/core/client/admin/routes/configure/components/__snapshots__/Moderation.spec.tsx.snap new file mode 100644 index 000000000..bb9252f68 --- /dev/null +++ b/src/core/client/admin/routes/configure/components/__snapshots__/Moderation.spec.tsx.snap @@ -0,0 +1,12 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+
+ Perspective Toxic Comment Filter +
+ + Using the Perspective API, the Toxic Comment filter warns users when comments exceed the predefined toxicity threshold. Toxic comments will not be published and are placed in the Pending Queue for review by a moderator. If approved by a moderator, the comment will be published. + +
+`; diff --git a/src/core/client/admin/routes/configure/sections/auth/components/ConfigDescription.tsx b/src/core/client/admin/routes/configure/sections/auth/components/ConfigDescription.tsx index 1e37450c3..7a424a6de 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/ConfigDescription.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/ConfigDescription.tsx @@ -3,13 +3,13 @@ import React, { StatelessComponent } from "react"; import { InputDescription } from "talk-ui/components"; import { PropTypesOf } from "talk-ui/types"; +import styles from "./ConfigDescription.css"; + interface Props { container?: PropTypesOf["container"]; children: React.ReactNode; } -import styles from "./ConfigDescription.css"; - const ConfigDescription: StatelessComponent = ({ children, container, diff --git a/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx b/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx index 36936f86c..e1c1c115f 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/OIDCConfig.tsx @@ -26,12 +26,13 @@ import ClientIDField from "./ClientIDField"; import ClientSecretField from "./ClientSecretField"; import ConfigBoxWithToggleField from "./ConfigBoxWithToggleField"; import ConfigDescription from "./ConfigDescription"; -import styles from "./OIDCConfig.css"; import RedirectField from "./RedirectField"; import RegistrationField from "./RegistrationField"; import TargetFilterField from "./TargetFilterField"; import ValidationMessage from "./ValidationMessage"; +import styles from "./OIDCConfig.css"; + interface Props { index: number; disabled?: boolean; diff --git a/src/core/client/admin/routes/configure/sections/auth/components/ValidationMessage.tsx b/src/core/client/admin/routes/configure/sections/auth/components/ValidationMessage.tsx index 649091875..40fb6300b 100644 --- a/src/core/client/admin/routes/configure/sections/auth/components/ValidationMessage.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/components/ValidationMessage.tsx @@ -2,12 +2,12 @@ import React, { StatelessComponent } from "react"; import { ValidationMessage as UIValidationMessage } from "talk-ui/components"; +import styles from "./ValidationMessage.css"; + interface Props { children: React.ReactNode; } -import styles from "./ValidationMessage.css"; - const ValidationMessage: StatelessComponent = ({ children }) => ( {children} ); diff --git a/src/core/client/admin/routes/configure/sections/auth/containers/AuthContainer.tsx b/src/core/client/admin/routes/configure/sections/auth/containers/AuthContainer.tsx index e32bb4ff8..d3e782527 100644 --- a/src/core/client/admin/routes/configure/sections/auth/containers/AuthContainer.tsx +++ b/src/core/client/admin/routes/configure/sections/auth/containers/AuthContainer.tsx @@ -79,7 +79,7 @@ export default class AuthContainer extends React.Component { }; private handleOnInitValues = (values: any) => { - this.initialValues = merge(this.initialValues, values); + this.initialValues = merge({}, this.initialValues, values); }; public render() { diff --git a/src/core/client/admin/routes/login/components/Login.tsx b/src/core/client/admin/routes/login/components/Login.tsx index 145c22ee7..692939e21 100644 --- a/src/core/client/admin/routes/login/components/Login.tsx +++ b/src/core/client/admin/routes/login/components/Login.tsx @@ -1,17 +1,20 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; -import AppBar from "talk-admin/components/AppBar"; -import BrandName from "talk-admin/components/BrandName"; -import { Flex, HorizontalGutter, Typography } from "talk-ui/components"; +import { + BrandIcon, + BrandName, + Flex, + HorizontalGutter, + Typography, +} from "talk-ui/components"; -import BrandIcon from "talk-admin/components/BrandIcon"; import SignInFormContainer from "../containers/SignInFormContainer"; + import styles from "./Login.css"; const Login: StatelessComponent = () => (
- diff --git a/src/core/client/admin/routes/login/components/__snapshots__/Login.spec.tsx.snap b/src/core/client/admin/routes/login/components/__snapshots__/Login.spec.tsx.snap index 8a2af4fd0..d9047af69 100644 --- a/src/core/client/admin/routes/login/components/__snapshots__/Login.spec.tsx.snap +++ b/src/core/client/admin/routes/login/components/__snapshots__/Login.spec.tsx.snap @@ -2,7 +2,6 @@ exports[`renders correctly 1`] = `
- @@ -16,7 +15,7 @@ exports[`renders correctly 1`] = `
-
@@ -32,7 +31,7 @@ exports[`renders correctly 1`] = ` Sign in to - diff --git a/src/core/client/admin/routes/moderate/components/AcceptButton.css b/src/core/client/admin/routes/moderate/components/AcceptButton.css new file mode 100644 index 000000000..7d0a16339 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/AcceptButton.css @@ -0,0 +1,25 @@ +.root { + border: 1px solid var(--palette-success-main); + box-sizing: border-box; + border-radius: 2px; + width: 65px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + color: var(--palette-success-main); + &:active { + background-color: var(--palette-success-main); + color: var(--palette-common-white); + } +} + +.invert { + background-color: var(--palette-success-main); + color: var(--palette-common-white); +} + +.icon { + font-weight: var(--font-weight-bold); + color: inherit; +} diff --git a/src/core/client/admin/routes/moderate/components/AcceptButton.spec.tsx b/src/core/client/admin/routes/moderate/components/AcceptButton.spec.tsx new file mode 100644 index 000000000..6a9534f59 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/AcceptButton.spec.tsx @@ -0,0 +1,22 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import AcceptButton from "./AcceptButton"; + +import { PropTypesOf } from "talk-framework/types"; + +it("renders correctly", () => { + const props: PropTypesOf = { + invert: false, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders correctly inverted", () => { + const props: PropTypesOf = { + invert: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/AcceptButton.tsx b/src/core/client/admin/routes/moderate/components/AcceptButton.tsx new file mode 100644 index 000000000..c77a7a083 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/AcceptButton.tsx @@ -0,0 +1,34 @@ +import cn from "classnames"; +import { Localized } from "fluent-react/compat"; +import React, { StatelessComponent } from "react"; + +import { PropTypesOf } from "talk-framework/types"; +import { BaseButton, Icon } from "talk-ui/components"; + +import styles from "./AcceptButton.css"; + +interface Props extends PropTypesOf { + invert?: boolean; +} + +const AcceptButton: StatelessComponent = ({ + invert, + className, + ...rest +}) => ( + + + + done + + + +); + +export default AcceptButton; diff --git a/src/core/client/admin/routes/moderate/components/CommentContent.css b/src/core/client/admin/routes/moderate/components/CommentContent.css new file mode 100644 index 000000000..203e7b2d0 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/CommentContent.css @@ -0,0 +1,11 @@ +.root { + mark { + background-color: var(--palette-highlight); + padding: 0 2px; + } + a { + color: var(--palette-primary-dark); + background-color: var(--palette-highlight); + padding: 0 2px; + } +} diff --git a/src/core/client/admin/routes/moderate/components/CommentContent.spec.tsx b/src/core/client/admin/routes/moderate/components/CommentContent.spec.tsx new file mode 100644 index 000000000..50a863671 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/CommentContent.spec.tsx @@ -0,0 +1,28 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import CommentContent from "./CommentContent"; + +import { PropTypesOf } from "talk-framework/types"; + +it("renders correctly", () => { + const props: PropTypesOf = { + suspectWords: ["idiot", "damn"], + bannedWords: ["fuck", "fucking"], + className: "custom", + children: "Hello idiot, you fucking bastard", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders empty words correctly", () => { + const props: PropTypesOf = { + suspectWords: [], + bannedWords: [], + className: "custom", + children: "Hello idiot, you fucking bastard", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/CommentContent.tsx b/src/core/client/admin/routes/moderate/components/CommentContent.tsx new file mode 100644 index 000000000..b5ac3ce2d --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/CommentContent.tsx @@ -0,0 +1,142 @@ +import cn from "classnames"; +import { memoize } from "lodash"; +import React, { StatelessComponent } from "react"; + +import { createPurify } from "talk-common/utils/purify"; +import { Typography } from "talk-ui/components"; + +import styles from "./CommentContent.css"; + +/** + * Create a purify instance that will be used to handle HTML content. + */ +const purify = createPurify(window, false); + +interface Props { + className?: string; + children: string; + suspectWords: ReadonlyArray; + bannedWords: ReadonlyArray; +} + +function escapeHTML(unsafe: string) { + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); +} + +function escapeRegExp(str: string) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string +} + +// generate a regulare expression that catches the `phrases`. +function generateRegExp(phrases: ReadonlyArray) { + const inner = phrases + .map(phrase => + phrase + .split(/\s+/) + .map(word => escapeRegExp(word)) + .join('[\\s"?!.]+') + ) + .join("|"); + + const pattern = `(^|[^\\w])(${inner})(?=[^\\w]|$)`; + try { + return new RegExp(pattern, "iu"); + } catch (_err) { + // IE does not support unicode support, so we'll create one without. + return new RegExp(pattern, "i"); + } +} + +// Generate a regular expression detecting `suspectWords` and `bannedWords` phrases. +function getPhrasesRegexp( + suspectWords: ReadonlyArray, + bannedWords: ReadonlyArray +) { + return generateRegExp([...suspectWords, ...bannedWords]); +} + +// Memoized version as arguments rarely change. +const getPhrasesRegexpMemoized = memoize(getPhrasesRegexp); + +// markPhrasesHTML looks for `supsectWords` and `bannedWords` inside `text` and highlights them by returning +// a HTML string. +function markPhrasesHTML( + text: string, + suspectWords: ReadonlyArray, + bannedWords: ReadonlyArray +) { + const regexp = getPhrasesRegexpMemoized(suspectWords, bannedWords); + const tokens = text.split(regexp); + if (tokens.length === 1) { + return text; + } + return tokens + .map( + (token, i) => + // Using our Regexp patterns it returns tokens arranged this way + // [STRING_WITH_NO_MATCH, NEW_WORD_DELIMITER, MATCHED_WORD, ...]. + // This pattern repeats throughout. Next line will mark MATCHED_WORD + // and escape all tokens. + i % 3 === 2 ? `${escapeHTML(token)}` : escapeHTML(token) + ) + .join(""); +} + +// markHTMLNode manipulates the node by looking for #text nodes and adding markers +// for `supsectWords` and `bannedWords`. +function markHTMLNode( + parentNode: Node, + suspectWords: ReadonlyArray, + bannedWords: ReadonlyArray +) { + parentNode.childNodes.forEach(node => { + if (node.nodeName === "#text") { + const newContent = markPhrasesHTML( + node.textContent!, + suspectWords, + bannedWords + ); + if (newContent !== node.textContent) { + const newNode = document.createElement("span"); + newNode.innerHTML = newContent; + parentNode.replaceChild(newNode, node); + } + } else { + markHTMLNode(node, suspectWords, bannedWords); + } + }); +} + +const CommentContent: StatelessComponent = ({ + suspectWords, + bannedWords, + className, + children, +}) => { + // We create a Shadow DOM Tree with the HTML body content and + // use it as a parser. + const node = document.createElement("div"); + node.innerHTML = purify.sanitize(children); + + if (suspectWords.length || bannedWords.length) { + // Then we traverse it recursively and manipulate it to highlight suspect words + // and banned words. + markHTMLNode(node, suspectWords, bannedWords); + } + + // Finally we render the content of the Shadow DOM Tree + return ( + + ); +}; + +export default CommentContent; diff --git a/src/core/client/admin/routes/moderate/components/InReplyTo.css b/src/core/client/admin/routes/moderate/components/InReplyTo.css new file mode 100644 index 000000000..219436e13 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/InReplyTo.css @@ -0,0 +1,9 @@ +.icon { + color: var(--palette-grey-main); +} +.inReplyTo { + color: var(--palette-grey-main); +} +.username { + color: var(--palette-grey-main); +} diff --git a/src/core/client/admin/routes/moderate/components/InReplyTo.spec.tsx b/src/core/client/admin/routes/moderate/components/InReplyTo.spec.tsx new file mode 100644 index 000000000..3aff82258 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/InReplyTo.spec.tsx @@ -0,0 +1,14 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import InReplyTo from "./InReplyTo"; + +it("renders correctly", () => { + const props: PropTypesOf = { + children: "Username", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/InReplyTo.tsx b/src/core/client/admin/routes/moderate/components/InReplyTo.tsx new file mode 100644 index 000000000..06870a1d6 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/InReplyTo.tsx @@ -0,0 +1,35 @@ +import { Localized } from "fluent-react/compat"; +import React, { StatelessComponent } from "react"; + +import { Flex, Icon, Typography } from "talk-ui/components"; + +import styles from "./InReplyTo.css"; + +interface Props { + children: string; +} + +const InReplyTo: StatelessComponent = ({ children }) => { + const Username = () => ( + + {children} + + ); + + return ( + + reply{" "} + }> + + {"Reply to "} + + + + ); +}; + +export default InReplyTo; diff --git a/src/core/client/admin/routes/moderate/components/LoadingQueue.tsx b/src/core/client/admin/routes/moderate/components/LoadingQueue.tsx new file mode 100644 index 000000000..397ac4820 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/LoadingQueue.tsx @@ -0,0 +1,11 @@ +import React, { StatelessComponent } from "react"; + +import { Flex, Spinner } from "talk-ui/components"; + +const LoadingQueue: StatelessComponent = () => ( + + + +); + +export default LoadingQueue; diff --git a/src/core/client/admin/routes/moderate/components/Moderate.css b/src/core/client/admin/routes/moderate/components/Moderate.css new file mode 100644 index 000000000..42bf1ca58 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Moderate.css @@ -0,0 +1,15 @@ +.background { + position: fixed; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 100%; + background-color: var(--palette-background-light); +} + +.main { + margin: calc(2 * var(--spacing-unit)) 0 calc(4 * var(--spacing-unit)) 0; + display: flex; + justify-content: center; +} diff --git a/src/core/client/admin/routes/moderate/components/Moderate.spec.tsx b/src/core/client/admin/routes/moderate/components/Moderate.spec.tsx index fa8dfb722..5c55ad911 100644 --- a/src/core/client/admin/routes/moderate/components/Moderate.spec.tsx +++ b/src/core/client/admin/routes/moderate/components/Moderate.spec.tsx @@ -3,7 +3,18 @@ import React from "react"; import Moderate from "./Moderate"; +import { PropTypesOf } from "talk-framework/types"; it("renders correctly", () => { const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); }); + +it("renders correctly with counts", () => { + const props: PropTypesOf = { + unmoderatedCount: 3, + reportedCount: 4, + pendingCount: 0, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/Moderate.tsx b/src/core/client/admin/routes/moderate/components/Moderate.tsx index d527ebd27..c16cd5502 100644 --- a/src/core/client/admin/routes/moderate/components/Moderate.tsx +++ b/src/core/client/admin/routes/moderate/components/Moderate.tsx @@ -1,10 +1,38 @@ import React, { StatelessComponent } from "react"; -import { HorizontalGutter, Typography } from "talk-ui/components"; -const Moderate: StatelessComponent = ({ children }) => ( - - Moderate - +import MainLayout from "talk-admin/components/MainLayout"; +import { SubBar } from "talk-ui/components/SubBar"; + +import Navigation from "./Navigation"; + +import styles from "./Moderate.css"; + +interface Props { + unmoderatedCount?: number; + reportedCount?: number; + pendingCount?: number; + children?: React.ReactNode; +} + +const Moderate: StatelessComponent = ({ + unmoderatedCount, + reportedCount, + pendingCount, + children, +}) => ( +
+ + + +
+ +
{children}
+
+
); export default Moderate; diff --git a/src/core/client/admin/routes/moderate/components/ModerateCard.css b/src/core/client/admin/routes/moderate/components/ModerateCard.css new file mode 100644 index 000000000..bf66bd1eb --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/ModerateCard.css @@ -0,0 +1,55 @@ +.topBar { + margin-bottom: var(--spacing-unit); +} + +.username { + margin-right: var(--spacing-unit); +} + +.footer { + margin-top: calc(2 * var(--spacing-unit)); +} + +.content { + min-height: calc(4.5 * var(--spacing-unit)); +} + +.mainContainer { + flex-grow: 1; +} + +.aside { + padding-top: 25px; + flex-shrink: 0; + flex-grow: 0; + box-sizing: border-box; +} + +.asideWithoutReplyTo { + padding-top: 10px; +} + +.decision { + font-size: calc(14rem / var(--rem-base)); + font-weight: var(--font-weight-medium); + font-family: var(--font-family-sans-serif); + line-height: calc(16em / 14); + letter-spacing: calc(0.2em / 14); + color: var(--palette-text-primary); + text-transform: uppercase; +} + +.separator { + flex-shrink: 0; + flex-grow: 0; + border-right: 1px solid var(--palette-divider); + margin: 0 calc(2 * var(--spacing-unit)); +} + +.root { + transition: background 100ms; +} + +.dangling { + background-color: var(--palette-grey-lightest); +} diff --git a/src/core/client/admin/routes/moderate/components/ModerateCard.spec.tsx b/src/core/client/admin/routes/moderate/components/ModerateCard.spec.tsx new file mode 100644 index 000000000..3d3eb4ed2 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/ModerateCard.spec.tsx @@ -0,0 +1,69 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { removeFragmentRefs } from "talk-framework/testHelpers"; +import { PropTypesOf } from "talk-framework/types"; + +import ModerateCard from "./ModerateCard"; + +const ModerateCardN = removeFragmentRefs(ModerateCard); + +const baseProps: PropTypesOf = { + id: "comment-id", + username: "Theon", + createdAt: "2018-11-29T16:01:51.897Z", + body: "content", + inReplyTo: null, + comment: {}, + status: "undecided", + viewContextHref: "http://localhost/comment", + suspectWords: ["idiot"], + bannedWords: ["fuck"], + onAccept: noop, + onReject: noop, +}; + +it("renders correctly", () => { + const props: PropTypesOf = { + ...baseProps, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders reply correctly", () => { + const props: PropTypesOf = { + ...baseProps, + inReplyTo: "Julian", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders accepted correctly", () => { + const props: PropTypesOf = { + ...baseProps, + status: "accepted", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders rejected correctly", () => { + const props: PropTypesOf = { + ...baseProps, + status: "rejected", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders dangling correctly", () => { + const props: PropTypesOf = { + ...baseProps, + dangling: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/ModerateCard.tsx b/src/core/client/admin/routes/moderate/components/ModerateCard.tsx new file mode 100644 index 000000000..df4eae13f --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/ModerateCard.tsx @@ -0,0 +1,127 @@ +import cn from "classnames"; +import { Localized } from "fluent-react/compat"; +import React, { StatelessComponent } from "react"; + +import { PropTypesOf } from "talk-framework/types"; +import { Button, Card, Flex, Icon } from "talk-ui/components"; + +import MarkersContainer from "../containers/MarkersContainer"; +import AcceptButton from "./AcceptButton"; +import CommentContent from "./CommentContent"; +import InReplyTo from "./InReplyTo"; +import RejectButton from "./RejectButton"; +import Timestamp from "./Timestamp"; +import Username from "./Username"; + +import styles from "./ModerateCard.css"; + +interface Props { + id: string; + username: string; + createdAt: string; + body: string; + inReplyTo: string | null; + comment: PropTypesOf["comment"]; + status: "accepted" | "rejected" | "undecided"; + viewContextHref: string; + suspectWords: ReadonlyArray; + bannedWords: ReadonlyArray; + onAccept: () => void; + onReject: () => void; + /** + * If set to true, it means this comment is about to be removed + * from the queue. This will trigger some styling changes to + * reflect that + */ + dangling?: boolean; +} + +const ModerateCard: StatelessComponent = ({ + id, + username, + createdAt, + body, + inReplyTo, + comment, + viewContextHref, + status, + suspectWords, + bannedWords, + onAccept, + onReject, + dangling, +}) => ( + + +
+
+
+ {username} + {createdAt} +
+ {inReplyTo && ( +
+ {inReplyTo} +
+ )} +
+ + {body} + +
+ + + + + + +
+
+
+ + +
DECISION
+
+ + + + +
+ + +); + +export default ModerateCard; diff --git a/src/core/client/admin/routes/moderate/components/Navigation.spec.tsx b/src/core/client/admin/routes/moderate/components/Navigation.spec.tsx new file mode 100644 index 000000000..b7ff37df2 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Navigation.spec.tsx @@ -0,0 +1,20 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import Navigation from "./Navigation"; + +import { PropTypesOf } from "talk-framework/types"; +it("renders correctly", () => { + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders correctly with counts", () => { + const props: PropTypesOf = { + unmoderatedCount: 3, + reportedCount: 4, + pendingCount: 0, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/Navigation.tsx b/src/core/client/admin/routes/moderate/components/Navigation.tsx new file mode 100644 index 000000000..ddc1f3140 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Navigation.tsx @@ -0,0 +1,63 @@ +import { Localized } from "fluent-react/compat"; +import React, { StatelessComponent } from "react"; + +import { Counter, Icon, SubBarNavigation } from "talk-ui/components"; + +import NavigationLink from "./NavigationLink"; + +interface Props { + unmoderatedCount?: number; + reportedCount?: number; + pendingCount?: number; + children?: React.ReactNode; +} + +const Navigation: StatelessComponent = ({ + unmoderatedCount, + reportedCount, + pendingCount, +}) => ( + + + flag + + Reported + + {reportedCount !== undefined && ( + + {reportedCount} + + )} + + + access_time + + Pending + + {pendingCount !== undefined && ( + + {pendingCount} + + )} + + + forum + + Unmoderated + + {unmoderatedCount !== undefined && ( + + {unmoderatedCount} + + )} + + + cancel + + Rejected + + + +); + +export default Navigation; diff --git a/src/core/client/admin/routes/moderate/components/NavigationLink.spec.tsx b/src/core/client/admin/routes/moderate/components/NavigationLink.spec.tsx new file mode 100644 index 000000000..285457c89 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/NavigationLink.spec.tsx @@ -0,0 +1,15 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import NavigationLink from "./NavigationLink"; + +it("renders correctly", () => { + const props: PropTypesOf = { + to: "/moderate", + children: "link", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/NavigationLink.tsx b/src/core/client/admin/routes/moderate/components/NavigationLink.tsx new file mode 100644 index 000000000..61b7bb60e --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/NavigationLink.tsx @@ -0,0 +1,17 @@ +import { Link, LocationDescriptor } from "found"; +import React, { StatelessComponent } from "react"; + +import { SubBarNavigationItem } from "talk-ui/components"; + +interface Props { + children: React.ReactNode; + to: string | LocationDescriptor; +} + +const NavigationLink: StatelessComponent = props => ( + + {props.children} + +); + +export default NavigationLink; diff --git a/src/core/client/admin/routes/moderate/components/Queue.css b/src/core/client/admin/routes/moderate/components/Queue.css new file mode 100644 index 000000000..3422f301c --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Queue.css @@ -0,0 +1,16 @@ +.root { + width: calc(75 * var(--spacing-unit)); +} + +.exitTransition { + opacity: 1; + transition: 300ms opacity; +} + +.exitTransitionActive { + opacity: 0; +} + +.exitTransitionDone { + opacity: 0; +} diff --git a/src/core/client/admin/routes/moderate/components/Queue.spec.tsx b/src/core/client/admin/routes/moderate/components/Queue.spec.tsx new file mode 100644 index 000000000..12c7d95db --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Queue.spec.tsx @@ -0,0 +1,36 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { removeFragmentRefs } from "talk-framework/testHelpers"; +import { PropTypesOf } from "talk-framework/types"; + +import Queue from "./Queue"; + +const QueueN = removeFragmentRefs(Queue); + +it("renders correctly with load more", () => { + const props: PropTypesOf = { + comments: [], + settings: {}, + onLoadMore: noop, + hasMore: true, + disableLoadMore: false, + danglingLogic: () => true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders correctly without load more", () => { + const props: PropTypesOf = { + comments: [], + settings: {}, + onLoadMore: noop, + hasMore: false, + disableLoadMore: false, + danglingLogic: () => true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/Queue.tsx b/src/core/client/admin/routes/moderate/components/Queue.tsx new file mode 100644 index 000000000..eb7fa4422 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Queue.tsx @@ -0,0 +1,62 @@ +import React, { StatelessComponent } from "react"; +import { CSSTransition, TransitionGroup } from "react-transition-group"; + +import { Flex, HorizontalGutter } from "talk-ui/components"; +import { PropTypesOf } from "talk-ui/types"; + +import AutoLoadMoreContainer from "../containers/AutoLoadMoreContainer"; +import ModerateCardContainer from "../containers/ModerateCardContainer"; + +import styles from "./Queue.css"; + +interface Props { + comments: Array< + { id: string } & PropTypesOf["comment"] + >; + settings: PropTypesOf["settings"]; + onLoadMore: () => void; + hasMore: boolean; + disableLoadMore: boolean; + danglingLogic: PropTypesOf["danglingLogic"]; +} + +const Queue: StatelessComponent = ({ + settings, + comments, + hasMore, + disableLoadMore, + onLoadMore, + danglingLogic, +}) => ( + + + {comments.map(c => ( + + + + ))} + + {hasMore && ( + + + + )} + +); + +export default Queue; diff --git a/src/core/client/admin/routes/moderate/components/RejectButton.css b/src/core/client/admin/routes/moderate/components/RejectButton.css new file mode 100644 index 000000000..b66f03011 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/RejectButton.css @@ -0,0 +1,24 @@ +.root { + border: 1px solid var(--palette-error-main); + box-sizing: border-box; + border-radius: 2px; + width: 65px; + height: 50px; + display: flex; + justify-content: center; + align-items: center; + color: var(--palette-error-main); + &:active { + background-color: var(--palette-error-main); + color: var(--palette-common-white); + } +} + +.invert { + background-color: var(--palette-error-main); + color: var(--palette-common-white); +} + +.icon { + color: inherit; +} diff --git a/src/core/client/admin/routes/moderate/components/RejectButton.spec.tsx b/src/core/client/admin/routes/moderate/components/RejectButton.spec.tsx new file mode 100644 index 000000000..eb345bae1 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/RejectButton.spec.tsx @@ -0,0 +1,22 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import RejectButton from "./RejectButton"; + +import { PropTypesOf } from "talk-framework/types"; + +it("renders correctly", () => { + const props: PropTypesOf = { + invert: false, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders correctly inverted", () => { + const props: PropTypesOf = { + invert: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/RejectButton.tsx b/src/core/client/admin/routes/moderate/components/RejectButton.tsx new file mode 100644 index 000000000..88d4b4aa0 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/RejectButton.tsx @@ -0,0 +1,34 @@ +import cn from "classnames"; +import { Localized } from "fluent-react/compat"; +import React, { StatelessComponent } from "react"; + +import { PropTypesOf } from "talk-framework/types"; +import { BaseButton, Icon } from "talk-ui/components"; + +import styles from "./RejectButton.css"; + +interface Props extends PropTypesOf { + invert?: boolean; +} + +const RejectButton: StatelessComponent = ({ + invert, + className, + ...rest +}) => ( + + + + close + + + +); + +export default RejectButton; diff --git a/src/core/client/admin/routes/moderate/components/SingleModerate.css b/src/core/client/admin/routes/moderate/components/SingleModerate.css new file mode 100644 index 000000000..08647251a --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/SingleModerate.css @@ -0,0 +1,47 @@ +.background { + position: fixed; + top: 0; + left: 0; + z-index: -1; + width: 100%; + height: 100%; + background-color: var(--palette-background-light); +} + +.main { + margin: calc(2 * var(--spacing-unit)) 0 calc(4 * var(--spacing-unit)) 0; + display: flex; + justify-content: center; +} + +.subBar { + height: calc(3 * var(--spacing-unit)); + background-color: var(--palette-primary-dark); + margin-top: -1px; +} + +.subBarBegin { + position: absolute; + left: 0; + + font-size: calc(12rem / var(--rem-base)); + font-weight: var(--font-weight-medium); + font-family: var(--font-family-sans-serif); + line-height: calc(14em / 12); + letter-spacing: calc(0.2em / 12); + + color: var(--palette-common-white); + text-transform: uppercase; + text-decoration: none; +} + +.subBarTitle { + font-size: calc(14rem / var(--rem-base)); + font-weight: var(--font-weight-medium); + font-family: var(--font-family-sans-serif); + line-height: calc(16em / 14); + letter-spacing: calc(0.2em / 14); + + color: var(--palette-common-white); + text-transform: uppercase; +} diff --git a/src/core/client/admin/routes/moderate/components/SingleModerate.spec.tsx b/src/core/client/admin/routes/moderate/components/SingleModerate.spec.tsx new file mode 100644 index 000000000..c865203e1 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/SingleModerate.spec.tsx @@ -0,0 +1,14 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import SingleModerate from "./SingleModerate"; + +it("renders correctly", () => { + const props: PropTypesOf = { + children: "singe comment queue", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/SingleModerate.tsx b/src/core/client/admin/routes/moderate/components/SingleModerate.tsx new file mode 100644 index 000000000..0bcd9cc52 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/SingleModerate.tsx @@ -0,0 +1,33 @@ +import { Localized } from "fluent-react/compat"; +import { Link } from "found"; +import React, { StatelessComponent } from "react"; + +import MainLayout from "talk-admin/components/MainLayout"; +import { SubBar } from "talk-ui/components"; + +import styles from "./SingleModerate.css"; + +interface Props { + children?: React.ReactNode; +} + +const Moderate: StatelessComponent = ({ children }) => ( +
+ + + + Go to moderation queues + + + +
Single Comment View
+
+
+
+ +
{children}
+
+
+); + +export default Moderate; diff --git a/src/core/client/admin/routes/moderate/components/Timestamp.css b/src/core/client/admin/routes/moderate/components/Timestamp.css new file mode 100644 index 000000000..e69cca547 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Timestamp.css @@ -0,0 +1,4 @@ +.root { + composes: timestamp from "talk-ui/shared/typography.css"; + color: var(--palette-grey-lighter); +} diff --git a/src/core/client/admin/routes/moderate/components/Timestamp.spec.tsx b/src/core/client/admin/routes/moderate/components/Timestamp.spec.tsx new file mode 100644 index 000000000..4a276b762 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Timestamp.spec.tsx @@ -0,0 +1,14 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import Timestamp from "./Timestamp"; + +it("renders correctly", () => { + const props: PropTypesOf = { + children: "1995-12-17T03:24:00.000Z", + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/Timestamp.tsx b/src/core/client/admin/routes/moderate/components/Timestamp.tsx new file mode 100644 index 000000000..8c982744f --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Timestamp.tsx @@ -0,0 +1,16 @@ +import React from "react"; +import { StatelessComponent } from "react"; + +import { RelativeTime } from "talk-ui/components"; + +import styles from "./Timestamp.css"; + +export interface TimestampProps { + children: string; +} + +const Timestamp: StatelessComponent = props => ( + +); + +export default Timestamp; diff --git a/src/core/client/admin/routes/moderate/components/Username.css b/src/core/client/admin/routes/moderate/components/Username.css new file mode 100644 index 000000000..c3bd53ac0 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Username.css @@ -0,0 +1,4 @@ +.root { + line-height: 1; + color: var(--palette-grey-dark); +} diff --git a/src/core/client/admin/routes/moderate/components/Username.spec.tsx b/src/core/client/admin/routes/moderate/components/Username.spec.tsx new file mode 100644 index 000000000..ec1ac95db --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Username.spec.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; + +import { PropTypesOf } from "talk-framework/types"; + +import Username from "./Username"; + +it("renders correctly", () => { + const props: PropTypesOf = { + children: "Marvin", + }; + + const testRenderer = TestRenderer.create(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/components/Username.tsx b/src/core/client/admin/routes/moderate/components/Username.tsx new file mode 100644 index 000000000..2d0a22e74 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/Username.tsx @@ -0,0 +1,26 @@ +import cn from "classnames"; +import React from "react"; +import { StatelessComponent } from "react"; + +import { Typography } from "talk-ui/components"; + +import styles from "./Username.css"; + +export interface UsernameProps { + className?: string; + children: string; +} + +const Username: StatelessComponent = props => { + return ( + + {props.children} + + ); +}; + +export default Username; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/AcceptButton.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/AcceptButton.spec.tsx.snap new file mode 100644 index 000000000..0e7f454e5 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/AcceptButton.spec.tsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + + done + + + +`; + +exports[`renders correctly inverted 1`] = ` + + + + done + + + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/CommentContent.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/CommentContent.spec.tsx.snap new file mode 100644 index 000000000..e9ee7fb73 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/CommentContent.spec.tsx.snap @@ -0,0 +1,25 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +idiot, you fucking bastard", + } + } +/> +`; + +exports[`renders empty words correctly 1`] = ` +idiot, you fucking bastard", + } + } +/> +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/InReplyTo.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/InReplyTo.spec.tsx.snap new file mode 100644 index 000000000..4e25b614f --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/InReplyTo.spec.tsx.snap @@ -0,0 +1,26 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + reply + + + } + > + + Reply to <username><username> + + + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/Moderate.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/Moderate.spec.tsx.snap index dd278bb5e..ca3e714d1 100644 --- a/src/core/client/admin/routes/moderate/components/__snapshots__/Moderate.spec.tsx.snap +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/Moderate.spec.tsx.snap @@ -1,11 +1,49 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` - - + - Moderate - - + + +
+ +
+ +
+`; + +exports[`renders correctly with counts 1`] = ` +
+ + + +
+ +
+ +
`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/ModerateCard.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/ModerateCard.spec.tsx.snap new file mode 100644 index 000000000..d2521f795 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/ModerateCard.spec.tsx.snap @@ -0,0 +1,550 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders accepted correctly 1`] = ` + + +
+
+
+ + Theon + + + 2018-11-29T16:01:51.897Z + +
+
+ + content + +
+ + + + + View Context + + + + + arrow_forward + + + + + + +
+
+
+ + +
+ DECISION +
+
+ + + + +
+ + +`; + +exports[`renders correctly 1`] = ` + + +
+
+
+ + Theon + + + 2018-11-29T16:01:51.897Z + +
+
+ + content + +
+ + + + + View Context + + + + + arrow_forward + + + + + + +
+
+
+ + +
+ DECISION +
+
+ + + + +
+ + +`; + +exports[`renders dangling correctly 1`] = ` + + +
+
+
+ + Theon + + + 2018-11-29T16:01:51.897Z + +
+
+ + content + +
+ + + + + View Context + + + + + arrow_forward + + + + + + +
+
+
+ + +
+ DECISION +
+
+ + + + +
+ + +`; + +exports[`renders rejected correctly 1`] = ` + + +
+
+
+ + Theon + + + 2018-11-29T16:01:51.897Z + +
+
+ + content + +
+ + + + + View Context + + + + + arrow_forward + + + + + + +
+
+
+ + +
+ DECISION +
+
+ + + + +
+ + +`; + +exports[`renders reply correctly 1`] = ` + + +
+
+
+ + Theon + + + 2018-11-29T16:01:51.897Z + +
+
+ + Julian + +
+
+ + content + +
+ + + + + View Context + + + + + arrow_forward + + + + + + +
+
+
+ + +
+ DECISION +
+
+ + + + +
+ + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/Navigation.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/Navigation.spec.tsx.snap new file mode 100644 index 000000000..5d3266425 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/Navigation.spec.tsx.snap @@ -0,0 +1,138 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + + flag + + + + Reported + + + + + + access_time + + + + Pending + + + + + + forum + + + + Unmoderated + + + + + + cancel + + + + Rejected + + + + +`; + +exports[`renders correctly with counts 1`] = ` + + + + flag + + + + Reported + + + + 4 + + + + + access_time + + + + Pending + + + + 0 + + + + + forum + + + + Unmoderated + + + + 3 + + + + + cancel + + + + Rejected + + + + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/NavigationLink.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/NavigationLink.spec.tsx.snap new file mode 100644 index 000000000..856a7af68 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/NavigationLink.spec.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + link + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/Queue.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/Queue.spec.tsx.snap new file mode 100644 index 000000000..db8a2e45a --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/Queue.spec.tsx.snap @@ -0,0 +1,37 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly with load more 1`] = ` + + + + + + +`; + +exports[`renders correctly without load more 1`] = ` + + + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/RejectButton.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/RejectButton.spec.tsx.snap new file mode 100644 index 000000000..1f5f8824c --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/RejectButton.spec.tsx.snap @@ -0,0 +1,47 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + + close + + + +`; + +exports[`renders correctly inverted 1`] = ` + + + + close + + + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/SingleModerate.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/SingleModerate.spec.tsx.snap new file mode 100644 index 000000000..608bf6b3f --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/SingleModerate.spec.tsx.snap @@ -0,0 +1,43 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +
+ + + + Go to moderation queues + + + +
+ Single Comment View +
+
+
+
+ +
+ singe comment queue +
+
+
+`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/Timestamp.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/Timestamp.spec.tsx.snap new file mode 100644 index 000000000..862d6cad7 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/Timestamp.spec.tsx.snap @@ -0,0 +1,8 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + +`; diff --git a/src/core/client/admin/routes/moderate/components/__snapshots__/Username.spec.tsx.snap b/src/core/client/admin/routes/moderate/components/__snapshots__/Username.spec.tsx.snap new file mode 100644 index 000000000..a83afc344 --- /dev/null +++ b/src/core/client/admin/routes/moderate/components/__snapshots__/Username.spec.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + Marvin + +`; diff --git a/src/core/client/admin/routes/moderate/containers/AutoLoadMoreContainer.tsx b/src/core/client/admin/routes/moderate/containers/AutoLoadMoreContainer.tsx new file mode 100644 index 000000000..37409dad1 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/AutoLoadMoreContainer.tsx @@ -0,0 +1,43 @@ +import React from "react"; + +import { withInView } from "talk-framework/lib/intersection"; +import { BaseButton, Spinner } from "talk-ui/components"; + +interface Props { + inView: boolean | undefined; + intersectionRef: React.Ref; + disableLoadMore: boolean; + onLoadMore: () => void; +} + +class AutoLoadMoresContainer extends React.Component { + public componentWillReceiveProps(nextProps: Props) { + if (nextProps.inView && !nextProps.disableLoadMore) { + nextProps.onLoadMore(); + } + } + public render() { + // We can't really test infinite scrolling behavior + // with jsdom in our feature tests, so we'll just a + // button here to make it testable. + if (process.env.NODE_ENV === "test") { + return ( + + Load More + + ); + } + return ( +
+ +
+ ); + } +} + +const enhanced = withInView(AutoLoadMoresContainer); + +export default enhanced; diff --git a/src/core/client/admin/routes/moderate/containers/MarkersContainer.spec.tsx b/src/core/client/admin/routes/moderate/containers/MarkersContainer.spec.tsx new file mode 100644 index 000000000..dcef13504 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/MarkersContainer.spec.tsx @@ -0,0 +1,57 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { removeFragmentRefs } from "talk-framework/testHelpers"; +import { PropTypesOf } from "talk-framework/types"; + +import { MarkersContainer } from "./MarkersContainer"; + +const MarkersContainerN = removeFragmentRefs(MarkersContainer); + +it("renders all markers", () => { + const props: PropTypesOf = { + comment: { + status: "PREMOD", + actionCounts: { + flag: { + reasons: { + COMMENT_DETECTED_TOXIC: 1, + COMMENT_DETECTED_SPAM: 1, + COMMENT_DETECTED_TRUST: 1, + COMMENT_DETECTED_LINKS: 1, + COMMENT_DETECTED_BANNED_WORD: 1, + COMMENT_DETECTED_SUSPECT_WORD: 1, + COMMENT_REPORTED_OFFENSIVE: 2, + COMMENT_REPORTED_SPAM: 3, + }, + }, + }, + }, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +it("renders some markers", () => { + const props: PropTypesOf = { + comment: { + status: "PREMOD", + actionCounts: { + flag: { + reasons: { + COMMENT_DETECTED_TOXIC: 1, + COMMENT_DETECTED_SPAM: 0, + COMMENT_DETECTED_TRUST: 1, + COMMENT_DETECTED_LINKS: 0, + COMMENT_DETECTED_BANNED_WORD: 1, + COMMENT_DETECTED_SUSPECT_WORD: 0, + COMMENT_REPORTED_OFFENSIVE: 2, + COMMENT_REPORTED_SPAM: 0, + }, + }, + }, + }, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/admin/routes/moderate/containers/MarkersContainer.tsx b/src/core/client/admin/routes/moderate/containers/MarkersContainer.tsx new file mode 100644 index 000000000..3ab6bd600 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/MarkersContainer.tsx @@ -0,0 +1,120 @@ +import { Localized } from "fluent-react/compat"; +import React from "react"; +import { graphql } from "react-relay"; + +import { MarkersContainer_comment as CommentData } from "talk-admin/__generated__/MarkersContainer_comment.graphql"; +import { withFragmentContainer } from "talk-framework/lib/relay"; +import { Marker, MarkerCount } from "talk-ui/components"; + +interface MarkersContainerProps { + comment: CommentData; +} + +let keyCounter = 0; +const markers: Array<(c: CommentData) => React.ReactElement | null> = [ + c => + (c.status === "PREMOD" && ( + + Pre-Mod + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_DETECTED_LINKS && ( + + Link + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_DETECTED_BANNED_WORD && ( + + Banned Word + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_DETECTED_SUSPECT_WORD && ( + + + Suspect Word + + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_DETECTED_SPAM && ( + + Spam + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_DETECTED_TOXIC && ( + + Toxic + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_DETECTED_TRUST && ( + + Karma + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_REPORTED_OFFENSIVE && ( + + + Offensive + {" "} + + {c.actionCounts.flag.reasons.COMMENT_REPORTED_OFFENSIVE} + + + )) || + null, + c => + (c.actionCounts.flag.reasons.COMMENT_REPORTED_SPAM && ( + + + Spam + {" "} + + {c.actionCounts.flag.reasons.COMMENT_REPORTED_SPAM} + + + )) || + null, +]; + +export class MarkersContainer extends React.Component { + public render() { + return markers.map(cb => cb(this.props.comment)); + } +} + +const enhanced = withFragmentContainer({ + comment: graphql` + fragment MarkersContainer_comment on Comment { + status + actionCounts { + flag { + reasons { + COMMENT_DETECTED_TOXIC + COMMENT_DETECTED_SPAM + COMMENT_DETECTED_TRUST + COMMENT_DETECTED_LINKS + COMMENT_DETECTED_BANNED_WORD + COMMENT_DETECTED_SUSPECT_WORD + COMMENT_REPORTED_OFFENSIVE + COMMENT_REPORTED_SPAM + } + } + } + } + `, +})(MarkersContainer); + +export default enhanced; diff --git a/src/core/client/admin/routes/moderate/containers/ModerateCardContainer.tsx b/src/core/client/admin/routes/moderate/containers/ModerateCardContainer.tsx new file mode 100644 index 000000000..bec8fd654 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/ModerateCardContainer.tsx @@ -0,0 +1,111 @@ +import React from "react"; +import { graphql } from "react-relay"; + +import { + COMMENT_STATUS, + ModerateCardContainer_comment as CommentData, +} from "talk-admin/__generated__/ModerateCardContainer_comment.graphql"; +import { ModerateCardContainer_settings as SettingsData } from "talk-admin/__generated__/ModerateCardContainer_settings.graphql"; +import { + AcceptCommentMutation, + withAcceptCommentMutation, +} from "talk-admin/mutations"; +import { + RejectCommentMutation, + withRejectCommentMutation, +} from "talk-admin/mutations"; +import { withFragmentContainer } from "talk-framework/lib/relay"; + +import ModerateCard from "../components/ModerateCard"; + +interface ModerateCardContainerProps { + comment: CommentData; + settings: SettingsData; + acceptComment: AcceptCommentMutation; + rejectComment: RejectCommentMutation; + danglingLogic: (status: COMMENT_STATUS) => boolean; +} + +function getStatus(comment: CommentData) { + switch (comment.status) { + case "ACCEPTED": + return "accepted"; + case "REJECTED": + return "rejected"; + default: + return "undecided"; + } +} + +class ModerateCardContainer extends React.Component< + ModerateCardContainerProps +> { + private handleAccept = () => { + this.props.acceptComment({ + commentID: this.props.comment.id, + commentRevisionID: this.props.comment.revision.id, + }); + }; + + private handleReject = () => { + this.props.rejectComment({ + commentID: this.props.comment.id, + commentRevisionID: this.props.comment.revision.id, + }); + }; + + public render() { + const { comment, settings, danglingLogic } = this.props; + return ( + + ); + } +} + +const enhanced = withFragmentContainer({ + comment: graphql` + fragment ModerateCardContainer_comment on Comment { + id + author { + username + } + createdAt + body + status + revision { + id + } + parent { + author { + username + } + } + permalink + ...MarkersContainer_comment + } + `, + settings: graphql` + fragment ModerateCardContainer_settings on Settings { + wordList { + banned + suspect + } + } + `, +})(withAcceptCommentMutation(withRejectCommentMutation(ModerateCardContainer))); + +export default enhanced; diff --git a/src/core/client/admin/routes/moderate/containers/ModerateContainer.tsx b/src/core/client/admin/routes/moderate/containers/ModerateContainer.tsx new file mode 100644 index 000000000..aa73613c9 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/ModerateContainer.tsx @@ -0,0 +1,50 @@ +import { RouteProps } from "found"; +import React from "react"; +import { graphql } from "react-relay"; + +import { ModerateContainerQueryResponse } from "talk-admin/__generated__/ModerateContainerQuery.graphql"; + +import Moderate from "../components/Moderate"; + +type Props = ModerateContainerQueryResponse; + +export default class ModerateContainer extends React.Component { + public static routeConfig: RouteProps; + + public render() { + if (!this.props.moderationQueues) { + return ; + } + return ( + + {this.props.children} + + ); + } +} + +ModerateContainer.routeConfig = { + Component: ModerateContainer, + query: graphql` + query ModerateContainerQuery { + moderationQueues { + unmoderated { + count + } + reported { + count + } + pending { + count + } + } + } + `, + cacheConfig: { force: true }, + render: ({ Component, props }) => + Component ? : undefined, +}; diff --git a/src/core/client/admin/routes/moderate/containers/QueueContainer.tsx b/src/core/client/admin/routes/moderate/containers/QueueContainer.tsx new file mode 100644 index 000000000..21e849181 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/QueueContainer.tsx @@ -0,0 +1,224 @@ +import { RouteProps } from "found"; +import React from "react"; +import { graphql, GraphQLTaggedNode, RelayPaginationProp } from "react-relay"; + +import { QueueContainer_queue as QueueData } from "talk-admin/__generated__/QueueContainer_queue.graphql"; +import { QueueContainer_settings as SettingsData } from "talk-admin/__generated__/QueueContainer_settings.graphql"; +import { QueueContainerPaginationPendingQueryVariables } from "talk-admin/__generated__/QueueContainerPaginationPendingQuery.graphql"; +import { IntersectionProvider } from "talk-framework/lib/intersection"; +import { withPaginationContainer } from "talk-framework/lib/relay"; + +import LoadingQueue from "../components/LoadingQueue"; +import Queue from "../components/Queue"; + +interface QueueContainerProps { + queue: QueueData; + settings: SettingsData; + relay: RelayPaginationProp; +} + +// TODO: use generated types +const danglingLogic = (status: string) => + ["ACCEPTED", "REJECTED"].indexOf(status) >= 0; + +export class QueueContainer extends React.Component { + public static routeConfig: RouteProps; + + public state = { + disableLoadMore: false, + }; + + public render() { + const comments = this.props.queue.comments.edges.map(edge => edge.node); + return ( + + + + ); + } + + private loadMore = () => { + if (!this.props.relay.hasMore() || this.props.relay.isLoading()) { + return; + } + this.setState({ disableLoadMore: true }); + this.props.relay.loadMore( + 10, // Fetch the next 10 feed items + error => { + this.setState({ disableLoadMore: false }); + if (error) { + // tslint:disable-next-line:no-console + console.error(error); + } + } + ); + }; +} + +// TODO: (cvle) This should be autogenerated. +interface FragmentVariables { + count: number; + cursor?: string; +} + +const createQueueContainer = ( + queueQuery: GraphQLTaggedNode, + paginationQuery: GraphQLTaggedNode +) => { + const enhanced = (withPaginationContainer< + QueueContainerProps, + QueueContainerPaginationPendingQueryVariables, + FragmentVariables + >( + { + queue: graphql` + fragment QueueContainer_queue on ModerationQueue + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + ) { + count + comments(first: $count, after: $cursor) + @connection(key: "Queue_comments") { + edges { + node { + id + ...ModerateCardContainer_comment + } + } + } + } + `, + settings: graphql` + fragment QueueContainer_settings on Settings { + ...ModerateCardContainer_settings + } + `, + }, + { + direction: "forward", + getConnectionFromProps(props) { + return props.queue && props.queue.comments; + }, + // This is also the default implementation of `getFragmentVariables` if it isn't provided. + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + count: totalCount, + }; + }, + getVariables(props, { count, cursor }, fragmentVariables) { + return { + count, + cursor, + }; + }, + query: paginationQuery, + } + )(QueueContainer) as any) as typeof QueueContainer; + + enhanced.routeConfig = { + Component: enhanced, + query: queueQuery, + cacheConfig: { force: true }, + render: ({ Component, props }) => { + const anyProps = props as any; + if (Component && props) { + const queue = + anyProps.moderationQueues[Object.keys(anyProps.moderationQueues)[0]]; + return ; + } + return ; + }, + }; + + return enhanced; +}; + +export const PendingQueueContainer = createQueueContainer( + graphql` + query QueueContainerPendingQuery { + moderationQueues { + pending { + ...QueueContainer_queue + } + } + settings { + ...QueueContainer_settings + } + } + `, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query QueueContainerPaginationPendingQuery($count: Int!, $cursor: Cursor) { + moderationQueues { + pending { + ...QueueContainer_queue @arguments(count: $count, cursor: $cursor) + } + } + } + ` +); + +export const ReportedQueueContainer = createQueueContainer( + graphql` + query QueueContainerReportedQuery { + moderationQueues { + reported { + ...QueueContainer_queue + } + } + settings { + ...QueueContainer_settings + } + } + `, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query QueueContainerPaginationReportedQuery($count: Int!, $cursor: Cursor) { + moderationQueues { + reported { + ...QueueContainer_queue @arguments(count: $count, cursor: $cursor) + } + } + } + ` +); + +export const UnmoderatedQueueContainer = createQueueContainer( + graphql` + query QueueContainerUnmoderatedQuery { + moderationQueues { + unmoderated { + ...QueueContainer_queue + } + } + settings { + ...QueueContainer_settings + } + } + `, + graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query QueueContainerPaginationUnmoderatedQuery( + $count: Int! + $cursor: Cursor + ) { + moderationQueues { + unmoderated { + ...QueueContainer_queue @arguments(count: $count, cursor: $cursor) + } + } + } + ` +); diff --git a/src/core/client/admin/routes/moderate/containers/RejectedQueueContainer.tsx b/src/core/client/admin/routes/moderate/containers/RejectedQueueContainer.tsx new file mode 100644 index 000000000..22635129f --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/RejectedQueueContainer.tsx @@ -0,0 +1,145 @@ +import { RouteProps } from "found"; +import React from "react"; +import { graphql, RelayPaginationProp } from "react-relay"; + +import { RejectedQueueContainer_query as QueryData } from "talk-admin/__generated__/RejectedQueueContainer_query.graphql"; +import { RejectedQueueContainerPaginationQueryVariables } from "talk-admin/__generated__/RejectedQueueContainerPaginationQuery.graphql"; +import { IntersectionProvider } from "talk-framework/lib/intersection"; +import { withPaginationContainer } from "talk-framework/lib/relay"; + +import LoadingQueue from "../components/LoadingQueue"; +import Queue from "../components/Queue"; + +interface RejectedQueueContainerProps { + query: QueryData; + relay: RelayPaginationProp; +} + +// TODO: use generated types +const danglingLogic = (status: string) => ["ACCEPTED"].indexOf(status) >= 0; + +export class RejectedQueueContainer extends React.Component< + RejectedQueueContainerProps +> { + public static routeConfig: RouteProps; + + public state = { + disableLoadMore: false, + }; + + public render() { + const comments = this.props.query.comments!.edges.map(edge => edge.node); + return ( + + {" "} + + ); + } + + private loadMore = () => { + if (!this.props.relay.hasMore() || this.props.relay.isLoading()) { + return; + } + this.setState({ disableLoadMore: true }); + this.props.relay.loadMore( + 10, // Fetch the next 10 feed items + error => { + this.setState({ disableLoadMore: false }); + if (error) { + // tslint:disable-next-line:no-console + console.error(error); + } + } + ); + }; +} + +// TODO: (cvle) This should be autogenerated. +interface FragmentVariables { + count: number; + cursor?: string; +} + +const enhanced = (withPaginationContainer< + RejectedQueueContainerProps, + RejectedQueueContainerPaginationQueryVariables, + FragmentVariables +>( + { + query: graphql` + fragment RejectedQueueContainer_query on Query + @argumentDefinitions( + count: { type: "Int!", defaultValue: 5 } + cursor: { type: "Cursor" } + ) { + comments(filter: { status: REJECTED }, first: $count, after: $cursor) + @connection(key: "RejectedQueue_comments") { + edges { + node { + id + ...ModerateCardContainer_comment + } + } + } + settings { + ...ModerateCardContainer_settings + } + } + `, + }, + { + direction: "forward", + getConnectionFromProps(props) { + return props.query && props.query.comments; + }, + // This is also the default implementation of `getFragmentVariables` if it isn't provided. + getFragmentVariables(prevVars, totalCount) { + return { + ...prevVars, + count: totalCount, + }; + }, + getVariables(props, { count, cursor }, fragmentVariables) { + return { + count, + cursor, + }; + }, + query: graphql` + # Pagination query to be fetched upon calling 'loadMore'. + # Notice that we re-use our fragment, and the shape of this query matches our fragment spec. + query RejectedQueueContainerPaginationQuery( + $count: Int! + $cursor: Cursor + ) { + ...RejectedQueueContainer_query + @arguments(count: $count, cursor: $cursor) + } + `, + } +)(RejectedQueueContainer) as any) as typeof RejectedQueueContainer; + +enhanced.routeConfig = { + Component: enhanced, + query: graphql` + query RejectedQueueContainerQuery { + ...RejectedQueueContainer_query + } + `, + cacheConfig: { force: true }, + render: ({ Component, props }) => { + if (Component && props) { + return ; + } + return ; + }, +}; + +export default enhanced; diff --git a/src/core/client/admin/routes/moderate/containers/SingleModerateContainer.tsx b/src/core/client/admin/routes/moderate/containers/SingleModerateContainer.tsx new file mode 100644 index 000000000..4b9101008 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/SingleModerateContainer.tsx @@ -0,0 +1,59 @@ +import { RouteProps } from "found"; +import { noop } from "lodash"; +import React from "react"; +import { graphql } from "react-relay"; + +import { SingleModerateContainerQueryResponse } from "talk-admin/__generated__/SingleModerateContainerQuery.graphql"; + +import NotFound from "../../NotFound"; +import LoadingQueue from "../components/LoadingQueue"; +import Queue from "../components/Queue"; +import SingleModerate from "../components/SingleModerate"; + +type Props = SingleModerateContainerQueryResponse; + +const danglingLogic = () => false; + +export default class SingleModerateContainer extends React.Component { + public static routeConfig: RouteProps; + + public render() { + if (!this.props.comment) { + return ; + } + return ( + + + + ); + } +} + +SingleModerateContainer.routeConfig = { + Component: SingleModerateContainer, + query: graphql` + query SingleModerateContainerQuery($commentID: ID!) { + comment(id: $commentID) { + id + ...ModerateCardContainer_comment + } + settings { + ...ModerateCardContainer_settings + } + } + `, + cacheConfig: { force: true }, + render: ({ Component, props }) => { + if (Component && props) { + return ; + } + return ; + }, +}; diff --git a/src/core/client/admin/routes/moderate/containers/__snapshots__/MarkersContainer.spec.tsx.snap b/src/core/client/admin/routes/moderate/containers/__snapshots__/MarkersContainer.spec.tsx.snap new file mode 100644 index 000000000..5b132b421 --- /dev/null +++ b/src/core/client/admin/routes/moderate/containers/__snapshots__/MarkersContainer.spec.tsx.snap @@ -0,0 +1,174 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders all markers 1`] = ` +Array [ + + + Pre-Mod + + , + + + Link + + , + + + Banned Word + + , + + + Suspect Word + + , + + + Spam + + , + + + Toxic + + , + + + Karma + + , + + + + Offensive + + + + + 2 + + , + + + + Spam + + + + + 3 + + , +] +`; + +exports[`renders some markers 1`] = ` +Array [ + + + Pre-Mod + + , + "", + + + Banned Word + + , + "", + "", + + + Toxic + + , + + + Karma + + , + + + + Offensive + + + + + 2 + + , + "", +] +`; diff --git a/src/core/client/admin/routes/stories/components/Stories.tsx b/src/core/client/admin/routes/stories/components/Stories.tsx index 148848bff..a12da7d25 100644 --- a/src/core/client/admin/routes/stories/components/Stories.tsx +++ b/src/core/client/admin/routes/stories/components/Stories.tsx @@ -1,10 +1,12 @@ import React, { StatelessComponent } from "react"; -import { HorizontalGutter, Typography } from "talk-ui/components"; + +import MainLayout from "talk-admin/components/MainLayout"; +import { Typography } from "talk-ui/components"; const Stories: StatelessComponent = ({ children }) => ( - + Stories - + ); export default Stories; diff --git a/src/core/client/admin/routes/stories/components/__snapshots__/Stories.spec.tsx.snap b/src/core/client/admin/routes/stories/components/__snapshots__/Stories.spec.tsx.snap index 676b2430b..c3eea3e2d 100644 --- a/src/core/client/admin/routes/stories/components/__snapshots__/Stories.spec.tsx.snap +++ b/src/core/client/admin/routes/stories/components/__snapshots__/Stories.spec.tsx.snap @@ -1,11 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` - + Stories - + `; diff --git a/src/core/client/admin/test/__snapshots__/login.spec.tsx.snap b/src/core/client/admin/test/__snapshots__/login.spec.tsx.snap index 9d5d1c4d5..6280a2831 100644 --- a/src/core/client/admin/test/__snapshots__/login.spec.tsx.snap +++ b/src/core/client/admin/test/__snapshots__/login.spec.tsx.snap @@ -2,65 +2,6 @@ exports[`accepts correct password 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -211,65 +152,6 @@ exports[`accepts correct password 1`] = ` exports[`accepts valid email 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -420,65 +302,6 @@ exports[`accepts valid email 1`] = ` exports[`checks for invalid email 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -642,65 +465,6 @@ exports[`checks for invalid email 1`] = ` exports[`renders sign in form 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -838,65 +602,6 @@ exports[`renders sign in form 1`] = ` exports[`shows error when submitting empty form 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -1060,65 +765,6 @@ exports[`shows error when submitting empty form 1`] = ` exports[`shows server error 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -1256,65 +902,6 @@ exports[`shows server error 1`] = ` exports[`shows server error 2`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -1457,65 +1044,6 @@ exports[`shows server error 2`] = ` exports[`submits form successfully 1`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
@@ -1653,65 +1181,6 @@ exports[`submits form successfully 1`] = ` exports[`submits form successfully 2`] = `
-
-
-
- - - - - - - - - - - logo mark2018 - - - - - -

- Talk -

-
-
-
diff --git a/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap b/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap index 33590a7c2..266b40806 100644 --- a/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap +++ b/src/core/client/admin/test/configure/__snapshots__/auth.spec.tsx.snap @@ -1687,6 +1687,7 @@ and all signed-in users will be signed out. exports[`renders configure auth 1`] = ` diff --git a/src/core/client/admin/test/configure/auth.spec.tsx b/src/core/client/admin/test/configure/auth.spec.tsx index fc8e43d30..3c3ca5b9e 100644 --- a/src/core/client/admin/test/configure/auth.spec.tsx +++ b/src/core/client/admin/test/configure/auth.spec.tsx @@ -23,7 +23,7 @@ const createTestRenderer = async (resolver: any = {}) => { ...resolver.Query, settings: sinon .stub() - .returns(merge(settings, get(resolver, "Query.settings"))), + .returns(merge({}, settings, get(resolver, "Query.settings"))), }, }; const { testRenderer } = create({ @@ -51,7 +51,7 @@ it("regenerate sso key", async () => { regenerateSSOKey: createSinonStub(s => s.callsFake((_: any, data: any) => { return { - settings: { + settings: merge({}, settings, { auth: { integrations: { sso: { @@ -60,7 +60,7 @@ it("regenerate sso key", async () => { }, }, }, - }, + }), clientMutationId: data.input.clientMutationId, }; }) @@ -112,7 +112,7 @@ it("prevents stream lock out", async () => { stream: false, }, }); - settingsRecord = merge(settingsRecord, data.input.settings); + settingsRecord = merge({}, settingsRecord, data.input.settings); return { settings: settingsRecord, clientMutationId: data.input.clientMutationId, @@ -187,7 +187,7 @@ it("change settings", async () => { clientID: "myClientID", clientSecret: "myClientSecret", }); - settingsRecord = merge(settingsRecord, data.input.settings); + settingsRecord = merge({}, settingsRecord, data.input.settings); return { settings: settingsRecord, clientMutationId: data.input.clientMutationId, @@ -241,6 +241,7 @@ it("change settings", async () => { tokenURL: "http://issuer.com/tokenURL", }); (settingsRecord.auth.integrations.oidc[0] as any) = merge( + {}, settingsRecord.auth.integrations.oidc[0], data.input.configuration ); diff --git a/src/core/client/admin/test/create.tsx b/src/core/client/admin/test/create.tsx index f752fe233..62881117d 100644 --- a/src/core/client/admin/test/create.tsx +++ b/src/core/client/admin/test/create.tsx @@ -14,6 +14,7 @@ import { createUUIDGenerator } from "talk-framework/testHelpers"; import createEnvironment from "./createEnvironment"; import createFluentBundle from "./createFluentBundle"; +import createNodeMock from "./createNodeMock"; interface CreateParams { logNetwork?: boolean; @@ -53,7 +54,8 @@ export default function create(params: CreateParams) { const testRenderer = TestRenderer.create( - + , + { createNodeMock } ); return { context, testRenderer }; diff --git a/src/core/client/admin/test/createNodeMock.ts b/src/core/client/admin/test/createNodeMock.ts new file mode 100644 index 000000000..54abed79a --- /dev/null +++ b/src/core/client/admin/test/createNodeMock.ts @@ -0,0 +1,13 @@ +import { noop } from "lodash"; +import { ReactElement } from "react"; + +export default function createNodeMock(element: ReactElement) { + if (element.type === "div") { + return { + innerHtml: "", + className: "", + focus: noop, + }; + } + return null; +} diff --git a/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap b/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap index decfa1756..ae298e738 100644 --- a/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap +++ b/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap @@ -74,7 +74,7 @@ exports[`loads more 1`] = `
@@ -138,7 +138,7 @@ exports[`loads more 1`] = `
@@ -202,7 +202,7 @@ exports[`loads more 1`] = `
@@ -331,7 +331,7 @@ exports[`render popover content 1`] = `
@@ -395,7 +395,7 @@ exports[`render popover content 1`] = `
diff --git a/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx b/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx index 1b40addac..3040122db 100644 --- a/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx +++ b/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx @@ -3,12 +3,11 @@ import sinon from "sinon"; import { createSinonStub, - getByTestID, - getByText, - limitSnapshotTo, replaceHistoryLocation, - wait, + toJSON, waitForElement, + waitUntilThrow, + within, } from "talk-framework/testHelpers"; import create from "../create"; @@ -68,7 +67,7 @@ const createTestRenderer = async (resolver: any = {}) => { }), settings: sinon .stub() - .returns(merge(settings, get(resolver, "Query.settings"))), + .returns(merge({}, settings, get(resolver, "Query.settings"))), }, }; const { testRenderer } = create({ @@ -79,9 +78,8 @@ const createTestRenderer = async (resolver: any = {}) => { localRecord.setValue(true, "loggedIn"); }, }); - await waitForElement(() => - getByTestID("decisionHistory-toggle", testRenderer.root) - ); + const { getByTestID } = within(testRenderer.root); + await waitForElement(() => getByTestID("decisionHistory-toggle")); return testRenderer; }; @@ -96,41 +94,51 @@ async function createTestRendererAndOpenPopover() { it("renders decision history popover button", async () => { const testRenderer = await createTestRenderer(); - expect( - limitSnapshotTo("decisionHistory-popover", testRenderer.toJSON()) - ).toMatchSnapshot(); + const popover = within(testRenderer.root).getByTestID( + "decisionHistory-popover" + ); + expect(toJSON(popover)).toMatchSnapshot(); }); it("opens popover when clicked on button showing loading state", async () => { const testRenderer = await createTestRendererAndOpenPopover(); - expect( - limitSnapshotTo("decisionHistory-loading-container", testRenderer.toJSON()) - ).toMatchSnapshot(); + const container = within(testRenderer.root).getByTestID( + "decisionHistory-loading-container" + ); + expect(toJSON(container)).toMatchSnapshot(); }); it("render popover content", async () => { const testRenderer = await createTestRendererAndOpenPopover(); - await waitForElement(() => - getByTestID("decisionHistory-container", testRenderer.root) + const container = await waitForElement(() => + within(testRenderer.root).getByTestID("decisionHistory-container") ); - expect( - limitSnapshotTo("decisionHistory-container", testRenderer.toJSON()) - ).toMatchSnapshot(); + expect(toJSON(container)).toMatchSnapshot(); }); it("loads more", async () => { const testRenderer = await createTestRendererAndOpenPopover(); + + // Wait for decision history to render. const decisionHistoryContainer = await waitForElement(() => - getByTestID("decisionHistory-container", testRenderer.root) + within(testRenderer.root).getByTestID("decisionHistory-container") ); - const ShowMoreButton = getByText("Show More", decisionHistoryContainer)!; + + const { getByText } = within(decisionHistoryContainer); + + // Find active show more button. + const ShowMoreButton = getByText("Show More"); expect(ShowMoreButton.props.disabled).toBeFalsy(); + + // Click show more! ShowMoreButton.props.onClick(); + + // Disable show more while loading. expect(ShowMoreButton.props.disabled).toBeTruthy(); - await wait(() => { - expect(() => getByText("Show More", decisionHistoryContainer)).toThrow(); - }); - expect( - limitSnapshotTo("decisionHistory-container", testRenderer.toJSON()) - ).toMatchSnapshot(); + + // Wait until show more disappears. + await waitUntilThrow(() => getByText("Show More")); + + // Make a snapshot. + expect(toJSON(decisionHistoryContainer)).toMatchSnapshot(); }); diff --git a/src/core/client/admin/test/fixtures.ts b/src/core/client/admin/test/fixtures.ts index e1669ce41..ac7b24037 100644 --- a/src/core/client/admin/test/fixtures.ts +++ b/src/core/client/admin/test/fixtures.ts @@ -1,4 +1,11 @@ +import { merge } from "lodash"; + export const settings = { + id: "settings", + wordList: { + banned: [], + suspect: [], + }, auth: { displayName: { enabled: false, @@ -131,3 +138,121 @@ export const moderationActions = [ __typename: "CommentModerationAction", }, ]; + +export const users = [ + { + id: "user-0", + username: "Markus", + }, + { + id: "user-1", + username: "Lukas", + }, + { + id: "user-2", + username: "Isabelle", + }, +]; + +export const baseComment = { + author: users[0], + body: "Comment Body", + createdAt: "2018-07-06T18:24:00.000Z", + status: "NONE", + actionCounts: { + flag: { + reasons: { + COMMENT_DETECTED_TOXIC: 0, + COMMENT_DETECTED_SPAM: 0, + COMMENT_DETECTED_TRUST: 0, + COMMENT_DETECTED_LINKS: 0, + COMMENT_DETECTED_BANNED_WORD: 0, + COMMENT_DETECTED_SUSPECT_WORD: 0, + COMMENT_REPORTED_OFFENSIVE: 0, + COMMENT_REPORTED_SPAM: 0, + }, + }, + }, +}; + +export const reportedComments = [ + merge({}, baseComment, { + id: "comment-0", + author: users[0], + revision: { + id: "comment-0-revision-0", + }, + permalink: "http://localhost/comment/0", + body: + "This is the last random sentence I will be writing and I am going to stop mid-sent", + actionCounts: { + flag: { + reasons: { + COMMENT_REPORTED_SPAM: 2, + }, + }, + }, + }), + merge({}, baseComment, { + id: "comment-1", + revision: { + id: "comment-1-revision-1", + }, + permalink: "http://localhost/comment/1", + author: users[1], + body: "Don't fool with me", + actionCounts: { + flag: { + reasons: { + COMMENT_REPORTED_OFFENSIVE: 3, + }, + }, + }, + }), + merge({}, baseComment, { + id: "comment-2", + revision: { + id: "comment-2-revision-2", + }, + permalink: "http://localhost/comment/2", + status: "PREMOD", + author: users[2], + body: "I think I deserve better", + actionCounts: { + flag: { + reasons: { + COMMENT_REPORTED_SPAM: 1, + COMMENT_REPORTED_OFFENSIVE: 1, + }, + }, + }, + }), +]; + +export const rejectedComments = reportedComments.map(c => ({ + ...c, + status: "REJECTED", +})); + +export const emptyModerationQueues = { + reported: { + id: "reported", + count: 0, + comments: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, + }, + pending: { + id: "pending", + count: 0, + comments: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, + }, + unmoderated: { + id: "unmoderated", + count: 0, + comments: { edges: [], pageInfo: { endCursor: null, hasNextPage: false } }, + }, +}; + +export const emptyRejectedComments = { + edges: [], + pageInfo: { endCursor: null, hasNextPage: false }, +}; diff --git a/src/core/client/admin/test/login.spec.tsx b/src/core/client/admin/test/login.spec.tsx index fa23d1b35..bc369e888 100644 --- a/src/core/client/admin/test/login.spec.tsx +++ b/src/core/client/admin/test/login.spec.tsx @@ -8,6 +8,19 @@ import { LOCAL_ID } from "talk-framework/lib/relay"; import { replaceHistoryLocation } from "talk-framework/testHelpers"; import create from "./create"; +import { + emptyModerationQueues, + emptyRejectedComments, + settings, +} from "./fixtures"; + +const resolvers = { + Query: { + settings: sinon.stub().returns(settings), + moderationQueues: sinon.stub().returns(emptyModerationQueues), + comments: sinon.stub().returns(emptyRejectedComments), + }, +}; const inputPredicate = (name: string) => (n: ReactTestInstance) => { return n.props.name === name && n.props.onChange; @@ -22,6 +35,7 @@ beforeEach(async () => { replaceHistoryLocation("http://localhost/admin/moderate"); ({ testRenderer, context } = create({ + resolvers, // Set this to true, to see graphql responses. logNetwork: false, initLocalState: localRecord => { diff --git a/src/core/client/admin/test/moderate/__snapshots__/moderate.spec.tsx.snap b/src/core/client/admin/test/moderate/__snapshots__/moderate.spec.tsx.snap new file mode 100644 index 000000000..2340c1376 --- /dev/null +++ b/src/core/client/admin/test/moderate/__snapshots__/moderate.spec.tsx.snap @@ -0,0 +1,1988 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`navigation bar renders navigation bar (empty queues) 1`] = ` + +`; + +exports[`rejected queue accepts comment in rejected queue: count should be 1 1`] = ` + + + 1 + + +`; + +exports[`rejected queue accepts comment in rejected queue: dangling 1`] = ` +
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`rejected queue renders rejected queue with comments 1`] = ` +
+
+
+
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+
+
+
+
+
+ + Lukas + + +
+
+
+
+ +
+ + + Offensive + + + + 3 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+
+ +
+
+`; + +exports[`rejected queue renders rejected queue with comments and load more 1`] = ` +
+
+
+
+
+ + Isabelle + + +
+
+
+
+ +
+ + + Offensive + + + + 1 + + + + + Spam + + + + 1 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`reported queue accepts comment in reported queue: count should be 1 1`] = ` + + + 1 + + +`; + +exports[`reported queue accepts comment in reported queue: dangling 1`] = ` +
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`reported queue rejects comment in reported queue: count should be 1 1`] = ` + + + 1 + + +`; + +exports[`reported queue rejects comment in reported queue: dangling 1`] = ` +
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`reported queue renders empty reported queue 1`] = ` +
+
+
+
+
+`; + +exports[`reported queue renders reported queue with comments 1`] = ` +
+
+
+
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+
+
+
+
+
+ + Lukas + + +
+
+
+
+ +
+ + + Offensive + + + + 3 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+
+
+
+`; + +exports[`reported queue renders reported queue with comments and load more 1`] = ` +
+
+
+
+
+ + Isabelle + + +
+
+
+
+ +
+ + Pre-Mod + + + + Offensive + + + + 1 + + + + + Spam + + + + 1 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`single comment view accepts single comment 1`] = ` +
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`single comment view rejects single comment 1`] = ` +
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+`; + +exports[`single comment view renders single comment view 1`] = ` +
+
+
+ + Go to moderation queues + +
+ Single Comment View +
+
+
+
+
+
+
+
+
+
+
+
+ + Markus + + +
+
+
+
+ +
+ + + Spam + + + + 2 + + +
+
+
+
+
+
+ Decision +
+
+ + +
+
+
+
+
+
+
+
+`; diff --git a/src/core/client/admin/test/moderate/moderate.spec.tsx b/src/core/client/admin/test/moderate/moderate.spec.tsx new file mode 100644 index 000000000..8b485d5ef --- /dev/null +++ b/src/core/client/admin/test/moderate/moderate.spec.tsx @@ -0,0 +1,639 @@ +import { get, merge } from "lodash"; +import sinon from "sinon"; + +import { + createSinonStub, + replaceHistoryLocation, + toJSON, + waitForElement, + waitUntilThrow, + within, +} from "talk-framework/testHelpers"; + +import create from "../create"; +import { + emptyModerationQueues, + emptyRejectedComments, + rejectedComments, + reportedComments, + settings, +} from "../fixtures"; + +beforeEach(async () => { + replaceHistoryLocation("http://localhost/admin/moderate"); +}); + +const createTestRenderer = async (resolver: any = {}) => { + const resolvers = { + ...resolver, + Query: { + ...resolver.Query, + settings: sinon + .stub() + .returns(merge({}, settings, get(resolver, "Query.settings"))), + moderationQueues: sinon + .stub() + .returns( + merge( + {}, + emptyModerationQueues, + get(resolver, "Query.moderationQueues") + ) + ), + comments: + get(resolver, "Query.comments") || + sinon.stub().returns(emptyRejectedComments), + }, + }; + const { testRenderer } = create({ + // Set this to true, to see graphql responses. + logNetwork: false, + resolvers, + initLocalState: localRecord => { + localRecord.setValue(true, "loggedIn"); + }, + }); + return testRenderer; +}; + +describe("navigation bar", () => { + it("renders navigation bar (empty queues)", async () => { + const testRenderer = await createTestRenderer(); + const { getByTestID } = within(testRenderer.root); + await waitForElement(() => getByTestID("moderate-container")); + expect(toJSON(getByTestID("moderate-subBar-container"))).toMatchSnapshot(); + }); +}); + +describe("reported queue", () => { + it("renders empty reported queue", async () => { + const testRenderer = await createTestRenderer(); + const { getByTestID } = within(testRenderer.root); + + await waitForElement(() => getByTestID("moderate-container")); + expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot(); + }); + + it("renders reported queue with comments", async () => { + const testRenderer = await createTestRenderer({ + Query: { + moderationQueues: { + reported: { + count: 2, + comments: sinon.stub().callsFake(data => { + expect(data).toEqual({ first: 5 }); + return { + edges: [ + { + node: reportedComments[0], + cursor: reportedComments[0].createdAt, + }, + { + node: reportedComments[1], + cursor: reportedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: reportedComments[1].createdAt, + hasNextPage: false, + }, + }; + }), + }, + }, + }, + }); + const { getByTestID } = within(testRenderer.root); + await waitForElement(() => getByTestID("moderate-container")); + expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot(); + }); + + it("renders reported queue with comments and load more", async () => { + const testRenderer = await createTestRenderer({ + Query: { + moderationQueues: { + reported: { + count: 2, + comments: createSinonStub( + s => + s.onFirstCall().callsFake(data => { + expect(data).toEqual({ first: 5 }); + return { + edges: [ + { + node: reportedComments[0], + cursor: reportedComments[0].createdAt, + }, + { + node: reportedComments[1], + cursor: reportedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: reportedComments[1].createdAt, + hasNextPage: true, + }, + }; + }), + s => + s.onSecondCall().callsFake(data => { + expect(data).toEqual({ + first: 10, + after: reportedComments[1].createdAt, + }); + return { + edges: [ + { + node: reportedComments[2], + cursor: reportedComments[2].createdAt, + }, + ], + pageInfo: { + endCursor: reportedComments[2].createdAt, + hasNextPage: false, + }, + }; + }) + ), + }, + }, + }, + }); + const moderateContainer = await waitForElement(() => + within(testRenderer.root).getByTestID("moderate-container") + ); + + const { getByText, getAllByTestID, getByTestID } = within( + moderateContainer + ); + + // Get previous count of comments. + const previousCount = getAllByTestID(/^moderate-comment-.*$/).length; + + const loadMore = await waitForElement(() => getByText("Load More")); + loadMore.props.onClick(); + + // Wait for load more to disappear. + await waitUntilThrow(() => getByText("Load More")); + + // Verify we have one more item now. + const comments = getAllByTestID(/^moderate-comment-.*$/); + expect(comments.length).toBe(previousCount + 1); + + // Verify last one added was our new one + expect(comments[comments.length - 1].props["data-test"]).toBe( + `moderate-comment-${reportedComments[2].id}` + ); + + // Snapshot of added comment. + expect( + toJSON(getByTestID(`moderate-comment-${reportedComments[2].id}`)) + ).toMatchSnapshot(); + }); + + it("accepts comment in reported queue", async () => { + const acceptCommentStub = sinon.stub().callsFake((_, data) => { + expect(data).toMatchObject({ + input: { + commentID: reportedComments[0].id, + commentRevisionID: reportedComments[0].revision.id, + }, + }); + return { + comment: { + id: reportedComments[0].id, + status: "ACCEPTED", + }, + moderationQueues: merge({}, emptyModerationQueues, { + reported: { + count: 1, + }, + }), + clientMutationId: data.input.clientMutationId, + }; + }); + + const testRenderer = await createTestRenderer({ + Query: { + moderationQueues: { + reported: { + count: 2, + comments: sinon.stub().callsFake(data => { + expect(data).toEqual({ first: 5 }); + return { + edges: [ + { + node: reportedComments[0], + cursor: reportedComments[0].createdAt, + }, + { + node: reportedComments[1], + cursor: reportedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: reportedComments[1].createdAt, + hasNextPage: false, + }, + }; + }), + }, + }, + }, + Mutation: { + acceptComment: acceptCommentStub, + }, + }); + + const testID = `moderate-comment-${reportedComments[0].id}`; + const { getByTestID } = within(testRenderer.root); + const comment = await waitForElement(() => getByTestID(testID)); + + const AcceptButton = await waitForElement(() => + within(comment).getByLabelText("Accept") + ); + AcceptButton.props.onClick(); + + // Snapshot dangling state of comment. + expect(toJSON(comment)).toMatchSnapshot("dangling"); + + // Wait until comment is gone. + await waitUntilThrow(() => getByTestID(testID)); + + expect(acceptCommentStub.called).toBe(true); + + // Count should have been updated. + expect( + toJSON(getByTestID("moderate-navigation-reported-count")) + ).toMatchSnapshot("count should be 1"); + }); + + it("rejects comment in reported queue", async () => { + const rejectCommentStub = sinon.stub().callsFake((_, data) => { + expect(data).toMatchObject({ + input: { + commentID: reportedComments[0].id, + commentRevisionID: reportedComments[0].revision.id, + }, + }); + return { + comment: { + id: reportedComments[0].id, + status: "REJECTED", + }, + moderationQueues: merge({}, emptyModerationQueues, { + reported: { + count: 1, + }, + }), + clientMutationId: data.input.clientMutationId, + }; + }); + + const testRenderer = await createTestRenderer({ + Query: { + moderationQueues: { + reported: { + count: 2, + comments: sinon.stub().callsFake(data => { + expect(data).toEqual({ first: 5 }); + return { + edges: [ + { + node: reportedComments[0], + cursor: reportedComments[0].createdAt, + }, + { + node: reportedComments[1], + cursor: reportedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: reportedComments[1].createdAt, + hasNextPage: false, + }, + }; + }), + }, + }, + }, + Mutation: { + rejectComment: rejectCommentStub, + }, + }); + + const testID = `moderate-comment-${reportedComments[0].id}`; + const { getByTestID } = within(testRenderer.root); + const comment = await waitForElement(() => getByTestID(testID)); + + const RejectButton = await waitForElement(() => + within(comment).getByLabelText("Reject") + ); + RejectButton.props.onClick(); + + // Snapshot dangling state of comment. + expect(toJSON(comment)).toMatchSnapshot("dangling"); + + // Wait until comment is gone. + await waitUntilThrow(() => getByTestID(testID)); + + expect(rejectCommentStub.called).toBe(true); + + // Count should have been updated. + expect( + toJSON(getByTestID("moderate-navigation-reported-count")) + ).toMatchSnapshot("count should be 1"); + }); +}); + +describe("rejected queue", () => { + beforeEach(() => { + replaceHistoryLocation(`http://localhost/admin/moderate/rejected`); + }); + + it("renders rejected queue with comments", async () => { + const testRenderer = await createTestRenderer({ + Query: { + comments: sinon.stub().callsFake((_, data) => { + expect(data).toEqual({ first: 5, filter: { status: "REJECTED" } }); + return { + edges: [ + { + node: rejectedComments[0], + cursor: rejectedComments[0].createdAt, + }, + { + node: rejectedComments[1], + cursor: rejectedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: rejectedComments[1].createdAt, + hasNextPage: false, + }, + }; + }), + }, + }); + const { getByTestID } = within(testRenderer.root); + await waitForElement(() => getByTestID("moderate-container")); + expect(toJSON(getByTestID("moderate-main-container"))).toMatchSnapshot(); + }); + + it("renders rejected queue with comments and load more", async () => { + const testRenderer = await createTestRenderer({ + Query: { + comments: createSinonStub( + s => + s.onFirstCall().callsFake((_, data) => { + expect(data).toEqual({ + first: 5, + filter: { status: "REJECTED" }, + }); + return { + edges: [ + { + node: rejectedComments[0], + cursor: rejectedComments[0].createdAt, + }, + { + node: rejectedComments[1], + cursor: rejectedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: rejectedComments[1].createdAt, + hasNextPage: true, + }, + }; + }), + s => + s.onSecondCall().callsFake((_, data) => { + expect(data).toEqual({ + first: 10, + after: rejectedComments[1].createdAt, + filter: { status: "REJECTED" }, + }); + return { + edges: [ + { + node: rejectedComments[2], + cursor: rejectedComments[2].createdAt, + }, + ], + pageInfo: { + endCursor: rejectedComments[2].createdAt, + hasNextPage: false, + }, + }; + }) + ), + }, + }); + + const moderateContainer = await waitForElement(() => + within(testRenderer.root).getByTestID("moderate-container") + ); + + const { getByText, getAllByTestID, getByTestID } = within( + moderateContainer + ); + + // Get previous count of comments. + const previousCount = getAllByTestID(/^moderate-comment-.*$/).length; + + const loadMore = await waitForElement(() => getByText("Load More")); + loadMore.props.onClick(); + + // Wait for load more to disappear. + await waitUntilThrow(() => getByText("Load More")); + + // Verify we have one more item now. + const comments = getAllByTestID(/^moderate-comment-.*$/); + expect(comments.length).toBe(previousCount + 1); + + // Verify last one added was our new one + expect(comments[comments.length - 1].props["data-test"]).toBe( + `moderate-comment-${rejectedComments[2].id}` + ); + + // Snapshot of added comment. + expect( + toJSON(getByTestID(`moderate-comment-${rejectedComments[2].id}`)) + ).toMatchSnapshot(); + }); + + it("accepts comment in rejected queue", async () => { + const acceptCommentStub = sinon.stub().callsFake((_, data) => { + expect(data).toMatchObject({ + input: { + commentID: rejectedComments[0].id, + commentRevisionID: rejectedComments[0].revision.id, + }, + }); + return { + comment: { + id: rejectedComments[0].id, + status: "ACCEPTED", + }, + moderationQueues: merge({}, emptyModerationQueues, { + reported: { + count: 1, + }, + }), + clientMutationId: data.input.clientMutationId, + }; + }); + + const testRenderer = await createTestRenderer({ + Query: { + comments: sinon.stub().callsFake((_, data) => { + expect(data).toEqual({ first: 5, filter: { status: "REJECTED" } }); + return { + edges: [ + { + node: rejectedComments[0], + cursor: rejectedComments[0].createdAt, + }, + { + node: rejectedComments[1], + cursor: rejectedComments[1].createdAt, + }, + ], + pageInfo: { + endCursor: rejectedComments[1].createdAt, + hasNextPage: false, + }, + }; + }), + }, + Mutation: { + acceptComment: acceptCommentStub, + }, + }); + + const testID = `moderate-comment-${rejectedComments[0].id}`; + const { getByTestID } = within(testRenderer.root); + const comment = await waitForElement(() => getByTestID(testID)); + + const AcceptButton = await waitForElement(() => + within(comment).getByLabelText("Accept") + ); + AcceptButton.props.onClick(); + + // Snapshot dangling state of comment. + expect(toJSON(getByTestID(testID))).toMatchSnapshot("dangling"); + + // Wait until comment is gone. + await waitUntilThrow(() => getByTestID(testID)); + + expect(acceptCommentStub.called).toBe(true); + + // Count should have been updated. + expect( + toJSON(getByTestID("moderate-navigation-reported-count")) + ).toMatchSnapshot("count should be 1"); + }); +}); + +describe("single comment view", () => { + const comment = rejectedComments[0]; + const commentStub = sinon.stub().callsFake((_, data) => { + expect(data).toEqual({ id: comment.id }); + return reportedComments[0]; + }); + + beforeEach(() => { + replaceHistoryLocation( + `http://localhost/admin/moderate/comment/${comment.id}` + ); + }); + + it("renders single comment view", async () => { + const testRenderer = await createTestRenderer({ + Query: { + comment: commentStub, + }, + }); + const { getByTestID } = within(testRenderer.root); + const container = await waitForElement(() => + getByTestID("single-moderate-container") + ); + expect(toJSON(container)).toMatchSnapshot(); + }); + + it("accepts single comment", async () => { + const acceptCommentStub = sinon.stub().callsFake((_, data) => { + expect(data).toMatchObject({ + input: { + commentID: comment.id, + commentRevisionID: comment.revision.id, + }, + }); + return { + comment: { + id: comment.id, + status: "ACCEPTED", + }, + moderationQueues: emptyModerationQueues, + clientMutationId: data.input.clientMutationId, + }; + }); + + const testRenderer = await createTestRenderer({ + Query: { + comment: commentStub, + }, + Mutation: { + acceptComment: acceptCommentStub, + }, + }); + + const { getByLabelText, getByTestID } = within(testRenderer.root); + const AcceptButton = await waitForElement(() => getByLabelText("Accept")); + AcceptButton.props.onClick(); + + expect( + toJSON(getByTestID(`moderate-comment-${comment.id}`)) + ).toMatchSnapshot(); + + expect(acceptCommentStub.called).toBe(true); + }); + + it("rejects single comment", async () => { + const rejectCommentStub = sinon.stub().callsFake((_, data) => { + expect(data).toMatchObject({ + input: { + commentID: comment.id, + commentRevisionID: comment.revision.id, + }, + }); + return { + comment: { + id: comment.id, + status: "REJECTED", + }, + moderationQueues: emptyModerationQueues, + clientMutationId: data.input.clientMutationId, + }; + }); + + const testRenderer = await createTestRenderer({ + Query: { + comment: commentStub, + }, + Mutation: { + rejectComment: rejectCommentStub, + }, + }); + + const { getByLabelText, getByTestID } = within(testRenderer.root); + const RejectButton = await waitForElement(() => getByLabelText("Reject")); + RejectButton.props.onClick(); + + expect( + toJSON(getByTestID(`moderate-comment-${comment.id}`)) + ).toMatchSnapshot(); + expect(rejectCommentStub.called).toBe(true); + }); +}); diff --git a/src/core/client/admin/test/redirectLoggedIn.spec.tsx b/src/core/client/admin/test/redirectLoggedIn.spec.tsx index 0d0addc8d..e92f62255 100644 --- a/src/core/client/admin/test/redirectLoggedIn.spec.tsx +++ b/src/core/client/admin/test/redirectLoggedIn.spec.tsx @@ -1,11 +1,26 @@ +import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { replaceHistoryLocation } from "talk-framework/testHelpers"; import create from "./create"; +import { + emptyModerationQueues, + emptyRejectedComments, + settings, +} from "./fixtures"; + +const resolvers = { + Query: { + settings: sinon.stub().returns(settings), + moderationQueues: sinon.stub().returns(emptyModerationQueues), + comments: sinon.stub().returns(emptyRejectedComments), + }, +}; it("redirect when already logged in", async () => { replaceHistoryLocation("http://localhost/admin/login"); create({ + resolvers, // Set this to true, to see graphql responses. logNetwork: false, initLocalState: localRecord => { @@ -13,12 +28,15 @@ it("redirect when already logged in", async () => { }, }); await timeout(); - expect(window.location.toString()).toBe("http://localhost/admin/moderate"); + expect(window.location.toString()).toBe( + "http://localhost/admin/moderate/reported" + ); }); it("redirect to redirectPath when already logged in", async () => { replaceHistoryLocation("http://localhost/admin/login"); create({ + resolvers, // Set this to true, to see graphql responses. logNetwork: false, initLocalState: localRecord => { diff --git a/src/core/client/admin/test/redirectLoggedOut.spec.tsx b/src/core/client/admin/test/redirectLoggedOut.spec.tsx index a46b4b1c2..d20852edd 100644 --- a/src/core/client/admin/test/redirectLoggedOut.spec.tsx +++ b/src/core/client/admin/test/redirectLoggedOut.spec.tsx @@ -1,4 +1,5 @@ import { ReactTestRenderer } from "react-test-renderer"; +import sinon from "sinon"; import { timeout } from "talk-common/utils"; import { TalkContext } from "talk-framework/lib/bootstrap"; @@ -6,14 +7,26 @@ import { LOCAL_ID } from "talk-framework/lib/relay"; import { replaceHistoryLocation } from "talk-framework/testHelpers"; import create from "./create"; +import { + emptyModerationQueues, + emptyRejectedComments, + settings, +} from "./fixtures"; function createTestRenderer(): { testRenderer: ReactTestRenderer; context: TalkContext; } { replaceHistoryLocation("http://localhost/admin/moderate"); - + const resolvers = { + Query: { + settings: sinon.stub().returns(settings), + moderationQueues: sinon.stub().returns(emptyModerationQueues), + comments: sinon.stub().returns(emptyRejectedComments), + }, + }; const { testRenderer, context } = create({ + resolvers, // Set this to true, to see graphql responses. logNetwork: false, initLocalState: localRecord => { @@ -31,6 +44,6 @@ it("redirect when not logged in", async () => { .getStore() .getSource() .get(LOCAL_ID)!.redirectPath - ).toBe("/admin/moderate"); + ).toBe("/admin/moderate/reported"); expect(window.location.toString()).toBe("http://localhost/admin/login"); }); diff --git a/src/core/client/admin/test/signOut.spec.tsx b/src/core/client/admin/test/signOut.spec.tsx index 70dc54405..faca10496 100644 --- a/src/core/client/admin/test/signOut.spec.tsx +++ b/src/core/client/admin/test/signOut.spec.tsx @@ -5,11 +5,25 @@ import { LOCAL_ID } from "talk-framework/lib/relay"; import { replaceHistoryLocation } from "talk-framework/testHelpers"; import create from "./create"; +import { + emptyModerationQueues, + emptyRejectedComments, + settings, +} from "./fixtures"; + +const resolvers = { + Query: { + settings: sinon.stub().returns(settings), + moderationQueues: sinon.stub().returns(emptyModerationQueues), + comments: sinon.stub().returns(emptyRejectedComments), + }, +}; it("logs out", async () => { replaceHistoryLocation("http://localhost/admin/moderate"); const { testRenderer, context } = create({ + resolvers, // Set this to true, to see graphql responses. logNetwork: false, initLocalState: localRecord => { diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx index bbef181a3..41475792d 100644 --- a/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx +++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx @@ -9,12 +9,18 @@ import DecisionHistory from "./DecisionHistory"; const DecisionHistoryN = removeFragmentRefs(DecisionHistory); +const baseProps: PropTypesOf = { + actions: [], + onLoadMore: noop, + hasMore: false, + disableLoadMore: false, + onClosePopover: noop, +}; + it("renders correctly", () => { const props: PropTypesOf = { + ...baseProps, actions: [{ id: "1" }, { id: "2" }, { id: "3" }], - onLoadMore: noop, - hasMore: false, - disableLoadMore: false, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -22,10 +28,7 @@ it("renders correctly", () => { it("renders empty state", () => { const props: PropTypesOf = { - actions: [], - onLoadMore: noop, - hasMore: false, - disableLoadMore: false, + ...baseProps, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -33,10 +36,9 @@ it("renders empty state", () => { it("renders hasMore", () => { const props: PropTypesOf = { + ...baseProps, actions: [{ id: "1" }, { id: "2" }, { id: "3" }], - onLoadMore: noop, hasMore: true, - disableLoadMore: false, }; const wrapper = shallow(); expect(wrapper).toMatchSnapshot(); @@ -44,8 +46,8 @@ it("renders hasMore", () => { it("renders disable load more", () => { const props: PropTypesOf = { + ...baseProps, actions: [{ id: "1" }, { id: "2" }, { id: "3" }], - onLoadMore: noop, hasMore: true, disableLoadMore: true, }; diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx index ea090ae32..2a58a1a61 100644 --- a/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx +++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx @@ -16,6 +16,7 @@ interface Props { onLoadMore?: () => void; hasMore?: boolean; disableLoadMore?: boolean; + onClosePopover: () => void; } const DecisionHistory: StatelessComponent = props => ( @@ -25,7 +26,11 @@ const DecisionHistory: StatelessComponent = props => ( {props.actions.length === 0 && } {props.actions.map(action => ( - + ))} {props.hasMore && ( diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx index 987f7e8ce..4a0bbffa7 100644 --- a/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx +++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx @@ -2,10 +2,11 @@ import React, { StatelessComponent } from "react"; import { Delay, Flex, Spinner } from "talk-ui/components"; -import styles from "./DecisionHistoryLoading.css"; import Main from "./Main"; import Title from "./Title"; +import styles from "./DecisionHistoryLoading.css"; + const DecisionHistoryLoading: StatelessComponent = () => (
diff --git a/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx index 96e3ec809..7a95a6b8f 100644 --- a/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx +++ b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx @@ -1,4 +1,5 @@ import { Localized } from "fluent-react/compat"; +import { Link } from "found"; import React, { StatelessComponent } from "react"; import { Icon, TextLink } from "talk-ui/components"; @@ -6,17 +7,24 @@ import { Icon, TextLink } from "talk-ui/components"; import styles from "./GoToCommentLink.css"; interface Props { - href?: string; + href: string; onClick?: React.EventHandler<React.MouseEvent>; } -const GoToCommentLink: StatelessComponent<Props> = props => ( - <TextLink className={styles.root} onClick={props.onClick} href={props.href}> - <Localized id="decisionHistory-goToComment"> - <span>Go to comment</span> - </Localized>{" "} - <Icon>chevron_right</Icon> - </TextLink> -); +const GoToCommentLink: StatelessComponent<Props> = props => { + return ( + <Link + Component={TextLink} + className={styles.root} + to={props.href} + onClick={props.onClick} + > + <Localized id="decisionHistory-goToComment"> + <span>Go to comment</span> + </Localized>{" "} + <Icon>chevron_right</Icon> + </Link> + ); +}; export default GoToCommentLink; diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap index 412f7ff66..2c4ce87a4 100644 --- a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap +++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap @@ -14,6 +14,7 @@ exports[`renders correctly 1`] = ` } } key="1" + onClosePopover={[Function]} /> <Relay(DecisionHistoryItemContainer) action={ @@ -22,6 +23,7 @@ exports[`renders correctly 1`] = ` } } key="2" + onClosePopover={[Function]} /> <Relay(DecisionHistoryItemContainer) action={ @@ -30,6 +32,7 @@ exports[`renders correctly 1`] = ` } } key="3" + onClosePopover={[Function]} /> </DecisionList> </Main> @@ -50,6 +53,7 @@ exports[`renders disable load more 1`] = ` } } key="1" + onClosePopover={[Function]} /> <Relay(DecisionHistoryItemContainer) action={ @@ -58,6 +62,7 @@ exports[`renders disable load more 1`] = ` } } key="2" + onClosePopover={[Function]} /> <Relay(DecisionHistoryItemContainer) action={ @@ -66,6 +71,7 @@ exports[`renders disable load more 1`] = ` } } key="3" + onClosePopover={[Function]} /> </DecisionList> <ShowMoreButton @@ -103,6 +109,7 @@ exports[`renders hasMore 1`] = ` } } key="1" + onClosePopover={[Function]} /> <Relay(DecisionHistoryItemContainer) action={ @@ -111,6 +118,7 @@ exports[`renders hasMore 1`] = ` } } key="2" + onClosePopover={[Function]} /> <Relay(DecisionHistoryItemContainer) action={ @@ -119,6 +127,7 @@ exports[`renders hasMore 1`] = ` } } key="3" + onClosePopover={[Function]} /> </DecisionList> <ShowMoreButton diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap index f91cab384..16c804569 100644 --- a/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap +++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap @@ -1,10 +1,11 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`renders correctly 1`] = ` -<withPropsOnChange(TextLinkProps) +<Link + Component={[Function]} className="GoToCommentLink-root" - href="#" onClick={[Function]} + to="#" > <Localized id="decisionHistory-goToComment" @@ -17,5 +18,5 @@ exports[`renders correctly 1`] = ` <withPropsOnChange(Icon)> chevron_right </withPropsOnChange(Icon)> -</withPropsOnChange(TextLinkProps)> +</Link> `; diff --git a/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx index f75d2db0c..4d3ec6774 100644 --- a/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx +++ b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx @@ -10,6 +10,7 @@ import DecisionHistory from "../components/DecisionHistory"; interface DecisionHistoryContainerProps { me: MeData; relay: RelayPaginationProp; + onClosePopover: () => void; } export class DecisionHistoryContainer extends React.Component< @@ -29,6 +30,7 @@ export class DecisionHistoryContainer extends React.Component< onLoadMore={this.loadMore} hasMore={this.props.relay.hasMore()} disableLoadMore={this.state.disableLoadMore} + onClosePopover={this.props.onClosePopover} /> ); } diff --git a/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx index 50d202efe..3dcce4f57 100644 --- a/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx +++ b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx @@ -9,15 +9,16 @@ import RejectedComment from "../components/RejectedComment"; interface DecisionHistoryItemContainerProps { action: ActionData; + onClosePopover: () => void; } class DecisionHistoryItemContainer extends React.Component< DecisionHistoryItemContainerProps > { - private handleGoToComment = (e: React.MouseEvent) => { - return; - }; public render() { + const href = `/admin/moderate/comment/${ + this.props.action.revision.comment.id + }`; const username = (this.props.action.revision.comment.author && this.props.action.revision.comment.author.username) || @@ -25,19 +26,19 @@ class DecisionHistoryItemContainer extends React.Component< if (this.props.action.status === "ACCEPTED") { return ( <AcceptedComment - href="#" + href={href} username={username} date={this.props.action.createdAt} - onGotoComment={this.handleGoToComment} + onGotoComment={this.props.onClosePopover} /> ); } else if (this.props.action.status === "REJECTED") { return ( <RejectedComment - href="#" + href={href} username={username} date={this.props.action.createdAt} - onGotoComment={this.handleGoToComment} + onGotoComment={this.props.onClosePopover} /> ); } @@ -52,6 +53,7 @@ const enhanced = withFragmentContainer<DecisionHistoryItemContainerProps>({ revision { id comment { + id author { username } diff --git a/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx b/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx index 3826902a3..81238740b 100644 --- a/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx +++ b/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx @@ -6,7 +6,11 @@ import { DecisionHistoryQuery as QueryTypes } from "talk-admin/__generated__/Dec import DecisionHistoryLoading from "../components/DecisionHistoryLoading"; import DecisionHistoryContainer from "../containers/DecisionHistoryContainer"; -class DecisionHistoryQuery extends Component { +interface Props { + onClosePopover: () => void; +} + +class DecisionHistoryQuery extends Component<Props> { public render() { return ( <QueryRenderer<QueryTypes> @@ -26,7 +30,12 @@ class DecisionHistoryQuery extends Component { return <DecisionHistoryLoading />; } - return <DecisionHistoryContainer me={props.me} />; + return ( + <DecisionHistoryContainer + me={props.me} + onClosePopover={this.props.onClosePopover} + /> + ); }} /> ); diff --git a/src/core/client/auth/components/App.tsx b/src/core/client/auth/components/App.tsx index 76096b66b..4a3271606 100644 --- a/src/core/client/auth/components/App.tsx +++ b/src/core/client/auth/components/App.tsx @@ -1,11 +1,12 @@ import React, { StatelessComponent } from "react"; -import styles from "./App.css"; import ForgotPasswordContainer from "../containers/ForgotPasswordContainer"; import ResetPasswordContainer from "../containers/ResetPasswordContainer"; import SignInContainer from "../containers/SignInContainer"; import SignUpContainer from "../containers/SignUpContainer"; +import styles from "./App.css"; + export type View = | "SIGN_UP" | "SIGN_IN" diff --git a/src/core/client/framework/lib/intersection/IntersectionContext.tsx b/src/core/client/framework/lib/intersection/IntersectionContext.tsx new file mode 100644 index 000000000..2ba5d8070 --- /dev/null +++ b/src/core/client/framework/lib/intersection/IntersectionContext.tsx @@ -0,0 +1,85 @@ +import * as React from "react"; + +import { createContextHOC } from "talk-framework/helpers"; +import ensurePolyfill from "./ensurePolyfill"; + +export type IntersectionCallback = (entry: IntersectionObserverEntry) => void; +export type Observe = ( + target: Element, + callback: IntersectionCallback +) => () => void; +export interface IntersectionContext { + observe: Observe; +} + +const { Provider, Consumer } = React.createContext<IntersectionContext>( + {} as any +); +export const IntersectionConsumer = Consumer; + +export class IntersectionProvider extends React.Component<any, any> { + private observer: IntersectionObserver; + private elements = new Map(); + private elementBuffer: Element[] = []; + private unmounted = false; + + public componentDidMount() { + ensurePolyfill().then(() => { + if (this.unmounted) { + return; + } + this.observer = new IntersectionObserver(this.onIntersect, { + root: this.props.node ? this.props.node : undefined, + rootMargin: "0px", + threshold: 0.25, + }); + this.elementBuffer.forEach(element => this.observer.observe(element)); + this.elementBuffer = []; + }); + } + + public componentWillUnmount() { + this.unmounted = true; + } + + private unobserve = (element: Element) => { + this.elements.delete(element); + if (!this.observer) { + this.elementBuffer = this.elementBuffer.filter(e => e !== element); + } else { + this.observer.unobserve(element); + } + }; + + private observe: Observe = (element, callback) => { + this.elements.set(element, callback); + // this funny bit to handle react's lifecycle order and also wait + // for polyfill. + if (!this.observer) { + this.elementBuffer.push(element); + } else { + this.observer.observe(element); + } + return () => this.unobserve(element); + }; + + private onIntersect = ( + entries: IntersectionObserverEntry[], + observer: IntersectionObserver + ) => { + entries.forEach(entry => this.elements.get(entry.target)(entry)); + }; + + public render() { + return ( + <Provider value={{ observe: this.observe }}> + {this.props.children} + </Provider> + ); + } +} + +export const withIntersectionContext = createContextHOC<IntersectionContext>( + "withContext", + IntersectionConsumer +); diff --git a/src/core/client/framework/lib/intersection/ensurePolyfill.ts b/src/core/client/framework/lib/intersection/ensurePolyfill.ts new file mode 100644 index 000000000..f61e9caac --- /dev/null +++ b/src/core/client/framework/lib/intersection/ensurePolyfill.ts @@ -0,0 +1,9 @@ +/** + * Loads intersection-observer polyfill if it doesn't exist. + */ +export default async function ensurePolyfill() { + if (!(window as any).IntersectionObserver) { + await import("intersection-observer"); + } + return; +} diff --git a/src/core/client/framework/lib/intersection/index.ts b/src/core/client/framework/lib/intersection/index.ts new file mode 100644 index 000000000..37a854d81 --- /dev/null +++ b/src/core/client/framework/lib/intersection/index.ts @@ -0,0 +1,7 @@ +export { + IntersectionProvider, + Observe, + withIntersectionContext, +} from "./IntersectionContext"; +export { default as withInView } from "./withInView"; +export { default as ensurePolyfill } from "./ensurePolyfill"; diff --git a/src/core/client/framework/lib/intersection/withInView.tsx b/src/core/client/framework/lib/intersection/withInView.tsx new file mode 100644 index 000000000..28823f034 --- /dev/null +++ b/src/core/client/framework/lib/intersection/withInView.tsx @@ -0,0 +1,81 @@ +import * as React from "react"; +import { DefaultingInferableComponentEnhancer, hoistStatics } from "recompose"; + +import { Observe, withIntersectionContext } from "./IntersectionContext"; + +interface InjectedProps { + inView: boolean | undefined; + intersectionRef: React.Ref<any>; +} + +interface Props { + observe: Observe; +} + +interface State { + inView: boolean | undefined; +} + +/** + * withInView provides a property `inView: boolean` + * to indicate whether or not the referenced element is + * in the current browser view. + */ +const withInView: DefaultingInferableComponentEnhancer< + InjectedProps +> = hoistStatics<InjectedProps>( + <T extends InjectedProps>(BaseComponent: React.ComponentType<T>) => { + class WithInView extends React.Component<Props, State> { + private unobserve: (() => void) | null = null; + + public state = { + inView: undefined, + }; + + private changeRef = (ref: any) => { + if (this.unobserve) { + this.unobserve(); + this.unobserve = null; + } + if (ref) { + this.unobserve = this.props.observe( + ref, + ({ intersectionRatio }: any) => { + // Callback is called whenever we run observe. + if (this.state.inView === undefined) { + this.setState({ inView: intersectionRatio > 0 }); + } else { + this.setState(s => ({ + inView: !s.inView, + })); + } + } + ); + } + }; + + public componentWillUnmount() { + if (this.unobserve) { + this.unobserve(); + } + } + + public render() { + return ( + <BaseComponent + {...this.props} + inView={this.state.inView} + intersectionRef={this.changeRef} + /> + ); + } + } + + const enhanced = withIntersectionContext(({ observe }) => ({ observe }))( + WithInView + ); + return enhanced as React.ComponentType<any>; + } +); + +export default withInView; diff --git a/src/core/client/framework/lib/relay/commitMutationPromise.ts b/src/core/client/framework/lib/relay/commitMutationPromise.ts index 35f1ca117..9b2a6f857 100644 --- a/src/core/client/framework/lib/relay/commitMutationPromise.ts +++ b/src/core/client/framework/lib/relay/commitMutationPromise.ts @@ -25,8 +25,7 @@ export async function commitMutationPromiseNormalized<T extends OperationBase>( config: MutationPromiseConfig<T> ): Promise<T["response"][keyof T["response"]]> { try { - const response = await commitMutationPromise(environment, config); - return extractPayload(response); + return await commitMutationPromise(environment, config); } catch (e) { throw e; } diff --git a/src/core/client/framework/testHelpers/byLabelText.ts b/src/core/client/framework/testHelpers/byLabelText.ts new file mode 100644 index 000000000..2450ac1a5 --- /dev/null +++ b/src/core/client/framework/testHelpers/byLabelText.ts @@ -0,0 +1,56 @@ +import { ReactTestInstance } from "react-test-renderer"; + +import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText"; + +const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( + i: ReactTestInstance +) => { + // Only look at dom components. + if (typeof i.type !== "string" || !i.props["aria-label"]) { + return false; + } + return matchText(pattern, i.props["aria-label"], { + collapseWhitespace: false, + ...options, + }); +}; + +export function getByLabelText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.find(matcher(pattern, options)); +} + +export function queryByLabelText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + try { + return container.find(matcher(pattern, options)); + } catch { + return null; + } +} + +export function queryAllByLabelText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + try { + return container.findAll(matcher(pattern, options)); + } catch { + return []; + } +} + +export function getAllByLabelText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.findAll(matcher(pattern, options)); +} diff --git a/src/core/client/framework/testHelpers/byTestID.ts b/src/core/client/framework/testHelpers/byTestID.ts new file mode 100644 index 000000000..8034fd0ef --- /dev/null +++ b/src/core/client/framework/testHelpers/byTestID.ts @@ -0,0 +1,56 @@ +import { ReactTestInstance } from "react-test-renderer"; + +import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText"; + +const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( + i: ReactTestInstance +) => { + // Only look at dom components. + if (typeof i.type !== "string" || !i.props["data-test"]) { + return false; + } + return matchText(pattern, i.props["data-test"], { + collapseWhitespace: false, + ...options, + }); +}; + +export function getByTestID( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.find(matcher(pattern, options)); +} + +export function queryByTestID( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + try { + return container.find(matcher(pattern, options)); + } catch { + return null; + } +} + +export function queryAllByTestID( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + try { + return container.findAll(matcher(pattern, options)); + } catch { + return []; + } +} + +export function getAllByTestID( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.findAll(matcher(pattern, options)); +} diff --git a/src/core/client/framework/testHelpers/byText.ts b/src/core/client/framework/testHelpers/byText.ts new file mode 100644 index 000000000..c4b0a518a --- /dev/null +++ b/src/core/client/framework/testHelpers/byText.ts @@ -0,0 +1,63 @@ +import React from "react"; +import { ReactTestInstance } from "react-test-renderer"; + +import matchText, { TextMatchOptions, TextMatchPattern } from "./matchText"; + +const matcher = (pattern: TextMatchPattern, options?: TextMatchOptions) => ( + i: ReactTestInstance +) => { + // Only look at dom components. + if (typeof i.type !== "string") { + return false; + } + if (!i.props.children) { + return false; + } + const children = React.Children.toArray(i.props.children); + for (const c of children) { + if (typeof c === "string" && matchText(pattern, c, options)) { + return true; + } + } + return false; +}; + +export function getByText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.find(matcher(pattern, options)); +} + +export function queryByText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + try { + return container.find(matcher(pattern, options)); + } catch { + return null; + } +} + +export function queryAllByText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + try { + return container.findAll(matcher(pattern, options)); + } catch { + return []; + } +} + +export function getAllByText( + container: ReactTestInstance, + pattern: TextMatchPattern, + options?: TextMatchOptions +) { + return container.findAll(matcher(pattern, options)); +} diff --git a/src/core/client/framework/testHelpers/getByTestID.ts b/src/core/client/framework/testHelpers/getByTestID.ts deleted file mode 100644 index 653d0899f..000000000 --- a/src/core/client/framework/testHelpers/getByTestID.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { ReactTestInstance } from "react-test-renderer"; - -export default function getByTestID(id: string, instance: ReactTestInstance) { - return instance.findByProps({ "data-test": id }); -} diff --git a/src/core/client/framework/testHelpers/getByText.ts b/src/core/client/framework/testHelpers/getByText.ts deleted file mode 100644 index 60132154c..000000000 --- a/src/core/client/framework/testHelpers/getByText.ts +++ /dev/null @@ -1,20 +0,0 @@ -import React from "react"; -import { ReactTestInstance } from "react-test-renderer"; - -export default function getByText(text: string, instance: ReactTestInstance) { - return instance.find(i => { - if (!i.props.children) { - return false; - } - const children = React.Children.toArray(i.props.children); - for (const c of children) { - if ( - typeof c === "string" && - c.toLowerCase().includes(text.toLowerCase()) - ) { - return true; - } - } - return false; - }); -} diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts index 0593be40f..7558a293a 100644 --- a/src/core/client/framework/testHelpers/index.ts +++ b/src/core/client/framework/testHelpers/index.ts @@ -13,7 +13,22 @@ export * from "./denormalize"; export { default as replaceHistoryLocation } from "./replaceHistoryLocation"; export { default as limitSnapshotTo } from "./limitSnapshotTo"; export { default as inputPredicate } from "./inputPredicate"; -export { default as getByTestID } from "./getByTestID"; -export { default as getByText } from "./getByText"; +export { + getByTestID, + getAllByTestID, + queryByTestID, + queryAllByTestID, +} from "./byTestID"; +export { getByText, getAllByText, queryByText, queryAllByText } from "./byText"; +export { + getByLabelText, + getAllByLabelText, + queryByLabelText, + queryAllByLabelText, +} from "./byLabelText"; +export { default as within } from "./within"; export { default as wait } from "./wait"; export { default as waitForElement } from "./waitForElement"; +export { default as waitUntilThrow } from "./waitUntilThrow"; +export { default as matchText } from "./matchText"; +export { default as toJSON } from "./toJSON"; diff --git a/src/core/client/framework/testHelpers/matchText.ts b/src/core/client/framework/testHelpers/matchText.ts new file mode 100644 index 000000000..10718ffec --- /dev/null +++ b/src/core/client/framework/testHelpers/matchText.ts @@ -0,0 +1,34 @@ +export interface TextMatchOptions { + exact?: boolean; // defaults to true + collapseWhitespace?: boolean; // defaults to true + trim?: boolean; // defaults to true +} + +export type TextMatchPattern = string | RegExp; + +export default function matchText( + pattern: TextMatchPattern, + text: string, + options: TextMatchOptions = {} +) { + if (typeof pattern === "string") { + let a = text; + let b = pattern; + if (options.trim || options.trim === undefined) { + a = a.trim(); + b = b.trim(); + } + if ( + options.collapseWhitespace || + options.collapseWhitespace === undefined + ) { + a = a.replace(/\s+/g, " "); + b = b.replace(/\s+/g, " "); + } + if (options.exact || options.exact === undefined) { + return a === b; + } + return text.toLowerCase().includes(pattern.toLowerCase()); + } + return pattern.test(text); +} diff --git a/src/core/client/framework/testHelpers/toJSON.tsx b/src/core/client/framework/testHelpers/toJSON.tsx new file mode 100644 index 000000000..dc79dc502 --- /dev/null +++ b/src/core/client/framework/testHelpers/toJSON.tsx @@ -0,0 +1,57 @@ +import { ReactTestInstance } from "react-test-renderer"; + +interface ReactTestRendererJSON { + type: string; + props: { [propName: string]: any }; + children: null | ReactTestRendererNode[]; + $$typeof?: symbol; // Optional because we add it with defineProperty(). +} +type ReactTestRendererNode = ReactTestRendererJSON | string; + +export function toJSONRecursive( + inst: ReactTestInstance +): ReactTestRendererNode[] | null { + const { children: _, ...props }: any = inst.props || {}; + let renderedChildren = null; + if (inst.children) { + for (const child of inst.children) { + const renderedChild = + typeof child === "string" ? [child] : toJSONRecursive(child); + if (renderedChild !== null) { + if (renderedChildren === null) { + renderedChildren = [...renderedChild]; + } else { + renderedChildren.push(...renderedChild); + } + } + } + } + if (typeof inst.type === "string") { + const json: ReactTestRendererJSON = { + type: inst.type, + props, + children: renderedChildren, + }; + Object.defineProperty(json, "$$typeof", { + value: Symbol.for("react.test.json"), + }); + return [json]; + } + return renderedChildren; +} + +/** + * Turns a ReactTestInstance into JSON for snapshotting purposes. + */ +export default function toJSON( + inst: ReactTestInstance +): ReactTestRendererNode | ReactTestRendererNode[] | null { + const result = toJSONRecursive(inst); + if (!result) { + return null; + } + if (result.length === 1) { + return result[0]; + } + return result; +} diff --git a/src/core/client/framework/testHelpers/waitUntilThrow.ts b/src/core/client/framework/testHelpers/waitUntilThrow.ts new file mode 100644 index 000000000..53b7fbb57 --- /dev/null +++ b/src/core/client/framework/testHelpers/waitUntilThrow.ts @@ -0,0 +1,17 @@ +import { ReactTestInstance } from "react-test-renderer"; + +import wait from "./wait"; + +interface Options { + timeout?: number; + interval?: number; +} + +export default async function waitUntilThrow( + callback: () => ReactTestInstance | null, + options?: Options +): Promise<void> { + await wait(() => { + expect(callback).toThrow(); + }, options); +} diff --git a/src/core/client/framework/testHelpers/within.ts b/src/core/client/framework/testHelpers/within.ts new file mode 100644 index 000000000..477a24b3d --- /dev/null +++ b/src/core/client/framework/testHelpers/within.ts @@ -0,0 +1,54 @@ +import { ReactTestInstance } from "react-test-renderer"; + +import { + getAllByLabelText, + getByLabelText, + queryAllByLabelText, + queryByLabelText, +} from "./byLabelText"; +import { + getAllByTestID, + getByTestID, + queryAllByTestID, + queryByTestID, +} from "./byTestID"; +import { getAllByText, getByText, queryAllByText, queryByText } from "./byText"; +import toJSON from "./toJSON"; + +type Func0<R> = () => R; +type Func1<A, R> = (a?: A) => R; +type Func2<A, B, R> = (a: A, b?: B) => R; +type Func3<A, B, C, R> = (a: A, b: B, c?: C) => R; + +type RemoveFirstArgument<T, R> = + T extends [any, any, any, any?] ? Func3<T[1], T[2], T[3], R> : + T extends [any, any, any?] ? Func2<T[1], T[2], R> : + T extends [any, any?] ? Func1<T[1], R> : + T extends [any] ? Func0<R> : + unknown +; + +// tslint:disable +// @TODO: currently tslint fails to parse this: `...any[]`. +function applyContainer<T extends [ReactTestInstance, ...any[]], R>(container: ReactTestInstance, fn: (...args: T) => R): RemoveFirstArgument<T, R> { + return ((...args: any[]) => fn(...[container, ...args] as any)) as any; +} +// tslint:enable + +export default function within(container: ReactTestInstance) { + return { + getByTestID: applyContainer(container, getByTestID), + getAllByTestID: applyContainer(container, getAllByTestID), + queryByTestID: applyContainer(container, queryByTestID), + queryAllByTestID: applyContainer(container, queryAllByTestID), + getByText: applyContainer(container, getByText), + getAllByText: applyContainer(container, getAllByText), + queryByText: applyContainer(container, queryByText), + queryAllByText: applyContainer(container, queryAllByText), + getByLabelText: applyContainer(container, getByLabelText), + getAllByLabelText: applyContainer(container, getAllByLabelText), + queryByLabelText: applyContainer(container, queryByLabelText), + queryAllByLabelText: applyContainer(container, queryAllByLabelText), + toJSON: () => toJSON(container), + } +} diff --git a/src/core/client/install/components/App.tsx b/src/core/client/install/components/App.tsx index f98add230..8cdfb10e3 100644 --- a/src/core/client/install/components/App.tsx +++ b/src/core/client/install/components/App.tsx @@ -1,9 +1,10 @@ import React, { Component } from "react"; -import styles from "./App.css"; import WizardContainer from "../containers/WizardContainer"; import MainBar from "./MainBar"; +import styles from "./App.css"; + class App extends Component { public render() { return ( diff --git a/src/core/client/install/components/Wizard.tsx b/src/core/client/install/components/Wizard.tsx index 9e8c5e6ec..02c6da4d1 100644 --- a/src/core/client/install/components/Wizard.tsx +++ b/src/core/client/install/components/Wizard.tsx @@ -6,6 +6,7 @@ import { Step, StepBar } from "talk-ui/components"; import { WizardProps } from "../components/Wizard"; import Header from "./Header"; + import styles from "./Wizard.css"; export interface WizardProps { diff --git a/src/core/client/stream/components/App.tsx b/src/core/client/stream/components/App.tsx index 3a117512e..fda940372 100644 --- a/src/core/client/stream/components/App.tsx +++ b/src/core/client/stream/components/App.tsx @@ -5,6 +5,7 @@ import { HorizontalGutter, TabContent, TabPane } from "talk-ui/components"; import CommentsPaneContainer from "../tabs/comments/containers/CommentsPaneContainer"; import ProfileQuery from "../tabs/profile/queries/ProfileQuery"; + import styles from "./App.css"; type TabValue = "COMMENTS" | "PROFILE" | "%future added value"; diff --git a/src/core/client/stream/components/UserBoxUnauthenticated.tsx b/src/core/client/stream/components/UserBoxUnauthenticated.tsx index a7a7dc0ff..5cdfc4e6b 100644 --- a/src/core/client/stream/components/UserBoxUnauthenticated.tsx +++ b/src/core/client/stream/components/UserBoxUnauthenticated.tsx @@ -2,8 +2,8 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; import { Button, Flex, Typography } from "talk-ui/components"; - import MatchMedia from "talk-ui/components/MatchMedia"; + import styles from "./UserBoxUnauthenticated.css"; export interface UserBoxUnauthenticatedProps { diff --git a/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx index 034621611..90c5b0fb7 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/Comment.tsx @@ -5,12 +5,13 @@ import HTMLContent from "talk-stream/components/HTMLContent"; import Timestamp from "talk-stream/components/Timestamp"; import { Flex, HorizontalGutter } from "talk-ui/components"; -import styles from "./Comment.css"; import EditedMarker from "./EditedMarker"; import InReplyTo from "./InReplyTo"; import TopBarLeft from "./TopBarLeft"; import Username from "./Username"; +import styles from "./Comment.css"; + export interface CommentProps { id?: string; className?: string; diff --git a/src/core/client/stream/tabs/comments/components/Comment/InReplyTo.tsx b/src/core/client/stream/tabs/comments/components/Comment/InReplyTo.tsx index 39fc839f1..0bf940e09 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/InReplyTo.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/InReplyTo.tsx @@ -2,6 +2,7 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; import { Flex, Icon, Typography } from "talk-ui/components"; + import styles from "./InReplyTo.css"; interface Props { diff --git a/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.tsx b/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.tsx index 30be393aa..61ea6c86f 100644 --- a/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.tsx +++ b/src/core/client/stream/tabs/comments/components/Comment/IndentedComment.tsx @@ -5,6 +5,7 @@ import { PropTypesOf } from "talk-framework/types"; import Indent from "../Indent"; import Comment from "./Comment"; + import styles from "./IndentedComment.css"; export interface IndentedCommentProps extends PropTypesOf<typeof Comment> { diff --git a/src/core/client/stream/tabs/comments/components/ConversationThread.tsx b/src/core/client/stream/tabs/comments/components/ConversationThread.tsx index 2ea994816..a5af01a8f 100644 --- a/src/core/client/stream/tabs/comments/components/ConversationThread.tsx +++ b/src/core/client/stream/tabs/comments/components/ConversationThread.tsx @@ -3,15 +3,15 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; import { PropTypesOf } from "talk-framework/types"; -import { Button, Flex, HorizontalGutter } from "talk-ui/components"; +import { Button, Counter, Flex, HorizontalGutter } from "talk-ui/components"; import CommentContainer from "../containers/CommentContainer"; import LocalReplyListContainer from "../containers/LocalReplyListContainer"; import { RootParent } from "./Comment"; -import styles from "./ConversationThread.css"; -import Counter from "./Counter"; import { Circle, Line } from "./Timeline"; +import styles from "./ConversationThread.css"; + export interface ConversationThreadProps { className?: string; me: PropTypesOf<typeof CommentContainer>["me"] & diff --git a/src/core/client/stream/tabs/comments/components/Counter.tsx b/src/core/client/stream/tabs/comments/components/Counter.tsx deleted file mode 100644 index fc8eef709..000000000 --- a/src/core/client/stream/tabs/comments/components/Counter.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import cn from "classnames"; -import React, { StatelessComponent } from "react"; - -import styles from "./Counter.css"; - -interface Props { - className?: string; -} - -const Counter: StatelessComponent<Props> = props => { - return ( - <span className={cn(styles.root, props.className)}>{props.children}</span> - ); -}; - -export default Counter; diff --git a/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx b/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx index e981dac55..aaa7fece2 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx +++ b/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkButton.tsx @@ -10,9 +10,10 @@ import { Popover, } from "talk-ui/components"; -import styles from "./PermalinkButton.css"; import PermalinkPopover from "./PermalinkPopover"; +import styles from "./PermalinkButton.css"; + interface PermalinkProps { commentID: string; url: string; diff --git a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx b/src/core/client/stream/tabs/comments/components/PermalinkView.tsx index ed7193cb2..5f5ed608b 100644 --- a/src/core/client/stream/tabs/comments/components/PermalinkView.tsx +++ b/src/core/client/stream/tabs/comments/components/PermalinkView.tsx @@ -7,6 +7,7 @@ import { Button, Flex, HorizontalGutter, Typography } from "talk-ui/components"; import UserBoxContainer from "../../../containers/UserBoxContainer"; import ConversationThreadContainer from "../containers/ConversationThreadContainer"; import ReplyListContainer from "../containers/ReplyListContainer"; + import styles from "./PermalinkView.css"; export interface PermalinkViewProps { diff --git a/src/core/client/stream/tabs/comments/components/PostCommentForm.tsx b/src/core/client/stream/tabs/comments/components/PostCommentForm.tsx index ea24917f3..0de3bbbf3 100644 --- a/src/core/client/stream/tabs/comments/components/PostCommentForm.tsx +++ b/src/core/client/stream/tabs/comments/components/PostCommentForm.tsx @@ -13,10 +13,11 @@ import { Typography, } from "talk-ui/components"; -import styles from "./PostCommentForm.css"; import PoweredBy from "./PoweredBy"; import RTE from "./RTE"; +import styles from "./PostCommentForm.css"; + interface FormProps { body: string; } diff --git a/src/core/client/stream/tabs/comments/components/PostCommentFormFake.tsx b/src/core/client/stream/tabs/comments/components/PostCommentFormFake.tsx index cdf51d046..50a6f6f9d 100644 --- a/src/core/client/stream/tabs/comments/components/PostCommentFormFake.tsx +++ b/src/core/client/stream/tabs/comments/components/PostCommentFormFake.tsx @@ -1,9 +1,12 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; + import { Button, HorizontalGutter } from "talk-ui/components"; -import styles from "./PostCommentFormFake.css"; + import RTE from "./RTE"; +import styles from "./PostCommentFormFake.css"; + const PostCommentFormFake: StatelessComponent = props => ( <HorizontalGutter className={styles.root}> <div aria-hidden="true"> diff --git a/src/core/client/stream/tabs/comments/components/RTE.tsx b/src/core/client/stream/tabs/comments/components/RTE.tsx index c6a56cf23..6a56522a1 100644 --- a/src/core/client/stream/tabs/comments/components/RTE.tsx +++ b/src/core/client/stream/tabs/comments/components/RTE.tsx @@ -3,8 +3,8 @@ import { Localized as LocalizedOriginal } from "fluent-react/compat"; import React, { Ref, StatelessComponent } from "react"; import { Icon } from "talk-ui/components"; - import { PropTypesOf } from "talk-ui/types"; + import styles from "./RTE.css"; // Use a special Localized version that forwards diff --git a/src/core/client/stream/tabs/comments/components/ReplyTo.tsx b/src/core/client/stream/tabs/comments/components/ReplyTo.tsx index 90112d6e2..7b8d1729d 100644 --- a/src/core/client/stream/tabs/comments/components/ReplyTo.tsx +++ b/src/core/client/stream/tabs/comments/components/ReplyTo.tsx @@ -2,6 +2,7 @@ import { Localized } from "fluent-react/compat"; import React, { StatelessComponent } from "react"; import { Flex, Icon, Typography } from "talk-ui/components"; + import styles from "./ReplyTo.css"; interface Props { diff --git a/src/core/client/stream/tabs/comments/components/Stream.tsx b/src/core/client/stream/tabs/comments/components/Stream.tsx index caf86de28..41bad883a 100644 --- a/src/core/client/stream/tabs/comments/components/Stream.tsx +++ b/src/core/client/stream/tabs/comments/components/Stream.tsx @@ -10,6 +10,7 @@ import CommentContainer from "../containers/CommentContainer"; import PostCommentFormContainer from "../containers/PostCommentFormContainer"; import ReplyListContainer from "../containers/ReplyListContainer"; import PostCommentFormFake from "./PostCommentFormFake"; + import styles from "./Stream.css"; export interface StreamProps { diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap b/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap index fd5b6efc8..0519d3fab 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap +++ b/src/core/client/stream/tabs/comments/components/__snapshots__/ConversationThread.spec.tsx.snap @@ -53,9 +53,9 @@ exports[`with 2 remaining parent comments renders correctly 1`] = ` Show hidden comments </withPropsOnChange(Button)> </Localized> - <Counter> + <withPropsOnChange(Counter)> 2 - </Counter> + </withPropsOnChange(Counter)> </withPropsOnChange(Flex)> </Circle> </withPropsOnChange(HorizontalGutter)> @@ -116,9 +116,9 @@ exports[`with 2 remaining parent comments renders with disabled load more 1`] = Show hidden comments </withPropsOnChange(Button)> </Localized> - <Counter> + <withPropsOnChange(Counter)> 2 - </Counter> + </withPropsOnChange(Counter)> </withPropsOnChange(Flex)> </Circle> </withPropsOnChange(HorizontalGutter)> diff --git a/src/core/client/stream/tabs/profile/components/HistoryComment.tsx b/src/core/client/stream/tabs/profile/components/HistoryComment.tsx index 7aebf2b5d..22100fcdd 100644 --- a/src/core/client/stream/tabs/profile/components/HistoryComment.tsx +++ b/src/core/client/stream/tabs/profile/components/HistoryComment.tsx @@ -10,7 +10,9 @@ import { MatchMedia, Typography, } from "talk-ui/components"; + import HTMLContent from "../../../components/HTMLContent"; + import styles from "./HistoryComment.css"; export interface HistoryCommentProps { diff --git a/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap b/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap index 432c50559..d4d72ee7f 100644 --- a/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap +++ b/src/core/client/stream/test/comments/__snapshots__/permalinkViewLoadMoreParents.spec.tsx.snap @@ -189,9 +189,13 @@ exports[`renders permalink view 1`] = ` SHOW HIDDEN COMMENTS </button> <span - className="Counter-root" + className="Counter-root Counter-colorInherit" > - 2 + <span + className="Counter-text" + > + 2 + </span> </span> </div> </div> diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index 00c9b3136..634f3c200 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -6,6 +6,7 @@ import { } from "talk-framework/testHelpers"; export const settings = { + id: "settings", auth: { integrations: { facebook: { diff --git a/src/core/client/test/mocks.ts b/src/core/client/test/mocks.ts index 06308436e..c53d7bdc4 100644 --- a/src/core/client/test/mocks.ts +++ b/src/core/client/test/mocks.ts @@ -1,3 +1,5 @@ +import React from "react"; + // TODO: Remove when fixed. // Mock React.createContext because of https://github.com/airbnb/enzyme/issues/1509. function mockReact() { @@ -20,3 +22,8 @@ function mockReact() { } jest.mock("react", () => mockReact()); +jest.mock("react-transition-group", () => ({ + CSSTransition: (props: { children: React.ReactNode }) => props.children, + Transition: (props: { children: React.ReactNode }) => props.children, + TransitionGroup: (props: { children: React.ReactNode }) => props.children, +})); diff --git a/src/core/client/test/setupTestFramework.ts b/src/core/client/test/setupTestFramework.ts index 2f7044378..ea9feeac2 100644 --- a/src/core/client/test/setupTestFramework.ts +++ b/src/core/client/test/setupTestFramework.ts @@ -1,2 +1,11 @@ // Automatically unmock console. import "jest-mock-console/dist/setupTestFramework"; + +// Log unhandled rejections. +// tslint:disable-next-line:no-console +process.on("unhandledRejection", err => { + // tslint:disable-next-line:no-console + console.error("Unhandled Rejection"); + // tslint:disable-next-line:no-console + console.error(err); +}); diff --git a/src/core/client/ui/components/AppBar/AppBar.css b/src/core/client/ui/components/AppBar/AppBar.css new file mode 100644 index 000000000..b0d90269e --- /dev/null +++ b/src/core/client/ui/components/AppBar/AppBar.css @@ -0,0 +1,18 @@ +.root { + background-color: var(--palette-common-white); + border-bottom: 1px solid var(--palette-divider); +} + +.container { + max-width: 1280px; + margin: 0 auto; + height: calc(5 * var(--spacing-unit)); + box-sizing: border-box; +} + +.gutterBegin { + padding-left: calc(2 * var(--spacing-unit)); +} +.gutterEnd { + padding-right: calc(2 * var(--spacing-unit)); +} diff --git a/src/core/client/ui/components/AppBar/AppBar.mdx b/src/core/client/ui/components/AppBar/AppBar.mdx new file mode 100644 index 000000000..a0b6c5a65 --- /dev/null +++ b/src/core/client/ui/components/AppBar/AppBar.mdx @@ -0,0 +1,37 @@ +--- +name: AppBar +menu: UI Kit +--- + +import { Playground, PropsTable } from "docz"; + +import { AppBar, Begin, End, Navigation, NavigationItem, Divider } from "./"; +import { Logo } from "../Brand"; +import Flex from "../Flex"; +import Icon from "../Icon"; +import BaseButton from "../BaseButton"; + +# AppBar + +## Basic usage + +<Playground> + <AppBar gutterBegin gutterEnd> + <Begin itemGutter="double"> + <Logo /> + <Navigation> + <NavigationItem active>Home</NavigationItem> + <NavigationItem>Team</NavigationItem> + </Navigation> + </Begin> + <End itemGutter> + <BaseButton> + <Icon size="lg">history</Icon> + </BaseButton> + <Divider /> + <BaseButton> + <Icon size="lg">account_box</Icon> + </BaseButton> + </End> + </AppBar> +</Playground> diff --git a/src/core/client/admin/components/AppBar.spec.tsx b/src/core/client/ui/components/AppBar/AppBar.spec.tsx similarity index 83% rename from src/core/client/admin/components/AppBar.spec.tsx rename to src/core/client/ui/components/AppBar/AppBar.spec.tsx index 1853ac189..a5ae0c527 100644 --- a/src/core/client/admin/components/AppBar.spec.tsx +++ b/src/core/client/ui/components/AppBar/AppBar.spec.tsx @@ -8,6 +8,9 @@ import AppBar from "./AppBar"; it("renders correctly", () => { const props: PropTypesOf<typeof AppBar> = { children: "child", + gutterBegin: true, + gutterEnd: true, + className: "custom", }; const wrapper = shallow(<AppBar {...props} />); expect(wrapper).toMatchSnapshot(); diff --git a/src/core/client/ui/components/AppBar/AppBar.tsx b/src/core/client/ui/components/AppBar/AppBar.tsx new file mode 100644 index 000000000..80f93076d --- /dev/null +++ b/src/core/client/ui/components/AppBar/AppBar.tsx @@ -0,0 +1,38 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { Flex } from "talk-ui/components"; +import { withStyles } from "talk-ui/hocs"; + +import styles from "./AppBar.css"; + +interface Props { + children?: React.ReactNode; + className?: string; + gutterBegin?: boolean; + gutterEnd?: boolean; + classes: typeof styles; +} + +const AppBar: StatelessComponent<Props> = ({ + gutterBegin, + gutterEnd, + className, + children, + classes, + ...rest +}) => { + return ( + <div + {...rest} + className={cn(classes.root, className, { + [classes.gutterBegin]: gutterBegin, + [classes.gutterEnd]: gutterEnd, + })} + > + <Flex className={classes.container}>{children}</Flex> + </div> + ); +}; + +export default withStyles(styles)(AppBar); diff --git a/src/core/client/ui/components/AppBar/Begin.css b/src/core/client/ui/components/AppBar/Begin.css new file mode 100644 index 000000000..365d8e488 --- /dev/null +++ b/src/core/client/ui/components/AppBar/Begin.css @@ -0,0 +1,3 @@ +.root { + flex-grow: 1; +} diff --git a/src/core/client/ui/components/AppBar/Begin.spec.tsx b/src/core/client/ui/components/AppBar/Begin.spec.tsx new file mode 100644 index 000000000..caec225c2 --- /dev/null +++ b/src/core/client/ui/components/AppBar/Begin.spec.tsx @@ -0,0 +1,16 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import End from "./End"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof End> = { + children: "children", + className: "custom", + itemGutter: true, + }; + const wrapper = shallow(<End {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/AppBar/Begin.tsx b/src/core/client/ui/components/AppBar/Begin.tsx new file mode 100644 index 000000000..a05e6a8be --- /dev/null +++ b/src/core/client/ui/components/AppBar/Begin.tsx @@ -0,0 +1,27 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { Flex } from "talk-ui/components"; +import { withStyles } from "talk-ui/hocs"; +import { PropTypesOf } from "talk-ui/types"; + +import styles from "./Begin.css"; + +interface Props extends PropTypesOf<typeof Flex> { + children?: React.ReactNode; + className?: string; + classes: typeof styles; +} + +const Begin: StatelessComponent<Props> = ({ + className, + children, + classes, + ...rest +}) => ( + <Flex className={cn(className, classes.root)} alignItems="center" {...rest}> + {children} + </Flex> +); + +export default withStyles(styles)(Begin); diff --git a/src/core/client/ui/components/AppBar/Divider.css b/src/core/client/ui/components/AppBar/Divider.css new file mode 100644 index 000000000..365190487 --- /dev/null +++ b/src/core/client/ui/components/AppBar/Divider.css @@ -0,0 +1,4 @@ +.root { + height: 75%; + border-right: 1px solid var(--palette-grey-lighter); +} diff --git a/src/core/client/admin/components/NavigationDivider.spec.tsx b/src/core/client/ui/components/AppBar/Divider.spec.tsx similarity index 56% rename from src/core/client/admin/components/NavigationDivider.spec.tsx rename to src/core/client/ui/components/AppBar/Divider.spec.tsx index 07eb79537..3f065a18d 100644 --- a/src/core/client/admin/components/NavigationDivider.spec.tsx +++ b/src/core/client/ui/components/AppBar/Divider.spec.tsx @@ -1,9 +1,9 @@ import { shallow } from "enzyme"; import React from "react"; -import NavigationDivider from "./NavigationDivider"; +import Divider from "./Divider"; it("renders correctly", () => { - const wrapper = shallow(<NavigationDivider />); + const wrapper = shallow(<Divider />); expect(wrapper).toMatchSnapshot(); }); diff --git a/src/core/client/ui/components/AppBar/Divider.tsx b/src/core/client/ui/components/AppBar/Divider.tsx new file mode 100644 index 000000000..137269389 --- /dev/null +++ b/src/core/client/ui/components/AppBar/Divider.tsx @@ -0,0 +1,19 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Divider.css"; + +interface Props { + className?: string; + classes: typeof styles; +} + +const Divider: StatelessComponent<Props> = ({ + className, + classes, + ...rest +}) => <div {...rest} className={cn(classes.root, className)} />; + +export default withStyles(styles)(Divider); diff --git a/src/core/client/ui/components/AppBar/End.css b/src/core/client/ui/components/AppBar/End.css new file mode 100644 index 000000000..8b4643190 --- /dev/null +++ b/src/core/client/ui/components/AppBar/End.css @@ -0,0 +1,4 @@ +.root { + flex-grow: 0; + flex-shrink: 0; +} diff --git a/src/core/client/ui/components/AppBar/End.spec.tsx b/src/core/client/ui/components/AppBar/End.spec.tsx new file mode 100644 index 000000000..caec225c2 --- /dev/null +++ b/src/core/client/ui/components/AppBar/End.spec.tsx @@ -0,0 +1,16 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import End from "./End"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof End> = { + children: "children", + className: "custom", + itemGutter: true, + }; + const wrapper = shallow(<End {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/AppBar/End.tsx b/src/core/client/ui/components/AppBar/End.tsx new file mode 100644 index 000000000..82bcb5c6d --- /dev/null +++ b/src/core/client/ui/components/AppBar/End.tsx @@ -0,0 +1,27 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { Flex } from "talk-ui/components"; +import { withStyles } from "talk-ui/hocs"; +import { PropTypesOf } from "talk-ui/types"; + +import styles from "./End.css"; + +interface Props extends PropTypesOf<typeof Flex> { + children?: React.ReactNode; + className?: string; + classes: typeof styles; +} + +const End: StatelessComponent<Props> = ({ + classes, + className, + children, + ...rest +}) => ( + <Flex className={cn(className, classes.root)} alignItems="center" {...rest}> + {children} + </Flex> +); + +export default withStyles(styles)(End); diff --git a/src/core/client/ui/components/AppBar/Navigation.css b/src/core/client/ui/components/AppBar/Navigation.css new file mode 100644 index 000000000..3c1d3b4a8 --- /dev/null +++ b/src/core/client/ui/components/AppBar/Navigation.css @@ -0,0 +1,11 @@ +.root { + height: 100%; +} + +.ul { + list-style: none; + padding: 0; + display: flex; + height: 100%; + margin: 0; +} diff --git a/src/core/client/ui/components/AppBar/Navigation.spec.tsx b/src/core/client/ui/components/AppBar/Navigation.spec.tsx new file mode 100644 index 000000000..350771ed3 --- /dev/null +++ b/src/core/client/ui/components/AppBar/Navigation.spec.tsx @@ -0,0 +1,14 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import Navigation from "./Navigation"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof Navigation> = { + children: "children", + }; + const wrapper = shallow(<Navigation {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/AppBar/Navigation.tsx b/src/core/client/ui/components/AppBar/Navigation.tsx new file mode 100644 index 000000000..fa3585f0f --- /dev/null +++ b/src/core/client/ui/components/AppBar/Navigation.tsx @@ -0,0 +1,24 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Navigation.css"; + +interface Props { + className?: string; + classes: typeof styles; + children?: React.ReactNode; +} + +const Navigation: StatelessComponent<Props> = ({ + children, + className, + classes, +}) => ( + <nav className={cn(classes.root, className)}> + <ul className={classes.ul}>{children}</ul> + </nav> +); + +export default withStyles(styles)(Navigation); diff --git a/src/core/client/ui/components/AppBar/NavigationItem.css b/src/core/client/ui/components/AppBar/NavigationItem.css new file mode 100644 index 000000000..d2044dd46 --- /dev/null +++ b/src/core/client/ui/components/AppBar/NavigationItem.css @@ -0,0 +1,23 @@ +.root { +} + +.anchor { + composes: navItem from "talk-ui/shared/typography.css"; + color: var(--palette-grey-dark); + height: 100%; + display: inline-flex; + align-items: center; + padding: 0 var(--spacing-unit); + text-transform: uppercase; + text-decoration: none; + &:hover { + cursor: pointer; + } +} + +.active { + composes: navItemActive from "talk-ui/shared/typography.css"; + background-color: var(--palette-brand); + text-decoration: none; + color: var(--palette-text-light); +} diff --git a/src/core/client/ui/components/AppBar/NavigationItem.spec.tsx b/src/core/client/ui/components/AppBar/NavigationItem.spec.tsx new file mode 100644 index 000000000..ee12b315c --- /dev/null +++ b/src/core/client/ui/components/AppBar/NavigationItem.spec.tsx @@ -0,0 +1,19 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import NavigationItem from "./NavigationItem"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof NavigationItem> = { + href: "/moderate", + children: "link", + onClick: noop, + active: true, + className: "custom", + }; + const wrapper = shallow(<NavigationItem {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/AppBar/NavigationItem.tsx b/src/core/client/ui/components/AppBar/NavigationItem.tsx new file mode 100644 index 000000000..d66cc5c73 --- /dev/null +++ b/src/core/client/ui/components/AppBar/NavigationItem.tsx @@ -0,0 +1,41 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./NavigationItem.css"; + +interface Props { + children: React.ReactNode; + href: string; + className?: string; + active?: boolean; + onClick?: React.EventHandler<React.MouseEvent>; + classes: typeof styles; +} + +const NavigationItem: StatelessComponent<Props> = ({ + className, + active, + href, + onClick, + children, + classes, + ...rest +}) => { + return ( + <li className={cn(className, classes.root)} {...rest}> + <a + className={cn(classes.anchor, { + [classes.active]: active, + })} + href={href} + onClick={onClick} + > + {children} + </a> + </li> + ); +}; + +export default withStyles(styles)(NavigationItem); diff --git a/src/core/client/ui/components/AppBar/__snapshots__/AppBar.spec.tsx.snap b/src/core/client/ui/components/AppBar/__snapshots__/AppBar.spec.tsx.snap new file mode 100644 index 000000000..cef384323 --- /dev/null +++ b/src/core/client/ui/components/AppBar/__snapshots__/AppBar.spec.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<AppBar + className="custom" + classes={ + Object { + "container": "AppBar-container", + "gutterBegin": "AppBar-gutterBegin", + "gutterEnd": "AppBar-gutterEnd", + "root": "AppBar-root", + } + } + gutterBegin={true} + gutterEnd={true} +> + child +</AppBar> +`; diff --git a/src/core/client/ui/components/AppBar/__snapshots__/Begin.spec.tsx.snap b/src/core/client/ui/components/AppBar/__snapshots__/Begin.spec.tsx.snap new file mode 100644 index 000000000..7f8ca63d8 --- /dev/null +++ b/src/core/client/ui/components/AppBar/__snapshots__/Begin.spec.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<End + className="custom" + classes={ + Object { + "root": "End-root", + } + } + itemGutter={true} +> + children +</End> +`; diff --git a/src/core/client/ui/components/AppBar/__snapshots__/Divider.spec.tsx.snap b/src/core/client/ui/components/AppBar/__snapshots__/Divider.spec.tsx.snap new file mode 100644 index 000000000..722ea5f94 --- /dev/null +++ b/src/core/client/ui/components/AppBar/__snapshots__/Divider.spec.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Divider + classes={ + Object { + "root": "Divider-root", + } + } +/> +`; diff --git a/src/core/client/ui/components/AppBar/__snapshots__/End.spec.tsx.snap b/src/core/client/ui/components/AppBar/__snapshots__/End.spec.tsx.snap new file mode 100644 index 000000000..7f8ca63d8 --- /dev/null +++ b/src/core/client/ui/components/AppBar/__snapshots__/End.spec.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<End + className="custom" + classes={ + Object { + "root": "End-root", + } + } + itemGutter={true} +> + children +</End> +`; diff --git a/src/core/client/ui/components/AppBar/__snapshots__/Navigation.spec.tsx.snap b/src/core/client/ui/components/AppBar/__snapshots__/Navigation.spec.tsx.snap new file mode 100644 index 000000000..54f9bae45 --- /dev/null +++ b/src/core/client/ui/components/AppBar/__snapshots__/Navigation.spec.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Navigation + classes={ + Object { + "root": "Navigation-root", + "ul": "Navigation-ul", + } + } +> + children +</Navigation> +`; diff --git a/src/core/client/ui/components/AppBar/__snapshots__/NavigationItem.spec.tsx.snap b/src/core/client/ui/components/AppBar/__snapshots__/NavigationItem.spec.tsx.snap new file mode 100644 index 000000000..7ad60ce3d --- /dev/null +++ b/src/core/client/ui/components/AppBar/__snapshots__/NavigationItem.spec.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<NavigationItem + active={true} + className="custom" + classes={ + Object { + "active": "NavigationItem-active", + "anchor": "NavigationItem-anchor", + "root": "NavigationItem-root", + } + } + href="/moderate" + onClick={[Function]} +> + link +</NavigationItem> +`; diff --git a/src/core/client/ui/components/AppBar/index.ts b/src/core/client/ui/components/AppBar/index.ts new file mode 100644 index 000000000..c3ef851d4 --- /dev/null +++ b/src/core/client/ui/components/AppBar/index.ts @@ -0,0 +1,6 @@ +export { default as AppBar } from "./AppBar"; +export { default as Begin } from "./Begin"; +export { default as End } from "./End"; +export { default as Navigation } from "./Navigation"; +export { default as NavigationItem } from "./NavigationItem"; +export { default as Divider } from "./Divider"; diff --git a/src/core/client/ui/components/BaseButton/BaseButton.tsx b/src/core/client/ui/components/BaseButton/BaseButton.tsx index 150a2ae48..e292d099e 100644 --- a/src/core/client/ui/components/BaseButton/BaseButton.tsx +++ b/src/core/client/ui/components/BaseButton/BaseButton.tsx @@ -1,12 +1,13 @@ import cn from "classnames"; import React, { + ButtonHTMLAttributes, EventHandler, FocusEvent, MouseEvent, Ref, + StatelessComponent, TouchEvent, } from "react"; -import { ButtonHTMLAttributes, StatelessComponent } from "react"; import { withForwardRef, diff --git a/src/core/client/ui/components/Brand/Brand.mdx b/src/core/client/ui/components/Brand/Brand.mdx new file mode 100644 index 000000000..668a963ce --- /dev/null +++ b/src/core/client/ui/components/Brand/Brand.mdx @@ -0,0 +1,20 @@ +--- +name: Brand +menu: UI Kit +--- + +import { Playground, PropsTable } from "docz"; +import { BrandName, BrandIcon, Logo } from "./"; +import HorizontalGutter from "../HorizontalGutter"; + +# Brand + +## Basic usage + +<Playground> + <HorizontalGutter> + <BrandName /> + <BrandIcon /> + <Logo /> + </HorizontalGutter> +</Playground> diff --git a/src/core/client/admin/components/BrandIcon.css b/src/core/client/ui/components/Brand/BrandIcon.css similarity index 65% rename from src/core/client/admin/components/BrandIcon.css rename to src/core/client/ui/components/Brand/BrandIcon.css index 32aa5aca0..ec97f5b54 100644 --- a/src/core/client/admin/components/BrandIcon.css +++ b/src/core/client/ui/components/Brand/BrandIcon.css @@ -2,8 +2,8 @@ } .md { - height: 35px; - width: 36px; + height: 25px; + width: 25px; } .lg { diff --git a/src/core/client/admin/components/BrandIcon.spec.tsx b/src/core/client/ui/components/Brand/BrandIcon.spec.tsx similarity index 100% rename from src/core/client/admin/components/BrandIcon.spec.tsx rename to src/core/client/ui/components/Brand/BrandIcon.spec.tsx diff --git a/src/core/client/admin/components/BrandIcon.tsx b/src/core/client/ui/components/Brand/BrandIcon.tsx similarity index 93% rename from src/core/client/admin/components/BrandIcon.tsx rename to src/core/client/ui/components/Brand/BrandIcon.tsx index 511354514..7d0f500b4 100644 --- a/src/core/client/admin/components/BrandIcon.tsx +++ b/src/core/client/ui/components/Brand/BrandIcon.tsx @@ -1,18 +1,27 @@ import cn from "classnames"; import React, { StatelessComponent } from "react"; +import { withStyles } from "talk-ui/hocs"; + import styles from "./BrandIcon.css"; interface Props { className?: string; + classes: typeof styles; size?: "md" | "lg"; } -const BrandIcon: StatelessComponent<Props> = props => ( +const BrandIcon: StatelessComponent<Props> = ({ + className, + classes, + size, + ...rest +}) => ( <svg - className={cn(styles.base, props.className, { - [styles.md]: props.size === "md", - [styles.lg]: props.size === "lg", + {...rest} + className={cn(classes.base, className, { + [classes.md]: size === "md", + [classes.lg]: size === "lg", })} id="Layer_1" data-name="Layer 1" @@ -42,4 +51,4 @@ BrandIcon.defaultProps = { size: "md", }; -export default BrandIcon; +export default withStyles(styles)(BrandIcon); diff --git a/src/core/client/admin/components/BrandName.css b/src/core/client/ui/components/Brand/BrandName.css similarity index 74% rename from src/core/client/admin/components/BrandName.css rename to src/core/client/ui/components/Brand/BrandName.css index d02986f0c..cf0fc4610 100644 --- a/src/core/client/admin/components/BrandName.css +++ b/src/core/client/ui/components/Brand/BrandName.css @@ -6,8 +6,8 @@ } .md { - font-size: calc(36rem / var(--rem-base)); - line-height: calc(38em / 36); + font-size: calc(24rem / var(--rem-base)); + line-height: calc(26em / 24); } .lg { font-size: calc(48rem / var(--rem-base)); diff --git a/src/core/client/admin/components/BrandName.spec.tsx b/src/core/client/ui/components/Brand/BrandName.spec.tsx similarity index 100% rename from src/core/client/admin/components/BrandName.spec.tsx rename to src/core/client/ui/components/Brand/BrandName.spec.tsx diff --git a/src/core/client/admin/components/BrandName.tsx b/src/core/client/ui/components/Brand/BrandName.tsx similarity index 59% rename from src/core/client/admin/components/BrandName.tsx rename to src/core/client/ui/components/Brand/BrandName.tsx index 56cb03dbc..cb388704b 100644 --- a/src/core/client/admin/components/BrandName.tsx +++ b/src/core/client/ui/components/Brand/BrandName.tsx @@ -4,23 +4,32 @@ import React, { StatelessComponent } from "react"; import { PropTypesOf } from "talk-framework/types"; import { Typography } from "talk-ui/components"; +import { withStyles } from "talk-ui/hocs"; import styles from "./BrandName.css"; interface Props { align?: PropTypesOf<typeof Typography>["align"]; className?: string; + classes: typeof styles; size?: "md" | "lg"; } -const BrandName: StatelessComponent<Props> = props => ( +const BrandName: StatelessComponent<Props> = ({ + align, + className, + classes, + size, + ...rest +}) => ( <Localized id="general-brandName"> <Typography + {...rest} variant="heading1" - align={props.align} - className={cn(props.className, styles.root, { - [styles.md]: props.size === "md", - [styles.lg]: props.size === "lg", + align={align} + className={cn(className, classes.root, { + [classes.md]: size === "md", + [classes.lg]: size === "lg", })} > Talk @@ -30,7 +39,7 @@ const BrandName: StatelessComponent<Props> = props => ( BrandName.defaultProps = { size: "md", - align: "center", + align: "left", }; -export default BrandName; +export default withStyles(styles)(BrandName); diff --git a/src/core/client/admin/components/Logo.css b/src/core/client/ui/components/Brand/Logo.css similarity index 100% rename from src/core/client/admin/components/Logo.css rename to src/core/client/ui/components/Brand/Logo.css diff --git a/src/core/client/admin/components/Logo.spec.tsx b/src/core/client/ui/components/Brand/Logo.spec.tsx similarity index 100% rename from src/core/client/admin/components/Logo.spec.tsx rename to src/core/client/ui/components/Brand/Logo.spec.tsx diff --git a/src/core/client/ui/components/Brand/Logo.tsx b/src/core/client/ui/components/Brand/Logo.tsx new file mode 100644 index 000000000..583fc10e0 --- /dev/null +++ b/src/core/client/ui/components/Brand/Logo.tsx @@ -0,0 +1,24 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { Flex } from "talk-ui/components"; +import { withStyles } from "talk-ui/hocs"; + +import BrandIcon from "./BrandIcon"; +import BrandName from "./BrandName"; + +import styles from "./Logo.css"; + +interface Props { + className?: string; + classes: typeof styles; +} + +const Logo: StatelessComponent<Props> = ({ className, classes, ...rest }) => ( + <Flex {...rest} alignItems="center" className={cn(classes.root, className)}> + <BrandIcon className={classes.icon} /> + <BrandName /> + </Flex> +); + +export default withStyles(styles)(Logo); diff --git a/src/core/client/ui/components/Brand/__snapshots__/BrandIcon.spec.tsx.snap b/src/core/client/ui/components/Brand/__snapshots__/BrandIcon.spec.tsx.snap new file mode 100644 index 000000000..1b81b2b33 --- /dev/null +++ b/src/core/client/ui/components/Brand/__snapshots__/BrandIcon.spec.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<BrandIcon + className="custom" + classes={ + Object { + "base": "BrandIcon-base", + "lg": "BrandIcon-lg", + "md": "BrandIcon-md", + } + } + size="lg" +/> +`; diff --git a/src/core/client/ui/components/Brand/__snapshots__/BrandName.spec.tsx.snap b/src/core/client/ui/components/Brand/__snapshots__/BrandName.spec.tsx.snap new file mode 100644 index 000000000..8b8c576f5 --- /dev/null +++ b/src/core/client/ui/components/Brand/__snapshots__/BrandName.spec.tsx.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<BrandName + align="center" + className="custom" + classes={ + Object { + "lg": "BrandName-lg", + "md": "BrandName-md", + "root": "BrandName-root", + } + } + size="lg" +/> +`; diff --git a/src/core/client/ui/components/Brand/__snapshots__/Logo.spec.tsx.snap b/src/core/client/ui/components/Brand/__snapshots__/Logo.spec.tsx.snap new file mode 100644 index 000000000..3fbcc07c3 --- /dev/null +++ b/src/core/client/ui/components/Brand/__snapshots__/Logo.spec.tsx.snap @@ -0,0 +1,13 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Logo + className="custom" + classes={ + Object { + "icon": "Logo-icon", + "root": "Logo-root", + } + } +/> +`; diff --git a/src/core/client/ui/components/Brand/index.ts b/src/core/client/ui/components/Brand/index.ts new file mode 100644 index 000000000..748b9a839 --- /dev/null +++ b/src/core/client/ui/components/Brand/index.ts @@ -0,0 +1,3 @@ +export { default as BrandIcon } from "./BrandIcon"; +export { default as BrandName } from "./BrandName"; +export { default as Logo } from "./Logo"; diff --git a/src/core/client/ui/components/Button/Button.tsx b/src/core/client/ui/components/Button/Button.tsx index 95ca5c305..a825aad8d 100644 --- a/src/core/client/ui/components/Button/Button.tsx +++ b/src/core/client/ui/components/Button/Button.tsx @@ -6,6 +6,7 @@ import { withForwardRef, withStyles } from "talk-ui/hocs"; import { PropTypesOf } from "talk-ui/types"; import BaseButton, { BaseButtonProps } from "../BaseButton"; + import styles from "./Button.css"; // This should extend from BaseButton instead but we can't because of this bug diff --git a/src/core/client/ui/components/CallOut/CallOut.tsx b/src/core/client/ui/components/CallOut/CallOut.tsx index 70176426b..3403bb02a 100644 --- a/src/core/client/ui/components/CallOut/CallOut.tsx +++ b/src/core/client/ui/components/CallOut/CallOut.tsx @@ -1,7 +1,9 @@ import cn from "classnames"; import React from "react"; import { ReactNode, StatelessComponent } from "react"; + import { withStyles } from "talk-ui/hocs"; + import styles from "./CallOut.css"; export interface CallOutProps { diff --git a/src/core/client/ui/components/Card/Card.css b/src/core/client/ui/components/Card/Card.css new file mode 100644 index 000000000..a860c84b5 --- /dev/null +++ b/src/core/client/ui/components/Card/Card.css @@ -0,0 +1,18 @@ +.root { + position: relative; + padding: calc(2 * var(--spacing-unit)); + box-sizing: border-box; + border: 1px solid var(--palette-grey-lighter); + box-shadow: 1px 1px 5px var(--palette-grey-lighter); + border-radius: var(--round-corners); + background-color: var(--palette-common-white); + /* + Fallback begin + + Unfortunately Firefox / IE does not support + `word-break: break-word` yet. + */ + word-break: break-all; + /* Fallback end */ + word-break: break-word; +} diff --git a/src/core/client/ui/components/Card/Card.mdx b/src/core/client/ui/components/Card/Card.mdx new file mode 100644 index 000000000..7a4301370 --- /dev/null +++ b/src/core/client/ui/components/Card/Card.mdx @@ -0,0 +1,22 @@ +--- +name: Card +menu: UI Kit +--- + +import { Playground, PropsTable } from "docz"; +import Card from "./Card"; + +# Card + +## Basic Use + +<Playground> + <Card> + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, + quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo + consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse + cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat + non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + </Card> +</Playground> diff --git a/src/core/client/ui/components/Card/Card.spec.tsx b/src/core/client/ui/components/Card/Card.spec.tsx new file mode 100644 index 000000000..0bb0fbe17 --- /dev/null +++ b/src/core/client/ui/components/Card/Card.spec.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; + +import { PropTypesOf } from "talk-ui/types"; + +import Card from "./Card"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof Card> = { + className: "custom", + children: "Hello World", + }; + const renderer = TestRenderer.create(<Card {...props} />); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/Card/Card.tsx b/src/core/client/ui/components/Card/Card.tsx new file mode 100644 index 000000000..955469c83 --- /dev/null +++ b/src/core/client/ui/components/Card/Card.tsx @@ -0,0 +1,36 @@ +import cn from "classnames"; +import React from "react"; +import { ReactNode, StatelessComponent } from "react"; +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Card.css"; + +export interface CardProps { + /** + * The content of the component. + */ + children: ReactNode; + /** + * Convenient prop to override the root styling. + */ + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; +} + +const Card: StatelessComponent<CardProps> = props => { + const { className, classes, children, ...rest } = props; + + const rootClassName = cn(classes.root, className); + + return ( + <div className={rootClassName} {...rest}> + {children} + </div> + ); +}; + +const enhanced = withStyles(styles)(Card); +export default enhanced; diff --git a/src/core/client/ui/components/Card/__snapshots__/Card.spec.tsx.snap b/src/core/client/ui/components/Card/__snapshots__/Card.spec.tsx.snap new file mode 100644 index 000000000..ab86ffe73 --- /dev/null +++ b/src/core/client/ui/components/Card/__snapshots__/Card.spec.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<div + className="Card-root custom" +> + Hello World +</div> +`; diff --git a/src/core/client/ui/components/Card/index.ts b/src/core/client/ui/components/Card/index.ts new file mode 100644 index 000000000..c0424cdb7 --- /dev/null +++ b/src/core/client/ui/components/Card/index.ts @@ -0,0 +1 @@ +export { default } from "./Card"; diff --git a/src/core/client/ui/components/CheckBox/CheckBox.tsx b/src/core/client/ui/components/CheckBox/CheckBox.tsx index d0dbf96d5..b8e4000b2 100644 --- a/src/core/client/ui/components/CheckBox/CheckBox.tsx +++ b/src/core/client/ui/components/CheckBox/CheckBox.tsx @@ -5,6 +5,7 @@ import uuid from "uuid/v4"; import { withKeyboardFocus, withStyles } from "talk-ui/hocs"; import styles from "./CheckBox.css"; + export interface CheckBoxProps { id?: string; /** diff --git a/src/core/client/stream/tabs/comments/components/Counter.css b/src/core/client/ui/components/Counter/Counter.css similarity index 55% rename from src/core/client/stream/tabs/comments/components/Counter.css rename to src/core/client/ui/components/Counter/Counter.css index e66f61201..2c6a84de3 100644 --- a/src/core/client/stream/tabs/comments/components/Counter.css +++ b/src/core/client/ui/components/Counter/Counter.css @@ -1,8 +1,22 @@ .root { composes: subTabCounter from "talk-ui/shared/typography.css"; - - background-color: var(--palette-grey-dark); - color: var(--palette-text-light); border-radius: 1px; padding: 2px 3px 3px 3px; + color: inherit; +} + +.colorPrimary { + background-color: var(--palette-primary-main); +} + +.colorGrey { + background-color: var(--palette-grey-dark); +} + +.colorInherit { + background-color: currentColor; +} + +.text { + color: var(--palette-text-light); } diff --git a/src/core/client/ui/components/Counter/Counter.mdx b/src/core/client/ui/components/Counter/Counter.mdx new file mode 100644 index 000000000..249e68dfa --- /dev/null +++ b/src/core/client/ui/components/Counter/Counter.mdx @@ -0,0 +1,19 @@ +--- +name: Counter +menu: UI Kit +--- + +import { Playground, PropsTable } from "docz"; + +import Flex from "../Flex"; +import Counter from "./Counter"; + +# Counter + +## Basic usage + +<Playground> + <Flex alignItems="center"> + <span>Comments</span> <Counter>23</Counter> + </Flex> +</Playground> diff --git a/src/core/client/stream/tabs/comments/components/Counter.spec.tsx b/src/core/client/ui/components/Counter/Counter.spec.tsx similarity index 100% rename from src/core/client/stream/tabs/comments/components/Counter.spec.tsx rename to src/core/client/ui/components/Counter/Counter.spec.tsx diff --git a/src/core/client/ui/components/Counter/Counter.tsx b/src/core/client/ui/components/Counter/Counter.tsx new file mode 100644 index 000000000..f60c1d646 --- /dev/null +++ b/src/core/client/ui/components/Counter/Counter.tsx @@ -0,0 +1,48 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Counter.css"; + +interface Props { + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; + /** + * The color of the component. It supports those theme colors that make sense for this component. + */ + color?: "inherit" | "grey" | "primary"; +} + +const Counter: StatelessComponent<Props> = ({ + classes, + color, + className, + children, + ...rest +}) => { + const rootClassName = cn( + classes.root, + { + [classes.colorPrimary]: color === "primary", + [classes.colorGrey]: color === "grey", + [classes.colorInherit]: color === "inherit", + }, + className + ); + return ( + <span className={rootClassName} {...rest}> + <span className={classes.text}>{children}</span> + </span> + ); +}; + +Counter.defaultProps = { + color: "inherit", +}; + +const enhanced = withStyles(styles)(Counter); +export default enhanced; diff --git a/src/core/client/ui/components/Counter/__snapshots__/Counter.spec.tsx.snap b/src/core/client/ui/components/Counter/__snapshots__/Counter.spec.tsx.snap new file mode 100644 index 000000000..bd0bb0827 --- /dev/null +++ b/src/core/client/ui/components/Counter/__snapshots__/Counter.spec.tsx.snap @@ -0,0 +1,18 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Counter + classes={ + Object { + "colorGrey": "Counter-colorGrey", + "colorInherit": "Counter-colorInherit", + "colorPrimary": "Counter-colorPrimary", + "root": "Counter-root", + "text": "Counter-text", + } + } + color="inherit" +> + 20 +</Counter> +`; diff --git a/src/core/client/ui/components/Counter/index.ts b/src/core/client/ui/components/Counter/index.ts new file mode 100644 index 000000000..e09713ba4 --- /dev/null +++ b/src/core/client/ui/components/Counter/index.ts @@ -0,0 +1 @@ +export { default } from "./Counter"; diff --git a/src/core/client/ui/components/FormField/FormField.tsx b/src/core/client/ui/components/FormField/FormField.tsx index 9f878ffd8..98a07a200 100644 --- a/src/core/client/ui/components/FormField/FormField.tsx +++ b/src/core/client/ui/components/FormField/FormField.tsx @@ -5,6 +5,7 @@ import { StatelessComponent } from "react"; import { withStyles } from "talk-ui/hocs"; import HorizontalGutter from "../HorizontalGutter"; + import styles from "./FormField.css"; interface InnerProps { diff --git a/src/core/client/ui/components/Icon/Icon.css b/src/core/client/ui/components/Icon/Icon.css index 6484d3410..b126c1040 100644 --- a/src/core/client/ui/components/Icon/Icon.css +++ b/src/core/client/ui/components/Icon/Icon.css @@ -11,6 +11,7 @@ vertical-align: middle; display: inline-block; letter-spacing: 0; + word-break: initial; /* Enable Ligatures */ font-feature-settings: "liga"; diff --git a/src/core/client/ui/components/InputLabel/InputLabel.tsx b/src/core/client/ui/components/InputLabel/InputLabel.tsx index d66d010e9..a13832b51 100644 --- a/src/core/client/ui/components/InputLabel/InputLabel.tsx +++ b/src/core/client/ui/components/InputLabel/InputLabel.tsx @@ -3,6 +3,7 @@ import React, { ReactNode } from "react"; import { StatelessComponent } from "react"; import { withStyles } from "talk-ui/hocs"; import Typography from "../Typography"; + import styles from "./InputLabel.css"; export interface InputLabelProps { diff --git a/src/core/client/ui/components/Marker/Count.css b/src/core/client/ui/components/Marker/Count.css new file mode 100644 index 000000000..415e63860 --- /dev/null +++ b/src/core/client/ui/components/Marker/Count.css @@ -0,0 +1,10 @@ +.root { + composes: markerText from "talk-ui/shared/typography.css"; + border-left: 1px solid currentColor; + margin-left: calc(0.5 * var(--spacing-unit)); + margin-right: calc(-0.5 * var(--spacing-unit)); + padding-top: 3px; + padding-bottom: 3px; + padding-left: calc(0.5 * var(--spacing-unit)); + white-space: nowrap; +} diff --git a/src/core/client/ui/components/Marker/Count.spec.tsx b/src/core/client/ui/components/Marker/Count.spec.tsx new file mode 100644 index 000000000..910b56e54 --- /dev/null +++ b/src/core/client/ui/components/Marker/Count.spec.tsx @@ -0,0 +1,15 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; + +import { PropTypesOf } from "talk-ui/types"; + +import Count from "./Count"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof Count> = { + className: "custom", + children: "30", + }; + const renderer = TestRenderer.create(<Count {...props}>Hello</Count>); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/Marker/Count.tsx b/src/core/client/ui/components/Marker/Count.tsx new file mode 100644 index 000000000..b99e3709d --- /dev/null +++ b/src/core/client/ui/components/Marker/Count.tsx @@ -0,0 +1,30 @@ +import cn from "classnames"; +import React from "react"; +import { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Count.css"; + +interface Props { + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; + children: React.ReactNode; +} + +const Count: StatelessComponent<Props> = props => { + const { className, children, classes, ...rest } = props; + const rootClassName = cn(classes.root, className); + + return ( + <span className={rootClassName} {...rest}> + {children} + </span> + ); +}; + +const enhanced = withStyles(styles)(Count); +export default enhanced; diff --git a/src/core/client/ui/components/Marker/Marker.css b/src/core/client/ui/components/Marker/Marker.css new file mode 100644 index 000000000..eb06136c4 --- /dev/null +++ b/src/core/client/ui/components/Marker/Marker.css @@ -0,0 +1,37 @@ +.root { + composes: markerText from "talk-ui/shared/typography.css"; + color: var(--palette-error-main); + border: 1px solid currentColor; + border-radius: 4px; + padding: 1px var(--spacing-unit); + white-space: nowrap; +} + +.colorPrimary { + color: var(--palette-primary-darkest); +} + +.colorGrey { + color: var(--palette-grey-darkest); +} + +.colorError { + color: var(--palette-error-darkest); +} + +.variantRegular { +} + +.variantFilled { + &.colorPrimary { + background-color: var(--palette-primary-lightest); + } + + &.colorGrey { + background-color: var(--palette-grey-lightest); + } + + &.colorError { + background-color: var(--palette-error-lightest); + } +} diff --git a/src/core/client/ui/components/Marker/Marker.mdx b/src/core/client/ui/components/Marker/Marker.mdx new file mode 100644 index 000000000..b1e6883ea --- /dev/null +++ b/src/core/client/ui/components/Marker/Marker.mdx @@ -0,0 +1,43 @@ +--- +name: Marker +menu: UI Kit +--- + +import { Playground, PropsTable } from "docz"; +import Marker from "./Marker"; +import Count from "./Count"; +import HorizontalGutter from "../HorizontalGutter"; +import Flex from "../Flex"; + +# Marker + +## Basic Use + +<Playground> + <HorizontalGutter> + <Flex itemGutter> + <Marker color="error">Toxic</Marker> + <Marker color="error">Karma</Marker> + <Marker color="error">Spam</Marker> + <Marker color="error">Banned Word</Marker> + <Marker color="error"> + With a count <Count>1</Count> + </Marker> + </Flex> + <Flex itemGutter> + <Marker color="primary">Premod</Marker> + <Marker color="primary">Link</Marker> + </Flex> + <Flex itemGutter> + <Marker color="error" variant="filled"> + Spam <Count>2</Count> + </Marker> + <Marker color="error" variant="filled"> + Suspect Word + </Marker> + <Marker color="error" variant="filled"> + Offensive <Count>1</Count> + </Marker> + </Flex> + </HorizontalGutter> +</Playground> diff --git a/src/core/client/ui/components/Marker/Marker.spec.tsx b/src/core/client/ui/components/Marker/Marker.spec.tsx new file mode 100644 index 000000000..8e798d448 --- /dev/null +++ b/src/core/client/ui/components/Marker/Marker.spec.tsx @@ -0,0 +1,17 @@ +import React from "react"; +import TestRenderer from "react-test-renderer"; + +import { PropTypesOf } from "talk-ui/types"; + +import Marker from "./Marker"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof Marker> = { + className: "custom", + color: "error", + variant: "filled", + children: "Spam", + }; + const renderer = TestRenderer.create(<Marker {...props}>Hello</Marker>); + expect(renderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/Marker/Marker.tsx b/src/core/client/ui/components/Marker/Marker.tsx new file mode 100644 index 000000000..5107ccef7 --- /dev/null +++ b/src/core/client/ui/components/Marker/Marker.tsx @@ -0,0 +1,48 @@ +import cn from "classnames"; +import React from "react"; +import { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Marker.css"; + +interface Props { + className?: string; + /** + * Override or extend the styles applied to the component. + */ + classes: typeof styles; + children: React.ReactNode; + /** + * The color of the component. It supports those theme colors that make sense for this component. + */ + color?: "grey" | "primary" | "error"; + + variant?: "regular" | "filled"; +} + +const Marker: StatelessComponent<Props> = props => { + const { className, children, classes, color, variant, ...rest } = props; + + const rootClassName = cn(classes.root, className, { + [classes.colorPrimary]: color === "primary", + [classes.colorGrey]: color === "grey", + [classes.colorError]: color === "error", + [classes.variantRegular]: variant === "regular", + [classes.variantFilled]: variant === "filled", + }); + + return ( + <span className={rootClassName} {...rest}> + {children} + </span> + ); +}; + +Marker.defaultProps = { + color: "grey", + variant: "regular", +}; + +const enhanced = withStyles(styles)(Marker); +export default enhanced; diff --git a/src/core/client/stream/tabs/comments/components/__snapshots__/Counter.spec.tsx.snap b/src/core/client/ui/components/Marker/__snapshots__/Count.spec.tsx.snap similarity index 71% rename from src/core/client/stream/tabs/comments/components/__snapshots__/Counter.spec.tsx.snap rename to src/core/client/ui/components/Marker/__snapshots__/Count.spec.tsx.snap index 17fb6637b..a0dfbda82 100644 --- a/src/core/client/stream/tabs/comments/components/__snapshots__/Counter.spec.tsx.snap +++ b/src/core/client/ui/components/Marker/__snapshots__/Count.spec.tsx.snap @@ -2,8 +2,8 @@ exports[`renders correctly 1`] = ` <span - className="Counter-root" + className="Count-root custom" > - 20 + Hello </span> `; diff --git a/src/core/client/ui/components/Marker/__snapshots__/Marker.spec.tsx.snap b/src/core/client/ui/components/Marker/__snapshots__/Marker.spec.tsx.snap new file mode 100644 index 000000000..c885b116a --- /dev/null +++ b/src/core/client/ui/components/Marker/__snapshots__/Marker.spec.tsx.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<span + className="Marker-root custom Marker-colorError Marker-variantFilled" +> + Hello +</span> +`; diff --git a/src/core/client/ui/components/Marker/index.ts b/src/core/client/ui/components/Marker/index.ts new file mode 100644 index 000000000..2622c15bd --- /dev/null +++ b/src/core/client/ui/components/Marker/index.ts @@ -0,0 +1,2 @@ +export { default, default as Marker } from "./Marker"; +export { default as Count } from "./Count"; diff --git a/src/core/client/ui/components/Message/Message.tsx b/src/core/client/ui/components/Message/Message.tsx index ae46c0ff0..a4473663d 100644 --- a/src/core/client/ui/components/Message/Message.tsx +++ b/src/core/client/ui/components/Message/Message.tsx @@ -1,6 +1,7 @@ import cn from "classnames"; import React, { ReactNode, StatelessComponent } from "react"; import { withStyles } from "talk-ui/hocs"; + import styles from "./Message.css"; export interface MessageProps { diff --git a/src/core/client/ui/components/Popover/Popover.tsx b/src/core/client/ui/components/Popover/Popover.tsx index 111bfcf61..d47aff060 100644 --- a/src/core/client/ui/components/Popover/Popover.tsx +++ b/src/core/client/ui/components/Popover/Popover.tsx @@ -11,6 +11,7 @@ import { import { withStyles } from "talk-ui/hocs"; import AriaInfo from "../AriaInfo"; + import styles from "./Popover.css"; type Placement = diff --git a/src/core/client/ui/components/RadioButton/RadioButton.tsx b/src/core/client/ui/components/RadioButton/RadioButton.tsx index 924760a43..ae5f9b41e 100644 --- a/src/core/client/ui/components/RadioButton/RadioButton.tsx +++ b/src/core/client/ui/components/RadioButton/RadioButton.tsx @@ -6,6 +6,7 @@ import { Flex } from "talk-ui/components"; import { withKeyboardFocus, withStyles } from "talk-ui/hocs"; import styles from "./RadioButton.css"; + export interface RadioButtonProps { id?: string; /** diff --git a/src/core/client/ui/components/Spinner/Spinner.tsx b/src/core/client/ui/components/Spinner/Spinner.tsx index c9fbbd99b..ef46ea31c 100644 --- a/src/core/client/ui/components/Spinner/Spinner.tsx +++ b/src/core/client/ui/components/Spinner/Spinner.tsx @@ -1,6 +1,7 @@ import cn from "classnames"; import React, { StatelessComponent } from "react"; import { withStyles } from "talk-ui/hocs"; + import styles from "./Spinner.css"; type Size = "sm" | "md"; diff --git a/src/core/client/ui/components/Steps/Circle.tsx b/src/core/client/ui/components/Steps/Circle.tsx index 9ff379bd8..ab1afb504 100644 --- a/src/core/client/ui/components/Steps/Circle.tsx +++ b/src/core/client/ui/components/Steps/Circle.tsx @@ -1,6 +1,7 @@ import cn from "classnames"; import React, { StatelessComponent } from "react"; import Icon from "../Icon"; + import styles from "./Circle.css"; interface CircleProps { diff --git a/src/core/client/ui/components/Steps/Line.tsx b/src/core/client/ui/components/Steps/Line.tsx index b6200a252..1feb26944 100644 --- a/src/core/client/ui/components/Steps/Line.tsx +++ b/src/core/client/ui/components/Steps/Line.tsx @@ -1,5 +1,6 @@ import cn from "classnames"; import React, { StatelessComponent } from "react"; + import styles from "./Line.css"; interface CircleProps { diff --git a/src/core/client/ui/components/Steps/Step.tsx b/src/core/client/ui/components/Steps/Step.tsx index 5d7b29a8a..bb5355355 100644 --- a/src/core/client/ui/components/Steps/Step.tsx +++ b/src/core/client/ui/components/Steps/Step.tsx @@ -3,6 +3,7 @@ import Flex from "../Flex"; import Typography from "../Typography"; import Circle from "./Circle"; import Line from "./Line"; + import styles from "./Step.css"; interface StepProps { diff --git a/src/core/client/ui/components/Steps/StepBar.tsx b/src/core/client/ui/components/Steps/StepBar.tsx index 2c4bdae5d..c267b89cb 100644 --- a/src/core/client/ui/components/Steps/StepBar.tsx +++ b/src/core/client/ui/components/Steps/StepBar.tsx @@ -1,6 +1,7 @@ import cn from "classnames"; import React, { Component, ReactNode } from "react"; import Flex from "../Flex"; + import styles from "./StepBar.css"; interface StepBarProps { diff --git a/src/core/client/ui/components/SubBar/Navigation.css b/src/core/client/ui/components/SubBar/Navigation.css new file mode 100644 index 000000000..2ee8227af --- /dev/null +++ b/src/core/client/ui/components/SubBar/Navigation.css @@ -0,0 +1,21 @@ +.root { + height: 100%; + border-bottom: 1px solid var(--palette-divider); + padding: 0 calc(1.5 * var(--spacing-unit)); +} + +.ul { + list-style: none; + padding: 0; + display: flex; + height: 100%; + margin: 0; + align-items: flex-end; + + & > * { + margin: 0 calc(3 * var(--spacing-unit)) 0 0; + } + & > *:last-child { + margin: 0; + } +} diff --git a/src/core/client/ui/components/SubBar/Navigation.spec.tsx b/src/core/client/ui/components/SubBar/Navigation.spec.tsx new file mode 100644 index 000000000..350771ed3 --- /dev/null +++ b/src/core/client/ui/components/SubBar/Navigation.spec.tsx @@ -0,0 +1,14 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import Navigation from "./Navigation"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof Navigation> = { + children: "children", + }; + const wrapper = shallow(<Navigation {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/SubBar/Navigation.tsx b/src/core/client/ui/components/SubBar/Navigation.tsx new file mode 100644 index 000000000..fa3585f0f --- /dev/null +++ b/src/core/client/ui/components/SubBar/Navigation.tsx @@ -0,0 +1,24 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./Navigation.css"; + +interface Props { + className?: string; + classes: typeof styles; + children?: React.ReactNode; +} + +const Navigation: StatelessComponent<Props> = ({ + children, + className, + classes, +}) => ( + <nav className={cn(classes.root, className)}> + <ul className={classes.ul}>{children}</ul> + </nav> +); + +export default withStyles(styles)(Navigation); diff --git a/src/core/client/ui/components/SubBar/NavigationItem.css b/src/core/client/ui/components/SubBar/NavigationItem.css new file mode 100644 index 000000000..43784ec6d --- /dev/null +++ b/src/core/client/ui/components/SubBar/NavigationItem.css @@ -0,0 +1,30 @@ +.root { + height: 100%; +} + +.anchor { + composes: adminTabItem from "talk-ui/shared/typography.css"; + color: var(--palette-grey-dark); + height: 100%; + display: inline-flex; + align-items: center; + text-transform: uppercase; + text-decoration: none; + box-sizing: border-box; + border-bottom: 3px solid transparent; + &:hover { + cursor: pointer; + } + & > * { + margin: 0 calc(0.5 * var(--spacing-unit)) 0 0; + } + & > *:last-child { + margin: 0; + } +} + +.active { + composes: adminTabItemActive from "talk-ui/shared/typography.css"; + border-bottom: 3px solid var(--palette-primary-main); + color: var(--palette-primary-main); +} diff --git a/src/core/client/ui/components/SubBar/NavigationItem.spec.tsx b/src/core/client/ui/components/SubBar/NavigationItem.spec.tsx new file mode 100644 index 000000000..ee12b315c --- /dev/null +++ b/src/core/client/ui/components/SubBar/NavigationItem.spec.tsx @@ -0,0 +1,19 @@ +import { shallow } from "enzyme"; +import { noop } from "lodash"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import NavigationItem from "./NavigationItem"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof NavigationItem> = { + href: "/moderate", + children: "link", + onClick: noop, + active: true, + className: "custom", + }; + const wrapper = shallow(<NavigationItem {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/SubBar/NavigationItem.tsx b/src/core/client/ui/components/SubBar/NavigationItem.tsx new file mode 100644 index 000000000..9d74c79b2 --- /dev/null +++ b/src/core/client/ui/components/SubBar/NavigationItem.tsx @@ -0,0 +1,41 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { withStyles } from "talk-ui/hocs"; + +import styles from "./NavigationItem.css"; + +interface Props { + children: React.ReactNode; + href?: string; + className?: string; + active?: boolean; + onClick?: React.EventHandler<React.MouseEvent>; + classes: typeof styles; +} + +const NavigationItem: StatelessComponent<Props> = ({ + children, + href, + className, + active, + onClick, + classes, + ...rest +}) => { + return ( + <li {...rest} className={cn(className, classes.root)}> + <a + className={cn(className, classes.anchor, { + [classes.active]: active, + })} + href={href} + onClick={onClick} + > + {children} + </a> + </li> + ); +}; + +export default withStyles(styles)(NavigationItem); diff --git a/src/core/client/ui/components/SubBar/SubBar.css b/src/core/client/ui/components/SubBar/SubBar.css new file mode 100644 index 000000000..b590228de --- /dev/null +++ b/src/core/client/ui/components/SubBar/SubBar.css @@ -0,0 +1,19 @@ +.root { + background-color: var(--palette-common-white); + height: calc(5 * var(--spacing-unit)); +} + +.container { + max-width: 1280px; + margin: 0 auto; + height: 100%; + box-sizing: border-box; + position: relative; +} + +.gutterBegin { + padding-left: calc(2 * var(--spacing-unit)); +} +.gutterEnd { + padding-right: calc(2 * var(--spacing-unit)); +} diff --git a/src/core/client/ui/components/SubBar/SubBar.mdx b/src/core/client/ui/components/SubBar/SubBar.mdx new file mode 100644 index 000000000..4db8fc439 --- /dev/null +++ b/src/core/client/ui/components/SubBar/SubBar.mdx @@ -0,0 +1,40 @@ +--- +name: SubBar +menu: UI Kit +--- + +import { Playground, PropsTable } from "docz"; + +import { SubBar, Navigation, NavigationItem, Divider } from "./"; +import Icon from "../Icon"; +import Counter from "../Counter"; + +# SubBar + +## Basic usage + +<Playground> + <SubBar> + <Navigation> + <NavigationItem active> + <Icon>flag</Icon> + <span>Reported</span> + <Counter color="primary">20</Counter> + </NavigationItem> + <NavigationItem> + <Icon>access_time</Icon> + <span>Pending</span> + <Counter>100</Counter> + </NavigationItem> + <NavigationItem> + <Icon>forum</Icon> + <span>Unmoderated</span> + <Counter>2.3k</Counter> + </NavigationItem> + <NavigationItem> + <Icon>cancel</Icon> + <span>Rejected</span> + </NavigationItem> + </Navigation> + </SubBar> +</Playground> diff --git a/src/core/client/ui/components/SubBar/SubBar.spec.tsx b/src/core/client/ui/components/SubBar/SubBar.spec.tsx new file mode 100644 index 000000000..2f908fc5c --- /dev/null +++ b/src/core/client/ui/components/SubBar/SubBar.spec.tsx @@ -0,0 +1,17 @@ +import { shallow } from "enzyme"; +import React from "react"; + +import { PropTypesOf } from "talk-framework/types"; + +import SubBar from "./SubBar"; + +it("renders correctly", () => { + const props: PropTypesOf<typeof SubBar> = { + children: "child", + gutterBegin: true, + gutterEnd: true, + className: "custom", + }; + const wrapper = shallow(<SubBar {...props} />); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/ui/components/SubBar/SubBar.tsx b/src/core/client/ui/components/SubBar/SubBar.tsx new file mode 100644 index 000000000..8473210b2 --- /dev/null +++ b/src/core/client/ui/components/SubBar/SubBar.tsx @@ -0,0 +1,44 @@ +import cn from "classnames"; +import React, { StatelessComponent } from "react"; + +import { Flex } from "talk-ui/components"; +import { withStyles } from "talk-ui/hocs"; + +import styles from "./SubBar.css"; + +interface Props { + children?: React.ReactNode; + className?: string; + gutterBegin?: boolean; + gutterEnd?: boolean; + classes: typeof styles; +} + +const SubBar: StatelessComponent<Props> = ({ + gutterBegin, + gutterEnd, + className, + children, + classes, + ...rest +}) => { + return ( + <div + {...rest} + className={cn(classes.root, className, { + [classes.gutterBegin]: gutterBegin, + [classes.gutterEnd]: gutterEnd, + })} + > + <Flex + className={classes.container} + justifyContent="center" + alignItems="center" + > + {children} + </Flex> + </div> + ); +}; + +export default withStyles(styles)(SubBar); diff --git a/src/core/client/ui/components/SubBar/__snapshots__/Navigation.spec.tsx.snap b/src/core/client/ui/components/SubBar/__snapshots__/Navigation.spec.tsx.snap new file mode 100644 index 000000000..54f9bae45 --- /dev/null +++ b/src/core/client/ui/components/SubBar/__snapshots__/Navigation.spec.tsx.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<Navigation + classes={ + Object { + "root": "Navigation-root", + "ul": "Navigation-ul", + } + } +> + children +</Navigation> +`; diff --git a/src/core/client/ui/components/SubBar/__snapshots__/NavigationItem.spec.tsx.snap b/src/core/client/ui/components/SubBar/__snapshots__/NavigationItem.spec.tsx.snap new file mode 100644 index 000000000..7ad60ce3d --- /dev/null +++ b/src/core/client/ui/components/SubBar/__snapshots__/NavigationItem.spec.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<NavigationItem + active={true} + className="custom" + classes={ + Object { + "active": "NavigationItem-active", + "anchor": "NavigationItem-anchor", + "root": "NavigationItem-root", + } + } + href="/moderate" + onClick={[Function]} +> + link +</NavigationItem> +`; diff --git a/src/core/client/ui/components/SubBar/__snapshots__/SubBar.spec.tsx.snap b/src/core/client/ui/components/SubBar/__snapshots__/SubBar.spec.tsx.snap new file mode 100644 index 000000000..22bc584a3 --- /dev/null +++ b/src/core/client/ui/components/SubBar/__snapshots__/SubBar.spec.tsx.snap @@ -0,0 +1,19 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` +<SubBar + className="custom" + classes={ + Object { + "container": "SubBar-container", + "gutterBegin": "SubBar-gutterBegin", + "gutterEnd": "SubBar-gutterEnd", + "root": "SubBar-root", + } + } + gutterBegin={true} + gutterEnd={true} +> + child +</SubBar> +`; diff --git a/src/core/client/ui/components/SubBar/index.ts b/src/core/client/ui/components/SubBar/index.ts new file mode 100644 index 000000000..7f912c793 --- /dev/null +++ b/src/core/client/ui/components/SubBar/index.ts @@ -0,0 +1,3 @@ +export { default as SubBar } from "./SubBar"; +export { default as Navigation } from "./Navigation"; +export { default as NavigationItem } from "./NavigationItem"; diff --git a/src/core/client/ui/components/Tabs/Tab.tsx b/src/core/client/ui/components/Tabs/Tab.tsx index ccf651e4b..f0ff887e1 100644 --- a/src/core/client/ui/components/Tabs/Tab.tsx +++ b/src/core/client/ui/components/Tabs/Tab.tsx @@ -1,7 +1,10 @@ import cn from "classnames"; import React from "react"; + import { withStyles } from "talk-ui/hocs"; + import BaseButton from "../BaseButton"; + import styles from "./Tab.css"; export interface TabProps { diff --git a/src/core/client/ui/components/Tabs/TabBar.tsx b/src/core/client/ui/components/Tabs/TabBar.tsx index 5c5f76071..c2a6ade29 100644 --- a/src/core/client/ui/components/Tabs/TabBar.tsx +++ b/src/core/client/ui/components/Tabs/TabBar.tsx @@ -1,6 +1,8 @@ import cn from "classnames"; import React, { StatelessComponent } from "react"; + import { withStyles } from "talk-ui/hocs"; + import styles from "./TabBar.css"; export interface TabBarProps { diff --git a/src/core/client/ui/components/TextField/TextField.tsx b/src/core/client/ui/components/TextField/TextField.tsx index e584b7684..6ceeaeeae 100644 --- a/src/core/client/ui/components/TextField/TextField.tsx +++ b/src/core/client/ui/components/TextField/TextField.tsx @@ -1,7 +1,9 @@ import cn from "classnames"; import React, { AllHTMLAttributes, ChangeEvent, EventHandler } from "react"; + import { StatelessComponent } from "react"; import { withStyles } from "talk-ui/hocs"; + import styles from "./TextField.css"; export interface TextFieldProps { diff --git a/src/core/client/ui/components/TextLink/TextLink.tsx b/src/core/client/ui/components/TextLink/TextLink.tsx index 0e02cb1ff..6c7b6885e 100644 --- a/src/core/client/ui/components/TextLink/TextLink.tsx +++ b/src/core/client/ui/components/TextLink/TextLink.tsx @@ -1,7 +1,8 @@ import cn from "classnames"; -import React, { AnchorHTMLAttributes } from "react"; -import { StatelessComponent } from "react"; +import React, { AnchorHTMLAttributes, StatelessComponent } from "react"; + import { withStyles } from "talk-ui/hocs"; + import styles from "./TextLink.css"; interface Props extends AnchorHTMLAttributes<HTMLAnchorElement> { diff --git a/src/core/client/ui/components/index.ts b/src/core/client/ui/components/index.ts index 7744d0068..01af92f25 100644 --- a/src/core/client/ui/components/index.ts +++ b/src/core/client/ui/components/index.ts @@ -28,3 +28,20 @@ export { default as TextLink } from "./TextLink"; export { default as CheckBox } from "./CheckBox"; export { default as RadioButton } from "./RadioButton"; export { default as Delay } from "./Delay"; +export { + AppBar, + Begin as AppBarBegin, + End as AppBarEnd, + Divider as AppBarDivider, + Navigation as AppBarNavigation, + NavigationItem as AppBarNavigationItem, +} from "./AppBar"; +export { + SubBar, + Navigation as SubBarNavigation, + NavigationItem as SubBarNavigationItem, +} from "./SubBar"; +export { BrandIcon, BrandName, Logo } from "./Brand"; +export { default as Counter } from "./Counter"; +export { Marker, Count as MarkerCount } from "./Marker"; +export { default as Card } from "./Card"; diff --git a/src/core/client/ui/shared/buttonReset.css b/src/core/client/ui/shared/buttonReset.css index f6d3d5517..81a4beb2e 100644 --- a/src/core/client/ui/shared/buttonReset.css +++ b/src/core/client/ui/shared/buttonReset.css @@ -22,6 +22,10 @@ font-size: inherit; -webkit-tap-highlight-color: rgba(0, 0, 0, 0) !important; + &:disabled { + cursor: default; + } + &::-moz-focus-inner { border: 0; } diff --git a/src/core/client/ui/shared/icon.css b/src/core/client/ui/shared/icon.css index add2c8b9a..7dcf5b28f 100644 --- a/src/core/client/ui/shared/icon.css +++ b/src/core/client/ui/shared/icon.css @@ -3,11 +3,11 @@ font-style: normal; font-weight: 400; src: local("Material Icons"), local("MaterialIcons-Regular"), - url(material-design-icons/iconfont/MaterialIcons-Regular.woff2) + url("material-design-icons/iconfont/MaterialIcons-Regular.woff2") format("woff2"), - url(material-design-icons/iconfont/MaterialIcons-Regular.woff) + url("material-design-icons/iconfont/MaterialIcons-Regular.woff") format("woff"), - url(material-design-icons/iconfont/MaterialIcons-Regular.ttf) + url("material-design-icons/iconfont/MaterialIcons-Regular.ttf") format("truetype"); } diff --git a/src/core/client/ui/shared/typography.css b/src/core/client/ui/shared/typography.css index 36d124aaa..224723eae 100644 --- a/src/core/client/ui/shared/typography.css +++ b/src/core/client/ui/shared/typography.css @@ -207,3 +207,42 @@ letter-spacing: calc(0.2em / 16); color: var(--palette-text-primary); } + +.navItem { + font-family: var(--font-family-sans-serif); + font-weight: var(--font-weight-medium); + font-size: calc(16rem / var(--rem-base)); + letter-spacing: calc(-0.1em / 16); + text-transform: uppercase; +} + +.navItemActive { + font-family: var(--font-family-sans-serif); + font-weight: var(--font-weight-bold); + font-size: calc(16rem / var(--rem-base)); + letter-spacing: calc(-0.35em / 16); + text-transform: uppercase; +} + +.adminTabItem { + font-family: var(--font-family-sans-serif); + font-weight: var(--font-weight-medium); + font-size: calc(14rem / var(--rem-base)); + letter-spacing: calc(-0.1em / 14); + text-transform: uppercase; +} + +.adminTabItemActive { + font-family: var(--font-family-sans-serif); + font-weight: var(--font-weight-bold); + font-size: calc(14rem / var(--rem-base)); + letter-spacing: calc(-0.35em / 14); + text-transform: uppercase; +} + +.markerText { + font-family: var(--font-family-sans-serif); + font-weight: var(--font-weight-medium); + font-size: calc(13rem / var(--rem-base)); + letter-spacing: calc(-0.35em / 13); +} diff --git a/src/core/client/ui/theme/variables.ts b/src/core/client/ui/theme/variables.ts index 31abf7df3..2f31a357f 100644 --- a/src/core/client/ui/theme/variables.ts +++ b/src/core/client/ui/theme/variables.ts @@ -52,7 +52,7 @@ const variables = { }, /* Background colors */ background: { - light: "#f5f6fa", + light: "#F6F6F6", }, /* Common colors */ common: { @@ -62,6 +62,7 @@ const variables = { /* Divider */ divider: "rgba(0, 0, 0, 0.12)", brand: "#F77160", + highlight: "#FFD863", }, /* gitter and spacing */ spacingUnitSmall: 5, diff --git a/src/core/common/utils/__snapshots__/purify.spec.ts.snap b/src/core/common/utils/__snapshots__/purify.spec.ts.snap index 29c58bd28..6e868b5ee 100644 --- a/src/core/common/utils/__snapshots__/purify.spec.ts.snap +++ b/src/core/common/utils/__snapshots__/purify.spec.ts.snap @@ -2,15 +2,15 @@ exports[`allows anchor links 1`] = ` Object { - "body": "<a href=\\"test\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">This is a link</a>", + "body": "<a href=\\"test\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">test</a>", "linkCount": 1, } `; exports[`allows anchor tags and counts them correctly 1`] = ` "<div> - <a href=\\"https://mozilla.org/\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Mozilla</a> - <a href=\\"https://mozilla.org/\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">Mozilla</a> + <a href=\\"https://mozilla.org/\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">https://mozilla.org/</a> + <a href=\\"https://mozilla.org/\\" target=\\"_blank\\" rel=\\"noopener noreferrer\\">https://mozilla.org/</a> </div> " `; diff --git a/src/core/common/utils/purify.ts b/src/core/common/utils/purify.ts index 89ad2a5ab..a8e5f86cf 100644 --- a/src/core/common/utils/purify.ts +++ b/src/core/common/utils/purify.ts @@ -25,6 +25,12 @@ export function createPurify<T extends boolean = true>( // Ensure we wrap all the links with the target + rel set. node.setAttribute("target", "_blank"); node.setAttribute("rel", "noopener noreferrer"); + + // Ensure that all the links have the same link as they do text. + const href = node.getAttribute("href"); + if (node.textContent !== href) { + node.textContent = href; + } } else { // The only tag that's allowed attributes is the "A" tag. node.removeAttribute("href"); diff --git a/src/core/server/app/index.ts b/src/core/server/app/index.ts index 263cee64e..863c9f513 100644 --- a/src/core/server/app/index.ts +++ b/src/core/server/app/index.ts @@ -6,11 +6,11 @@ import { Db } from "mongodb"; import nunjucks from "nunjucks"; import path from "path"; -import { Config } from "talk-common/config"; import { cacheHeadersMiddleware } from "talk-server/app/middleware/cacheHeaders"; import { errorHandler } from "talk-server/app/middleware/error"; import { notFoundMiddleware } from "talk-server/app/middleware/notFound"; import { createPassport } from "talk-server/app/middleware/passport"; +import { Config } from "talk-server/config"; import { handleSubscriptions } from "talk-server/graph/common/subscriptions/middleware"; import { Schemas } from "talk-server/graph/schemas"; import { TaskQueue } from "talk-server/queue"; diff --git a/src/core/server/app/middleware/context/tenant.ts b/src/core/server/app/middleware/context/tenant.ts index 7aca25892..b642d6aa2 100644 --- a/src/core/server/app/middleware/context/tenant.ts +++ b/src/core/server/app/middleware/context/tenant.ts @@ -1,7 +1,7 @@ import { RequestHandler } from "express-jwt"; import { Db } from "mongodb"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import TenantContext from "talk-server/graph/tenant/context"; import { TaskQueue } from "talk-server/queue"; import { AugmentedRedis } from "talk-server/services/redis"; diff --git a/src/core/server/app/middleware/passport/index.ts b/src/core/server/app/middleware/passport/index.ts index 22ecf0a42..4cca7769f 100644 --- a/src/core/server/app/middleware/passport/index.ts +++ b/src/core/server/app/middleware/passport/index.ts @@ -6,13 +6,13 @@ import { Db } from "mongodb"; import passport, { Authenticator } from "passport"; import now from "performance-now"; -import { Config } from "talk-common/config"; import FacebookStrategy from "talk-server/app/middleware/passport/strategies/facebook"; import GoogleStrategy from "talk-server/app/middleware/passport/strategies/google"; import { JWTStrategy } from "talk-server/app/middleware/passport/strategies/jwt"; import { createLocalStrategy } from "talk-server/app/middleware/passport/strategies/local"; import OIDCStrategy from "talk-server/app/middleware/passport/strategies/oidc"; import { validate } from "talk-server/app/request/body"; +import { Config } from "talk-server/config"; import logger from "talk-server/logger"; import { User } from "talk-server/models/user"; import { diff --git a/src/core/server/app/middleware/passport/strategies/facebook.ts b/src/core/server/app/middleware/passport/strategies/facebook.ts index 70442d54b..0034b345a 100644 --- a/src/core/server/app/middleware/passport/strategies/facebook.ts +++ b/src/core/server/app/middleware/passport/strategies/facebook.ts @@ -1,9 +1,9 @@ import { Db } from "mongodb"; import { Profile, Strategy } from "passport-facebook"; -import { Config } from "talk-common/config"; import OAuth2Strategy from "talk-server/app/middleware/passport/strategies/oauth2"; import { constructTenantURL } from "talk-server/app/url"; +import { Config } from "talk-server/config"; import { GQLAuthIntegrations, GQLFacebookAuthIntegration, diff --git a/src/core/server/app/middleware/passport/strategies/google.ts b/src/core/server/app/middleware/passport/strategies/google.ts index 0b6edc56e..bc46c754c 100644 --- a/src/core/server/app/middleware/passport/strategies/google.ts +++ b/src/core/server/app/middleware/passport/strategies/google.ts @@ -1,9 +1,9 @@ import { Db } from "mongodb"; import { Profile, Strategy } from "passport-google-oauth2"; -import { Config } from "talk-common/config"; import OAuth2Strategy from "talk-server/app/middleware/passport/strategies/oauth2"; import { constructTenantURL } from "talk-server/app/url"; +import { Config } from "talk-server/config"; import { GQLAuthIntegrations, GQLGoogleAuthIntegration, diff --git a/src/core/server/app/middleware/passport/strategies/oauth2.ts b/src/core/server/app/middleware/passport/strategies/oauth2.ts index dbcdcfe89..e47d985b9 100644 --- a/src/core/server/app/middleware/passport/strategies/oauth2.ts +++ b/src/core/server/app/middleware/passport/strategies/oauth2.ts @@ -3,7 +3,7 @@ import { Strategy } from "passport-strategy"; import { Profile } from "passport"; import { VerifyCallback } from "passport-oauth2"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { GQLAuthIntegrations } from "talk-server/graph/tenant/schema/__generated__/types"; import { Tenant } from "talk-server/models/tenant"; import { User } from "talk-server/models/user"; diff --git a/src/core/server/app/url.ts b/src/core/server/app/url.ts index 37609fb19..9733fd4ae 100644 --- a/src/core/server/app/url.ts +++ b/src/core/server/app/url.ts @@ -1,4 +1,4 @@ -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { Tenant } from "talk-server/models/tenant"; import { Request } from "talk-server/types/express"; import { URL } from "url"; diff --git a/src/core/common/config.ts b/src/core/server/config.ts similarity index 97% rename from src/core/common/config.ts rename to src/core/server/config.ts index 1ae579a03..b37a2a5d6 100644 --- a/src/core/common/config.ts +++ b/src/core/server/config.ts @@ -129,9 +129,5 @@ const config = convict({ export type Config = typeof config; -export const createClientEnv = (c: Config) => ({ - NODE_ENV: c.get("env"), -}); - // Setup the base configuration. export default config; diff --git a/src/core/server/graph/common/context.ts b/src/core/server/graph/common/context.ts index e229e7f57..147001256 100644 --- a/src/core/server/graph/common/context.ts +++ b/src/core/server/graph/common/context.ts @@ -1,6 +1,6 @@ import uuid from "uuid"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import logger from "talk-server/logger"; import { User } from "talk-server/models/user"; import { Request } from "talk-server/types/express"; diff --git a/src/core/server/graph/common/middleware/index.ts b/src/core/server/graph/common/middleware/index.ts index ac3d451fb..f67a95d29 100644 --- a/src/core/server/graph/common/middleware/index.ts +++ b/src/core/server/graph/common/middleware/index.ts @@ -8,8 +8,8 @@ import { graphqlExpress, } from "apollo-server-express/dist/expressApollo"; -import { Config } from "talk-common/config"; import { Omit } from "talk-common/types"; +import { Config } from "talk-server/config"; import { LoggerExtension } from "talk-server/graph/common/middleware/extensions/logger"; // Sourced from: https://github.com/apollographql/apollo-server/blob/958846887598491fadea57b3f9373d129300f250/packages/apollo-server-core/src/ApolloServer.ts#L46-L57 diff --git a/src/core/server/graph/common/subscriptions/pubsub.ts b/src/core/server/graph/common/subscriptions/pubsub.ts index ed9bf3c4d..64e03c7cd 100644 --- a/src/core/server/graph/common/subscriptions/pubsub.ts +++ b/src/core/server/graph/common/subscriptions/pubsub.ts @@ -1,5 +1,5 @@ import { RedisPubSub } from "graphql-redis-subscriptions"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { createRedisClient } from "talk-server/services/redis"; export function createPubSub(config: Config): RedisPubSub { diff --git a/src/core/server/graph/management/context.ts b/src/core/server/graph/management/context.ts index a384d49b1..9ac404cd8 100644 --- a/src/core/server/graph/management/context.ts +++ b/src/core/server/graph/management/context.ts @@ -1,6 +1,6 @@ import { Db } from "mongodb"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import CommonContext from "talk-server/graph/common/context"; import { Request } from "talk-server/types/express"; diff --git a/src/core/server/graph/management/middleware.ts b/src/core/server/graph/management/middleware.ts index 5e96a6a36..d6847b3ef 100644 --- a/src/core/server/graph/management/middleware.ts +++ b/src/core/server/graph/management/middleware.ts @@ -1,7 +1,7 @@ import { GraphQLSchema } from "graphql"; import { Db } from "mongodb"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { graphqlMiddleware } from "talk-server/graph/common/middleware"; import { Request } from "talk-server/types/express"; diff --git a/src/core/server/graph/tenant/context.ts b/src/core/server/graph/tenant/context.ts index d4e39f62e..08c66f247 100644 --- a/src/core/server/graph/tenant/context.ts +++ b/src/core/server/graph/tenant/context.ts @@ -1,6 +1,6 @@ import { Db } from "mongodb"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import CommonContext from "talk-server/graph/common/context"; import { Tenant } from "talk-server/models/tenant"; import { User } from "talk-server/models/user"; diff --git a/src/core/server/graph/tenant/middleware.ts b/src/core/server/graph/tenant/middleware.ts index b320566ef..69cf11978 100644 --- a/src/core/server/graph/tenant/middleware.ts +++ b/src/core/server/graph/tenant/middleware.ts @@ -1,8 +1,8 @@ import { GraphQLSchema } from "graphql"; // import { graphqlBatchHTTPWrapper } from "react-relay-network-layer"; -import { Config } from "talk-common/config"; import { graphqlBatchMiddleware } from "talk-server/app/middleware/graphqlBatch"; +import { Config } from "talk-server/config"; import { graphqlMiddleware } from "talk-server/graph/common/middleware"; import { Request } from "talk-server/types/express"; diff --git a/src/core/server/graph/tenant/resolvers/AcceptCommentPayload.ts b/src/core/server/graph/tenant/resolvers/AcceptCommentPayload.ts new file mode 100644 index 000000000..adff7abea --- /dev/null +++ b/src/core/server/graph/tenant/resolvers/AcceptCommentPayload.ts @@ -0,0 +1,7 @@ +import { GQLAcceptCommentPayloadTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; + +import { moderationQueuesPayloadResolver } from "./ModerationQueues"; + +export const AcceptCommentPayload: GQLAcceptCommentPayloadTypeResolver = { + moderationQueues: moderationQueuesPayloadResolver, +}; diff --git a/src/core/server/graph/tenant/resolvers/Comment.ts b/src/core/server/graph/tenant/resolvers/Comment.ts index 2c107f07e..ad90e647d 100644 --- a/src/core/server/graph/tenant/resolvers/Comment.ts +++ b/src/core/server/graph/tenant/resolvers/Comment.ts @@ -10,6 +10,7 @@ import { getLatestRevision } from "talk-server/models/comment"; import { createConnection } from "talk-server/models/connection"; import TenantContext from "../context"; +import { getURLWithCommentID } from "./util"; const maybeLoadOnlyID = ( ctx: TenantContext, @@ -77,4 +78,12 @@ export const Comment: GQLCommentTypeResolver<comment.Comment> = { // Some resolver optimization. c.parentID ? ctx.loaders.Comments.parents(c, input) : createConnection(), story: (c, input, ctx) => ctx.loaders.Stories.story.load(c.storyID), + permalink: async (c, input, ctx) => { + const story = await ctx.loaders.Stories.story.load(c.storyID); + if (!story) { + // TODO: better error reporting? + throw new Error("Story not found"); + } + return getURLWithCommentID(story.url, c.id); + }, }; diff --git a/src/core/server/graph/tenant/resolvers/ModerationQueue.ts b/src/core/server/graph/tenant/resolvers/ModerationQueue.ts index 40d0950a2..7c8676616 100644 --- a/src/core/server/graph/tenant/resolvers/ModerationQueue.ts +++ b/src/core/server/graph/tenant/resolvers/ModerationQueue.ts @@ -8,6 +8,7 @@ import { } from "../schema/__generated__/types"; export interface ModerationQueueInput { + selector: string; connection: Partial<CommentConnectionInput>; count: number; } @@ -15,6 +16,14 @@ export interface ModerationQueueInput { export const ModerationQueue: GQLModerationQueueTypeResolver< ModerationQueueInput > = { + id: ({ selector, connection: { filter } }) => { + // NOTE: (wyattjoh) when the queues change shape in the future, investigate adding more dynamicness to this id generation + if (filter && filter.storyID) { + return selector + "::storyID:" + filter.storyID; + } + + return selector; + }, comments: ({ connection }, { first = 10, after }, { mongo, tenant }) => retrieveCommentConnection(mongo, tenant.id, { ...connection, diff --git a/src/core/server/graph/tenant/resolvers/ModerationQueues.ts b/src/core/server/graph/tenant/resolvers/ModerationQueues.ts index 03bc6ca56..f0ac57da4 100644 --- a/src/core/server/graph/tenant/resolvers/ModerationQueues.ts +++ b/src/core/server/graph/tenant/resolvers/ModerationQueues.ts @@ -1,16 +1,24 @@ +import { + AcceptCommentPayloadToModerationQueuesResolver, + GQLModerationQueuesTypeResolver, + RejectCommentPayloadToModerationQueuesResolver, +} from "talk-server/graph/tenant/schema/__generated__/types"; import { CommentConnectionInput } from "talk-server/models/comment"; import { FilterQuery } from "talk-server/models/query"; -import { CommentModerationCountsPerQueue } from "talk-server/models/story"; +import { + CommentModerationCountsPerQueue, + Story, +} from "talk-server/models/story"; import { PENDING_STATUS, REPORTED_STATUS, UNMODERATED_STATUSES, } from "talk-server/services/comments/moderation/counts"; -import { GQLModerationQueuesTypeResolver } from "../schema/__generated__/types"; +import TenantContext from "../context"; import { ModerationQueueInput } from "./ModerationQueue"; -export interface ModerationQueuesInput { +interface ModerationQueuesInput { connection: Partial<CommentConnectionInput>; counts: CommentModerationCountsPerQueue; } @@ -19,6 +27,7 @@ const mergeModerationInputFilters = ( filter: FilterQuery<Comment>, selector: keyof CommentModerationCountsPerQueue ) => (input: ModerationQueuesInput): ModerationQueueInput => ({ + selector, connection: { ...input.connection, filter: { @@ -29,6 +38,72 @@ const mergeModerationInputFilters = ( count: input.counts[selector], }); +/** + * storyModerationInputResolver can be used to retrieve the moderationQueue for + * a specific Story. + * + * @param story the story that will be used to base the comment moderation + * queues on + */ +export const storyModerationInputResolver = ( + story: Story +): ModerationQueuesInput => ({ + connection: { + filter: { + // This moderationQueues is being sourced from the Story, so require + // that all the comments for theses queues are also for this Story. + storyID: story.id, + }, + }, + counts: story.commentCounts.moderationQueue.queues, +}); + +/** + * sharedModerationInputResolver implements the resolver function style which + * allows it to be used in a type resolver. + * + * @param source the source of the type, not used + * @param args the args of the type, not used + * @param ctx the TenantContext that will be used to get the shared counts + */ +export const sharedModerationInputResolver = async ( + source: any, + args: any, + ctx: TenantContext +): Promise<ModerationQueuesInput> => ({ + // We don't need to filter the connection, as this is tenant wide (tenant + // filtering is completed at the model layer). + connection: {}, + counts: await ctx.loaders.Comments.sharedModerationQueueQueuesCounts.load(), +}); + +/** + * moderationQueuesPayloadResolver implements the resolver that can be used for + * moderation actions payloads. + * + * @param source the source of the payload, not used + * @param args the args of the payload containing potentially a Story ID + * @param ctx the TenantContext for which we can use to retrieve the shared data + */ +export const moderationQueuesPayloadResolver: + | AcceptCommentPayloadToModerationQueuesResolver + | RejectCommentPayloadToModerationQueuesResolver = async ( + source, + args, + ctx +): Promise<ModerationQueuesInput | null> => { + if (args.storyID) { + const story = await ctx.loaders.Stories.story.load(args.storyID); + if (!story) { + return null; + } + + return storyModerationInputResolver(story); + } + + return sharedModerationInputResolver(source, args, ctx); +}; + export const ModerationQueues: GQLModerationQueuesTypeResolver< ModerationQueuesInput > = { diff --git a/src/core/server/graph/tenant/resolvers/Query.ts b/src/core/server/graph/tenant/resolvers/Query.ts index 1e6cadbd2..66b147526 100644 --- a/src/core/server/graph/tenant/resolvers/Query.ts +++ b/src/core/server/graph/tenant/resolvers/Query.ts @@ -1,6 +1,6 @@ import { GQLQueryTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; -import { ModerationQueuesInput } from "./ModerationQueues"; +import { sharedModerationInputResolver } from "./ModerationQueues"; export const Query: GQLQueryTypeResolver<void> = { story: (source, args, ctx) => ctx.loaders.Stories.findOrCreate.load(args), @@ -13,14 +13,5 @@ export const Query: GQLQueryTypeResolver<void> = { ctx.loaders.Auth.discoverOIDCConfiguration.load(issuer), debugScrapeStoryMetadata: (source, { url }, ctx) => ctx.loaders.Stories.debugScrapeMetadata.load(url), - moderationQueues: async ( - source, - args, - ctx - ): Promise<ModerationQueuesInput> => ({ - // We don't need to filter the connection, as this is tenant wide (tenant - // filtering is completed at the model layer). - connection: {}, - counts: await ctx.loaders.Comments.sharedModerationQueueQueuesCounts.load(), - }), + moderationQueues: sharedModerationInputResolver, }; diff --git a/src/core/server/graph/tenant/resolvers/RejectCommentPayload.ts b/src/core/server/graph/tenant/resolvers/RejectCommentPayload.ts new file mode 100644 index 000000000..dc3eb6243 --- /dev/null +++ b/src/core/server/graph/tenant/resolvers/RejectCommentPayload.ts @@ -0,0 +1,7 @@ +import { GQLRejectCommentPayloadTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; + +import { moderationQueuesPayloadResolver } from "./ModerationQueues"; + +export const RejectCommentPayload: GQLRejectCommentPayloadTypeResolver = { + moderationQueues: moderationQueuesPayloadResolver, +}; diff --git a/src/core/server/graph/tenant/resolvers/Story.ts b/src/core/server/graph/tenant/resolvers/Story.ts index b9cba2f53..4683cd892 100644 --- a/src/core/server/graph/tenant/resolvers/Story.ts +++ b/src/core/server/graph/tenant/resolvers/Story.ts @@ -3,7 +3,8 @@ import { DateTime } from "luxon"; import { GQLStoryTypeResolver } from "talk-server/graph/tenant/schema/__generated__/types"; import { decodeActionCounts } from "talk-server/models/action/comment"; import * as story from "talk-server/models/story"; -import { ModerationQueuesInput } from "./ModerationQueues"; + +import { storyModerationInputResolver } from "./ModerationQueues"; export const Story: GQLStoryTypeResolver<story.Story> = { comments: (s, input, ctx) => ctx.loaders.Comments.forStory(s.id, input), @@ -23,14 +24,5 @@ export const Story: GQLStoryTypeResolver<story.Story> = { }, commentActionCounts: s => decodeActionCounts(s.commentCounts.action), commentCounts: s => s.commentCounts.status, - moderationQueues: (s): ModerationQueuesInput => ({ - connection: { - filter: { - // This moderationQueues is being sourced from the Story, so require - // that all the comments for theses queues are also for this Story. - storyID: s.id, - }, - }, - counts: s.commentCounts.moderationQueue.queues, - }), + moderationQueues: storyModerationInputResolver, }; diff --git a/src/core/server/graph/tenant/resolvers/index.ts b/src/core/server/graph/tenant/resolvers/index.ts index e131b355d..b4a03bef6 100644 --- a/src/core/server/graph/tenant/resolvers/index.ts +++ b/src/core/server/graph/tenant/resolvers/index.ts @@ -3,6 +3,7 @@ import Time from "talk-server/graph/common/scalars/time"; import { GQLResolver } from "talk-server/graph/tenant/schema/__generated__/types"; +import { AcceptCommentPayload } from "./AcceptCommentPayload"; import { AuthIntegrations } from "./AuthIntegrations"; import { Comment } from "./Comment"; import { CommentCounts } from "./CommentCounts"; @@ -16,10 +17,12 @@ import { Mutation } from "./Mutation"; import { OIDCAuthIntegration } from "./OIDCAuthIntegration"; import { Profile } from "./Profile"; import { Query } from "./Query"; +import { RejectCommentPayload } from "./RejectCommentPayload"; import { Story } from "./Story"; import { User } from "./User"; const Resolvers: GQLResolver = { + AcceptCommentPayload, AuthIntegrations, Comment, CommentCounts, @@ -34,6 +37,7 @@ const Resolvers: GQLResolver = { GoogleAuthIntegration, Profile, Query, + RejectCommentPayload, Time, Story, User, diff --git a/src/core/server/graph/tenant/resolvers/util.ts b/src/core/server/graph/tenant/resolvers/util.ts index f35b4789d..c93f5c9eb 100644 --- a/src/core/server/graph/tenant/resolvers/util.ts +++ b/src/core/server/graph/tenant/resolvers/util.ts @@ -1,6 +1,8 @@ import { GraphQLResolveInfo } from "graphql"; import graphqlFields from "graphql-fields"; import { pull } from "lodash"; +import { parseQuery, stringifyQuery } from "talk-common/utils"; +import { URL } from "url"; /** * getRequestedFields returns the fields in an array that are being queried for. @@ -10,3 +12,17 @@ import { pull } from "lodash"; export function getRequestedFields<T>(info: GraphQLResolveInfo) { return pull(Object.keys(graphqlFields<T>(info)), "__typename"); } + +/** + * getURLWithCommentID returns the url with the comment id. + * + * @param storyURL url of the story + * @param commentID id of the comment + */ +export function getURLWithCommentID(storyURL: string, commentID?: string) { + const url = new URL(storyURL); + const query = parseQuery(url.search); + url.search = stringifyQuery({ ...query, commentID }); + + return url.toString(); +} diff --git a/src/core/server/graph/tenant/schema/schema.graphql b/src/core/server/graph/tenant/schema/schema.graphql index d4bfb239b..f8b038724 100644 --- a/src/core/server/graph/tenant/schema/schema.graphql +++ b/src/core/server/graph/tenant/schema/schema.graphql @@ -248,6 +248,11 @@ type WordList { ModerationQueue returns the Comments associated with a Moderation Queue. """ type ModerationQueue { + """ + id is a canonical identifier for this specific moderation queue. + """ + id: ID! + """ count will return the number of Comments that are in this queue. """ @@ -782,6 +787,11 @@ type ReactionConfiguration { Settings stores the global settings for a given Tenant. """ type Settings { + """ + id is a canonical identifier for this Tenant. + """ + id: ID! + """ domain is the domain that is associated with this Tenant. """ @@ -1268,6 +1278,11 @@ type Comment { story is the Story that the Comment was written on. """ story: Story! + + """ + The permalink for this comment. + """ + permalink: String! } type PageInfo { @@ -1463,7 +1478,7 @@ type Story { moderationQueues returns the set of ModerationQueues that are available for this Story. """ - moderationQueues: ModerationQueues @auth(roles: [ADMIN, MODERATOR]) + moderationQueues: ModerationQueues! @auth(roles: [ADMIN, MODERATOR]) """ closedAt is the Time that the Story is closed for commenting. @@ -1591,7 +1606,7 @@ type Query { moderationQueues returns the set of ModerationQueues that are available for all stories. """ - moderationQueues: ModerationQueues @auth(roles: [ADMIN, MODERATOR]) + moderationQueues: ModerationQueues! @auth(roles: [ADMIN, MODERATOR]) } ################################################################################ @@ -2867,6 +2882,13 @@ type AcceptCommentPayload { """ comment: Comment + """ + moderationQueues will return the selected moderation queues. If the `storyID` + is provided, it will filter the moderation queues for only comments in that + Story. + """ + moderationQueues(storyID: ID): ModerationQueues + """ clientMutationId is required for Relay support. """ @@ -2900,6 +2922,13 @@ type RejectCommentPayload { """ comment: Comment + """ + moderationQueues will return the selected moderation queues. If the `storyID` + is provided, it will filter the moderation queues for only comments in that + Story. + """ + moderationQueues(storyID: ID): ModerationQueues + """ clientMutationId is required for Relay support. """ diff --git a/src/core/server/index.ts b/src/core/server/index.ts index 44e5da226..0a016f09f 100644 --- a/src/core/server/index.ts +++ b/src/core/server/index.ts @@ -1,7 +1,6 @@ import express, { Express } from "express"; import http from "http"; -import config, { Config } from "talk-common/config"; import getManagementSchema from "talk-server/graph/management/schema"; import { Schemas } from "talk-server/graph/schemas"; import getTenantSchema from "talk-server/graph/tenant/schema"; @@ -10,6 +9,7 @@ import TenantCache from "talk-server/services/tenant/cache"; import { createJWTSigningConfig } from "talk-server/services/jwt"; import { attachSubscriptionHandlers, createApp, listenAndServe } from "./app"; +import config, { Config } from "./config"; import logger from "./logger"; import { createMongoDB } from "./services/mongodb"; import { createRedisClient } from "./services/redis"; diff --git a/src/core/server/logger.ts b/src/core/server/logger.ts index 7a2128124..e5ec52f22 100644 --- a/src/core/server/logger.ts +++ b/src/core/server/logger.ts @@ -1,7 +1,7 @@ import bunyan, { LogLevelString, stdSerializers as serializers } from "bunyan"; import PrettyStream from "bunyan-prettystream"; -import config from "talk-common/config"; +import config from "talk-server/config"; function getStreams() { // If we aren't in production mode, use the pretty stream printer. diff --git a/src/core/server/queue/index.ts b/src/core/server/queue/index.ts index 54b307074..2bac5e778 100644 --- a/src/core/server/queue/index.ts +++ b/src/core/server/queue/index.ts @@ -1,7 +1,7 @@ import Queue from "bull"; import { Db } from "mongodb"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import Task from "talk-server/queue/Task"; import { createMailerTask, Mailer } from "talk-server/queue/tasks/mailer"; import { diff --git a/src/core/server/queue/tasks/mailer/content.ts b/src/core/server/queue/tasks/mailer/content.ts index d9b8c476b..9505c49dc 100644 --- a/src/core/server/queue/tasks/mailer/content.ts +++ b/src/core/server/queue/tasks/mailer/content.ts @@ -1,7 +1,7 @@ import nunjucks from "nunjucks"; import path from "path"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; /** * templateDirectory is the directory containing the email templates. diff --git a/src/core/server/queue/tasks/mailer/index.ts b/src/core/server/queue/tasks/mailer/index.ts index 13cb5de32..df8184ba8 100644 --- a/src/core/server/queue/tasks/mailer/index.ts +++ b/src/core/server/queue/tasks/mailer/index.ts @@ -4,7 +4,7 @@ import Joi from "joi"; import { Db } from "mongodb"; import { createTransport } from "nodemailer"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import logger from "talk-server/logger"; import Task from "talk-server/queue/Task"; import MailerContent from "talk-server/queue/tasks/mailer/content"; diff --git a/src/core/server/services/jwt/index.spec.ts b/src/core/server/services/jwt/index.spec.ts index e2179221f..27ec09b68 100644 --- a/src/core/server/services/jwt/index.spec.ts +++ b/src/core/server/services/jwt/index.spec.ts @@ -1,6 +1,6 @@ import sinon from "sinon"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { createJWTSigningConfig, extractJWTFromRequest, diff --git a/src/core/server/services/jwt/index.ts b/src/core/server/services/jwt/index.ts index 597e164b4..8adefb966 100644 --- a/src/core/server/services/jwt/index.ts +++ b/src/core/server/services/jwt/index.ts @@ -3,7 +3,7 @@ import jwt, { SignOptions } from "jsonwebtoken"; import { Bearer } from "permit"; import uuid from "uuid/v4"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { User } from "talk-server/models/user"; import { Request } from "talk-server/types/express"; diff --git a/src/core/server/services/jwt/jwt.spec.ts b/src/core/server/services/jwt/jwt.spec.ts index 31c65dbcb..4a3adfc1d 100644 --- a/src/core/server/services/jwt/jwt.spec.ts +++ b/src/core/server/services/jwt/jwt.spec.ts @@ -1,6 +1,6 @@ import sinon from "sinon"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import { Request } from "talk-server/types/express"; import { createJWTSigningConfig, extractJWTFromRequest } from "."; diff --git a/src/core/server/services/mongodb/index.ts b/src/core/server/services/mongodb/index.ts index 866bb8502..1486d4da4 100644 --- a/src/core/server/services/mongodb/index.ts +++ b/src/core/server/services/mongodb/index.ts @@ -1,5 +1,5 @@ import { Db, MongoClient } from "mongodb"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; export async function createMongoClient(config: Config): Promise<MongoClient> { return MongoClient.connect( diff --git a/src/core/server/services/redis/index.ts b/src/core/server/services/redis/index.ts index 8fe20a0fa..44f4dc02c 100644 --- a/src/core/server/services/redis/index.ts +++ b/src/core/server/services/redis/index.ts @@ -1,7 +1,7 @@ import RedisClient, { Pipeline, Redis } from "ioredis"; -import { Config } from "talk-common/config"; import { Omit } from "talk-common/types"; +import { Config } from "talk-server/config"; export interface AugmentedRedisCommands { mhincrby(key: string, ...args: any[]): Promise<void>; diff --git a/src/core/server/services/tenant/cache/index.ts b/src/core/server/services/tenant/cache/index.ts index 03a826e9a..caffb4b4a 100644 --- a/src/core/server/services/tenant/cache/index.ts +++ b/src/core/server/services/tenant/cache/index.ts @@ -4,7 +4,7 @@ import { Db } from "mongodb"; import uuid from "uuid"; import { EventEmitter } from "events"; -import { Config } from "talk-common/config"; +import { Config } from "talk-server/config"; import logger from "talk-server/logger"; import { countTenants, diff --git a/src/locales/en-US/admin.ftl b/src/locales/en-US/admin.ftl index 31bffddb1..271611af5 100644 --- a/src/locales/en-US/admin.ftl +++ b/src/locales/en-US/admin.ftl @@ -5,10 +5,10 @@ general-brandName = { -product-name } ## Navigation -navigation-moderate = moderate -navigation-community = community -navigation-stories = stories -navigation-configure = configure +navigation-moderate = Moderate +navigation-community = Community +navigation-stories = Stories +navigation-configure = Configure navigation-signOutButton = Sign Out ## Login @@ -119,3 +119,30 @@ decisionHistory-yourDecisionHistory = Your Decision History decisionHistory-rejectedCommentBy = Rejected comment by <username></username> decisionHistory-acceptedCommentBy = Accepted comment by <username></username> decisionHistory-goToComment = Go to comment + +## moderate +moderate-navigation-reported = reported +moderate-navigation-pending = Pending +moderate-navigation-unmoderated = unmoderated +moderate-navigation-rejected = rejected + +moderate-marker-preMod = Pre-Mod +moderate-marker-link = Link +moderate-marker-bannedWord = Banned Word +moderate-marker-suspectWord = Suspect Word +moderate-marker-spam = Spam +moderate-marker-toxic = Toxic +moderate-marker-karma = Karma +moderate-marker-bodyCount = Body Count +moderate-marker-offensive = Offensive + +moderate-inReplyTo = Reply to <username><username> +moderate-viewContext = View Context +moderate-rejectButton = + .aria-label = Reject +moderate-acceptButton = + .aria-label = Accept +moderate-decision = Decision + +moderate-single-goToModerationQueues = Go to moderation queues +moderate-single-singleCommentView = Single Comment View diff --git a/src/types/linkifyjs.d.ts b/src/types/linkifyjs.d.ts new file mode 100644 index 000000000..01277f164 --- /dev/null +++ b/src/types/linkifyjs.d.ts @@ -0,0 +1,4 @@ +declare module "linkifyjs/html" { + function linkify(html: string, options?: any): string; + export default linkify; +} diff --git a/src/types/optimize-cssnano-plugin.d.ts b/src/types/optimize-cssnano-plugin.d.ts new file mode 100644 index 000000000..2a348b679 --- /dev/null +++ b/src/types/optimize-cssnano-plugin.d.ts @@ -0,0 +1 @@ +declare module "@intervolga/optimize-cssnano-plugin"; diff --git a/src/types/webpack-deep-scope-plugin.d.ts b/src/types/webpack-deep-scope-plugin.d.ts deleted file mode 100644 index a57d47fd6..000000000 --- a/src/types/webpack-deep-scope-plugin.d.ts +++ /dev/null @@ -1 +0,0 @@ -declare module "webpack-deep-scope-plugin";