mirror of
https://github.com/wassname/talk.git
synced 2026-09-13 13:10:43 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
871d3f80c1 | ||
|
|
f7d01174d7 | ||
|
|
6966bb8d12 | ||
|
|
40ad2ed0cb | ||
|
|
802f656caa | ||
|
|
dfdea2b9d2 | ||
|
|
9a9c92d360 | ||
|
|
ceadcc7ae1 | ||
|
|
e0db803a21 | ||
|
|
7a099bbf05 | ||
|
|
51112a11c0 | ||
|
|
6e50b28d8b | ||
|
|
1f72cdbfb3 | ||
|
|
87aba459d6 | ||
|
|
9aa4591655 | ||
|
|
87d32680f0 | ||
|
|
25a1dcdf8d | ||
|
|
e90272aa58 | ||
|
|
bbbff98270 | ||
|
|
fe19ffd7e1 | ||
|
|
7f98f13990 | ||
|
|
1a9bd27236 | ||
|
|
536e804d61 | ||
|
|
43d5d56c90 | ||
|
|
cdcc991255 | ||
|
|
b221baff82 | ||
|
|
5b589bfe2b | ||
|
|
4f5c0e285c | ||
|
|
f26ab60398 | ||
|
|
e7f395190d | ||
|
|
ace252eb72 | ||
|
|
933d82e7c9 | ||
|
|
6e64e6a01c | ||
|
|
bb48e1fe33 | ||
|
|
58a91c2e42 | ||
|
|
6a5a18e3a4 | ||
|
|
d43fbe357a | ||
|
|
5c863f1307 | ||
|
|
d4cb9cde9b | ||
|
|
c59c345756 | ||
|
|
f8234e53ed | ||
|
|
7b2cdbad49 | ||
|
|
00e074d49d | ||
|
|
d848fc193e | ||
|
|
2acc761a5d | ||
|
|
0db5bc846d | ||
|
|
e57940c418 | ||
|
|
ccb8f1defa | ||
|
|
878c3bb3c0 | ||
|
|
86c81c7b9f | ||
|
|
302613a185 | ||
|
|
bf04b4c2a6 | ||
|
|
f3ef9ef778 | ||
|
|
db8c9de0e7 | ||
|
|
f2bf026740 | ||
|
|
625c38a758 | ||
|
|
9022532525 | ||
|
|
caa4895b97 | ||
|
|
d1ee7d39f3 | ||
|
|
feaf4858df | ||
|
|
ebe7731222 | ||
|
|
8966dad344 | ||
|
|
4179306cd0 | ||
|
|
2c0153047e | ||
|
|
21cd47f6ed | ||
|
|
2f03b788a4 | ||
|
|
c19f4a35ca | ||
|
|
0fa27ae41b | ||
|
|
0065875f12 | ||
|
|
b39af1f7e6 | ||
|
|
a949d7e881 | ||
|
|
eee909f05b | ||
|
|
72e1b249b1 | ||
|
|
ba08447d5e | ||
|
|
92c72f7041 | ||
|
|
08a9788bcc | ||
|
|
3e0241b523 | ||
|
|
fe8cb90a1c | ||
|
|
74366bec55 | ||
|
|
ed92f4916d | ||
|
|
ceb96dba75 | ||
|
|
f73597d7d1 | ||
|
|
79e0da2c2f | ||
|
|
1f69bb2f14 | ||
|
|
d0ab2ef8c5 | ||
|
|
1624787004 | ||
|
|
8ea88f9b96 | ||
|
|
45873398dd | ||
|
|
739d5b6611 | ||
|
|
192c3486af | ||
|
|
0e9621c499 | ||
|
|
5b92f7e968 | ||
|
|
c9a0ab8848 | ||
|
|
e180cf87d7 | ||
|
|
de976a5397 | ||
|
|
257830a6c5 | ||
|
|
518056f382 | ||
|
|
84936ca35c | ||
|
|
4194b08a12 | ||
|
|
98e6a3ccc7 | ||
|
|
64f5171ff2 | ||
|
|
6711f09a79 | ||
|
|
8966a8201b | ||
|
|
7fc0e2e489 | ||
|
|
7c3c510bbc | ||
|
|
94ece10284 | ||
|
|
ef01f427de | ||
|
|
5db096942a | ||
|
|
4645ed4cd7 | ||
|
|
7fd524cd6a | ||
|
|
3cb5d0ff9f | ||
|
|
44216221a4 | ||
|
|
0f9b2ba114 | ||
|
|
071666e8b8 | ||
|
|
2a9a5099dc | ||
|
|
d0f51d8815 | ||
|
|
b228725370 | ||
|
|
58f2f71743 | ||
|
|
8fd266deeb | ||
|
|
1b73a5fcd7 | ||
|
|
9a58673545 | ||
|
|
2dc63d3a27 | ||
|
|
1503b735e5 | ||
|
|
6db72db837 | ||
|
|
18b274a6a9 | ||
|
|
78cfb4cac8 | ||
|
|
d513e3807a | ||
|
|
faf4ad9480 | ||
|
|
e5ea1ee179 | ||
|
|
75c0bad073 | ||
|
|
8b92878e9b | ||
|
|
2b2ff7b9c4 | ||
|
|
2057b750f3 | ||
|
|
7abd703f72 | ||
|
|
6f1014289b | ||
|
|
45b778c522 | ||
|
|
7d967fc93b | ||
|
|
ae7b0fbb37 | ||
|
|
2478e9a250 | ||
|
|
fe8b6535a9 | ||
|
|
7989f87d02 | ||
|
|
769ab2a910 | ||
|
|
c052d37a6f | ||
|
|
a059e70d05 | ||
|
|
1a3401710f | ||
|
|
184f145c4a | ||
|
|
dcb2a10e72 | ||
|
|
7497e33046 | ||
|
|
330c30f1b3 | ||
|
|
e2185a33f5 | ||
|
|
d6db287c55 | ||
|
|
ca52cc3253 | ||
|
|
c2dadcc034 | ||
|
|
6865a71684 | ||
|
|
33556044a8 | ||
|
|
d883ab029c | ||
|
|
4b637a2dd5 | ||
|
|
e42c2b925d | ||
|
|
34ba2da88d | ||
|
|
cec76e0046 | ||
|
|
707d65a119 | ||
|
|
014aa2d86a | ||
|
|
94e1c75c0b | ||
|
|
3014017976 | ||
|
|
1388e0bc5e | ||
|
|
86191e388a | ||
|
|
2bf3e34218 | ||
|
|
75818cf808 | ||
|
|
503eb0b06a | ||
|
|
9cc3a57295 | ||
|
|
e42cb59661 | ||
|
|
a1a8652f7e | ||
|
|
a7b2af85fc | ||
|
|
516611e289 | ||
|
|
190010c898 | ||
|
|
2b1c9aea78 | ||
|
|
9c4a9753bb | ||
|
|
e43a3ddf75 | ||
|
|
6364eced63 | ||
|
|
62ce3a2c49 | ||
|
|
48d815aebb | ||
|
|
38ccbd0ef8 | ||
|
|
cfebe7733e | ||
|
|
e7b83c6b39 | ||
|
|
5c46884a97 | ||
|
|
4bd929f3f0 | ||
|
|
785735d36a | ||
|
|
20da16331e | ||
|
|
a95694dff6 | ||
|
|
f58f65c7ae | ||
|
|
22516457ef | ||
|
|
533d7633e1 | ||
|
|
e8fb234838 | ||
|
|
9b8ab6de5f | ||
|
|
a88644d98e | ||
|
|
745fb4056c | ||
|
|
a582773e1d | ||
|
|
6057edcb07 | ||
|
|
852466e9a1 | ||
|
|
f53db233ce | ||
|
|
31cf9a4f7d | ||
|
|
81338d1386 | ||
|
|
84bbc3d0c3 | ||
|
|
d26e331a4f | ||
|
|
e3e2e0f52e | ||
|
|
0dc3e8968a | ||
|
|
f900050ef4 | ||
|
|
0f456330c5 | ||
|
|
9d050e8b51 | ||
|
|
7581f0201d | ||
|
|
86b2ec1e56 | ||
|
|
fd1ee39db4 | ||
|
|
8c6f12c24d | ||
|
|
11a4a5ebe3 | ||
|
|
f3ecbf5dc6 | ||
|
|
116d3c0c81 | ||
|
|
3358cef433 | ||
|
|
ec93789cff | ||
|
|
f60fac651b | ||
|
|
514a549a77 | ||
|
|
88f6cad840 | ||
|
|
7615dc2aaf | ||
|
|
51bfde8cf8 | ||
|
|
5ebc39a2e1 | ||
|
|
d5020efa70 | ||
|
|
a396efef0d | ||
|
|
9618e5dab1 | ||
|
|
54296fa484 | ||
|
|
c109188efe | ||
|
|
08ead49629 | ||
|
|
bc27d7fbec | ||
|
|
4ee8cf7c96 | ||
|
|
0e15bc46d2 | ||
|
|
9bc41ad020 | ||
|
|
f1cdcb149b | ||
|
|
bf89fc2b3e | ||
|
|
7c2e56b751 | ||
|
|
4542dbdb43 | ||
|
|
787f1c68e7 | ||
|
|
09cf8c5490 | ||
|
|
ddedf4dc7e | ||
|
|
ca811a3496 | ||
|
|
10719bd874 | ||
|
|
bb23c80004 | ||
|
|
2491445579 | ||
|
|
3642c642d6 | ||
|
|
fce7b08ca0 | ||
|
|
aa9dcb4e04 |
+19
-13
@@ -1,19 +1,19 @@
|
||||
# job_environment will setup the environment for any job being executed.
|
||||
job_environment: &job_environment
|
||||
NODE_ENV: test
|
||||
WEBPACK_MAX_CORES: 4
|
||||
NODE_OPTIONS: --max-old-space-size=8192
|
||||
NODE_ENV: "test"
|
||||
WEBPACK_MAX_CORES: "4"
|
||||
NODE_OPTIONS: "--max-old-space-size=8192"
|
||||
|
||||
# job_defaults applies all the defaults for each job.
|
||||
job_defaults: &job_defaults
|
||||
working_directory: ~/coralproject/talk
|
||||
resource_class: large
|
||||
docker:
|
||||
- image: circleci/node:10
|
||||
- image: circleci/node:12
|
||||
environment:
|
||||
<<: *job_environment
|
||||
|
||||
version: 2
|
||||
version: 2.1
|
||||
jobs:
|
||||
# npm_dependencies will install the dependencies used by all other steps.
|
||||
npm_dependencies:
|
||||
@@ -49,7 +49,8 @@ jobs:
|
||||
- ~/.npm
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths: node_modules
|
||||
paths:
|
||||
- node_modules
|
||||
|
||||
# lint will perform file linting.
|
||||
lint:
|
||||
@@ -65,18 +66,20 @@ jobs:
|
||||
name: Lint Source Code
|
||||
command: npm run lint
|
||||
- run:
|
||||
name: Lint README.md
|
||||
name: Lint Markdown
|
||||
command: |
|
||||
cp README.md README.md.orig
|
||||
npm run doctoc
|
||||
diff -q README.md README.md.orig
|
||||
git diff --exit-code
|
||||
- run:
|
||||
name: Lint Versions
|
||||
command: npx @coralproject/package-version-lint
|
||||
|
||||
# unit_tests will run the unit tests.
|
||||
unit_tests:
|
||||
<<: *job_defaults
|
||||
environment:
|
||||
<<: *job_environment
|
||||
CI: true
|
||||
CI: "true"
|
||||
JEST_JUNIT_OUTPUT: "reports/junit/js-test-results.xml"
|
||||
steps:
|
||||
- checkout
|
||||
@@ -113,14 +116,15 @@ jobs:
|
||||
no_output_timeout: 30m
|
||||
- run:
|
||||
name: Verify Bundle Size
|
||||
command: npx bundlesize
|
||||
command: npx bundlesize2 --enable-github-checks
|
||||
- save_cache:
|
||||
key: v1-build-cache-{{ .Branch }}-{{ .Revision }}
|
||||
paths:
|
||||
- ./dist
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths: dist
|
||||
paths:
|
||||
- dist
|
||||
|
||||
# docker_tests will test that the docker build process completes.
|
||||
docker_tests:
|
||||
@@ -142,6 +146,9 @@ jobs:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout
|
||||
- run:
|
||||
name: Verify release version
|
||||
command: npx @coralproject/package-version-lint --expect ${CIRCLE_TAG/#v}
|
||||
- setup_remote_docker
|
||||
- deploy:
|
||||
name: Deploy the code
|
||||
@@ -168,7 +175,6 @@ filter_develop: &filter_develop
|
||||
- next
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build-test:
|
||||
jobs:
|
||||
- docker_tests:
|
||||
|
||||
@@ -27,4 +27,3 @@ yarn.lock
|
||||
dist
|
||||
*.css.d.ts
|
||||
__generated__
|
||||
README.md.orig
|
||||
|
||||
+83
-51
@@ -1,10 +1,11 @@
|
||||
const typescriptEslintRecommended = require('@typescript-eslint/eslint-plugin/dist/configs/eslint-recommended').default.overrides[0];
|
||||
const typescriptRecommended = require('@typescript-eslint/eslint-plugin/dist/configs/recommended.json');
|
||||
const typescriptRecommendedTypeChecking = require('@typescript-eslint/eslint-plugin/dist/configs/recommended-requiring-type-checking.json');
|
||||
const typescriptEslintPrettier = require('eslint-config-prettier/@typescript-eslint');
|
||||
const react = require('eslint-plugin-react').configs.recommended;
|
||||
const jsxA11y = require('eslint-plugin-jsx-a11y').configs.recommended;
|
||||
const reactPrettier = require('eslint-config-prettier/react');
|
||||
const typescriptEslintRecommended = require("@typescript-eslint/eslint-plugin/dist/configs/eslint-recommended")
|
||||
.overrides[0];
|
||||
const typescriptRecommended = require("@typescript-eslint/eslint-plugin/dist/configs/recommended.js");
|
||||
const typescriptRecommendedTypeChecking = require("@typescript-eslint/eslint-plugin/dist/configs/recommended-requiring-type-checking.js");
|
||||
const typescriptEslintPrettier = require("eslint-config-prettier/@typescript-eslint");
|
||||
const react = require("eslint-plugin-react").configs.recommended;
|
||||
const jsxA11y = require("eslint-plugin-jsx-a11y").configs.recommended;
|
||||
const reactPrettier = require("eslint-config-prettier/react");
|
||||
|
||||
const typescriptOverrides = {
|
||||
files: ["*.ts", "*.tsx"],
|
||||
@@ -24,7 +25,7 @@ const typescriptOverrides = {
|
||||
settings: {
|
||||
react: {
|
||||
version: "detect",
|
||||
}
|
||||
},
|
||||
},
|
||||
rules: Object.assign(
|
||||
typescriptEslintRecommended.rules,
|
||||
@@ -36,32 +37,45 @@ const typescriptOverrides = {
|
||||
{
|
||||
"@typescript-eslint/adjacent-overload-signatures": "error",
|
||||
// TODO: (cvle) change `readonly` param to `array-simple` when upgraded typescript.
|
||||
"@typescript-eslint/array-type": ["error", { "default": "array-simple", "readonly": "generic"}],
|
||||
"@typescript-eslint/ban-types": "error",
|
||||
"@typescript-eslint/array-type": [
|
||||
"error",
|
||||
{ default: "array-simple", readonly: "generic" },
|
||||
],
|
||||
"@typescript-eslint/ban-types": [
|
||||
"error",
|
||||
{
|
||||
types: {
|
||||
"{}": false,
|
||||
object: false,
|
||||
extendDefaults: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/camelcase": "off",
|
||||
"@typescript-eslint/consistent-type-assertions": "error",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"@typescript-eslint/class-name-casing": "error",
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/explicit-module-boundary-types": "off",
|
||||
"@typescript-eslint/explicit-member-accessibility": [
|
||||
"error",
|
||||
{
|
||||
"overrides": {
|
||||
"constructors": "off",
|
||||
overrides: {
|
||||
constructors: "off",
|
||||
},
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/indent": "off",
|
||||
"@typescript-eslint/interface-name-prefix": "error",
|
||||
"@typescript-eslint/member-delimiter-style": "off",
|
||||
"@typescript-eslint/no-empty-function": "error",
|
||||
"@typescript-eslint/no-empty-function": "off",
|
||||
"@typescript-eslint/no-empty-interface": "error",
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
"@typescript-eslint/no-misused-new": "error",
|
||||
"@typescript-eslint/no-namespace": "error",
|
||||
"@typescript-eslint/no-non-null-assertion": "off",
|
||||
"@typescript-eslint/no-parameter-properties": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {"args": "none", "ignoreRestSiblings": true}],
|
||||
"@typescript-eslint/no-unused-vars": [
|
||||
"error",
|
||||
{ args: "none", ignoreRestSiblings: true },
|
||||
],
|
||||
"@typescript-eslint/no-use-before-define": "off", // TODO: (cvle) Should be on?
|
||||
"@typescript-eslint/no-use-before-declare": "off",
|
||||
"@typescript-eslint/no-var-requires": "error",
|
||||
@@ -77,36 +91,59 @@ const typescriptOverrides = {
|
||||
"react/display-name": "error",
|
||||
"react/prop-types": "off",
|
||||
"react/no-unescaped-entities": "off",
|
||||
"no-empty-function": "off",
|
||||
// (tessalt) disabled because video elements are only used to display gifs, which have no audio
|
||||
"jsx-a11y/media-has-caption": "off",
|
||||
}
|
||||
),
|
||||
};
|
||||
|
||||
let typescriptTypeCheckingOverrides = {
|
||||
const jestTypeCheckingOverrides = {
|
||||
files: ["test/**/*.ts", "test/**/*.tsx"],
|
||||
rules: {
|
||||
"@typescript-eslint/no-floating-promises": "off",
|
||||
},
|
||||
};
|
||||
|
||||
const typescriptTypeCheckingOverrides = {
|
||||
files: ["*.ts", "*.tsx"],
|
||||
parserOptions: {
|
||||
project: ["tsconfig.json", "./src/tsconfig.json", "./src/core/client/tsconfig.json"],
|
||||
project: [
|
||||
"./tsconfig.json",
|
||||
"./src/tsconfig.json",
|
||||
"./src/core/client/tsconfig.json",
|
||||
],
|
||||
// TODO: (cvle) this is a workaround, see: https://github.com/typescript-eslint/typescript-eslint/issues/1091.
|
||||
createDefaultProgram: true,
|
||||
},
|
||||
rules: Object.assign(
|
||||
typescriptRecommendedTypeChecking.rules,
|
||||
{
|
||||
"@typescript-eslint/tslint/config": ["error", {
|
||||
"rules": {
|
||||
rules: Object.assign(typescriptRecommendedTypeChecking.rules, {
|
||||
"@typescript-eslint/tslint/config": [
|
||||
"error",
|
||||
{
|
||||
rules: {
|
||||
"ordered-imports": {
|
||||
"options": {
|
||||
"import-sources-order": "case-insensitive",
|
||||
options: {
|
||||
// Legacy sorting until this is fixed: https://github.com/SoominHan/import-sorter/issues/60
|
||||
"import-sources-order": "case-insensitive-legacy",
|
||||
"module-source-path": "full",
|
||||
"named-imports-order": "case-insensitive",
|
||||
"named-imports-order": "case-insensitive-legacy",
|
||||
},
|
||||
},
|
||||
},
|
||||
}],
|
||||
"@typescript-eslint/require-await": "off",
|
||||
"@typescript-eslint/no-misused-promises": "off",
|
||||
"@typescript-eslint/unbound-method": "off", // 10.10.19: (cvle) seems to give false positive.
|
||||
}
|
||||
),
|
||||
},
|
||||
],
|
||||
"@typescript-eslint/no-misused-promises": "off",
|
||||
"@typescript-eslint/no-unsafe-assignment": "off",
|
||||
"@typescript-eslint/no-unsafe-member-access": "off",
|
||||
"@typescript-eslint/no-unsafe-call": "off",
|
||||
"@typescript-eslint/no-unsafe-return": "off",
|
||||
// 28.11.19: (cvle) Disabled because behavior of regexp.exec seems different than str.match?
|
||||
"@typescript-eslint/prefer-regexp-exec": "off",
|
||||
"@typescript-eslint/require-await": "off",
|
||||
"@typescript-eslint/restrict-template-expressions": "off",
|
||||
"@typescript-eslint/unbound-method": "off", // 10.10.19: (cvle) seems to give false positive.
|
||||
}),
|
||||
overrides: [jestTypeCheckingOverrides],
|
||||
};
|
||||
|
||||
const jestOverrides = {
|
||||
@@ -115,8 +152,8 @@ const jestOverrides = {
|
||||
},
|
||||
files: ["test/**/*.ts", "test/**/*.tsx"],
|
||||
globals: {
|
||||
"expectAndFail": "readonly",
|
||||
"fail": "readonly",
|
||||
expectAndFail: "readonly",
|
||||
fail: "readonly",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -140,33 +177,28 @@ module.exports = {
|
||||
"plugin:prettier/recommended",
|
||||
],
|
||||
parserOptions: {
|
||||
"ecmaVersion": 2018,
|
||||
ecmaVersion: 2018,
|
||||
},
|
||||
rules: {
|
||||
"arrow-body-style": "off",
|
||||
"arrow-parens": [
|
||||
"off",
|
||||
"as-needed",
|
||||
],
|
||||
"camelcase": "off",
|
||||
"complexity": "off",
|
||||
"arrow-parens": ["off", "as-needed"],
|
||||
camelcase: "off",
|
||||
complexity: "off",
|
||||
"constructor-super": "error",
|
||||
"spaced-comment": ["error", "always"],
|
||||
"curly": "error",
|
||||
curly: "error",
|
||||
"dot-notation": "error",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "error",
|
||||
eqeqeq: "error",
|
||||
"guard-for-in": "error",
|
||||
"jsdoc/check-param-names": "off",
|
||||
"jsdoc/require-jsdoc": "off",
|
||||
"jsdoc/require-returns": "off",
|
||||
"jsdoc/require-param": "off",
|
||||
"jsdoc/require-param-type": "off",
|
||||
"jsdoc/require-returns-type": "off",
|
||||
"linebreak-style": "off",
|
||||
"max-classes-per-file": [
|
||||
"error",
|
||||
1,
|
||||
],
|
||||
"max-classes-per-file": ["error", 1],
|
||||
"member-ordering": "off",
|
||||
"new-parens": "off",
|
||||
"newline-per-chained-call": "off",
|
||||
@@ -183,7 +215,7 @@ module.exports = {
|
||||
"no-irregular-whitespace": "off",
|
||||
"no-multiple-empty-lines": "off",
|
||||
"no-new-wrappers": "error",
|
||||
"no-prototype-builtins": "off",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-shadow": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-undef": "off",
|
||||
@@ -191,14 +223,14 @@ module.exports = {
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unused-expressions": "error",
|
||||
"no-unused-labels": "error",
|
||||
"no-unused-vars": ["error", {"args": "none", "ignoreRestSiblings": true}],
|
||||
"no-unused-vars": ["error", { args: "none", ignoreRestSiblings: true }],
|
||||
"no-var": "error",
|
||||
"object-shorthand": "error",
|
||||
"one-var": "off",
|
||||
"prefer-arrow-callback": "off",
|
||||
"prefer-const": "error",
|
||||
"quote-props": "off",
|
||||
"radix": "error",
|
||||
radix: "error",
|
||||
"require-atomic-updates": "off",
|
||||
"space-before-function-paren": "off",
|
||||
"sort-imports": "off",
|
||||
|
||||
@@ -1,14 +1,48 @@
|
||||
<!--
|
||||
Thank you for submitting a pull request! Please note that by contributing to Coral, you agree to our Code of Conduct: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
Before submitting your PR, please verify that:
|
||||
Thank you for submitting a pull request! Please note that by contributing to
|
||||
Coral, you agree to our Code of Conduct: http://code-of-conduct.voxmedia.com/
|
||||
|
||||
Before submitting your Pull Request (or PR), please verify that:
|
||||
|
||||
* [ ] Your code is up-to-date with the base branch
|
||||
* [ ] You've successfully run `npm run test` locally
|
||||
|
||||
Refer to CONTRIBUTING.MD for more details.
|
||||
|
||||
https://github.com/coralproject/talk/blob/master/CONTRIBUTING.md
|
||||
|
||||
-->
|
||||
|
||||
## What does this PR do?
|
||||
|
||||
<!--
|
||||
|
||||
In this section, you should be describing what other Github issues or tickets
|
||||
that this PR is designed to addressed.
|
||||
|
||||
Any related Github issue should be linked by adding its URL to this section.
|
||||
|
||||
-->
|
||||
|
||||
## What changes to the GraphQL/Database Schema does this PR introduce?
|
||||
|
||||
<!--
|
||||
|
||||
In this section, you should describe any changes to be made to the GraphQL
|
||||
schema file (located https://github.com/coralproject/talk/blob/master/src/core/server/graph/schema/schema.graphql) or any
|
||||
database model (located as types in the https://github.com/coralproject/talk/blob/master/src/core/server/models directory).
|
||||
|
||||
If no changes were added to the GraphQL/Database Schema as a part of this PR,
|
||||
simply write "None".
|
||||
|
||||
-->
|
||||
|
||||
## How do I test this PR?
|
||||
|
||||
<!--
|
||||
|
||||
In this section, you should be describing any manual testing that can be used to
|
||||
verify features introduced or bugs fixed in this PR.
|
||||
|
||||
-->
|
||||
|
||||
@@ -20,5 +20,4 @@ yarn.lock
|
||||
dist
|
||||
*.css.d.ts
|
||||
__generated__
|
||||
README.md.orig
|
||||
persisted-queries.json
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"projects": {
|
||||
"tenant": {
|
||||
"schemaPath": "src/core/server/graph/tenant/schema/schema.graphql"
|
||||
"schemaPath": "src/core/server/graph/schema/schema.graphql"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
# .kodiak.toml
|
||||
|
||||
# Kodiak's configuration file should be placed at `.kodiak.toml` (repository
|
||||
# root) or `.github/.kodiak.toml`.
|
||||
# docs: https://kodiakhq.com/docs/config-reference
|
||||
|
||||
# version is the only required setting in a kodiak config.
|
||||
# `1` is the only valid setting for this field.
|
||||
version = 1
|
||||
|
||||
[merge]
|
||||
# Label to enable Kodiak to merge a PR.
|
||||
|
||||
# By default, Kodiak will only act on PRs that have this label. You can disable
|
||||
# this requirement via `merge.require_automerge_label`.
|
||||
automerge_label = "🚀 merge it!" # default: "automerge"
|
||||
|
||||
# Kodiak will not merge a PR with any of these labels.
|
||||
blocking_labels = ["don't merge"] # default: [], options: list of label names (e.g. ["wip"])
|
||||
|
||||
# Choose merge method for Kodiak to use.
|
||||
#
|
||||
# Kodiak will report a configuration error if the selected merge method is
|
||||
# disabled for a repository.
|
||||
#
|
||||
# If you're using the "Require signed commits" GitHub Branch Protection setting
|
||||
# to require commit signatures, _`"merge"` is the only compatible option_. Any
|
||||
# other option will cause Kodiak to raise a configuration error.
|
||||
method = "squash" # default: "merge", options: "merge", "squash", "rebase"
|
||||
|
||||
# Once a PR is merged, delete the branch. This option behaves like the GitHub
|
||||
# repository setting "Automatically delete head branches", which automatically
|
||||
# deletes head branches after pull requests are merged.
|
||||
delete_branch_on_merge = true # default: false
|
||||
|
||||
# Don't wait for in-progress status checks on a PR to finish before updating the
|
||||
# branch.
|
||||
optimistic_updates = false # default: true
|
||||
|
||||
[merge.message]
|
||||
# Strip HTML comments (`<!-- some HTML comment -->`) from merge commit body.
|
||||
# This setting is useful for stripping HTML comments created by PR templates.
|
||||
# This option only applies when `merge.message.body_type = "markdown"`.
|
||||
strip_html_comments = true # default: false
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"trailingComma": "es5"
|
||||
}
|
||||
Vendored
+10
-9
@@ -12,10 +12,10 @@
|
||||
},
|
||||
"tslint.enable": false,
|
||||
"eslint.validate": [
|
||||
{ "language": "javascript", "autoFix": true },
|
||||
{ "language": "typescript", "autoFix": true },
|
||||
{ "language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
{ "language": "javascript", "autoFix": true },
|
||||
{ "language": "typescript", "autoFix": true },
|
||||
{ "language": "typescriptreact", "autoFix": true }
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"postcss.validate": false,
|
||||
"javascript.preferences.importModuleSpecifier": "non-relative",
|
||||
@@ -44,14 +44,15 @@
|
||||
{
|
||||
"regex": "^[.]",
|
||||
"orderLevel": 50
|
||||
},
|
||||
}
|
||||
],
|
||||
"importSorter.importStringConfiguration.maximumNumberOfImportExpressionsPerLine.type": "newLineEachExpressionAfterCountLimitExceptIfOnlyOne",
|
||||
"importSorter.importStringConfiguration.trailingComma": "multiLine",
|
||||
"importSorter.importStringConfiguration.tabSize": 2,
|
||||
"eslint.enable": true,
|
||||
"importSorter.generalConfiguration.exclude": [
|
||||
"d\\.ts$",
|
||||
"__generated__"
|
||||
],
|
||||
"importSorter.generalConfiguration.exclude": ["d\\.ts$", "__generated__"],
|
||||
"search.exclude": {
|
||||
"package-lock.json": true
|
||||
},
|
||||
"debug.node.autoAttach": "off"
|
||||
}
|
||||
|
||||
@@ -1,31 +1,52 @@
|
||||
# Client Events Guide
|
||||
|
||||
This serves as a guide to events emitted by the javascript via the embed events
|
||||
hook, as described below in [Viewer Events](#viewer-events).
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Viewer Events](#viewer-events)
|
||||
- [Viewer Network Events](#viewer-network-events)
|
||||
- [Event List](#event-list)
|
||||
- [Index](#index)
|
||||
- [Events](#events)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Viewer Events
|
||||
|
||||
_Viewer Events_ are emitted when the viewer performs certain actions.
|
||||
They can be subscribed to using the `events` parameter in
|
||||
`Coral.createStreamEmbed`.
|
||||
|
||||
```html
|
||||
<script>
|
||||
const CoralStreamEmbed = Coral.createStreamEmbed({
|
||||
events: function(events) {
|
||||
events.onAny(function(eventName, data) {
|
||||
console.log(eventName, data);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
const CoralStreamEmbed = Coral.createStreamEmbed({
|
||||
events: function(events) {
|
||||
events.onAny(function(eventName, data) {
|
||||
console.log(eventName, data);
|
||||
});
|
||||
},
|
||||
});
|
||||
</script>
|
||||
```
|
||||
|
||||
Example events:
|
||||
|
||||
- `setMainTab {tab: "PROFILE"}`
|
||||
- `showFeaturedCommentTooltip`
|
||||
- `viewConversation {from: "FEATURED_COMMENTS", commentID: "c45fb5f5-03f9-49a3-a755-488c698ca0df"}`
|
||||
|
||||
### Viewer Network Events
|
||||
|
||||
_Viewer Network Events_ are events that involves a network request and thus can succeed or fail. Succeeding events will have a `.success` appended to the event name while failing events have an `.error` appended to the event name.
|
||||
_Viewer Network Events_ are events that involves a network request and thus can succeed or fail. Succeeding events will have a `.success` appended to the event name while failing events have an `.error` appended to the event name.
|
||||
|
||||
Moreover _Viewer Network Events_ contains the `rtt` field which indicates the time it needed from initiating the request until the _UI_ has been updated with the response data.
|
||||
|
||||
Example events:
|
||||
|
||||
```
|
||||
createComment.success
|
||||
{
|
||||
@@ -53,6 +74,7 @@ createComment.error
|
||||
```
|
||||
|
||||
## Event List
|
||||
|
||||
<!-- START docs:events -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:events -->
|
||||
### Index
|
||||
@@ -107,6 +129,7 @@ createComment.error
|
||||
- <a href="#showSharePopover">showSharePopover</a>
|
||||
- <a href="#showUserPopover">showUserPopover</a>
|
||||
- <a href="#signOut">signOut</a>
|
||||
- <a href="#signedIn">signedIn</a>
|
||||
- <a href="#unfeatureComment">unfeatureComment</a>
|
||||
- <a href="#updateNotificationSettings">updateNotificationSettings</a>
|
||||
- <a href="#updateStorySettings">updateStorySettings</a>
|
||||
@@ -516,6 +539,7 @@ createComment.error
|
||||
};
|
||||
}
|
||||
```
|
||||
- <a id="signedIn">**signedIn**</a>: This event is emitted when the viewer signed in (not applicable for SSO).
|
||||
- <a id="unfeatureComment">**unfeatureComment.success**, **unfeatureComment.error**</a>: This event is emitted when the viewer unfeatures a comment.
|
||||
```ts
|
||||
{
|
||||
+507
-12
@@ -5,20 +5,60 @@ Welcome! We are very excited that you are interested in contributing to Coral.
|
||||
This document is a companion to help you approach contributing. If it does not
|
||||
do so, please [let us know how we can improve it](https://github.com/coralproject/talk/issues)!
|
||||
|
||||
By contributing to this project you agree to the
|
||||
[Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
By contributing to this project you agree to the [Code of Conduct](CODE_OF_CONDUCT.md).
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [What should I Contribute?](#what-should-i-contribute)
|
||||
- [Writing Code](#writing-code)
|
||||
- [When should I create an issue?](#when-should-i-create-an-issue)
|
||||
- [What should I include in my issue?](#what-should-i-include-in-my-issue)
|
||||
- [When should I create a pull request?](#when-should-i-create-a-pull-request)
|
||||
- [What should I include in my pull request?](#what-should-i-include-in-my-pull-request)
|
||||
- [Reviewing pull requests](#reviewing-pull-requests)
|
||||
- [Ensure contributions are linted and tested](#ensure-contributions-are-linted-and-tested)
|
||||
- [Review the feature/fixes](#review-the-featurefixes)
|
||||
- [Review architectural decisions](#review-architectural-decisions)
|
||||
- [Verify localizations](#verify-localizations)
|
||||
- [Localization](#localization)
|
||||
- [Documentation](#documentation)
|
||||
- [Design Principles](#design-principles)
|
||||
- [GraphQL](#graphql)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## What should I Contribute?
|
||||
|
||||
There are at least three ways to contribute to Coral:
|
||||
There are at least three different ways to contribute to Coral:
|
||||
|
||||
- Writing Code
|
||||
- Providing Translations
|
||||
- [Writing Code](#writing-code)
|
||||
- [Reviewing pull requests](#reviewing-pull-requests)
|
||||
- [Localization](#localization)
|
||||
- [Documentation](#documentation)
|
||||
|
||||
Typically these take the form of creating a Pull Request for Coral, and
|
||||
submitting it to be reviewed by a member of our team and the greater Coral
|
||||
community.
|
||||
|
||||
Working on your first Pull Request? You can learn how from this free video
|
||||
series:
|
||||
|
||||
[How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github)
|
||||
|
||||
If you decide to fix an issue, please be sure to check the comment thread in
|
||||
case somebody is already working on a fix. If nobody is working on it at the
|
||||
moment, please leave a comment stating that you intend to work on it so other
|
||||
people don’t accidentally duplicate your effort.
|
||||
|
||||
If somebody claims an issue but doesn’t follow up for more than two weeks, it’s
|
||||
fine to take it over but you should still leave a comment.
|
||||
|
||||
## Writing Code
|
||||
|
||||
Conversation surrounding contributions begins in
|
||||
[issues](https://github.com/coralproject/talk/issues).
|
||||
Conversation surrounding contributions begins when you can create an issue
|
||||
describing your issue or suggestion.
|
||||
|
||||
### When should I create an issue?
|
||||
|
||||
@@ -26,9 +66,7 @@ File an issue as soon as you have an idea of something you'd like to contribute.
|
||||
We would love to hear what you're thinking and help refine the idea to make it
|
||||
into the Coral ecosystem.
|
||||
|
||||
Please file issues if you would like to contribute to Coral.
|
||||
|
||||
### What should I include?
|
||||
### What should I include in my issue?
|
||||
|
||||
Coral has adopted an iterative, agile development philosophy. All contributions
|
||||
that make it into the Coral repository should start with a user story in this
|
||||
@@ -38,8 +76,8 @@ form:
|
||||
|
||||
This exercise does two things:
|
||||
|
||||
- allows us to ground our technical choices in a clear, simple product need.
|
||||
- expresses that product need in a way that doesn't imply a specific technical
|
||||
- Allows us to ground our technical choices in a clear, simple product need.
|
||||
- Expresses that product need in a way that doesn't imply a specific technical
|
||||
solution allowing for debate as to the best way to solve the problem.
|
||||
|
||||
Please feel free to provide as much detail as possible when filing the issue but
|
||||
@@ -49,6 +87,72 @@ technical or design solutions.
|
||||
If you have a specific technical or design solution in mind, please submit it as
|
||||
the first comment on the thread.
|
||||
|
||||
### When should I create a pull request?
|
||||
|
||||
File a pull request if you've created an issue in our [issues](https://github.com/coralproject/talk/issues)
|
||||
page and have heard back from a member or contributor to Coral. This allows our
|
||||
team to review the proposed changes prior to time being spent if the team
|
||||
already has the feature/fix in the road map.
|
||||
|
||||
### What should I include in my pull request?
|
||||
|
||||
When you create a pull request, the template will describe the required
|
||||
components needed for it to be reviewed by a member of the Coral team. You
|
||||
should end up filling out:
|
||||
|
||||
- What does this PR (pull request) do?
|
||||
- How do I test this PR?
|
||||
|
||||
You should describe what Github issue or ticket that the PR is associated with
|
||||
to assist the review process. If this PR is resolving a particular bug, a
|
||||
testing strategy should be described in the testing section. If this PR is
|
||||
contributing a new feature, a description should describe a scenario to test or
|
||||
verify the new functionality.
|
||||
|
||||
## Reviewing pull requests
|
||||
|
||||
Reviewing pull requests in Coral is generally completed by the core Coral team
|
||||
that is composed of developers employed by Vox Media Inc, but external reviews
|
||||
or suggestions are also welcomed.
|
||||
|
||||
Our review process generally follows a few core principles:
|
||||
|
||||
### Ensure contributions are linted and tested
|
||||
|
||||
It is the job of CI linting and tests to notify of style issues within the
|
||||
codebase. If it is not possible for style issues to be encapsulated as a
|
||||
linting rule, it shouldn't be concretely enforced during the review process.
|
||||
This can ensure that code reviews contain more meaningful feedback tied to the
|
||||
contribution rather than nit-picking on stylistic choices.
|
||||
|
||||
Reviewers must ensure that linting and tests pass in CI and locally prior to a
|
||||
review taking place. You can do this by running `npm run generate` followed by
|
||||
`npm run lint` and `npm run test`.
|
||||
|
||||
### Review the feature/fixes
|
||||
|
||||
Any new features added to Coral should be reviewed for bugs through a manual
|
||||
verification process to ensure that they function on your machine. If possible
|
||||
you should review any automated tests that were added (or not added) related to
|
||||
the feature.
|
||||
|
||||
While the Coral team is not strict on test driven development (or TDD), any
|
||||
contributions that include tests are greatly appreciated, and preferred over
|
||||
those that do not.
|
||||
|
||||
### Review architectural decisions
|
||||
|
||||
Any substantial changes made to the codebase should be reviewed to ensure
|
||||
that they conform to the current way code/services are laid out.
|
||||
|
||||
Architecture Decision Records (or [ADR](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions)) are now being used to describe architectural decisions and can be found in the `src/docs/architecture/decisions` directory.
|
||||
|
||||
### Verify localizations
|
||||
|
||||
While we don't have any automated tools at the time of writing that will
|
||||
verify this in CI, any strings being added that are presented to the end user
|
||||
should be wrapped in localization components to support other languages.
|
||||
|
||||
## Localization
|
||||
|
||||
We use the [fluent](http://projectfluent.org/) library and store our
|
||||
@@ -64,3 +168,394 @@ where the string is used.
|
||||
|
||||
Once a language has enough coverage, it should be added to
|
||||
`src/core/common/helpers/i18n/locales.ts`.
|
||||
|
||||
The [Perspective API](https://github.com/conversationai/perspectiveapi/blob/master/2-api/methods.md#analyzecomment-request)
|
||||
also supports comments in specific languages. When the language is supported in
|
||||
Coral and supported by the Perspective API, the language should be added to the
|
||||
language map in `src/core/server/services/comments/pipeline/phases/toxic.ts`.
|
||||
|
||||
To assist with the translation process, we have a script that is based on the
|
||||
work by @cristiandean in https://github.com/coralproject/talk/pull/2949 that
|
||||
will detect missing, new, or changed translation keys for the specified
|
||||
language. You can use this with:
|
||||
|
||||
```sh
|
||||
# usage: ./scripts/i18n/validate.ts <locale>
|
||||
./scripts/i18n/validate.ts pt-BR
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
Documentation that is publicly shown on
|
||||
[docs.coralproject.net](https://docs.coralproject.net/coral/) is stored under
|
||||
the `release/4` branch as it contains information for versions 4.0 onwards of
|
||||
Coral.
|
||||
|
||||
To contribute new docs, you can either click the "Suggest Edits" in the top
|
||||
right of each page, or you can edit directly via source. We suggest that for
|
||||
individual fixes or contributions to the documentation.
|
||||
|
||||
If you want to contribute via source files, you can follow the procedure
|
||||
outlined below:
|
||||
|
||||
1. Clone the Talk repository via `git clone https://github.com/coralproject/talk.git`
|
||||
2. Switch to the `release/4` branch via `git checkout release/4`
|
||||
3. Follow the procedure outlined on that branch's [CONTRIBUTING.md](https://github.com/coralproject/talk/blob/release/4/CONTRIBUTING.md#contributing-documentation) file for contributing documentation changes
|
||||
4. Create a pull request to merge your changes back into the `release/4` branch
|
||||
|
||||
## Design Principles
|
||||
|
||||
### GraphQL
|
||||
|
||||
Coral relies heavily on [GraphQL](https://graphql.org) as the query language for
|
||||
the API and the runtime on the server that powers resolving data from data
|
||||
sources. This heavily influences a lot of the decisions around how we create and
|
||||
consume it's API internally and how we expose it to others to interact with.
|
||||
|
||||
There are many GraphQL types in our [`schema.graphql`](https://github.com/coralproject/talk/blob/master/src/core/server/graph/schema/schema.graphql)
|
||||
that define the way we handle data in our API. We'll try to outline a few of
|
||||
them here with examples to help you understand their uses.
|
||||
|
||||
#### Types
|
||||
|
||||
Similar to defining an interface or a _struct_ definition, GraphQL has flexible
|
||||
types that can be used to define data types that are used for querying data from
|
||||
the API. This retrieval can happen directly via a query, or after executing an
|
||||
action using a mutation and querying its response result.
|
||||
|
||||
An example of these types is the `Comment` and its nested `CommentRevision`
|
||||
type:
|
||||
|
||||
```graphql
|
||||
"""
|
||||
Comment is a comment left by a User on an Story or another Comment as a reply.
|
||||
"""
|
||||
type Comment {
|
||||
"""
|
||||
id is the identifier of the Comment.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
body is the content of the Comment, and is an alias to the body of the
|
||||
`revision.body`.
|
||||
"""
|
||||
body: String
|
||||
|
||||
"""
|
||||
revision is the current revision of the Comment's body.
|
||||
"""
|
||||
revision: CommentRevision
|
||||
|
||||
"""
|
||||
revisionHistory stores the previous CommentRevision's, with the most recent
|
||||
edit last.
|
||||
"""
|
||||
revisionHistory: [CommentRevision!]!
|
||||
@auth(
|
||||
roles: [MODERATOR, ADMIN]
|
||||
userIDField: "author_id"
|
||||
permit: [SUSPENDED, BANNED, PENDING_DELETION]
|
||||
)
|
||||
|
||||
"""
|
||||
status represents the Comment's current status.
|
||||
"""
|
||||
status: COMMENT_STATUS!
|
||||
}
|
||||
```
|
||||
|
||||
Notice how the `Comment` type can nest more custom defined types. A `Comment`
|
||||
can have a current `CommentRevision` named `revision`. It also has a list of its
|
||||
historical `revisionHistory`:
|
||||
|
||||
```graphql
|
||||
type CommentRevision {
|
||||
"""
|
||||
id is the identifier of the CommentRevision.
|
||||
"""
|
||||
id: ID!
|
||||
|
||||
"""
|
||||
comment is the reference to the original Comment associated with the current
|
||||
Comment.
|
||||
"""
|
||||
comment: Comment!
|
||||
|
||||
"""
|
||||
actionCounts stores the counts of all the actions for the CommentRevision
|
||||
specifically.
|
||||
"""
|
||||
actionCounts: ActionCounts! @auth(roles: [MODERATOR, ADMIN])
|
||||
|
||||
"""
|
||||
body is the content of the CommentRevision. If null, it indicates that the
|
||||
body text was deleted.
|
||||
"""
|
||||
body: String
|
||||
}
|
||||
```
|
||||
|
||||
Another thing to note, see how `CommentRevision` is not only referenced by
|
||||
`revision` and `revisionHistory` on the `Comment` type. The `CommentRevision`
|
||||
also references back to its parent `Comment` via the `comment: Comment!`
|
||||
property. This is how defined types interact between each other in the GraphQL
|
||||
schema.
|
||||
|
||||
Our naming scheme is upper camel case (also known as Pascal Case) for these
|
||||
types:
|
||||
|
||||
- Start with a capital letter
|
||||
- Following characters are lower case
|
||||
- Every new word in the type name begins with a new capital letter
|
||||
- Acronyms are always capitalized (with the only exception being the
|
||||
`clientMutationId: String!` field in mutation input/payload types)
|
||||
|
||||
Some of the properties have an `!` beside their type (i.e `id: ID!`) which
|
||||
indicates that this property is required and is non-nullable. GraphQL will
|
||||
validate the input request for these properties and ensure they are provided
|
||||
during the GraphQL request.
|
||||
|
||||
You can learn more about GraphQL types in their documentation:
|
||||
|
||||
[Learn GraphQL: Schemas and Types](https://graphql.org/learn/schema/)
|
||||
|
||||
#### Enumeration Types
|
||||
|
||||
In the previous example with the `Comment` type. We also had a property called
|
||||
`status` which was of type `COMMENT_STATUS`.
|
||||
|
||||
This is another kind of defined type, an [Enumeration Type](https://graphql.org/learn/schema/#enumeration-types),
|
||||
also called _enums_.
|
||||
|
||||
```graphql
|
||||
enum COMMENT_STATUS {
|
||||
NONE
|
||||
APPROVED
|
||||
REJECTED
|
||||
PREMOD
|
||||
SYSTEM_WITHHELD
|
||||
}
|
||||
```
|
||||
|
||||
Like all enumeration types this definition enumerates out typed, named values
|
||||
that are reusable for state elsewhere on other types.
|
||||
|
||||
Our naming scheme for enumeration types and values in those types as:
|
||||
|
||||
- All capital letters
|
||||
- Spaces delimited with underscores
|
||||
|
||||
This is because they are treated as shared constant values across the schema.
|
||||
Rather than storing strings or numbers to capture selected state, we prefer
|
||||
using enumeration types because they are much more stricter in terms of value.
|
||||
|
||||
You can learn more about GraphQL Enumeration types in their documentation:
|
||||
|
||||
[Learn GraphQL: Enumeration Types](https://graphql.org/learn/schema/#enumeration-types)
|
||||
|
||||
#### Mutation Types
|
||||
|
||||
Mutations are a request to GraphQL to initiate an action which will result in a response. As such,they're broken up into an `Input` and `Payload` pair that matches a mutation's request and response pair.
|
||||
|
||||
An example is the `CreateCommentInput` and `CreateCommentPayload`:
|
||||
|
||||
```graphql
|
||||
"""
|
||||
CreateCommentInput provides the input for the createComment Mutation.
|
||||
"""
|
||||
input CreateCommentInput {
|
||||
"""
|
||||
storyID is the ID of the Story where we are creating a comment on.
|
||||
"""
|
||||
storyID: ID!
|
||||
|
||||
"""
|
||||
nudge when true will instead return an error related to recoverable moderation
|
||||
faults such as a toxic comment or spam comment to provide user feedback to
|
||||
nudge the user to correct the comment.
|
||||
"""
|
||||
nudge: Boolean = false
|
||||
|
||||
"""
|
||||
body is the Comment body, the content of the Comment.
|
||||
"""
|
||||
body: String!
|
||||
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
}
|
||||
|
||||
"""
|
||||
CreateCommentPayload contains the created Comment after the createComment
|
||||
mutation.
|
||||
"""
|
||||
type CreateCommentPayload {
|
||||
"""
|
||||
edge is the possibly created comment edge.
|
||||
"""
|
||||
edge: CommentEdge!
|
||||
|
||||
"""
|
||||
clientMutationId is required for Relay support.
|
||||
"""
|
||||
clientMutationId: String!
|
||||
}
|
||||
```
|
||||
|
||||
The `CreateCommentInput` type contains some parameters that allow us to execute
|
||||
the mutation. These are:
|
||||
|
||||
- `storyID` - the story we are submitting the comment to.
|
||||
- `body` - the body of our comment.
|
||||
- `nudge` - whether we should return validation prompts to the user about
|
||||
improper comment language.
|
||||
- `clientMutationId` - the identifier used by Relay to identify this mutation,
|
||||
our front-end state cache to process the mutation request. This is the only
|
||||
place in the code-base that we do not capitalize the acronym when written in
|
||||
camel-case, this is unfortunately due to legacy reasons from within Relay.
|
||||
|
||||
The returned response for a mutation is a what we call a Payload, in this case
|
||||
it's `CreateCommentPayload`. This usually has a response that is the full data
|
||||
type of whatever was modified by the earlier called mutation action. The
|
||||
properties on this type are:
|
||||
|
||||
- `edge` - we return the full comment edge that was created by the previous
|
||||
input. This is done so that the mutation request can query whatever it needs
|
||||
to from the returned input to update state on the client.
|
||||
- `clientMutationId` - an identifier used by Relay (our front-end client state
|
||||
cache) to process the mutation request.
|
||||
|
||||
#### Access Permissions On Types
|
||||
|
||||
Sometimes, you only want specific users to be allowed to view certain
|
||||
information. For instance, we have roles that are defined in our schema so we
|
||||
can filter who can have access to what.
|
||||
|
||||
These roles are used with directives on our schema that GraphQL then enforces
|
||||
when trying to resolve requests a user makes. If the user has the specified role
|
||||
associated with their signed-in account, they are given access to the requested
|
||||
data.
|
||||
|
||||
An example of this is the `revisionHistory` on the `Comment` type:
|
||||
|
||||
```graphql
|
||||
fragment on Comment {
|
||||
"""
|
||||
revisionHistory stores the previous CommentRevision's, with the most recent
|
||||
edit last.
|
||||
"""
|
||||
revisionHistory: [CommentRevision!]!
|
||||
@auth(
|
||||
roles: [MODERATOR, ADMIN]
|
||||
userIDField: "author_id"
|
||||
permit: [SUSPENDED, BANNED, PENDING_DELETION]
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
Here we see the `@auth` directive. It has documentation describing the various
|
||||
parameters allowed located in the [`schema.graphql`](https://github.com/coralproject/talk/blob/master/src/core/server/graph/schema/schema.graphql)
|
||||
file, we'll discuss below what this particular set of parameters can be read as:
|
||||
|
||||
- The roles that are allowed to access this information are `MODERATOR` and
|
||||
`ADMIN` as defined by the `roles` argument.
|
||||
- We let the directive know that the author of the comment is `author_id` from
|
||||
the `Comment` by defining the `userIDField`. It's a rule of thumb in Coral if
|
||||
the Author created the document, they have permission to view it. A Comment
|
||||
for example is authored by a user, with the underlying field associated with
|
||||
the id of that author living on the `author_id` field. You can see how this
|
||||
is related if you look at the resolver for the `Comment` type to see that it
|
||||
is based off of the `Comment` interface from `src/core/server/models/comment`.
|
||||
- We permit returning comments when the author has the following conditions
|
||||
associated with their account: `SUSPENDED`, `BANNED`, or `PENDING_DELETION`.
|
||||
|
||||
These directives can be simpler, for example the `metadata` property on the
|
||||
`CommentRevision`:
|
||||
|
||||
```graphql
|
||||
fragment on CommentRevision {
|
||||
"""
|
||||
metadata stores details on a CommentRevision.
|
||||
"""
|
||||
metadata: CommentRevisionMetadata! @auth(roles: [ADMIN, MODERATOR])
|
||||
}
|
||||
```
|
||||
|
||||
Here we see an auth directive with only roles defined. This is sufficient to
|
||||
make sure that the metadata property is only accessible to `ADMIN` and
|
||||
`MODERATOR` user roles.
|
||||
|
||||
Note: Wondering how the user roles are defined? They're simply an enumeration
|
||||
type that is also defined in the schema.
|
||||
|
||||
```graphql
|
||||
enum USER_ROLE {
|
||||
COMMENTER
|
||||
STAFF
|
||||
MODERATOR
|
||||
ADMIN
|
||||
}
|
||||
```
|
||||
|
||||
#### Arrays of Items
|
||||
|
||||
Sometimes you don't want a singular property, your property is instead a
|
||||
collection of items.
|
||||
|
||||
The `revisionHistory` from the `Comment` is again useful as an example:
|
||||
|
||||
```graphql
|
||||
fragment on Comment {
|
||||
"""
|
||||
revisionHistory stores the previous CommentRevision's, with the most recent
|
||||
edit last.
|
||||
"""
|
||||
revisionHistory: [CommentRevision!]!
|
||||
}
|
||||
```
|
||||
|
||||
The interior type `CommentRevision` is required using the `!` and the outer
|
||||
array is also required using `!`.
|
||||
|
||||
We do this for a couple of reasons:
|
||||
|
||||
- This ensures that we do not return null/undefined values within the array.
|
||||
- Why would we return a null when we can just return nothing for null values?
|
||||
- We want the array to always be defined, if empty, we return and empty array (i.e. `[]`).
|
||||
- This can be handled nicely in our resolvers. We simply check if the retrieved values is null or undefined and simply return an empty array in its stead.
|
||||
|
||||
These little tweaks aren't necessary, but they ease the use of our API by making
|
||||
the results for arrays predictable and strongly typed.
|
||||
|
||||
#### Documenting
|
||||
|
||||
As you may have noticed, there is quite a bit of documentation in the schema examples listed here.
|
||||
|
||||
We typically follow these two rules in commenting our GraphQL types:
|
||||
|
||||
- Always comment the property within a type describing its purpose on its parent type
|
||||
|
||||
i.e. `createdAt` on our `Setting` type:
|
||||
|
||||
```graphql
|
||||
fragment on Setting {
|
||||
"""
|
||||
createdAt is the time that the Settings was created at.
|
||||
"""
|
||||
createdAt: Time! @auth(roles: [ADMIN])
|
||||
}
|
||||
```
|
||||
|
||||
- Always comment the purpose of each type
|
||||
|
||||
i.e. the `Comment` type:
|
||||
|
||||
```graphql
|
||||
"""
|
||||
Comment is a comment left by a User on an Story or another Comment as a reply.
|
||||
"""
|
||||
type Comment { }
|
||||
```
|
||||
|
||||
@@ -0,0 +1,272 @@
|
||||
|
||||
# CSS Variables
|
||||
|
||||
Coral defines a set of CSS Variables that you can use to broadly customize the
|
||||
look and feel of your comment stream. For example you can easily redefine the colors
|
||||
or fonts that are being used.
|
||||
|
||||
To change the CSS Variables, use the following example in your custom CSS file:
|
||||
|
||||
```
|
||||
:root {
|
||||
--font-family-primary: 'Verdana';
|
||||
--round-corners: 0px;
|
||||
}
|
||||
```
|
||||
|
||||
The following list contains the CSS Variables that can be customized.
|
||||
|
||||
**Info**: *Before 6.3.0 Coral uses a different set of CSS Variables. If you are still using
|
||||
the old CSS Variables, please upgrade. Currently we have a compatibility layer to bridge the old CSS Variables to the new ones. This mechanism will be removed in the future.*
|
||||
|
||||
<!-- START docs:css-variables -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:css-variables -->
|
||||
### Index
|
||||
- <a href="#variables">Variables</a>
|
||||
- <a href="#palette">palette</a>
|
||||
- <a href="#palette-primary">palette-primary</a>
|
||||
- <a href="#palette-background">palette-background</a>
|
||||
- <a href="#palette-text">palette-text</a>
|
||||
- <a href="#palette-grey">palette-grey</a>
|
||||
- <a href="#palette-error">palette-error</a>
|
||||
- <a href="#palette-success">palette-success</a>
|
||||
- <a href="#palette-warning">palette-warning</a>
|
||||
- <a href="#font-family">font-family</a>
|
||||
- <a href="#font-weight">font-weight</a>
|
||||
- <a href="#font-weight-primary">font-weight-primary</a>
|
||||
- <a href="#font-weight-secondary">font-weight-secondary</a>
|
||||
- <a href="#font-size">font-size</a>
|
||||
- <a href="#shadow">shadow</a>
|
||||
- <a href="#spacing">spacing</a>
|
||||
- <a href="#mini-unit">mini-unit</a>
|
||||
|
||||
|
||||
### Variables
|
||||
|
||||
Use this to remove round corners or make them more round.
|
||||
|
||||
`--round-corners: 3px;`
|
||||
|
||||
### palette
|
||||
|
||||
`--palette-divider: rgba(0, 0, 0, 0.12);`
|
||||
|
||||
#### palette-primary
|
||||
|
||||
Color palette that is used as the primary color.
|
||||
|
||||
`--palette-primary-100: #EBF6FF; /* Before 6.3.0: --palette-primary-lightest */`
|
||||
|
||||
`--palette-primary-200: #B7DCFF; /* Before 6.3.0: --palette-primary-lighter */`
|
||||
|
||||
`--palette-primary-300: #61B3FF; /* Before 6.3.0: --palette-primary-light */`
|
||||
|
||||
`--palette-primary-400: #2897FF; /* Before 6.3.0: --palette-primary-main */`
|
||||
|
||||
`--palette-primary-500: #0070D9; /* Before 6.3.0: --palette-primary-main */`
|
||||
|
||||
`--palette-primary-600: #0062BE; /* Before 6.3.0: --palette-primary-main */`
|
||||
|
||||
`--palette-primary-700: #005AAE; /* Before 6.3.0: --palette-primary-main */`
|
||||
|
||||
`--palette-primary-800: #004688; /* Before 6.3.0: --palette-primary-dark */`
|
||||
|
||||
`--palette-primary-900: #00386D; /* Before 6.3.0: --palette-primary-darkest */`
|
||||
|
||||
#### palette-background
|
||||
|
||||
Color palette that is used for background colors.
|
||||
|
||||
`--palette-background-body: #FFFFFF;`
|
||||
|
||||
`--palette-background-popover: #FFFFFF;`
|
||||
|
||||
`--palette-background-tooltip: #65696B;`
|
||||
|
||||
`--palette-background-input: #FFFFFF;`
|
||||
|
||||
`--palette-background-input-disabled: #EFEFEF;`
|
||||
|
||||
#### palette-text
|
||||
|
||||
Color palette that is used for text.
|
||||
|
||||
`--palette-text-000: #FFFFFF; /* Before 6.3.0: --palette-text-light */`
|
||||
|
||||
`--palette-text-100: #65696B; /* Before 6.3.0: --palette-text-secondary */`
|
||||
|
||||
`--palette-text-500: #353F44; /* Before 6.3.0: --palette-text-primary */`
|
||||
|
||||
`--palette-text-900: #14171A; /* Before 6.3.0: --palette-text-dark */`
|
||||
|
||||
`--palette-text-placeholder: #9FA4A6; /* Before 6.3.0: --palette-grey-lighter */`
|
||||
|
||||
`--palette-text-input-disabled: #9FA4A6; /* Before 6.3.0: --palette-grey-lighter */`
|
||||
|
||||
#### palette-grey
|
||||
|
||||
Color palette that is used for grey shades.
|
||||
|
||||
`--palette-grey-100: #F4F7F7; /* Before 6.3.0: --palette-grey-lightest */`
|
||||
|
||||
`--palette-grey-200: #EAEFF0; /* Before 6.3.0: --palette-grey-lightest */`
|
||||
|
||||
`--palette-grey-300: #CBD1D2; /* Before 6.3.0: --palette-grey-lighter */`
|
||||
|
||||
`--palette-grey-400: #9FA4A6; /* Before 6.3.0: --palette-grey-lighter */`
|
||||
|
||||
`--palette-grey-500: #65696B; /* Before 6.3.0: --palette-grey-main */`
|
||||
|
||||
`--palette-grey-600: #49545C; /* Before 6.3.0: --palette-grey-dark */`
|
||||
|
||||
`--palette-grey-700: #32404D; /* Before 6.3.0: --palette-grey-darkest */`
|
||||
|
||||
`--palette-grey-800: #202E3E; /* Before 6.3.0: --palette-grey-darkest */`
|
||||
|
||||
`--palette-grey-900: #132033; /* Before 6.3.0: --palette-grey-darkest */`
|
||||
|
||||
#### palette-error
|
||||
|
||||
Color palette that is used for indicating something is error red.
|
||||
|
||||
`--palette-error-100: #FCE5D9; /* Before 6.3.0: --palette-error-lightest */`
|
||||
|
||||
`--palette-error-200: #FAC6B4; /* Before 6.3.0: --palette-error-lighter */`
|
||||
|
||||
`--palette-error-300: #F29D8B; /* Before 6.3.0: --palette-error-lighter */`
|
||||
|
||||
`--palette-error-400: #E5766C; /* Before 6.3.0: --palette-error-light */`
|
||||
|
||||
`--palette-error-500: #D53F3F; /* Before 6.3.0: --palette-error-main */`
|
||||
|
||||
`--palette-error-600: #B72E39; /* Before 6.3.0: --palette-error-main */`
|
||||
|
||||
`--palette-error-700: #991F34; /* Before 6.3.0: --palette-error-dark */`
|
||||
|
||||
`--palette-error-800: #7B142E; /* Before 6.3.0: --palette-error-darkest */`
|
||||
|
||||
`--palette-error-900: #660C2B; /* Before 6.3.0: --palette-error-darkest */`
|
||||
|
||||
#### palette-success
|
||||
|
||||
Color palette that is used for indicating something is success green.
|
||||
|
||||
`--palette-success-100: #D8F9D5; /* Before 6.3.0: --palette-success-lightest */`
|
||||
|
||||
`--palette-success-200: #ADF3AD; /* Before 6.3.0: --palette-success-lighter */`
|
||||
|
||||
`--palette-success-300: #7CDB85; /* Before 6.3.0: --palette-success-lighter */`
|
||||
|
||||
`--palette-success-400: #54B767; /* Before 6.3.0: --palette-success-light */`
|
||||
|
||||
`--palette-success-500: #268742; /* Before 6.3.0: --palette-success-main */`
|
||||
|
||||
`--palette-success-600: #1B743D; /* Before 6.3.0: --palette-success-main */`
|
||||
|
||||
`--palette-success-700: #136138; /* Before 6.3.0: --palette-success-dark */`
|
||||
|
||||
`--palette-success-800: #0C4E32; /* Before 6.3.0: --palette-success-darkest */`
|
||||
|
||||
`--palette-success-900: #07402E; /* Before 6.3.0: --palette-success-darkest */`
|
||||
|
||||
#### palette-warning
|
||||
|
||||
Color palette that is used for indicating a warning and is usually yellow.
|
||||
|
||||
`--palette-warning-100: #FFFACC; /* Before 6.3.0: --palette-warning-main */`
|
||||
|
||||
`--palette-warning-500: #FFE91F; /* Before 6.3.0: --palette-warning-main */`
|
||||
|
||||
### font-family
|
||||
|
||||
Different font families currently in use.
|
||||
|
||||
`--font-family-primary: "Open Sans"; /* Before 6.3.0: --font-family-sans-serif */`
|
||||
|
||||
`--font-family-secondary: "Nunito"; /* Before 6.3.0: --font-family-serif */`
|
||||
|
||||
### font-weight
|
||||
|
||||
Different font weights with matching values for the fonts.
|
||||
|
||||
#### font-weight-primary
|
||||
|
||||
`--font-weight-primary-bold: 700; /* Before 6.3.0: --font-weight-bold */`
|
||||
|
||||
`--font-weight-primary-semi-bold: 600; /* Before 6.3.0: --font-weight-medium */`
|
||||
|
||||
`--font-weight-primary-regular: 300; /* Before 6.3.0: --font-weight-light */`
|
||||
|
||||
#### font-weight-secondary
|
||||
|
||||
`--font-weight-secondary-bold: 700; /* Before 6.3.0: --font-weight-bold */`
|
||||
|
||||
`--font-weight-secondary-regular: 300; /* Before 6.3.0: --font-weight-light */`
|
||||
|
||||
### font-size
|
||||
|
||||
`--font-size-1: 0.75rem;`
|
||||
|
||||
`--font-size-2: 0.875rem;`
|
||||
|
||||
`--font-size-3: 1rem;`
|
||||
|
||||
`--font-size-4: 1.125rem;`
|
||||
|
||||
`--font-size-5: 1.25rem;`
|
||||
|
||||
`--font-size-6: 1.5rem;`
|
||||
|
||||
`--font-size-7: 1.75rem;`
|
||||
|
||||
`--font-size-8: 2rem;`
|
||||
|
||||
`--font-size-9: 2.25rem;`
|
||||
|
||||
`--font-size-icon-xl: 2.25rem;`
|
||||
|
||||
`--font-size-icon-lg: 1.5rem;`
|
||||
|
||||
`--font-size-icon-md: 1.125rem;`
|
||||
|
||||
`--font-size-icon-sm: 0.875rem;`
|
||||
|
||||
`--font-size-icon-xs: 0.75rem;`
|
||||
|
||||
### shadow
|
||||
|
||||
Different shadows that are currently used in Coral.
|
||||
|
||||
`--shadow-popover: 1px 0px 4px rgba(0, 0, 0, 0.25); /* Before 6.3.0: --elevation-main */`
|
||||
|
||||
### spacing
|
||||
|
||||
Different spacing units currenty used in Coral.
|
||||
|
||||
`--spacing-1: 4px;`
|
||||
|
||||
`--spacing-2: 8px;`
|
||||
|
||||
`--spacing-3: 12px;`
|
||||
|
||||
`--spacing-4: 16px;`
|
||||
|
||||
`--spacing-5: 24px;`
|
||||
|
||||
`--spacing-6: 32px;`
|
||||
|
||||
`--spacing-7: 44px;`
|
||||
|
||||
`--spacing-8: 60px;`
|
||||
|
||||
`--spacing-9: 84px;`
|
||||
|
||||
### mini-unit
|
||||
|
||||
Grid units for smaller and larger screens.
|
||||
|
||||
`--mini-unit-small: 4;`
|
||||
|
||||
`--mini-unit-large: 8;`
|
||||
|
||||
<!-- END docs:css-variables -->
|
||||
+3
-8
@@ -1,4 +1,4 @@
|
||||
FROM node:10-alpine
|
||||
FROM node:12-alpine
|
||||
|
||||
# Install build dependancies.
|
||||
RUN apk --no-cache add git python
|
||||
@@ -7,9 +7,6 @@ RUN apk --no-cache add git python
|
||||
RUN mkdir -p /usr/src/app
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Setup the environment for production.
|
||||
ENV NODE_ENV production
|
||||
|
||||
# Bundle application source.
|
||||
COPY . /usr/src/app
|
||||
|
||||
@@ -19,15 +16,13 @@ RUN mkdir -p dist/core/common/__generated__ && \
|
||||
echo "{\"revision\": \"${REVISION_HASH}\"}" > dist/core/common/__generated__/revision.json
|
||||
|
||||
# Install build static assets and clear caches.
|
||||
RUN NODE_ENV=development npm install && \
|
||||
npm run generate && \
|
||||
RUN npm ci && \
|
||||
npm run build && \
|
||||
npm prune --production
|
||||
|
||||
# Setup the environment
|
||||
ENV PATH /usr/src/app/bin:$PATH
|
||||
ENV NODE_ENV production
|
||||
ENV PORT 5000
|
||||
EXPOSE 5000
|
||||
ENV NODE_ENV production
|
||||
|
||||
CMD ["npm", "run", "start"]
|
||||
|
||||
@@ -0,0 +1,240 @@
|
||||
# External Moderation Phases Guide
|
||||
|
||||
This document is in reference to external moderation phases emitted by Coral.
|
||||
You can configure external moderation phases on your installation of Coral by
|
||||
visiting `/admin/configure/moderation/phases`.
|
||||
|
||||
Once you've configured a external moderation phase in Coral, you will start to
|
||||
receive moderation requests in the form of a
|
||||
[External Moderation Requests](#external-moderation-request) at the provided
|
||||
callback URL. These will be in the form of `POST` requests with a `JSON`
|
||||
payload.
|
||||
|
||||
When a comment is created or edited, it will be processed by moderation phases in
|
||||
a predefined order. Any external moderation phase is run last, and only if all
|
||||
other moderation phases before it do not return a status. The current set of
|
||||
moderation phases is listed in order [here](https://github.com/coralproject/talk/blob/master/src/core/server/services/comments/pipeline/phases/index.ts).
|
||||
|
||||
Once you have received a moderation request, you must respond within the
|
||||
provided timeout else the phase will be skipped and it will continue. It is
|
||||
strongly recommended to [verify the request signature](#request-signing).
|
||||
|
||||
The external moderation phase must respond with one of the following:
|
||||
|
||||
1. Do not moderate the comment, and return a 204 without a body.
|
||||
2. Perform a moderation action and return a 200 with a [External Moderation Response](#external-moderation-response)
|
||||
as a `JSON` encoded body containing the operations you want to perform on the
|
||||
comment.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Request Signing](#request-signing)
|
||||
- [Schema](#schema)
|
||||
- [External Moderation Request](#external-moderation-request)
|
||||
- [External Moderation Response](#external-moderation-response)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Request Signing
|
||||
|
||||
Requests sent by Coral for external moderation phases use the same process as
|
||||
those used by webhooks. Refer to the [webhooks documentation](WEBHOOKS.md#webhook-signing)
|
||||
for instructions on how to verify signatures sent by Coral.
|
||||
|
||||
## Schema
|
||||
|
||||
### External Moderation Request
|
||||
|
||||
```ts
|
||||
interface ExternalModerationRequest {
|
||||
/**
|
||||
* action refers to the specific operation being performed. If `NEW`, this
|
||||
* is referring to a new comment being created. If `EDIT`, then this refers to
|
||||
* an operation involving an edit operation on an existing Comment.
|
||||
*/
|
||||
action: "NEW" | "EDIT";
|
||||
|
||||
/**
|
||||
* comment refers to the actual Comment data for the Comment being
|
||||
* created/edited.
|
||||
*/
|
||||
comment: {
|
||||
/**
|
||||
* body refers to the actual body text of the Comment being created/edited.
|
||||
*/
|
||||
body: string;
|
||||
|
||||
/**
|
||||
* parentID is the identifier for the parent comment (if this Comment is a
|
||||
* reply, null otherwise).
|
||||
*/
|
||||
parentID: string | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* author refers to the User that is creating/editing the Comment.
|
||||
*/
|
||||
author: {
|
||||
/**
|
||||
* id is the identifier for this User.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* role refers to the role of this User.
|
||||
*/
|
||||
role: "COMMENTER" | "STAFF" | "MODERATOR" | "ADMIN";
|
||||
};
|
||||
|
||||
/**
|
||||
* story refers to the Story being commented on.
|
||||
*/
|
||||
story: {
|
||||
/**
|
||||
* id is the identifier for this Story.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* url is the URL for this Story.
|
||||
*/
|
||||
url: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* site refers to the Site that the story being commented on belongs to.
|
||||
*/
|
||||
site: {
|
||||
/**
|
||||
* id is the identifier for this Site.
|
||||
*/
|
||||
id: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* tenantID is the identifer of the Tenant that this Comment is being
|
||||
* created/edited on.
|
||||
*/
|
||||
tenantID: string;
|
||||
|
||||
/**
|
||||
* tenantDomain is the domain that is associated with this Tenant that this
|
||||
* Comment is being created/edited on.
|
||||
*/
|
||||
tenantDomain: string;
|
||||
}
|
||||
```
|
||||
|
||||
#### Example
|
||||
|
||||
New comment on a story:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "NEW",
|
||||
"comment": {
|
||||
"body": "Here's a comment!",
|
||||
"parentID": null
|
||||
},
|
||||
"author": {
|
||||
"id": "baf4e943-3594-4fcc-b2ba-3e8de7a76352",
|
||||
"role": "COMMENTER"
|
||||
},
|
||||
"story": {
|
||||
"id": "245b3856-b0a0-4d2f-a6bb-58c71f18d6a6",
|
||||
"url": "http://localhost:1313/posts/a-story-url/"
|
||||
},
|
||||
"site": {
|
||||
"id": "a4bede88-2d2c-4424-bc18-4322a9e285a6"
|
||||
},
|
||||
"tenantID": "19ba5794-7eeb-4d46-a81b-c00c61672501",
|
||||
"tenantDomain": "localhost"
|
||||
}
|
||||
```
|
||||
|
||||
New reply on a comment on a story:
|
||||
|
||||
```json
|
||||
{
|
||||
"action": "NEW",
|
||||
"comment": {
|
||||
"body": "Here's a reply!",
|
||||
"parentID": "d79b787f-f406-49a0-a179-72e3652e54be"
|
||||
},
|
||||
"author": {
|
||||
"id": "baf4e943-3594-4fcc-b2ba-3e8de7a76352",
|
||||
"role": "COMMENTER"
|
||||
},
|
||||
"story": {
|
||||
"id": "245b3856-b0a0-4d2f-a6bb-58c71f18d6a6",
|
||||
"url": "http://localhost:1313/posts/a-story-url/"
|
||||
},
|
||||
"site": {
|
||||
"id": "a4bede88-2d2c-4424-bc18-4322a9e285a6"
|
||||
},
|
||||
"tenantID": "19ba5794-7eeb-4d46-a81b-c00c61672501",
|
||||
"tenantDomain": "localhost"
|
||||
}
|
||||
```
|
||||
|
||||
### External Moderation Response
|
||||
|
||||
```ts
|
||||
interface ExternalModerationResponse {
|
||||
/**
|
||||
* actions is an optional list of any flags to be added to this Comment.
|
||||
*/
|
||||
actions?: Array<{
|
||||
actionType: "FLAG";
|
||||
reason: "COMMENT_DETECTED_TOXIC" | "COMMENT_DETECTED_SPAM";
|
||||
}>;
|
||||
|
||||
/**
|
||||
* tags are any listed tags that should be added to the comment.
|
||||
*/
|
||||
tags?: Array<"FEATURED" | "STAFF">;
|
||||
|
||||
/**
|
||||
* status when provided decides and terminates the moderation process by
|
||||
* setting the status of the comment.
|
||||
*/
|
||||
status?: "NONE" | "APPROVED" | "REJECTED" | "PREMOD" | "SYSTEM_WITHHELD";
|
||||
}
|
||||
```
|
||||
|
||||
#### Examples
|
||||
|
||||
Add a flag to a comment and do not set a status:
|
||||
|
||||
```json
|
||||
{
|
||||
"actions": [{ "actionType": "FLAG", "reason": "COMMENT_DETECTED_TOXIC" }]
|
||||
}
|
||||
```
|
||||
|
||||
Reject a comment:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "REJECTED"
|
||||
}
|
||||
```
|
||||
|
||||
Feature a comment and do not set a status:
|
||||
|
||||
```json
|
||||
{
|
||||
"tags": ["FEATURED"]
|
||||
}
|
||||
```
|
||||
|
||||
Approve a comment and mark it as featured:
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "APPROVED",
|
||||
"tags": ["FEATURED"]
|
||||
}
|
||||
```
|
||||
@@ -1,4 +1,4 @@
|
||||
Copyright 2019 Vox Media, Inc
|
||||
Copyright 2020 Vox Media, Inc
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,41 +1,52 @@
|
||||
# Coral 
|
||||
<p align="center">
|
||||
<a href="https://coralproject.net" target="_blank"><img width="250" src="https://docs.coralproject.net/coral/images/coralproject_by_voxmedia.svg" alt="Coral by Vox Media" /></a>
|
||||
</p>
|
||||
|
||||
Online comments are broken. Our open-source commenting platform, Coral, rethinks
|
||||
how moderation, comment display, and conversation function, creating the
|
||||
opportunity for safer, smarter discussions around your work.
|
||||
[Read more about Coral here](https://coralproject.net/talk).
|
||||
<p align="center">
|
||||
A better commenting experience from <a href="https://product.voxmedia.com/" target="_blank">Vox Media</a>.
|
||||
</p>
|
||||
|
||||
Built with <3 by The Coral Project, a part of [Vox Media](https://product.voxmedia.com/).
|
||||
<p align="center">
|
||||
<a href="https://circleci.com/gh/coralproject/talk" target="_blank"><img src="https://img.shields.io/circleci/build/gh/coralproject/talk?style=flat-square" alt="CircleCI" /></a>
|
||||
<a href="https://hub.docker.com/r/coralproject/talk" target="_blank"><img src="https://img.shields.io/docker/v/coralproject/talk?label=docker%20hub&sort=semver&style=flat-square" alt="Docker Image Version" /></a>
|
||||
<a href="https://hub.docker.com/r/coralproject/talk" target="_blank"><img src="https://img.shields.io/docker/image-size/coralproject/talk?label=docker%20image%20size&sort=semver&style=flat-square" alt="Docker Image Size" /></a>
|
||||
<a href="https://twitter.com/coralproject" target="_blank"><img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/coralproject?style=flat-square"></a>
|
||||
</p>
|
||||
|
||||
Preview Coral easily by running Coral via a Heroku App:
|
||||
## Description
|
||||
|
||||
[](https://heroku.com/deploy?template=https://github.com/coralproject/talk)
|
||||
Online comments are broken. Our open-source commenting platform,
|
||||
[Coral](https://coralproject.net), rethinks how moderation, comment display, and
|
||||
conversation function, creating the opportunity for safer, smarter discussions
|
||||
around your work.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Documentation](#documentation)
|
||||
- [Pre-Launch Guide](#pre-launch-guide)
|
||||
- [More Resources](#more-resources)
|
||||
- [License](#license)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
We offer hosting and support packages for Coral, as well as exclusive,
|
||||
customer-only features. [Contact us](https://coralproject.net/pricing/) for more
|
||||
information or [sign up for a webinar](https://coralproject.net).
|
||||
|
||||
## Documentation
|
||||
|
||||
You can get started with Coral using our [Documentation](https://docs.coralproject.net/talk/).
|
||||
If you're new to Coral, the [Coral documentation](https://docs.coralproject.net/)
|
||||
is a great place to start running and developing with Coral.
|
||||
|
||||
## Pre-Launch Guide
|
||||
You’ve installed Coral, and you’re preparing to launch it on your site. The real
|
||||
community work starts now, before you go live. You have a unique opportunity
|
||||
pre-launch to set your community up for success. Read our
|
||||
[Community Guides](https://guides.coralproject.net/start-here/) to learn more.
|
||||
|
||||
You’ve installed Talk on your server, and you’re preparing to launch it on your site. The real community work starts now, before you go live. You have a unique opportunity pre-launch to set your community up for success. Read our [Talk Community Guide](https://coralproject.net/blog/youve-installed-talk-now-what/).
|
||||
## Support
|
||||
|
||||
## More Resources
|
||||
We can help you set up Coral, migrate your comments from another system,
|
||||
integrate your registration platform, pair with your programmers, and help you
|
||||
with bespoke installs. To learn more, [contact us](https://coralproject.net/pricing/).
|
||||
|
||||
- [Our Blog](https://coralproject.net/blog)
|
||||
- [Community Guides for Journalism](https://guides.coralproject.net/)
|
||||
- [More About Us](https://coralproject.net/)
|
||||
## Contributing
|
||||
|
||||
Coral is a Apache-2.0 licensed open-source project built with <3 by the Coral
|
||||
team, a part of [Vox Media](https://product.voxmedia.com/).
|
||||
|
||||
If you are interested in contributing to Coral, check out our [Contributor's Guide](CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
Coral is released under the [Apache License, v2.0](/LICENSE).
|
||||
Coral is [Apache-2.0 licensed](LICENSE).
|
||||
|
||||
+220
@@ -0,0 +1,220 @@
|
||||
# Webhooks Guide
|
||||
|
||||
This document is in reference to webhooks emitted by Coral. You can configure
|
||||
webhooks on your installation of Coral by visiting `/admin/configure/webhooks`.
|
||||
|
||||
Once you've configured a webhook endpoint in Coral, you will receive updates
|
||||
from Coral when those events occur. These will be in the form of `POST` requests
|
||||
with a `JSON` payload consisting of the schema represented below.
|
||||
|
||||
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
|
||||
## Table of Contents
|
||||
|
||||
- [Webhook Signing](#webhook-signing)
|
||||
- [How to verify the signature(s)](#how-to-verify-the-signatures)
|
||||
- [Schema](#schema)
|
||||
- [Events Listing](#events-listing)
|
||||
- [Events](#events)
|
||||
|
||||
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
|
||||
|
||||
## Webhook Signing
|
||||
|
||||
Each webhook sent by Coral is signed by your webhook endpoint signing secret.
|
||||
The signature method closely resembles the signing method used by Stripe for
|
||||
their `v1` signing method. The `X-Coral-Signature` header contains one or more
|
||||
signatures prefixed by `sha256=`.
|
||||
|
||||
If you receive a signature containing multiple signatures, it is typically when
|
||||
you have rolled the signing secret from the administrative panel, and chosen to
|
||||
keep the previous secret active for a duration of time.
|
||||
|
||||
### How to verify the signature(s)
|
||||
|
||||
```js
|
||||
// Set your signing secret here from the administration panel.
|
||||
const SIGNING_SECRET = "< YOUR SIGNING SECRET HERE >";
|
||||
|
||||
// We're using crypto to verify the signatures.
|
||||
const crypto = require("crypto");
|
||||
|
||||
// We're using express to receive webhooks here.
|
||||
const app = require("express")();
|
||||
|
||||
// Use the body-parser to get the raw body as a buffer so we can use it with the
|
||||
// hashing functions.
|
||||
const parser = require("body-parser");
|
||||
|
||||
function extractEvent(body, sig) {
|
||||
// Step 1: Extract signatures from the header.
|
||||
const signatures = sig
|
||||
// Split the header by `,` to get a list of elements.
|
||||
.split(",")
|
||||
// Split each element by `=` to get a prefix and value pair.
|
||||
.map(element => element.split("="))
|
||||
// Grab all the elements with the prefix of `sha256`.
|
||||
.filter(([prefix]) => prefix === "sha256")
|
||||
// Grab the value from the prefix and value pair.
|
||||
.map(([, value]) => value);
|
||||
|
||||
// Step 2: Prepare the `signed_payload`.
|
||||
const signed_payload = body;
|
||||
|
||||
// Step 3: Calculate the expected signature.
|
||||
const expected = crypto
|
||||
.createHmac("sha256", SIGNING_SECRET)
|
||||
.update(signed_payload)
|
||||
.digest()
|
||||
.toString("hex");
|
||||
|
||||
// Step 4: Compare signatures.
|
||||
if (
|
||||
// For each of the signatures on the request...
|
||||
!signatures.some(signature =>
|
||||
// Compare the expected signature to the signature on in the header. If at
|
||||
// least one of the match, we should continue to process the event.
|
||||
crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
|
||||
)
|
||||
) {
|
||||
throw new Error("Invalid signature");
|
||||
}
|
||||
|
||||
// Parse the JSON for the event.
|
||||
return JSON.parse(body.toString());
|
||||
}
|
||||
|
||||
app.post("/webhook", parser.raw({ type: "application/json" }), (req, res) => {
|
||||
const sig = req.headers["x-coral-signature"];
|
||||
|
||||
let event;
|
||||
|
||||
try {
|
||||
// Parse the JSON for the event.
|
||||
event = extractEvent(req.body, sig);
|
||||
} catch (err) {
|
||||
return res.status(400).send(`Webhook Error: ${err.message}`);
|
||||
}
|
||||
|
||||
// Handle the event.
|
||||
switch (event.type) {
|
||||
case "STORY_CREATED":
|
||||
const data = event.data;
|
||||
console.log(
|
||||
`A Story with ID ${data.storyID} and URL ${data.storyURL} was created!`
|
||||
);
|
||||
break;
|
||||
// ... handle other event types.
|
||||
default:
|
||||
// Unexpected event type
|
||||
return response.status(400).end();
|
||||
}
|
||||
|
||||
// Return a response to acknowledge receipt of the event
|
||||
res.json({ received: true });
|
||||
});
|
||||
|
||||
app.listen(4242, () => console.log("Running on port 4242"));
|
||||
```
|
||||
|
||||
The procedure of how to verify the signatures follows.
|
||||
|
||||
#### **Step 1**: Extract signatures from the header
|
||||
|
||||
Split the header using `,` as the separator, to get a list of elements. Then
|
||||
split each of these elements using `=` as the separator, to get a prefix and
|
||||
value pair. The value for the prefix `sha256` corresponds to the signature(s).
|
||||
|
||||
#### **Step 2**: Prepare the `signed_payload` string
|
||||
|
||||
You can do this by taking the string contents of the body (before parsing or the
|
||||
request body).
|
||||
|
||||
#### **Step 3**: Calculate the expected signature
|
||||
|
||||
Compute an HMAC signature using the SHA256 hash function. You can use the
|
||||
webhook endpoint's signing secret as the key, and the above calculated
|
||||
`signed_payload` as the message.
|
||||
|
||||
#### **Step 4**: Compare signatures
|
||||
|
||||
Compare the signature(s) in the header to the expected signature. To protect
|
||||
against timing attacks, ensure you use a constant-time string comparison
|
||||
function when comparing signatures.
|
||||
|
||||
## Schema
|
||||
|
||||
```ts
|
||||
{
|
||||
/**
|
||||
* id is the identifier for this event, each event
|
||||
* will have a unique id.
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* type is the name of this event, this indicates
|
||||
* what is stored in the following `data` property.
|
||||
* Refer to the `Events List` below to see what the
|
||||
* type is for each event.
|
||||
*/
|
||||
type: string;
|
||||
|
||||
/**
|
||||
* data is the object representing this particular
|
||||
* event. Each type of event has a different shape
|
||||
* to the data property. Refer to the `Events List`
|
||||
* below to see what the data looks like for each
|
||||
* event.
|
||||
*/
|
||||
data: object;
|
||||
|
||||
/**
|
||||
* createdAt is the ISO 8601 representation of the
|
||||
* date when this event was created.
|
||||
*/
|
||||
createdAt: string;
|
||||
|
||||
/**
|
||||
* tenantID is the ID of the Tenant that this event originated at.
|
||||
*/
|
||||
tenantID: string;
|
||||
|
||||
/**
|
||||
* tenantDomain is the domain that is associated with this Tenant that this event originated at.
|
||||
*/
|
||||
tenantDomain: string;
|
||||
}
|
||||
```
|
||||
|
||||
## Events Listing
|
||||
|
||||
- [`STORY_CREATED`](#story-created-event)
|
||||
|
||||
## Events
|
||||
|
||||
- <a id="story-created-event">**STORY_CREATED**</a>
|
||||
|
||||
```ts
|
||||
{
|
||||
id: string;
|
||||
type: "STORY_CREATED";
|
||||
data: {
|
||||
/**
|
||||
* storyID is the ID of the newly created Story.
|
||||
*/
|
||||
storyID: string;
|
||||
|
||||
/**
|
||||
* storyURL is the URL of the newly created Story.
|
||||
*/
|
||||
storyURL: string;
|
||||
|
||||
/**
|
||||
* siteID is the Site that the newly created Story was created on.
|
||||
*/
|
||||
siteID: string;
|
||||
}
|
||||
createdAt: string;
|
||||
}
|
||||
```
|
||||
@@ -10,10 +10,6 @@
|
||||
"description": "The shared secret to use to sign JSON Web Tokens (JWT) with the selected signing algorithm.",
|
||||
"generator": "secret"
|
||||
},
|
||||
"CONCURRENCY": {
|
||||
"description": "The number of worker nodes to spawn to handle traffic.",
|
||||
"value": "1"
|
||||
},
|
||||
"LOCALE": {
|
||||
"description": "Specify the default locale to use for all requests without a locale specified",
|
||||
"value": "en-US"
|
||||
|
||||
+10
-18
@@ -1,28 +1,20 @@
|
||||
/**
|
||||
* This is a project wide babel configuration.
|
||||
* https://babeljs.io/docs/en/config-files#project-wide-configuration
|
||||
*
|
||||
* We use this file to apply babel configuration to packages in `node_modules`
|
||||
*/
|
||||
|
||||
const lodashOptimizations =
|
||||
// Note: If Webpack env is set, we are building for the client.
|
||||
|
||||
const plugins =
|
||||
process.env.WEBPACK === "true" ? ["use-lodash-es", "lodash"] : [];
|
||||
|
||||
const environment =
|
||||
process.env.WEBPACK === "true"
|
||||
? { modules: false }
|
||||
: { targets: { node: "current" }, modules: "commonjs" };
|
||||
|
||||
module.exports = {
|
||||
babelrcRoots: ["./src/core/client/*"],
|
||||
env: {
|
||||
production: {
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
development: {
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
test: {
|
||||
presets: [
|
||||
["@babel/env", { targets: { node: "current" } }],
|
||||
"@babel/react",
|
||||
],
|
||||
plugins: ["dynamic-import-node"],
|
||||
},
|
||||
},
|
||||
plugins,
|
||||
presets: [["@babel/env", environment]],
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFiles: [
|
||||
"<rootDir>/src/core/build/polyfills.js",
|
||||
"<rootDir>/src/core/client/test/polyfills.ts",
|
||||
"<rootDir>/src/core/client/test/setup.ts",
|
||||
],
|
||||
setupFilesAfterEnv: ["<rootDir>/src/core/client/test/setupTestFramework.ts"],
|
||||
@@ -40,11 +40,9 @@ module.exports = {
|
||||
snapshotSerializers: ["enzyme-to-json/serializer"],
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
useBabelrc: true,
|
||||
tsConfigFile: path.resolve(
|
||||
__dirname,
|
||||
"../../src/core/client/tsconfig.json"
|
||||
),
|
||||
babelConfig: true,
|
||||
tsConfig: path.resolve(__dirname, "../../src/core/client/tsconfig.json"),
|
||||
},
|
||||
},
|
||||
preset: "ts-jest/presets/js-with-babel",
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ module.exports = {
|
||||
roots: ["<rootDir>/src"],
|
||||
collectCoverageFrom: ["**/*.{js,jsx,mjs,ts,tsx}"],
|
||||
coveragePathIgnorePatterns: ["/node_modules/"],
|
||||
setupFilesAfterEnv: ["<rootDir>/src/core/server/test/setupTestFramework.ts"],
|
||||
testMatch: ["**/*.spec.{js,jsx,mjs,ts,tsx}"],
|
||||
testPathIgnorePatterns: ["/node_modules/", "/client/"],
|
||||
testEnvironment: "node",
|
||||
@@ -20,8 +21,9 @@ module.exports = {
|
||||
moduleFileExtensions: ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||
globals: {
|
||||
"ts-jest": {
|
||||
useBabelrc: true,
|
||||
tsConfigFile: path.resolve(__dirname, "../../src/tsconfig.json"),
|
||||
babelConfig: true,
|
||||
tsConfig: path.resolve(__dirname, "../../src/tsconfig.json"),
|
||||
},
|
||||
},
|
||||
preset: "ts-jest",
|
||||
};
|
||||
|
||||
@@ -1,107 +1,10 @@
|
||||
// Apply all the configuration provided in the .env file.
|
||||
require("dotenv").config();
|
||||
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
const extensions = [".ts", ".tsx", ".js"];
|
||||
// TODO: There is some weird issue with including paths.ts here
|
||||
const postCSSConfigPath = "./src/core/build/postcss.config";
|
||||
const isProduction = process.NODE_ENV === "production";
|
||||
const appDirectory = fs.realpathSync(process.cwd());
|
||||
|
||||
const styleLoader = {
|
||||
loader: require.resolve("style-loader"),
|
||||
options: {
|
||||
sourceMap: true,
|
||||
hmr: true,
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
title: "Coral 5.0",
|
||||
source: "./src",
|
||||
typescript: true,
|
||||
module.exports = {
|
||||
title: "Coral 5",
|
||||
src: "./src",
|
||||
host: process.env.HOST || "0.0.0.0",
|
||||
port: parseInt(process.env.DOCZ_PORT, 10) || 3030,
|
||||
codeSandbox: false, // Too large to create code sandboxes..
|
||||
modifyBundlerConfig: config => {
|
||||
config.entry.app.push(
|
||||
`${appDirectory}/src/core/client/ui/theme/variables.css.ts`
|
||||
);
|
||||
config.module.rules.push({
|
||||
test: /\.css\.ts$/,
|
||||
use: [
|
||||
styleLoader,
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 2,
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: postCSSConfigPath,
|
||||
},
|
||||
parser: "postcss-js",
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
presets: [
|
||||
"@babel/typescript",
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: { node: "10.0.0" }, modules: "commonjs" },
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
config.module.rules.push({
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
styleLoader,
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
importLoaders: 1,
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
sourceMap: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: postCSSConfigPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
config.resolve.plugins = [
|
||||
new TsconfigPathsPlugin({
|
||||
extensions,
|
||||
// TODO: There is some weird issue with including paths.ts here
|
||||
configFile: "./src/core/client/tsconfig.json",
|
||||
}),
|
||||
];
|
||||
// fs.writeFileSync(path.resolve(__dirname, "tmp"), stringify(config, null, 2));
|
||||
return config;
|
||||
},
|
||||
files: "**/*.mdx",
|
||||
};
|
||||
|
||||
+216
@@ -0,0 +1,216 @@
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const TsconfigPathsPlugin = require("tsconfig-paths-webpack-plugin");
|
||||
|
||||
/** Path to postCSSConfig */
|
||||
const postCSSConfigPath = path.resolve(
|
||||
__dirname,
|
||||
"../src/core/build/postcss.config"
|
||||
);
|
||||
const rootDir = path.resolve(__dirname, "../");
|
||||
const srcDir = path.resolve(rootDir, "./src");
|
||||
const appTsconfig = path.resolve(rootDir, "./src/core/client/tsconfig.json");
|
||||
|
||||
const CSS_PATTERN = /\.css$/;
|
||||
const MODULE_CSS_PATTERN = /\.module\.css$/;
|
||||
|
||||
// Define `RegExp.toJSON` so that we can stringify RegExp.
|
||||
Object.defineProperty(RegExp.prototype, "toJSON", {
|
||||
value: RegExp.prototype.toString,
|
||||
});
|
||||
|
||||
const isCssRules = (rule) =>
|
||||
rule.test &&
|
||||
(rule.test.toString() === CSS_PATTERN.toString() ||
|
||||
rule.test.toString() === MODULE_CSS_PATTERN.toString());
|
||||
|
||||
const findCssRules = (config) =>
|
||||
config.module.rules.find(
|
||||
(rule) => Array.isArray(rule.oneOf) && rule.oneOf.every(isCssRules)
|
||||
);
|
||||
|
||||
exports.onCreateWebpackConfig = ({
|
||||
stage,
|
||||
rules,
|
||||
loaders,
|
||||
plugins,
|
||||
actions,
|
||||
getConfig,
|
||||
}) => {
|
||||
// Get webpack config.
|
||||
const config = getConfig();
|
||||
|
||||
if (stage === "develop") {
|
||||
config.entry.commons.push(
|
||||
// Add our stream css variables file.
|
||||
`${srcDir}/core/client/ui/theme/stream.css.ts`
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: (cvle) couldn't get build to work...
|
||||
if (stage === "build-javascript") {
|
||||
config.entry.app = [
|
||||
config.entry.app,
|
||||
`${appDir}/core/client/ui/theme/stream.css.ts`,
|
||||
];
|
||||
}
|
||||
*/
|
||||
|
||||
// Find the gatsby CSS rules.
|
||||
const cssRules = findCssRules(config);
|
||||
// Exclude them from our src dir because they are incomaptible with our
|
||||
// CSS rules.
|
||||
cssRules.exclude = srcDir;
|
||||
// Add .tx .tsx to modules
|
||||
config.resolve.extensions.push(".ts", ".tsx");
|
||||
actions.replaceWebpackConfig(config);
|
||||
|
||||
// Write out webpack config to .docz folder.
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, "webpack-" + stage),
|
||||
JSON.stringify(config, {}, 2)
|
||||
);
|
||||
|
||||
// Turn on sourceMap during develop.
|
||||
const sourceMap = stage.startsWith("develop");
|
||||
|
||||
// CSS loaders to prepend.
|
||||
const prependCSSLoaders = [];
|
||||
if (stage === "develop") {
|
||||
prependCSSLoaders.push(loaders.style());
|
||||
}
|
||||
|
||||
/*
|
||||
TODO: (cvle) couldn't get build to work...
|
||||
if (stage === "build-javascript") {
|
||||
moreLoaders.push(loaders.style());
|
||||
}
|
||||
*/
|
||||
|
||||
actions.setWebpackConfig({
|
||||
resolve: {
|
||||
plugins: [
|
||||
// Resolve our custom paths.
|
||||
new TsconfigPathsPlugin({
|
||||
extensions: [".ts", ".tsx", ".js"],
|
||||
configFile: path.resolve(rootDir, "./src/core/client/tsconfig.json"),
|
||||
}),
|
||||
],
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
include: srcDir,
|
||||
oneOf: [
|
||||
{
|
||||
test: /\.css\.ts$/,
|
||||
use: [
|
||||
...prependCSSLoaders,
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
},
|
||||
importLoaders: 2,
|
||||
sourceMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: postCSSConfigPath,
|
||||
},
|
||||
parser: "postcss-js",
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
configFile: false,
|
||||
babelrc: false,
|
||||
presets: [
|
||||
"@babel/typescript",
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: { node: "current" }, modules: "commonjs" },
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.css$/,
|
||||
use: [
|
||||
...prependCSSLoaders,
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: {
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
},
|
||||
importLoaders: 1,
|
||||
sourceMap,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("postcss-loader"),
|
||||
options: {
|
||||
config: {
|
||||
path: postCSSConfigPath,
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
test: /\.tsx?$/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
root: rootDir,
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
// It enables caching results in ./node_modules/.cache/babel-loader/
|
||||
// directory for faster rebuilds.
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
loader: require.resolve("ts-loader"),
|
||||
options: {
|
||||
configFile: appTsconfig,
|
||||
compilerOptions: {
|
||||
target: "es2015",
|
||||
module: "esnext",
|
||||
jsx: "preserve",
|
||||
noEmit: false,
|
||||
},
|
||||
transpileOnly: true,
|
||||
// Overwrites the behavior of `include` and `exclude` to only
|
||||
// include files that are actually being imported and which
|
||||
// are necessary to compile the bundle.
|
||||
onlyCompileBundledFiles: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
// Write out processed webpack config to .docz folder.
|
||||
fs.writeFileSync(
|
||||
path.resolve(__dirname, "webpack-" + stage + "-processed"),
|
||||
JSON.stringify(getConfig(), {}, 2)
|
||||
);
|
||||
};
|
||||
Generated
+34991
-16648
File diff suppressed because it is too large
Load Diff
+326
-263
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "@coralproject/talk",
|
||||
"version": "5.3.0",
|
||||
"version": "6.3.1",
|
||||
"author": "The Coral Project",
|
||||
"homepage": "https://coralproject.net/",
|
||||
"sideEffects": [
|
||||
"*.css.ts",
|
||||
"*.css"
|
||||
],
|
||||
"repository": {
|
||||
@@ -11,22 +12,24 @@
|
||||
"url": "git://github.com/coralproject/talk.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.0.0",
|
||||
"npm": ">=6.9.0"
|
||||
"node": "^12",
|
||||
"npm": "^6"
|
||||
},
|
||||
"bugs": "https://github.com/coralproject/talk/issues",
|
||||
"contributors": [
|
||||
"https://github.com/coralproject/talk/graphs/contributors"
|
||||
],
|
||||
"description": "A better commenting experience from Mozilla, The Washington Post, and The New York Times.",
|
||||
"description": "A better commenting experience from Vox Media.",
|
||||
"scripts": {
|
||||
"clean": "gulp clean",
|
||||
"build": "NODE_ENV=production npm-run-all generate-persist --parallel build:client build:server",
|
||||
"build:development": "NODE_ENV=development npm-run-all generate --parallel build:client build:server",
|
||||
"build:client": "ts-node --transpile-only ./scripts/build.ts",
|
||||
"build:server": "gulp server",
|
||||
"migration:create": "ts-node --transpile-only ./scripts/migration/create.ts",
|
||||
"doctoc": "doctoc --title='## Table of Contents' --github README.md",
|
||||
"docs:events": "ts-node ./scripts/generateEventDocs.ts ./src/core/client/stream/events.ts ./events.md",
|
||||
"docs:events": "ts-node ./scripts/generateEventDocs.ts ./src/core/client/stream/events.ts ./CLIENT_EVENTS.md",
|
||||
"docs:css-variables": "ts-node ./scripts/generateCSSVariablesDocs.ts ./src/core/client/ui/theme/streamVariables.ts ./CSS_VARIABLES.md",
|
||||
"doctoc": "doctoc --maxlevel=3 --title '## Table of Contents' CLIENT_EVENTS.md CONTRIBUTING.md WEBHOOKS.md EXTERNAL_MODERATION_PHASES.md",
|
||||
"generate": "npm-run-all generate:css-types generate:schema generate:relay",
|
||||
"generate-persist": "npm-run-all generate:css-types generate:schema generate:relay-persist",
|
||||
"generate:css-types": "tcm src/core/client/",
|
||||
@@ -40,15 +43,15 @@
|
||||
"generate:schema": "node ./scripts/generateSchemaTypes.js",
|
||||
"docz": "docz",
|
||||
"start": "NODE_ENV=production node dist/index.js",
|
||||
"start:development": "NODE_ENV=development CONCURRENCY=${CONCURRENCY:-1} TS_NODE_PROJECT=./src/tsconfig.json ts-node-dev --inspect --transpile-only --no-notify -r tsconfig-paths/register ./src/index.ts",
|
||||
"start:development": "NODE_ENV=development TS_NODE_PROJECT=./src/tsconfig.json ts-node-dev --inspect --transpile-only --no-notify -r tsconfig-paths/register ./src/index.ts",
|
||||
"start:webpackDevServer": "ts-node --transpile-only ./scripts/start.ts",
|
||||
"lint": "npm-run-all --parallel lint:* tscheck:*",
|
||||
"lint:server": "eslint 'src/**/*.{js,ts,tsx}' --ignore-pattern 'src/core/client/**'",
|
||||
"lint:client": "eslint 'src/core/client/**/*.{js,ts,tsx}'",
|
||||
"lint:scripts": "eslint 'scripts/**/*.{js,ts,tsx}'",
|
||||
"lint:graphql": "graphql-schema-linter src/core/server/graph/tenant/schema/schema.graphql",
|
||||
"lint:graphql": "graphql-schema-linter src/core/server/graph/schema/schema.graphql",
|
||||
"lint-fix": "npm run lint:server -- --fix && npm run lint:client -- --fix && npm run lint:scripts -- --fix",
|
||||
"test": "node scripts/test.js --env=jsdom",
|
||||
"test": "node --trace-warnings scripts/test.js --env=jsdom",
|
||||
"tscheck": "npm-run-all --parallel tscheck:*",
|
||||
"tscheck:server": "tsc --project ./src/tsconfig.json --noEmit",
|
||||
"tscheck:client": "tsc --project ./src/core/client/tsconfig.json --noEmit",
|
||||
@@ -58,318 +61,361 @@
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@coralproject/bunyan-prettystream": "^0.1.4",
|
||||
"@metascraper/helpers": "^5.7.21",
|
||||
"akismet-api": "^4.2.0",
|
||||
"apollo-server-express": "^2.8.1",
|
||||
"archiver": "^3.0.3",
|
||||
"@emotion/core": "^10.0.28",
|
||||
"@fluent/bundle": "^0.15.0",
|
||||
"@fluent/dom": "^0.6.0",
|
||||
"@hapi/joi": "^17.1.1",
|
||||
"@metascraper/helpers": "^5.11.6",
|
||||
"@rudderstack/rudder-sdk-node": "0.0.2",
|
||||
"@types/recharts": "^1.8.9",
|
||||
"abort-controller": "^3.0.0",
|
||||
"akismet-api": "^5.0.0",
|
||||
"apollo-server-core": "^2.14.4",
|
||||
"apollo-server-express": "^2.14.2",
|
||||
"archiver": "^3.1.1",
|
||||
"basic-auth": "^2.0.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"bull": "^3.8.1",
|
||||
"bull": "^3.13.0",
|
||||
"bunyan": "^1.8.12",
|
||||
"cheerio": "^1.0.0-rc.2",
|
||||
"bytes": "^3.1.0",
|
||||
"cheerio": "^1.0.0-rc.3",
|
||||
"consolidate": "0.14.0",
|
||||
"content-security-policy-builder": "^2.0.0",
|
||||
"convict": "^4.3.1",
|
||||
"content-security-policy-builder": "^2.1.0",
|
||||
"convict": "^5.2.0",
|
||||
"cookie": "^0.4.0",
|
||||
"cookie-parser": "^1.4.4",
|
||||
"cors": "^2.8.4",
|
||||
"cron": "^1.7.1",
|
||||
"csv-stringify": "^5.3.0",
|
||||
"dataloader": "^1.4.0",
|
||||
"dompurify": "^2.0.7",
|
||||
"dotenv": "^6.0.0",
|
||||
"dotenv-expand": "^4.2.0",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cors": "^2.8.5",
|
||||
"cron": "^1.8.2",
|
||||
"csv-stringify": "^5.3.6",
|
||||
"dataloader": "1.4.0",
|
||||
"dompurify": "^2.0.8",
|
||||
"dotenv": "^8.2.0",
|
||||
"emailjs": "^3.2.0",
|
||||
"env-rewrite": "^1.0.2",
|
||||
"express": "^4.16.3",
|
||||
"express-static-gzip": "^0.3.2",
|
||||
"fluent": "^0.10.0",
|
||||
"fluent-dom": "^0.4.1",
|
||||
"fs-extra": "^6.0.1",
|
||||
"graphql": "^0.13.2",
|
||||
"graphql-config": "^2.0.1",
|
||||
"graphql-extensions": "^0.2.1",
|
||||
"graphql-fields": "^1.1.0",
|
||||
"graphql-playground-html": "^1.6.0",
|
||||
"graphql-redis-subscriptions": "^2.1.0",
|
||||
"express": "^4.17.1",
|
||||
"express-enforces-ssl": "^1.1.0",
|
||||
"express-static-gzip": "^2.0.6",
|
||||
"fs-extra": "^8.1.0",
|
||||
"graphql": "^14.6.0",
|
||||
"graphql-config": "^2.2.2",
|
||||
"graphql-extensions": "^0.11.0",
|
||||
"graphql-fields": "2.0.3",
|
||||
"graphql-import": "^0.7.1",
|
||||
"graphql-playground-html": "1.6.13",
|
||||
"graphql-redis-subscriptions": "^2.2.1",
|
||||
"graphql-subscriptions": "^1.1.0",
|
||||
"graphql-tools": "^3.0.5",
|
||||
"html-minifier": "^3.5.21",
|
||||
"html-to-text": "^4.0.0",
|
||||
"ioredis": "^4.9.0",
|
||||
"joi": "^13.4.0",
|
||||
"jsdom": "^15.0.0",
|
||||
"jsonwebtoken": "^8.3.0",
|
||||
"juice": "^5.2.0",
|
||||
"jwks-rsa": "^1.3.0",
|
||||
"keymaster": "^1.6.2",
|
||||
"linkifyjs": "^2.1.8",
|
||||
"lodash": "^4.17.15",
|
||||
"graphql-tools": "^4.0.7",
|
||||
"helmet": "^3.22.0",
|
||||
"html-minifier": "^4.0.0",
|
||||
"html-to-text": "^5.1.1",
|
||||
"ioredis": "^4.16.1",
|
||||
"jsdom": "^16.2.2",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"juice": "^6.0.0",
|
||||
"jwks-rsa": "^1.7.0",
|
||||
"linkifyjs": "^2.1.9",
|
||||
"lodash": "^4.17.19",
|
||||
"long-settimeout": "^1.0.1",
|
||||
"lru-cache": "^5.1.1",
|
||||
"luxon": "^1.12.0",
|
||||
"metascraper-author": "^5.8.0",
|
||||
"metascraper-description": "^5.8.0",
|
||||
"metascraper-image": "^5.8.0",
|
||||
"metascraper-title": "^5.8.0",
|
||||
"mongodb": "^3.2.7",
|
||||
"luxon": "^1.22.2",
|
||||
"metascraper-author": "^5.11.6",
|
||||
"metascraper-description": "^5.11.6",
|
||||
"metascraper-image": "^5.11.6",
|
||||
"metascraper-title": "^5.11.6",
|
||||
"mongodb": "^3.5.9",
|
||||
"mongodb-core": "^3.2.7",
|
||||
"ms": "^2.1.1",
|
||||
"node-fetch": "^2.2.0",
|
||||
"nodemailer": "^4.6.7",
|
||||
"nunjucks": "^3.1.3",
|
||||
"ms": "^2.1.2",
|
||||
"node-fetch": "^2.6.0",
|
||||
"nunjucks": "^3.2.1",
|
||||
"on-finished": "^2.3.0",
|
||||
"passport": "^0.4.0",
|
||||
"passport-facebook": "^2.1.1",
|
||||
"passport-google-oauth2": "^0.1.6",
|
||||
"passport": "^0.4.1",
|
||||
"passport-facebook": "^3.0.0",
|
||||
"passport-google-oauth2": "^0.2.0",
|
||||
"passport-local": "^1.0.0",
|
||||
"passport-oauth2": "^1.4.0",
|
||||
"passport-oauth2": "^1.5.0",
|
||||
"passport-strategy": "^1.0.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"permit": "^0.2.4",
|
||||
"prom-client": "^11.3.0",
|
||||
"prom-client": "^12.0.0",
|
||||
"proxy-agent": "^3.1.1",
|
||||
"querystringify": "^2.1.0",
|
||||
"querystringify": "^2.1.1",
|
||||
"react-helmet": "^5.2.1",
|
||||
"source-map-support": "^0.5.12",
|
||||
"stack-utils": "^1.0.2",
|
||||
"recharts": "^1.8.5",
|
||||
"source-map-support": "^0.5.16",
|
||||
"stack-utils": "^2.0.1",
|
||||
"striptags": "^3.1.1",
|
||||
"throng": "^4.0.0",
|
||||
"tsscmp": "^1.0.6",
|
||||
"url-regex": "^5.0.0",
|
||||
"uuid": "^3.3.3",
|
||||
"verror": "^1.10.0"
|
||||
"uuid": "^7.0.3",
|
||||
"verror": "^1.10.0",
|
||||
"xregexp": "^4.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.4.5",
|
||||
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
|
||||
"@babel/plugin-transform-modules-commonjs": "^7.4.4",
|
||||
"@babel/polyfill": "^7.4.4",
|
||||
"@babel/preset-env": "^7.4.5",
|
||||
"@babel/preset-react": "^7.0.0",
|
||||
"@babel/preset-typescript": "^7.3.3",
|
||||
"@babel/core": "^7.10.3",
|
||||
"@babel/preset-env": "^7.10.3",
|
||||
"@babel/preset-react": "^7.10.1",
|
||||
"@babel/preset-typescript": "^7.10.1",
|
||||
"@babel/runtime-corejs3": "^7.10.3",
|
||||
"@coralproject/npm-run-all": "^4.1.5",
|
||||
"@coralproject/rte": "^0.11.0",
|
||||
"@coralproject/rte": "^1.1.1",
|
||||
"@fluent/react": "^0.11.1",
|
||||
"@intervolga/optimize-cssnano-plugin": "^1.0.6",
|
||||
"@types/agent-base": "^4.2.0",
|
||||
"@types/archiver": "^3.0.0",
|
||||
"@types/basic-auth": "^1.1.2",
|
||||
"@types/bcryptjs": "^2.4.1",
|
||||
"@types/bull": "^3.5.12",
|
||||
"@types/bunyan": "^1.8.4",
|
||||
"@types/case-sensitive-paths-webpack-plugin": "^2.1.2",
|
||||
"@types/cheerio": "^0.22.8",
|
||||
"@types/classnames": "^2.2.7",
|
||||
"@types/commander": "^2.12.2",
|
||||
"@types/archiver": "^3.1.0",
|
||||
"@types/basic-auth": "^1.1.3",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
"@types/bull": "^3.12.1",
|
||||
"@types/bunyan": "^1.8.6",
|
||||
"@types/bytes": "^3.1.0",
|
||||
"@types/case-sensitive-paths-webpack-plugin": "^2.1.4",
|
||||
"@types/cheerio": "^0.22.17",
|
||||
"@types/classnames": "^2.2.10",
|
||||
"@types/common-tags": "^1.8.0",
|
||||
"@types/compression-webpack-plugin": "^2.0.0",
|
||||
"@types/consolidate": "0.0.34",
|
||||
"@types/convict": "^4.2.0",
|
||||
"@types/compression-webpack-plugin": "^2.0.1",
|
||||
"@types/consolidate": "0.14.0",
|
||||
"@types/convict": "^5.2.1",
|
||||
"@types/cookie": "^0.3.3",
|
||||
"@types/cookie-parser": "^1.4.1",
|
||||
"@types/cors": "^2.8.4",
|
||||
"@types/cron": "^1.7.1",
|
||||
"@types/cross-spawn": "^6.0.0",
|
||||
"@types/dompurify": "0.0.33",
|
||||
"@types/dotenv": "^4.0.3",
|
||||
"@types/enzyme": "^3.1.15",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.3",
|
||||
"@types/cookie-parser": "^1.4.2",
|
||||
"@types/cors": "^2.8.6",
|
||||
"@types/cron": "^1.7.2",
|
||||
"@types/cross-spawn": "^6.0.1",
|
||||
"@types/dompurify": "^2.0.1",
|
||||
"@types/enzyme": "^3.10.5",
|
||||
"@types/enzyme-adapter-react-16": "^1.0.6",
|
||||
"@types/eventemitter2": "^4.1.0",
|
||||
"@types/express": "^4.16.0",
|
||||
"@types/flat": "0.0.28",
|
||||
"@types/fs-extra": "^5.0.4",
|
||||
"@types/graphql": "^0.13.3",
|
||||
"@types/html-minifier": "^3.5.2",
|
||||
"@types/express": "^4.17.4",
|
||||
"@types/express-enforces-ssl": "^1.1.1",
|
||||
"@types/express-serve-static-core": "^4.17.3",
|
||||
"@types/flat": "^5.0.0",
|
||||
"@types/fs-extra": "^8.1.0",
|
||||
"@types/hapi__joi": "^16.0.12",
|
||||
"@types/helmet": "^0.0.45",
|
||||
"@types/html-minifier": "^3.5.3",
|
||||
"@types/html-minifier-terser": "^5.0.0",
|
||||
"@types/html-to-text": "^1.4.31",
|
||||
"@types/html-webpack-plugin": "^3.2.0",
|
||||
"@types/ioredis": "^4.0.10",
|
||||
"@types/jest": "^24.0.13",
|
||||
"@types/joi": "^13.0.8",
|
||||
"@types/jsdom": "^12.2.3",
|
||||
"@types/jsonwebtoken": "^7.2.7",
|
||||
"@types/linkifyjs": "^2.1.1",
|
||||
"@types/lodash": "^4.14.118",
|
||||
"@types/html-webpack-plugin": "^3.2.3",
|
||||
"@types/ioredis": "^4.14.9",
|
||||
"@types/jest": "^26.0.3",
|
||||
"@types/jest-axe": "^3.2.2",
|
||||
"@types/jsdom": "^16.2.0",
|
||||
"@types/jsonwebtoken": "^8.3.8",
|
||||
"@types/linkifyjs": "^2.1.3",
|
||||
"@types/lodash": "^4.14.149",
|
||||
"@types/lru-cache": "^5.1.0",
|
||||
"@types/luxon": "^1.12.0",
|
||||
"@types/marked": "^0.6.0",
|
||||
"@types/mini-css-extract-plugin": "^0.2.0",
|
||||
"@types/mongodb": "^3.1.22",
|
||||
"@types/ms": "^0.7.30",
|
||||
"@types/node": "^10.5.2",
|
||||
"@types/node-fetch": "^2.3.3",
|
||||
"@types/nodemailer": "^4.6.2",
|
||||
"@types/nunjucks": "^3.1.1",
|
||||
"@types/object-diff": "0.0.0",
|
||||
"@types/luxon": "^1.22.0",
|
||||
"@types/marked": "^0.7.3",
|
||||
"@types/mini-css-extract-plugin": "^0.9.1",
|
||||
"@types/mongodb": "3.1.22",
|
||||
"@types/ms": "^0.7.31",
|
||||
"@types/node": "^12.12.34",
|
||||
"@types/node-fetch": "^2.5.5",
|
||||
"@types/nunjucks": "^3.1.3",
|
||||
"@types/on-finished": "^2.3.1",
|
||||
"@types/passport": "^0.4.6",
|
||||
"@types/passport-facebook": "^2.1.8",
|
||||
"@types/passport": "^1.0.3",
|
||||
"@types/passport-facebook": "^2.1.9",
|
||||
"@types/passport-local": "^1.0.33",
|
||||
"@types/passport-oauth2": "^1.4.5",
|
||||
"@types/passport-strategy": "^0.2.33",
|
||||
"@types/passport-oauth2": "^1.4.8",
|
||||
"@types/passport-strategy": "^0.2.35",
|
||||
"@types/permit": "^0.2.1",
|
||||
"@types/prop-types": "^15.5.8",
|
||||
"@types/react": "^16.8.15",
|
||||
"@types/react-copy-to-clipboard": "^4.2.6",
|
||||
"@types/react-dom": "^16.8.4",
|
||||
"@types/react-helmet": "^5.0.10",
|
||||
"@types/react-relay": "^1.3.14",
|
||||
"@types/react-responsive": "^3.0.1",
|
||||
"@types/react-test-renderer": "^16.8.1",
|
||||
"@types/react-transition-group": "^2.0.14",
|
||||
"@types/recompose": "^0.26.5",
|
||||
"@types/relay-runtime": "^1.3.12",
|
||||
"@types/prettier": "^1.19.1",
|
||||
"@types/react": "^16.9.31",
|
||||
"@types/react-axe": "^3.1.0",
|
||||
"@types/react-copy-to-clipboard": "^4.3.0",
|
||||
"@types/react-dom": "^16.9.6",
|
||||
"@types/react-helmet": "^5.0.15",
|
||||
"@types/react-relay": "^7.0.3",
|
||||
"@types/react-responsive": "^8.0.2",
|
||||
"@types/react-test-renderer": "^16.9.2",
|
||||
"@types/react-transition-group": "^4.2.4",
|
||||
"@types/recompose": "^0.30.7",
|
||||
"@types/relay-runtime": "^8.0.7",
|
||||
"@types/sane": "^2.0.0",
|
||||
"@types/shallow-equals": "^1.0.0",
|
||||
"@types/simplemde": "^1.11.7",
|
||||
"@types/sinon": "^7.0.11",
|
||||
"@types/source-map-support": "^0.5.0",
|
||||
"@types/sinon": "^7.5.2",
|
||||
"@types/source-map-support": "^0.5.1",
|
||||
"@types/stack-trace": "0.0.29",
|
||||
"@types/stack-utils": "^1.0.1",
|
||||
"@types/throng": "^4.0.2",
|
||||
"@types/uuid": "^3.4.4",
|
||||
"@types/uuid": "^7.0.2",
|
||||
"@types/verror": "^1.10.3",
|
||||
"@types/vinyl": "^2.0.2",
|
||||
"@types/webpack": "^4.4.31",
|
||||
"@types/webpack-assets-manifest": "^3.0.0",
|
||||
"@types/webpack-bundle-analyzer": "^2.13.1",
|
||||
"@types/webpack-dev-server": "^3.1.5",
|
||||
"@types/ws": "^5.1.2",
|
||||
"@typescript-eslint/eslint-plugin": "2.3.3",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "2.3.3",
|
||||
"@typescript-eslint/parser": "2.3.3",
|
||||
"acorn": "^6.1.1",
|
||||
"ansi-styles": "^3.2.0",
|
||||
"autoprefixer": "^9.5.1",
|
||||
"@types/vinyl": "^2.0.4",
|
||||
"@types/webpack": "^4.41.17",
|
||||
"@types/webpack-assets-manifest": "^3.0.1",
|
||||
"@types/webpack-bundle-analyzer": "^3.8.0",
|
||||
"@types/webpack-dev-server": "^3.11.0",
|
||||
"@types/ws": "^7.2.3",
|
||||
"@types/xregexp": "^4.3.0",
|
||||
"@typescript-eslint/eslint-plugin": "^3.4.0",
|
||||
"@typescript-eslint/eslint-plugin-tslint": "^3.4.0",
|
||||
"@typescript-eslint/parser": "^3.4.0",
|
||||
"autoprefixer": "^9.7.5",
|
||||
"babel-core": "^7.0.0-bridge.0",
|
||||
"babel-jest": "^24.8.0",
|
||||
"babel-loader": "^8.0.5",
|
||||
"babel-plugin-dynamic-import-node": "^2.2.0",
|
||||
"babel-jest": "^26.1.0",
|
||||
"babel-loader": "^8.1.0",
|
||||
"babel-plugin-lodash": "^3.3.4",
|
||||
"babel-plugin-module-resolver": "^3.2.0",
|
||||
"babel-plugin-relay": "^4.0.0",
|
||||
"babel-plugin-module-resolver": "^4.0.0",
|
||||
"babel-plugin-relay": "^9.1.0",
|
||||
"babel-plugin-use-lodash-es": "^0.2.0",
|
||||
"babel-preset-react-optimize": "^1.0.1",
|
||||
"bowser": "^1.9.4",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.2.0",
|
||||
"chalk": "^2.4.2",
|
||||
"chokidar": "^3.0.0",
|
||||
"bowser": "^2.9.0",
|
||||
"case-sensitive-paths-webpack-plugin": "^2.3.0",
|
||||
"chalk": "^3.0.0",
|
||||
"chokidar": "^3.3.1",
|
||||
"classnames": "^2.2.6",
|
||||
"commander": "^2.20.0",
|
||||
"comment-json": "^1.1.3",
|
||||
"commander": "^5.0.0",
|
||||
"comment-json": "^3.0.2",
|
||||
"common-tags": "^1.8.0",
|
||||
"compression-webpack-plugin": "^2.0.0",
|
||||
"copy-webpack-plugin": "^5.0.3",
|
||||
"cross-spawn": "^6.0.5",
|
||||
"css-loader": "^1.0.1",
|
||||
"del": "^4.1.1",
|
||||
"compression-webpack-plugin": "^3.1.0",
|
||||
"core-js": "^3.6.4",
|
||||
"cross-spawn": "^7.0.1",
|
||||
"css-loader": "^3.4.2",
|
||||
"css-vars-ponyfill": "^2.2.1",
|
||||
"del": "^5.1.0",
|
||||
"doctoc": "^1.4.0",
|
||||
"docz": "^0.13.7",
|
||||
"docz-theme-default": "^0.13.7",
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-adapter-react-16": "^1.12.1",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"eslint": "^6.5.1",
|
||||
"eslint-config-prettier": "^6.3.0",
|
||||
"eslint-plugin-jsdoc": "^15.9.7",
|
||||
"eslint-plugin-jsx-a11y": "^6.2.3",
|
||||
"eslint-plugin-prettier": "^3.1.1",
|
||||
"eslint-plugin-react": "^7.15.1",
|
||||
"eventemitter2": "^5.0.1",
|
||||
"farce": "^0.2.6",
|
||||
"final-form": "4.11.0",
|
||||
"flat": "^4.1.0",
|
||||
"docz": "^v2.3.0-alpha.14",
|
||||
"enzyme": "^3.11.0",
|
||||
"enzyme-adapter-react-16": "^1.15.2",
|
||||
"enzyme-to-json": "^3.5.0",
|
||||
"eslint": "^7.3.1",
|
||||
"eslint-config-prettier": "^6.11.0",
|
||||
"eslint-plugin-jsdoc": "^28.5.1",
|
||||
"eslint-plugin-jsx-a11y": "^6.3.1",
|
||||
"eslint-plugin-prettier": "^3.1.4",
|
||||
"eslint-plugin-react": "^7.20.0",
|
||||
"eventemitter2": "^6.3.1",
|
||||
"farce": "^0.2.8",
|
||||
"final-form": "4.18.6",
|
||||
"final-form-arrays": "^3.0.2",
|
||||
"flat": "^5.0.0",
|
||||
"fluent-intl-polyfill": "^0.1.0",
|
||||
"fluent-langneg": "^0.1.1",
|
||||
"fluent-react": "^0.8.4",
|
||||
"fork-ts-checker-webpack-plugin": "^1.5.0",
|
||||
"found": "^0.4.0-alpha.17",
|
||||
"found-relay": "^0.4.0-alpha.8",
|
||||
"graphql-schema-linter": "^0.2.0",
|
||||
"graphql-schema-typescript": "^1.2.9",
|
||||
"fork-ts-checker-webpack-plugin": "^4.1.2",
|
||||
"found": "^0.4.10",
|
||||
"found-relay": "^0.7.0",
|
||||
"graphql-schema-linter": "^0.4.0",
|
||||
"graphql-schema-typescript": "^1.3.2",
|
||||
"gulp": "^4.0.2",
|
||||
"gulp-babel": "^8.0.0",
|
||||
"gulp-cli": "^2.2.0",
|
||||
"gulp-cli": "^2.3.0",
|
||||
"gulp-sourcemaps": "^2.6.5",
|
||||
"gulp-typescript": "^5.0.1",
|
||||
"html-webpack-plugin": "^4.0.0-beta.5",
|
||||
"husky": "^2.2.0",
|
||||
"intersection-observer": "^0.6.0",
|
||||
"jest": "^24.8.0",
|
||||
"jest-junit": "^6.4.0",
|
||||
"jest-localstorage-mock": "^2.4.0",
|
||||
"jest-mock-console": "^1.0.0",
|
||||
"lint-staged": "^8.1.6",
|
||||
"loader-utils": "^1.2.3",
|
||||
"lodash-es": "^4.17.14",
|
||||
"marked": "^0.7.0",
|
||||
"gulp-typescript": "^6.0.0-alpha.1",
|
||||
"html-webpack-plugin": "^4.0.4",
|
||||
"husky": "^4.2.3",
|
||||
"intersection-observer": "^0.7.0",
|
||||
"jest": "^26.1.0",
|
||||
"jest-axe": "^3.4.0",
|
||||
"jest-junit": "^11.0.1",
|
||||
"jest-localstorage-mock": "^2.4.2",
|
||||
"jest-mock-console": "^1.0.1",
|
||||
"keymaster": "^1.6.2",
|
||||
"lint-staged": "^10.2.11",
|
||||
"loader-utils": "^2.0.0",
|
||||
"lodash-es": "^4.17.15",
|
||||
"marked": "^0.8.2",
|
||||
"material-design-icons": "^3.0.1",
|
||||
"mini-css-extract-plugin": "^0.6.0",
|
||||
"object-diff": "0.0.4",
|
||||
"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-js": "^2.0.1",
|
||||
"mini-css-extract-plugin": "^0.9.0",
|
||||
"postcss-advanced-variables": "^3.0.1",
|
||||
"postcss-calc-function": "^1.1.0",
|
||||
"postcss-flexbugs-fixes": "^4.2.0",
|
||||
"postcss-font-magician": "^2.3.1",
|
||||
"postcss-import": "^12.0.1",
|
||||
"postcss-js": "^2.0.3",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"postcss-mixins": "^6.2.1",
|
||||
"postcss-nested": "^4.1.1",
|
||||
"postcss-mixins": "^6.2.3",
|
||||
"postcss-nested": "^4.2.1",
|
||||
"postcss-prepend-imports": "^1.0.1",
|
||||
"postcss-preset-env": "^6.5.0",
|
||||
"prettier": "^1.18.2",
|
||||
"prop-types": "^15.6.2",
|
||||
"pstree.remy": "^1.1.6",
|
||||
"postcss-preset-env": "^6.7.0",
|
||||
"prettier": "^2.0.2",
|
||||
"proxy-polyfill": "^0.3.1",
|
||||
"pstree.remy": "^1.1.7",
|
||||
"pym.js": "^1.3.2",
|
||||
"raw-loader": "^0.5.1",
|
||||
"react": "^16.9.0-alpha.0",
|
||||
"react-copy-to-clipboard": "^5.0.1",
|
||||
"react-dev-utils": "^9.0.0",
|
||||
"react-dom": "^16.9.0-alpha.0",
|
||||
"react-error-overlay": "^5.1.6",
|
||||
"react-final-form": "4.0.2",
|
||||
"react-popper": "^1.3.2",
|
||||
"react-relay": "^4.0.0",
|
||||
"react-relay-network-modern": "^4.0.4",
|
||||
"react-responsive": "^7.0.0",
|
||||
"react-test-renderer": "^16.9.0-alpha.0",
|
||||
"react-timeago": "^4.1.9",
|
||||
"react-transition-group": "^2.9.0",
|
||||
"react-with-state-props": "^2.0.4",
|
||||
"recompose": "0.27.1",
|
||||
"relay-compiler": "^4.0.0",
|
||||
"relay-compiler-language-typescript": "^4.1.0",
|
||||
"raw-loader": "^4.0.0",
|
||||
"react": "^16.13.1",
|
||||
"react-axe": "^3.4.1",
|
||||
"react-copy-to-clipboard": "^5.0.2",
|
||||
"react-dev-utils": "^10.2.1",
|
||||
"react-dom": "^16.13.1",
|
||||
"react-error-overlay": "^6.0.7",
|
||||
"react-final-form": "6.3.0",
|
||||
"react-final-form-arrays": "^3.1.1",
|
||||
"react-popper": "^1.3.7",
|
||||
"react-relay": "^9.0.0",
|
||||
"react-relay-network-modern": "^4.6.1",
|
||||
"react-responsive": "^8.0.3",
|
||||
"react-test-renderer": "^16.13.1",
|
||||
"react-timeago": "^4.4.0",
|
||||
"react-transition-group": "^4.3.0",
|
||||
"recompose": "^0.30.0",
|
||||
"regenerator-runtime": "^0.13.5",
|
||||
"relay-compiler": "^9.0.0",
|
||||
"relay-compiler-language-typescript": "^12.0.3",
|
||||
"relay-local-schema": "^0.8.0",
|
||||
"relay-runtime": "^4.0.0",
|
||||
"relay-runtime": "^9.0.0",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"sane": "^4.1.0",
|
||||
"shallow-equals": "^1.0.0",
|
||||
"simplemde": "^1.11.2",
|
||||
"simulant": "^0.2.2",
|
||||
"sinon": "^7.3.2",
|
||||
"sockjs-client": "^1.3.0",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"style-loader": "^0.23.1",
|
||||
"sinon": "^9.0.1",
|
||||
"sockjs-client": "^1.4.0",
|
||||
"stack-trace": "^0.0.10",
|
||||
"strip-ansi": "^6.0.0",
|
||||
"style-loader": "^1.1.3",
|
||||
"subscriptions-transport-ws": "^0.9.16",
|
||||
"terser-webpack-plugin": "^1.2.3",
|
||||
"thread-loader": "^2.1.2",
|
||||
"terser-webpack-plugin": "^2.3.5",
|
||||
"thread-loader": "^2.1.3",
|
||||
"timekeeper": "^2.2.0",
|
||||
"ts-jest": "<23.10.0",
|
||||
"ts-loader": "^6.0.0",
|
||||
"ts-node": "^8.1.0",
|
||||
"ts-node-dev": "^1.0.0-pre.37",
|
||||
"tsconfig-paths": "^3.8.0",
|
||||
"ts-jest": "26.1.1",
|
||||
"ts-loader": "^7.0.5",
|
||||
"ts-node": "^8.10.2",
|
||||
"ts-node-dev": "1.0.0-pre.44",
|
||||
"tsconfig-paths": "^3.9.0",
|
||||
"tsconfig-paths-webpack-plugin": "^3.2.0",
|
||||
"tslint": "^5.20.0",
|
||||
"typed-css-modules": "^0.4.2",
|
||||
"typeface-manuale": "^0.0.71",
|
||||
"typeface-source-sans-pro": "^0.0.54",
|
||||
"typescript": "3.3.4000",
|
||||
"typescript-snapshots-plugin": "^1.6.0",
|
||||
"wait-for-expect": "^1.1.1",
|
||||
"webpack": "^4.30.0",
|
||||
"tslint": "^6.1.2",
|
||||
"typed-css-modules": "^0.6.4",
|
||||
"typeface-manuale": "^1.1.4",
|
||||
"typeface-nunito": "^1.1.3",
|
||||
"typeface-open-sans": "^0.0.75",
|
||||
"typeface-source-sans-pro": "^1.1.5",
|
||||
"typescript": "^3.9.5",
|
||||
"typescript-snapshots-plugin": "^1.7.0",
|
||||
"wait-for-expect": "^1.3.0",
|
||||
"webpack": "^4.43.0",
|
||||
"webpack-assets-manifest": "^3.1.1",
|
||||
"webpack-bundle-analyzer": "^3.3.2",
|
||||
"webpack-cli": "^3.3.2",
|
||||
"webpack-dev-server": "3.2.1",
|
||||
"webpack-bundle-analyzer": "^3.8.0",
|
||||
"webpack-cli": "^3.3.12",
|
||||
"webpack-dev-server": "^3.11.0",
|
||||
"whatwg-fetch": "^3.0.0"
|
||||
},
|
||||
"dependencies-pins-documentation": {
|
||||
"ts-node-dev@1.0.0-pre.44": [
|
||||
"Newer versions unbearibly slow"
|
||||
],
|
||||
"ts-jest@25.4.0": [
|
||||
"Newer versions require every definition file to be included",
|
||||
"in tsconfig.json in the `files` field. This is cumbersome and",
|
||||
"here is a discussion of it: https://github.com/kulshekhar/ts-jest/issues/1604"
|
||||
],
|
||||
"wait-for-expect@1.x.x": [
|
||||
"Newer versions breaks the use of jest fake timers"
|
||||
],
|
||||
"consolidate@14.0.0": [
|
||||
"Newer versions has problems with using nunjucks.",
|
||||
"Update when the following issue is resolved:",
|
||||
"https://github.com/tj/consolidate.js/issues/244"
|
||||
],
|
||||
"final-form@4.18.6": [
|
||||
"Newer versions has problems with resetting forms.",
|
||||
"Update when the following issue is resolved:",
|
||||
"https://github.com/final-form/final-form/issues/317"
|
||||
],
|
||||
"react-final-form@6.3.0": [
|
||||
"Newer versions has problems with radio and checkbox fields.",
|
||||
"Update when the following issue is resolved:",
|
||||
"https://github.com/final-form/react-final-form/issues/683"
|
||||
],
|
||||
"graphql-playground-html@1.6.13": [
|
||||
"A template was broken in a release.",
|
||||
"Update when the following issue is resolved:",
|
||||
"https://github.com/prisma-labs/graphql-playground/pull/1238"
|
||||
]
|
||||
},
|
||||
"husky": {
|
||||
"hooks": {
|
||||
"pre-commit": "FAST_LINT=true lint-staged"
|
||||
@@ -379,22 +425,39 @@
|
||||
"*.{j,t}s{,x}": [
|
||||
"eslint"
|
||||
],
|
||||
"src/core/server/graph/tenant/schema/schema.graphql": [
|
||||
"src/core/server/graph/schema/schema.graphql": [
|
||||
"graphql-schema-linter"
|
||||
],
|
||||
"{src/core/client/stream/events.ts,scripts/generateEventDocs.ts,events.md}": [
|
||||
"{src/core/client/stream/events.ts,scripts/generateEventDocs.ts,CLIENT_EVENTS.md}": [
|
||||
"npm run docs:events -- --verify"
|
||||
],
|
||||
"{src/core/client/ui/theme/sharedVariables.ts,src/core/client/ui/theme/streamVariables.ts,scripts/generateCSSVariablesDocs.ts,CSS_VARIABLES.md}": [
|
||||
"npm run docs:css-variables -- --verify"
|
||||
],
|
||||
"{CLIENT_EVENTS,CONTRIBUTING,WEBHOOKS,EXTERNAL_MODERATION_PHASES}.md": [
|
||||
"npm run doctoc"
|
||||
]
|
||||
},
|
||||
"bundlesize": [
|
||||
{
|
||||
"path": "./dist/static/assets/js/embed.js",
|
||||
"maxSize": "15 kB"
|
||||
},
|
||||
{
|
||||
"path": "./dist/static/assets/js/count.js",
|
||||
"maxSize": "2 kB"
|
||||
}
|
||||
],
|
||||
"graphql-schema-linter": {
|
||||
"rules": [
|
||||
"types-are-capitalized"
|
||||
]
|
||||
}
|
||||
},
|
||||
"browsers": [
|
||||
">1%",
|
||||
"last 4 versions",
|
||||
"Firefox ESR",
|
||||
"IE 11",
|
||||
"not dead"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -27,14 +27,14 @@ const forkTsCheckerWebpackPlugin = require("react-dev-utils/ForkTsCheckerWebpack
|
||||
const isInteractive = false;
|
||||
|
||||
function prepareUrls(protocol, host, port) {
|
||||
const formatUrl = hostname =>
|
||||
const formatUrl = (hostname) =>
|
||||
url.format({
|
||||
protocol,
|
||||
hostname,
|
||||
port,
|
||||
pathname: "/",
|
||||
});
|
||||
const prettyPrintUrl = hostname =>
|
||||
const prettyPrintUrl = (hostname) =>
|
||||
url.format({
|
||||
protocol,
|
||||
hostname,
|
||||
@@ -144,12 +144,12 @@ function createCompiler({
|
||||
const tsMessagesPromises = [];
|
||||
|
||||
if (useTypeScript) {
|
||||
compiler.compilers.forEach(singleCompiler => {
|
||||
compiler.compilers.forEach((singleCompiler) => {
|
||||
let tsMessagesPromise;
|
||||
let tsMessagesResolver;
|
||||
singleCompiler.hooks.beforeCompile.tap("beforeCompile", () => {
|
||||
tsMessagesPromise = new Promise(resolve => {
|
||||
tsMessagesResolver = msgs => resolve(msgs);
|
||||
tsMessagesPromise = new Promise((resolve) => {
|
||||
tsMessagesResolver = (msgs) => resolve(msgs);
|
||||
});
|
||||
tsMessagesPromises.push(tsMessagesPromise);
|
||||
});
|
||||
@@ -170,13 +170,15 @@ function createCompiler({
|
||||
(diagnostics, lints) => {
|
||||
console.log("RECEIVED");
|
||||
const allMsgs = [...diagnostics, ...lints];
|
||||
const format = message =>
|
||||
const format = (message) =>
|
||||
`${message.file}\n${typescriptFormatter(message, true)}`;
|
||||
|
||||
tsMessagesResolver({
|
||||
errors: allMsgs.filter(msg => msg.severity === "error").map(format),
|
||||
errors: allMsgs
|
||||
.filter((msg) => msg.severity === "error")
|
||||
.map(format),
|
||||
warnings: allMsgs
|
||||
.filter(msg => msg.severity === "warning")
|
||||
.filter((msg) => msg.severity === "warning")
|
||||
.map(format),
|
||||
});
|
||||
}
|
||||
@@ -186,7 +188,7 @@ function createCompiler({
|
||||
|
||||
// "done" event fires when Webpack has finished recompiling the bundle.
|
||||
// Whether or not you have warnings or errors, you will get this event.
|
||||
compiler.hooks.done.tap("done", async stats => {
|
||||
compiler.hooks.done.tap("done", async (stats) => {
|
||||
if (isInteractive) {
|
||||
clearConsole();
|
||||
}
|
||||
@@ -203,7 +205,7 @@ function createCompiler({
|
||||
...config.stats,
|
||||
};
|
||||
if (Array.isArray(config)) {
|
||||
statOptions.children = config.map(c => c.stats || {});
|
||||
statOptions.children = config.map((c) => c.stats || {});
|
||||
}
|
||||
const statsData = stats.toJson(statOptions);
|
||||
|
||||
@@ -220,7 +222,7 @@ function createCompiler({
|
||||
const results = await Promise.all(tsMessagesPromises);
|
||||
clearTimeout(delayedMsg);
|
||||
|
||||
results.forEach(msgs => {
|
||||
results.forEach((msgs) => {
|
||||
statsData.errors.push(...msgs.errors);
|
||||
statsData.warnings.push(...msgs.warnings);
|
||||
|
||||
|
||||
+4
-2
@@ -31,7 +31,7 @@ const isProduction = process.env.NODE_ENV === "production";
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on("unhandledRejection", err => {
|
||||
process.on("unhandledRejection", (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
@@ -117,7 +117,9 @@ function build(previousFileSizes: any) {
|
||||
return reject(err);
|
||||
}
|
||||
const messages = formatWebpackMessages(
|
||||
stats.toJson({ children: webpackConfig.map(c => c.stats || {}) as any })
|
||||
stats.toJson({
|
||||
children: webpackConfig.map((c) => c.stats || {}) as any,
|
||||
})
|
||||
);
|
||||
if (messages.errors.length) {
|
||||
// Only keep the first error. Others are often indicative
|
||||
|
||||
@@ -52,6 +52,9 @@ const args = [
|
||||
"--language",
|
||||
"typescript",
|
||||
"--no-watchman",
|
||||
"--customScalars.Time=String",
|
||||
"--customScalars.Cursor=unknown",
|
||||
"--customScalars.Locale=string",
|
||||
"--src",
|
||||
program.src,
|
||||
"--artifactDirectory",
|
||||
@@ -74,7 +77,7 @@ if (persist) {
|
||||
if (fs.existsSync(persist)) {
|
||||
// Create the new filename.
|
||||
const name = path.basename(program.src);
|
||||
const generated = "./src/core/server/graph/common/persisted/__generated__";
|
||||
const generated = "./src/core/server/graph/persisted/__generated__";
|
||||
|
||||
// Create the generated directory if it doesn't exist.
|
||||
fs.ensureDirSync(generated);
|
||||
|
||||
@@ -0,0 +1,306 @@
|
||||
import { stripIndent } from "common-tags";
|
||||
import fs from "fs";
|
||||
import { kebabCase, trim } from "lodash";
|
||||
import path from "path";
|
||||
import ts from "typescript";
|
||||
|
||||
import colors from "../src/core/client/ui/theme/colors";
|
||||
|
||||
/** We collect inforamtion from the AST and put it into DocEntry */
|
||||
interface DocEntry {
|
||||
/** Name of property */
|
||||
key: string;
|
||||
/** Documentation if available */
|
||||
docs?: string;
|
||||
/** Value if it is a leaf */
|
||||
value?: string;
|
||||
/** Children if it is a node */
|
||||
children?: DocEntry[];
|
||||
}
|
||||
|
||||
/**
|
||||
* We use this regexp to find a previous block that we
|
||||
* are going to update in the readme file.
|
||||
*/
|
||||
const BLOCK_REGEXP = /<!-- START docs:css-variables -->(.|\n)*<!-- END docs:css-variables -->/gm;
|
||||
|
||||
/** Sort doc entries will sort childrenless entries first */
|
||||
function sortDocEntries(data: DocEntry[]) {
|
||||
data.forEach((d) => {
|
||||
if (d.children) {
|
||||
d.children = sortDocEntries(d.children);
|
||||
}
|
||||
});
|
||||
return data.sort((a, b) => {
|
||||
if (a.children && !b.children) {
|
||||
return 1;
|
||||
}
|
||||
if (b.children && !a.children) {
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
});
|
||||
}
|
||||
|
||||
/** Generate documentation for all classes in a set of .ts files */
|
||||
function gatherEntries(
|
||||
fileName: string,
|
||||
options: ts.CompilerOptions
|
||||
): DocEntry[] {
|
||||
// Build a program using the set of root file names in fileNames
|
||||
const program = ts.createProgram([fileName], options);
|
||||
|
||||
// Get the checker, we will use it to find more about classes
|
||||
const checker = program.getTypeChecker();
|
||||
|
||||
const data: DocEntry[] = [];
|
||||
const currentSourceFile = program.getSourceFile(fileName)!;
|
||||
|
||||
ts.forEachChild(currentSourceFile, visit);
|
||||
|
||||
return sortDocEntries(data);
|
||||
|
||||
/** visit nodes finding css variables */
|
||||
function visit(node: ts.Node) {
|
||||
if (ts.isVariableStatement(node)) {
|
||||
// TODO (cvle) - Currently the variable name is hardcoded. We might want to change that.
|
||||
if (!node.getFullText().includes("streamVariables")) {
|
||||
return;
|
||||
}
|
||||
const firstChild = node.declarationList.declarations[0];
|
||||
if (ts.isVariableDeclaration(firstChild)) {
|
||||
const symbol = checker.getSymbolAtLocation(firstChild.name);
|
||||
if (symbol) {
|
||||
const type = checker.getTypeOfSymbolAtLocation(
|
||||
symbol,
|
||||
symbol.valueDeclaration
|
||||
);
|
||||
type.getProperties().forEach((property) => {
|
||||
serializePropertySymbol(property);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** This will evaluate `symbol` and addthe doc entries */
|
||||
function serializePropertySymbol(symbol: ts.Symbol, parent?: DocEntry) {
|
||||
const entry: DocEntry = { key: symbol.name };
|
||||
const pt = checker.getTypeOfSymbolAtLocation(
|
||||
symbol,
|
||||
symbol.valueDeclaration
|
||||
);
|
||||
if (pt.symbol?.name === "__object") {
|
||||
pt.getProperties().forEach((p2) => {
|
||||
serializePropertySymbol(p2, entry);
|
||||
});
|
||||
} else {
|
||||
entry.value = symbol.valueDeclaration
|
||||
// Last child contains value.
|
||||
.getChildAt(symbol.valueDeclaration.getChildCount() - 1)!
|
||||
.getFullText();
|
||||
}
|
||||
entry.docs = ts.displayPartsToString(
|
||||
symbol.getDocumentationComment(checker)
|
||||
);
|
||||
if (parent) {
|
||||
if (parent.children) {
|
||||
parent.children.push(entry);
|
||||
} else {
|
||||
parent.children = [entry];
|
||||
}
|
||||
} else {
|
||||
data.push(entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds any references to `object` by `variableName` and replaces the text
|
||||
* with the actual value.
|
||||
*
|
||||
* E.g. `replaceObjectVariablesInText("colors.teal100", colors, "colors") === "#E2FAF7"`;
|
||||
*/
|
||||
function replaceObjectVariablesInText(
|
||||
text: string,
|
||||
object: any,
|
||||
variableName: string
|
||||
) {
|
||||
let result = text;
|
||||
Object.keys(object).forEach((c: string) => {
|
||||
if (typeof object[c] === "object") {
|
||||
result = replaceObjectVariablesInText(
|
||||
result,
|
||||
object[c],
|
||||
`${variableName}.${c}`
|
||||
);
|
||||
} else {
|
||||
result = result.replace(`${variableName}.${c}`, object[c]);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* transforms value to be used in the documentation.
|
||||
*/
|
||||
function transformValue(value: string) {
|
||||
let compat = "";
|
||||
value = trim(value);
|
||||
// Detect compat value.
|
||||
if (value.startsWith("compat")) {
|
||||
const result = /compat\((.*), *"(.*)"\)/.exec(value);
|
||||
if (!result) {
|
||||
throw new Error("Unrecognized compat format");
|
||||
}
|
||||
value = result[1];
|
||||
compat = result[2];
|
||||
}
|
||||
// If it's a raw string, evaluate it to get rid of the initial quotes.
|
||||
if (value[0] === '"' || value[0] === "'") {
|
||||
// eslint-disable-next-line no-eval
|
||||
value = eval(value);
|
||||
}
|
||||
// Replace all references to colors.
|
||||
value = replaceObjectVariablesInText(value, colors, "colors");
|
||||
if (compat) {
|
||||
// add compat information.
|
||||
return `${value}; /* Before 6.3.0: --${compat} */`;
|
||||
}
|
||||
return `${value};`;
|
||||
}
|
||||
|
||||
function prefixLines(text: string, prefix: string) {
|
||||
return text.split("\n").join(`\n${prefix}`);
|
||||
}
|
||||
|
||||
function entries2Summary(
|
||||
entries: DocEntry[],
|
||||
keyprefix = "",
|
||||
nestprefix = ""
|
||||
): string {
|
||||
let doc = "";
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.children) {
|
||||
return;
|
||||
}
|
||||
const header = kebabCase(keyprefix + entry.key);
|
||||
doc += `${nestprefix}- <a href="#${header}">${header}</a>\n`;
|
||||
if (entry.children) {
|
||||
doc += entries2Summary(
|
||||
entry.children,
|
||||
keyprefix + entry.key + "-",
|
||||
nestprefix + " "
|
||||
);
|
||||
}
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
function entries2Doc(entries: DocEntry[], header = "###", prefix = ""): string {
|
||||
let doc = "";
|
||||
entries.forEach((entry) => {
|
||||
if (entry.children) {
|
||||
doc += `\n${header} ${kebabCase(prefix + entry.key)}\n`;
|
||||
if (entry.docs) {
|
||||
doc += `\n${entry.docs}\n`;
|
||||
}
|
||||
doc += entries2Doc(
|
||||
entry.children,
|
||||
header + "#",
|
||||
`${prefix}${entry.key}-`
|
||||
);
|
||||
} else {
|
||||
if (entry.docs) {
|
||||
doc += `\n${entry.docs}\n`;
|
||||
}
|
||||
doc += `\n\`--${kebabCase(prefix + entry.key)}: ${transformValue(
|
||||
entry.value!
|
||||
)}\`\n`;
|
||||
}
|
||||
});
|
||||
return doc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Append or update previous documention in markdownFile.
|
||||
*
|
||||
* @param markdownFile The markdown file we want to inject the docs too.
|
||||
* @param entries data as returned by gatherEntries.
|
||||
*/
|
||||
function emitDocs(markdownFile: string, entries: DocEntry[], verify = false) {
|
||||
const previousContent = fs.existsSync(markdownFile)
|
||||
? fs.readFileSync(markdownFile).toString()
|
||||
: "";
|
||||
|
||||
const summary = entries2Summary(entries, "", " ");
|
||||
const list = entries2Doc(entries);
|
||||
|
||||
const output = stripIndent`
|
||||
<!-- START docs:css-variables -->
|
||||
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN npm run docs:css-variables -->
|
||||
### Index
|
||||
- <a href="#variables">Variables</a>
|
||||
${prefixLines(summary, " ")}
|
||||
|
||||
### Variables
|
||||
${prefixLines(list, " ")}
|
||||
<!-- END docs:css-variables -->
|
||||
`;
|
||||
|
||||
let newContent;
|
||||
// Find previous block.
|
||||
if (BLOCK_REGEXP.test(previousContent)) {
|
||||
newContent = previousContent.replace(BLOCK_REGEXP, output);
|
||||
} else {
|
||||
newContent = previousContent + "\n" + output;
|
||||
}
|
||||
if (previousContent === newContent) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${markdownFile} is up to date`);
|
||||
return;
|
||||
}
|
||||
if (verify) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(
|
||||
`${markdownFile} is outdated, please run \`npm run docs:css-variables\``
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
fs.writeFileSync(markdownFile, newContent);
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Successfully injected documentation into ${markdownFile}`);
|
||||
}
|
||||
|
||||
function main() {
|
||||
if (process.argv.length < 4) {
|
||||
throw new Error("Must provide path to css variables and a markdown file.");
|
||||
}
|
||||
|
||||
const variableFile = process.argv[2];
|
||||
const markdownFile = process.argv[3];
|
||||
|
||||
// Find tsconfig file.
|
||||
const configFile = ts.findConfigFile(variableFile, fs.existsSync);
|
||||
if (!configFile) {
|
||||
throw new Error("tsconfig file not found");
|
||||
}
|
||||
const configText = fs.readFileSync(configFile).toString();
|
||||
const result = ts.parseConfigFileTextToJson(configFile, configText);
|
||||
if (result.error) {
|
||||
throw result.error;
|
||||
}
|
||||
|
||||
// Parse the JSON raw data into actual consumable compiler options.
|
||||
const config = ts.parseJsonConfigFileContent(
|
||||
result.config,
|
||||
ts.sys,
|
||||
path.dirname(configFile)
|
||||
);
|
||||
|
||||
const entries = gatherEntries(variableFile, config.options);
|
||||
emitDocs(markdownFile, entries, process.argv[4] === "--verify");
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable no-bitwise */
|
||||
|
||||
import { codeBlock, stripIndent } from "common-tags";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import ts from "typescript";
|
||||
|
||||
interface DocEntry {
|
||||
@@ -104,7 +104,7 @@ function gatherEntries(
|
||||
docs: ts.displayPartsToString(symbol.getDocumentationComment(checker)),
|
||||
type: type.getSymbol()!.getName() as DocEntry["type"],
|
||||
};
|
||||
typeNode.forEachChild(ch => {
|
||||
typeNode.forEachChild((ch) => {
|
||||
if (ts.isTypeLiteralNode(ch)) {
|
||||
const text = printer.printNode(
|
||||
ts.EmitHint.Unspecified,
|
||||
@@ -179,13 +179,13 @@ function emitDocs(markdownFile: string, entries: DocEntry[], verify = false) {
|
||||
const summary = stripIndent`
|
||||
- ${entries
|
||||
.map(
|
||||
e => `<a href="#${getEventName(e.name)}">${getEventName(e.name)}</a>`
|
||||
(e) => `<a href="#${getEventName(e.name)}">${getEventName(e.name)}</a>`
|
||||
)
|
||||
.join("\n - ")}
|
||||
`;
|
||||
const list = entries
|
||||
.map(
|
||||
e =>
|
||||
(e) =>
|
||||
codeBlock`
|
||||
- ${
|
||||
e.type === "ViewerEvent"
|
||||
|
||||
@@ -12,12 +12,12 @@ async function main() {
|
||||
name: "tenant",
|
||||
fileName: path.join(
|
||||
__dirname,
|
||||
"../src/core/server/graph/tenant/schema/__generated__/types.ts"
|
||||
"../src/core/server/graph/schema/__generated__/types.ts"
|
||||
),
|
||||
config: {
|
||||
contextType: "TenantContext",
|
||||
contextType: "GraphContext",
|
||||
importStatements: [
|
||||
'import TenantContext from "coral-server/graph/tenant/context";',
|
||||
'import GraphContext from "coral-server/graph/context";',
|
||||
'import { Cursor } from "coral-server/models/helpers";',
|
||||
],
|
||||
customScalarType: { Cursor: "Cursor", Time: "Date" },
|
||||
@@ -47,7 +47,7 @@ async function main() {
|
||||
}
|
||||
|
||||
// Create the types for this file.
|
||||
const types = await generateTSTypesAsString(schema, {
|
||||
const types = await generateTSTypesAsString(schema, file.fileName, {
|
||||
tabSpaces: 2,
|
||||
typePrefix: "GQL",
|
||||
strictNulls: false,
|
||||
@@ -66,13 +66,13 @@ if (require.main === module) {
|
||||
// Only run the main module on file load if this is the main module (we're
|
||||
// executing this file directly).
|
||||
main()
|
||||
.then(files => {
|
||||
.then((files) => {
|
||||
for (const { fileName } of files) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Generated ${fileName}`);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
.catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
});
|
||||
|
||||
Executable
+153
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env ./node_modules/.bin/tsnd
|
||||
|
||||
//
|
||||
// Based on the script written by @cristiandean
|
||||
//
|
||||
// Source: https://gist.github.com/cristiandean/8196bc2b965f9acf9ad8f9e4530011c1
|
||||
//
|
||||
|
||||
import { FluentResource } from "@fluent/bundle";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
const PROJECT_ROOT = path.resolve(__dirname, "..", "..");
|
||||
|
||||
const LOCALE_DIRECTORIES = [
|
||||
path.join(PROJECT_ROOT, "src/locales"),
|
||||
path.join(PROJECT_ROOT, "src/core/server/locales"),
|
||||
];
|
||||
|
||||
const wrap = (color: string) => (msg: string) => {
|
||||
if (!msg) {
|
||||
return;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`${color}%s\x1b[0m`, msg);
|
||||
};
|
||||
|
||||
const log = {
|
||||
red: wrap("\x1b[31m"),
|
||||
green: wrap("\x1b[32m"),
|
||||
plain: wrap("\x1b[0m"),
|
||||
};
|
||||
|
||||
interface LocaleFileListing {
|
||||
locale: string;
|
||||
files: LocaleFile[];
|
||||
}
|
||||
|
||||
interface LocaleFile {
|
||||
fileName: string;
|
||||
directory: string;
|
||||
filePath: string;
|
||||
keys: Set<string>;
|
||||
}
|
||||
|
||||
function loadLocale(locale: string) {
|
||||
const listing: LocaleFileListing = { locale, files: [] };
|
||||
|
||||
for (const directory of LOCALE_DIRECTORIES) {
|
||||
// Resolve the directory with the language code, this should be a folder
|
||||
// containing language files for the specified context.
|
||||
const localeDirectory = path.join(directory, locale);
|
||||
if (!fs.existsSync(localeDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The folder exists, list all files in this directory and begin loading
|
||||
// them.
|
||||
const localeFiles = fs.readdirSync(localeDirectory);
|
||||
for (const fileName of localeFiles) {
|
||||
const filePath = path.join(localeDirectory, fileName);
|
||||
|
||||
// Load the file.
|
||||
const source = fs.readFileSync(filePath, "utf-8").toString();
|
||||
|
||||
// Create the resource based on the file.
|
||||
const resource = new FluentResource(source);
|
||||
|
||||
// Iterate over the ids to create the set of ID's.
|
||||
const keys = new Set<string>();
|
||||
|
||||
for (const entry of resource.body) {
|
||||
if (entry.id.startsWith("-")) {
|
||||
// Identifiers starting with a `-` define terms, these are not used
|
||||
// for this as we're only comparing messages.
|
||||
continue;
|
||||
}
|
||||
|
||||
keys.add(entry.id);
|
||||
}
|
||||
|
||||
// Add the new file.
|
||||
listing.files.push({ fileName, directory, filePath, keys });
|
||||
}
|
||||
}
|
||||
|
||||
return listing;
|
||||
}
|
||||
|
||||
function diffSet(a: Set<string>, b: Set<string>): Set<string> {
|
||||
const n = new Set(a);
|
||||
for (const e of b) {
|
||||
n.delete(e);
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
function prefixDiffSet(prefix: string, a: Set<string>, b: Set<string>) {
|
||||
return [...diffSet(a, b).values()]
|
||||
.map((value) => `${prefix}${value}`)
|
||||
.join("\n");
|
||||
}
|
||||
|
||||
function diff(from: LocaleFileListing, to: LocaleFileListing) {
|
||||
for (const f of from.files) {
|
||||
// Log the header information for this file.
|
||||
log.plain(`* From: ${f.filePath}`);
|
||||
|
||||
// Find the associated "to" file.
|
||||
const t = to.files.find(
|
||||
(file) => file.fileName === f.fileName && file.directory === f.directory
|
||||
);
|
||||
if (!t) {
|
||||
log.red(
|
||||
`* To: ${path.join(f.directory, to.locale, f.fileName)} (missing)`
|
||||
);
|
||||
log.plain("-".repeat(60));
|
||||
continue;
|
||||
}
|
||||
|
||||
log.plain(`* To: ${t.filePath}`);
|
||||
|
||||
log.green(prefixDiffSet(" + ", f.keys, t.keys));
|
||||
log.red(prefixDiffSet(" - ", t.keys, f.keys));
|
||||
|
||||
log.plain("-".repeat(60));
|
||||
}
|
||||
}
|
||||
|
||||
function main() {
|
||||
try {
|
||||
if (process.argv.length < 3) {
|
||||
throw new Error("usage: ./scripts/i18n/missingTranslations.ts <locale>");
|
||||
}
|
||||
|
||||
// Load the target translation.
|
||||
const to = loadLocale(process.argv[2]);
|
||||
|
||||
// Load the base language (US English).
|
||||
const from = loadLocale("en-US");
|
||||
|
||||
// Report the difference in key existence.
|
||||
diff(from, to);
|
||||
} catch (err) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env ts-node
|
||||
|
||||
import dotenv from "dotenv";
|
||||
|
||||
// Apply all the configuration provided in the .env file if it isn't already in
|
||||
@@ -26,7 +27,7 @@ process.env.NODE_ENV = "development";
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on("unhandledRejection", err => {
|
||||
process.on("unhandledRejection", (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -17,7 +17,7 @@ process.env.NODE_ENV = "test";
|
||||
// Makes the script crash on unhandled rejections instead of silently
|
||||
// ignoring them. In the future, promise rejections that are not handled will
|
||||
// terminate the Node.js process with a non-zero exit code.
|
||||
process.on("unhandledRejection", err => {
|
||||
process.on("unhandledRejection", (err) => {
|
||||
throw err;
|
||||
});
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import chokidar from "chokidar";
|
||||
import path from "path";
|
||||
|
||||
import { Watcher, WatchOptions } from "./types";
|
||||
|
||||
function prependRootDir(
|
||||
@@ -23,9 +24,10 @@ export default class ChokidarWatcher implements Watcher {
|
||||
let firstError: Error | null = null;
|
||||
|
||||
// If this is set, a pending promise is waiting for the next result.
|
||||
let pending:
|
||||
| ({ resolve: (result: string) => void; reject: (error: Error) => void })
|
||||
| null = null;
|
||||
let pending: {
|
||||
resolve: (result: string) => void;
|
||||
reject: (error: Error) => void;
|
||||
} | null = null;
|
||||
|
||||
// Only start client if we have something to watch.
|
||||
if (paths.length) {
|
||||
|
||||
@@ -76,7 +76,7 @@ export default class CommandExecutor implements Executor {
|
||||
});
|
||||
}
|
||||
|
||||
public execute(filePath: string) {
|
||||
public execute(filePath: string): void {
|
||||
this.spawnProcessPotentiallyDebounced();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,14 +99,14 @@ export default class LongRunningExecutor implements Executor {
|
||||
}
|
||||
|
||||
// This is called before exiting.
|
||||
public async onCleanup() {
|
||||
public async onCleanup(): Promise<void> {
|
||||
this.restartDebounced.cancel();
|
||||
if (this.isRunning) {
|
||||
await this.kill();
|
||||
}
|
||||
}
|
||||
|
||||
public execute(filePath: string) {
|
||||
public execute(filePath: string): void {
|
||||
if (this.isRunning) {
|
||||
this.restartDebounced();
|
||||
return;
|
||||
|
||||
@@ -48,7 +48,7 @@ export default class SaneWatcher implements Watcher {
|
||||
const queue: string[] = [];
|
||||
|
||||
// If this is set, a pending promise is waiting for the next result.
|
||||
let pending: ({ resolve: (result: string) => void }) | null = null;
|
||||
let pending: { resolve: (result: string) => void } | null = null;
|
||||
|
||||
// Only start client if we have something to watch.
|
||||
if (paths.length) {
|
||||
|
||||
@@ -30,7 +30,7 @@ const cmd = program
|
||||
.description("Run watchers defined in <configFile>")
|
||||
.parse(process.argv);
|
||||
|
||||
run(cmd.args, cmd.opts()).catch(err => {
|
||||
run(cmd.args, cmd.opts()).catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Joi from "joi";
|
||||
import Joi from "@hapi/joi";
|
||||
|
||||
export interface WatchOptions {
|
||||
ignore?: ReadonlyArray<string>;
|
||||
@@ -44,23 +44,13 @@ export const configSchema = Joi.object({
|
||||
watchers: Joi.object().pattern(
|
||||
/.*/,
|
||||
Joi.object({
|
||||
paths: Joi.array()
|
||||
.items(Joi.string())
|
||||
.unique(),
|
||||
ignore: Joi.array()
|
||||
.items(Joi.string())
|
||||
.unique()
|
||||
.optional(),
|
||||
paths: Joi.array().items(Joi.string()).unique(),
|
||||
ignore: Joi.array().items(Joi.string()).unique().optional(),
|
||||
executor: Joi.object(),
|
||||
})
|
||||
),
|
||||
defaultSet: Joi.string().optional(),
|
||||
sets: Joi.object()
|
||||
.pattern(
|
||||
/.*/,
|
||||
Joi.array()
|
||||
.items(Joi.string())
|
||||
.unique()
|
||||
)
|
||||
.pattern(/.*/, Joi.array().items(Joi.string()).unique())
|
||||
.optional(),
|
||||
}).with("defaultSet", "sets");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import Joi from "@hapi/joi";
|
||||
import chalk from "chalk";
|
||||
import Joi from "joi";
|
||||
import { pickBy } from "lodash";
|
||||
|
||||
import SaneWatcher from "./SaneWatcher";
|
||||
@@ -28,7 +28,7 @@ async function beginWatch(
|
||||
}
|
||||
|
||||
function setupCleanup(watcher: Watcher, config: Config) {
|
||||
["SIGINT", "SIGTERM"].forEach(signal =>
|
||||
["SIGINT", "SIGTERM"].forEach((signal) =>
|
||||
process.once(signal as any, async () => {
|
||||
const cleanups = [];
|
||||
if (watcher.onCleanup) {
|
||||
@@ -50,7 +50,7 @@ function resolveSets(
|
||||
value: ReadonlyArray<string>
|
||||
) {
|
||||
const resolved: string[] = [];
|
||||
value.forEach(v => {
|
||||
value.forEach((v) => {
|
||||
if (v in sets) {
|
||||
resolved.push(...sets[v]);
|
||||
return;
|
||||
@@ -66,12 +66,12 @@ function filterOnly(
|
||||
sets?: Record<string, ReadonlyArray<string>>
|
||||
): Config["watchers"] {
|
||||
const resolved = sets ? resolveSets(sets, only) : only;
|
||||
const unknown = resolved.filter(r => !(r in watchers));
|
||||
const unknown = resolved.filter((r) => !(r in watchers));
|
||||
if (unknown.length) {
|
||||
throw new Error(`Watcher Configuration or Set for ${unknown} not found`);
|
||||
}
|
||||
return pickBy(watchers, (value, key) => {
|
||||
if (resolved.indexOf(key) === -1) {
|
||||
if (!resolved.includes(key)) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(chalk.grey(`Disabled watcher "${key}"`));
|
||||
return false;
|
||||
@@ -80,7 +80,10 @@ function filterOnly(
|
||||
}) as Config["watchers"];
|
||||
}
|
||||
|
||||
export default async function watch(config: Config, options: Options = {}) {
|
||||
export default async function watch(
|
||||
config: Config,
|
||||
options: Options = {}
|
||||
): Promise<void> {
|
||||
Joi.assert(config, configSchema);
|
||||
const watcher: Watcher = config.backend || new SaneWatcher();
|
||||
const rootDir = config.rootDir || process.cwd();
|
||||
@@ -101,7 +104,7 @@ export default async function watch(config: Config, options: Options = {}) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(chalk.cyanBright(`Start watcher "${key}"`));
|
||||
const watcherConfig = watchersConfigs[key];
|
||||
beginWatch(watcher, key, watcherConfig, rootDir).catch(err => {
|
||||
beginWatch(watcher, key, watcherConfig, rootDir).catch((err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
|
||||
@@ -24,7 +24,8 @@ var url = require("url");
|
||||
var launchEditorEndpoint = require("react-dev-utils/launchEditorEndpoint");
|
||||
var formatWebpackMessages = require("react-dev-utils/formatWebpackMessages");
|
||||
var ErrorOverlay = require("react-error-overlay");
|
||||
var { debounce } = require("lodash");
|
||||
var lodash = require("lodash");
|
||||
var debounce = lodash.debounce;
|
||||
|
||||
ErrorOverlay.setEditorHandler(function editorHandler(errorLocation) {
|
||||
// Keep this sync with errorOverlayMiddleware.js
|
||||
@@ -124,7 +125,7 @@ function handleWarnings(warnings) {
|
||||
|
||||
// TODO: remove this workaround when we can upgrade to WebpackDevServer >= v3.3.0,
|
||||
// which includes proper `warningsFilter` support.
|
||||
warnings = warnings.filter(w => !/export .* was not found in/.test(w));
|
||||
warnings = warnings.filter(function(w){ return !/export .* was not found in/.test(w)});
|
||||
|
||||
function printWarnings() {
|
||||
// Print warnings to the console.
|
||||
@@ -198,7 +199,7 @@ function handleAvailableHash(hash) {
|
||||
mostRecentCompilationHash = hash;
|
||||
}
|
||||
|
||||
const debouncedReload = debounce(() => {
|
||||
const debouncedReload = debounce(function() {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import convict from "convict";
|
||||
import os from "os";
|
||||
|
||||
import { LOCALES } from "../common/helpers/i18n/locales";
|
||||
|
||||
@@ -67,7 +68,7 @@ const config = convict({
|
||||
maxCores: {
|
||||
doc: "Set maximum of available cores",
|
||||
format: "nat",
|
||||
default: require("os").cpus().length,
|
||||
default: os.cpus().length,
|
||||
env: "WEBPACK_MAX_CORES",
|
||||
arg: "maxCores",
|
||||
},
|
||||
@@ -77,6 +78,7 @@ export type Config = typeof config;
|
||||
|
||||
export const createClientEnv = (c: Config) => ({
|
||||
NODE_ENV: c.get("env"),
|
||||
WEBPACK: "true",
|
||||
});
|
||||
|
||||
// Setup the base configuration.
|
||||
|
||||
@@ -7,7 +7,6 @@ import HtmlWebpackPlugin from "html-webpack-plugin";
|
||||
import { identity } from "lodash";
|
||||
import MiniCssExtractPlugin from "mini-css-extract-plugin";
|
||||
import path from "path";
|
||||
import typescriptFormatter from "react-dev-utils/typescriptFormatter";
|
||||
import WatchMissingNodeModulesPlugin from "react-dev-utils/WatchMissingNodeModulesPlugin";
|
||||
import TerserPlugin from "terser-webpack-plugin";
|
||||
import TsconfigPathsPlugin from "tsconfig-paths-webpack-plugin";
|
||||
@@ -18,8 +17,7 @@ import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
|
||||
// TODO: import form coral-common/version, for some reason this fails currently.
|
||||
// Try again when we have a chance to upgrade typescript.
|
||||
import { version } from "../common/version";
|
||||
import { Config } from "./config";
|
||||
import { createClientEnv } from "./config";
|
||||
import { Config, createClientEnv } from "./config";
|
||||
import paths from "./paths";
|
||||
|
||||
/**
|
||||
@@ -83,10 +81,6 @@ export default function createWebpackConfig(
|
||||
|
||||
const styleLoader = {
|
||||
loader: require.resolve("style-loader"),
|
||||
options: {
|
||||
sourceMap: !disableSourcemaps,
|
||||
hmr: watch,
|
||||
},
|
||||
};
|
||||
|
||||
const localesOptions = {
|
||||
@@ -115,10 +109,10 @@ export default function createWebpackConfig(
|
||||
...ifBuild(
|
||||
new MiniCssExtractPlugin({
|
||||
filename: isProduction
|
||||
? "assets/css/[name].[hash].css"
|
||||
? "assets/css/[name].[contenthash].css"
|
||||
: "assets/css/[name].css",
|
||||
chunkFilename: isProduction
|
||||
? "assets/css/[id].[hash].css"
|
||||
? "assets/css/[id].[contenthash].css"
|
||||
: "assets/css/[id].css",
|
||||
}),
|
||||
isProduction &&
|
||||
@@ -223,10 +217,10 @@ export default function createWebpackConfig(
|
||||
// Generated JS file names (with nested folders).
|
||||
// There will be one main bundle, and one file per asynchronous chunk.
|
||||
filename: isProduction
|
||||
? "assets/js/[name].[chunkhash].js"
|
||||
? "assets/js/[name].[contenthash].js"
|
||||
: "assets/js/[name].js",
|
||||
chunkFilename: isProduction
|
||||
? "assets/js/[name].[chunkhash].chunk.js"
|
||||
? "assets/js/[name].[contenthash].chunk.js"
|
||||
: "assets/js/[name].chunk.js",
|
||||
// We inferred the "public path" (such as / or /my-project) from homepage.
|
||||
publicPath,
|
||||
@@ -357,7 +351,7 @@ export default function createWebpackConfig(
|
||||
options: {
|
||||
limit: 10000,
|
||||
name: isProduction
|
||||
? "assets/media/[name].[hash].[ext]"
|
||||
? "assets/media/[name].[contenthash].[ext]"
|
||||
: "assets/media/[name].[ext]",
|
||||
},
|
||||
},
|
||||
@@ -368,9 +362,10 @@ export default function createWebpackConfig(
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
modules: {
|
||||
localIdentName: "[name]-[local]-[contenthash]",
|
||||
},
|
||||
importLoaders: 2,
|
||||
localIdentName: "[name]-[local]-[contenthash]",
|
||||
sourceMap: !disableSourcemaps,
|
||||
},
|
||||
},
|
||||
@@ -392,7 +387,7 @@ export default function createWebpackConfig(
|
||||
"@babel/typescript",
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: { node: "10.0.0" }, modules: "commonjs" },
|
||||
{ targets: { node: "current" }, modules: "commonjs" },
|
||||
],
|
||||
],
|
||||
// This is a feature of `babel-loader` for webpack (not Babel itself).
|
||||
@@ -450,19 +445,11 @@ export default function createWebpackConfig(
|
||||
{
|
||||
test: /\.js$/,
|
||||
include: /node_modules\//,
|
||||
exclude: /node_modules\/(@babel|babel|core-js|regenerator-runtime)/,
|
||||
use: [
|
||||
{
|
||||
loader: require.resolve("babel-loader"),
|
||||
options: {
|
||||
// This will ensure that all packages in node_modules that
|
||||
// import lodash do so in a way that supports tree shaking.
|
||||
plugins: ["lodash"],
|
||||
presets: [
|
||||
[
|
||||
"@babel/env",
|
||||
{ targets: "last 2 versions", modules: false },
|
||||
],
|
||||
],
|
||||
cacheDirectory: true,
|
||||
},
|
||||
},
|
||||
@@ -480,9 +467,10 @@ export default function createWebpackConfig(
|
||||
{
|
||||
loader: require.resolve("css-loader"),
|
||||
options: {
|
||||
modules: true,
|
||||
modules: {
|
||||
localIdentName: "[name]-[local]-[contenthash]",
|
||||
},
|
||||
importLoaders: 1,
|
||||
localIdentName: "[name]-[local]-[hash:base64:5]",
|
||||
sourceMap: !disableSourcemaps,
|
||||
},
|
||||
},
|
||||
@@ -510,13 +498,8 @@ export default function createWebpackConfig(
|
||||
exclude: [/\.(js|ts|tsx)$/, /\.html$/, /\.json$/],
|
||||
loader: require.resolve("file-loader"),
|
||||
options: {
|
||||
// Because the resources loaded via CSS can sometimes be loaded
|
||||
// directly from a CSS file, this will ensure that they are
|
||||
// relative to those referencing files.
|
||||
publicPath: (loaderPublicPath: string) =>
|
||||
"../../" + loaderPublicPath,
|
||||
name: isProduction
|
||||
? "assets/media/[name].[hash:8].[ext]"
|
||||
? "assets/media/[name].[contenthash].[ext]"
|
||||
: "assets/media/[name].[ext]",
|
||||
},
|
||||
},
|
||||
@@ -539,11 +522,6 @@ export default function createWebpackConfig(
|
||||
useTypescriptIncrementalApi: false,
|
||||
checkSyntacticErrors: true,
|
||||
tsconfig: paths.appTsconfig,
|
||||
watch: paths.appSrc,
|
||||
// TODO: (cvle) ForkTsCheckerWebpackPlugin are currently not working, so we resort to default reporting.
|
||||
silent: false,
|
||||
// The formatter is normally invoked directly in WebpackDevServerUtils during development
|
||||
formatter: typescriptFormatter,
|
||||
})
|
||||
),
|
||||
// Makes some environment variables available to the JS code, for example:
|
||||
@@ -712,8 +690,7 @@ export default function createWebpackConfig(
|
||||
sideEffects: true,
|
||||
},
|
||||
entry: [
|
||||
/* Use minimal amount of polyfills (for IE) */
|
||||
"intersection-observer", // also for Safari
|
||||
// Polyfills are in the index.ts file.
|
||||
paths.appEmbedIndex,
|
||||
],
|
||||
output: {
|
||||
|
||||
@@ -40,7 +40,7 @@ function getFiles(target, pathToLocale, context) {
|
||||
|
||||
const files = fs.readdirSync(pathToLocale);
|
||||
|
||||
files.forEach(f => {
|
||||
files.forEach((f) => {
|
||||
if (commonFiles.includes(f)) {
|
||||
common.push(f);
|
||||
return;
|
||||
@@ -62,12 +62,12 @@ function generateTarget(target, context) {
|
||||
locales,
|
||||
bundled,
|
||||
} = context;
|
||||
const getLocalePath = locale => path.join(pathToLocales, locale);
|
||||
const getLocaleFiles = memoize(locale =>
|
||||
const getLocalePath = (locale) => path.join(pathToLocales, locale);
|
||||
const getLocaleFiles = memoize((locale) =>
|
||||
getFiles(target, getLocalePath(locale), context)
|
||||
);
|
||||
|
||||
const loadables = locales.filter(locale => !bundled.includes(locale));
|
||||
const loadables = locales.filter((locale) => !bundled.includes(locale));
|
||||
|
||||
return `
|
||||
var ret = {
|
||||
@@ -81,22 +81,22 @@ function generateTarget(target, context) {
|
||||
// Bundled locales are directly available in the main bundle.
|
||||
${bundled
|
||||
.map(
|
||||
locale => `
|
||||
(locale) => `
|
||||
{
|
||||
var suffixes = ${JSON.stringify(getLocaleFiles(locale).suffixes)};
|
||||
var contents = [];
|
||||
${getLocaleFiles(locale)
|
||||
.common.map(
|
||||
file => `
|
||||
(file) => `
|
||||
contents.push(require(${JSON.stringify(
|
||||
path.join(getLocalePath(locale), file).replace(/\\/g, "/")
|
||||
)}));
|
||||
)}).default);
|
||||
`
|
||||
)
|
||||
.join("\n")}
|
||||
contents = contents.concat(suffixes.map(function(suffix) { return require("${path
|
||||
.join(getLocalePath(locale), target)
|
||||
.replace(/\\/g, "/")}" + suffix); }));
|
||||
.replace(/\\/g, "/")}" + suffix).default; }));
|
||||
ret.bundled[${JSON.stringify(locale)}] = contents.join("\\n");
|
||||
}
|
||||
`
|
||||
@@ -106,13 +106,13 @@ function generateTarget(target, context) {
|
||||
// Loadables are in a separate bundle, that can be easily loaded.
|
||||
${loadables
|
||||
.map(
|
||||
locale => `
|
||||
(locale) => `
|
||||
ret.loadables[${JSON.stringify(locale)}] = function() {
|
||||
var suffixes = ${JSON.stringify(getLocaleFiles(locale).suffixes)};
|
||||
var promises = [];
|
||||
${getLocaleFiles(locale)
|
||||
.common.map(
|
||||
file => `
|
||||
(file) => `
|
||||
promises.push(
|
||||
import(
|
||||
/* webpackChunkName: ${JSON.stringify(
|
||||
@@ -147,7 +147,7 @@ function generateTarget(target, context) {
|
||||
`;
|
||||
}
|
||||
|
||||
module.exports = function(source) {
|
||||
module.exports = function (source) {
|
||||
const options = Object.assign(
|
||||
{},
|
||||
DEFAULT_QUERY_VALUES,
|
||||
@@ -165,7 +165,7 @@ module.exports = function(source) {
|
||||
|
||||
let locales = fs.readdirSync(pathToLocales);
|
||||
if (availableLocales) {
|
||||
availableLocales.forEach(locale => {
|
||||
availableLocales.forEach((locale) => {
|
||||
if (!locales.includes(locale)) {
|
||||
throw new Error(`locale ${fallbackLocale} not available`);
|
||||
}
|
||||
|
||||
@@ -16,11 +16,13 @@ export default {
|
||||
appLoaders: resolveSrc("core/build/loaders"),
|
||||
appSrc: resolveSrc("."),
|
||||
appTsconfig: resolveSrc("core/client/tsconfig.json"),
|
||||
appPolyfill: resolveSrc("core/build/polyfills.js"),
|
||||
appPolyfill: resolveSrc("core/build/polyfills.ts"),
|
||||
appPublicPath: resolveSrc("core/build/publicPath.js"),
|
||||
appLocales: resolveSrc("locales"),
|
||||
appThemeVariables: resolveSrc("core/client/ui/theme/variables.ts"),
|
||||
appThemeVariablesCSS: resolveSrc("core/client/ui/theme/variables.css"),
|
||||
appSassLikeVariables: resolveSrc("core/client/ui/theme/sassLikeVariables.ts"),
|
||||
appThemeStreamCSS: resolveSrc("core/client/ui/theme/stream.css"),
|
||||
appThemeAdminCSS: resolveSrc("core/client/ui/theme/admin.css"),
|
||||
appThemeMixinsCSS: resolveSrc("core/client/ui/theme/mixins.css"),
|
||||
|
||||
appStreamHTML: resolveSrc("core/client/stream/index.html"),
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
require("@babel/polyfill");
|
||||
require("intersection-observer");
|
||||
@@ -0,0 +1,6 @@
|
||||
import polyfillNodeListForEach from "../client/framework/helpers/polyfillNodeListForEach";
|
||||
|
||||
import "core-js/stable";
|
||||
import "regenerator-runtime/runtime";
|
||||
|
||||
polyfillNodeListForEach();
|
||||
@@ -3,10 +3,10 @@ require("ts-node/register");
|
||||
const kebabCase = require("lodash/kebabCase");
|
||||
const mapKeys = require("lodash/mapKeys");
|
||||
const mapValues = require("lodash/mapValues");
|
||||
const pickBy = require("lodash/pickBy");
|
||||
const flat = require("flat");
|
||||
const paths = require("./paths").default;
|
||||
const autoprefixer = require("autoprefixer");
|
||||
const postcssCalcFunction = require("postcss-calc-function").default;
|
||||
const postcssFontMagician = require("postcss-font-magician");
|
||||
const postcssFlexbugsFixes = require("postcss-flexbugs-fixes");
|
||||
const postcssPresetEnv = require("postcss-preset-env");
|
||||
@@ -16,23 +16,28 @@ const postcssMixins = require("postcss-mixins");
|
||||
const postcssPrependImports = require("postcss-prepend-imports");
|
||||
const postcssAdvancedVariables = require("postcss-advanced-variables");
|
||||
|
||||
delete require.cache[paths.appThemeVariables];
|
||||
const variables = require(paths.appThemeVariables).default;
|
||||
const flatKebabVariables = mapKeys(
|
||||
mapValues(flat(variables, { delimiter: "-" }), v => v.toString()),
|
||||
delete require.cache[paths.appSassLikeVariables];
|
||||
const sassLikeVariables = require(paths.appSassLikeVariables).default;
|
||||
|
||||
const kebabs = mapKeys(
|
||||
mapValues(flat(sassLikeVariables, { delimiter: "-" }), (v) => v.toString()),
|
||||
(_, k) => kebabCase(k)
|
||||
);
|
||||
|
||||
// These are sass style variables used in media queries.
|
||||
const mediaQueryVariables = mapValues(
|
||||
pickBy(flatKebabVariables, (v, k) => k.startsWith("breakpoints-")),
|
||||
// Generate sass-style variables to inject into css
|
||||
const postCssVariables = mapValues(kebabs, (value, key) => {
|
||||
// Add unit to breakpoints.
|
||||
// Add 1 to support mobile first approach where we start
|
||||
// with the smallest screen and gradually add styling for the
|
||||
// next bigger screen. This is realized using `min-width` without
|
||||
// ever using `max-width`.
|
||||
v => `${Number.parseInt(v, 10) + 1}px`
|
||||
);
|
||||
if (key.toString().startsWith("breakpoints-")) {
|
||||
return `${Number.parseInt(value, 10) + 1}px`;
|
||||
}
|
||||
|
||||
// Default return the raw value
|
||||
return value;
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
// Necessary for external CSS imports to work
|
||||
@@ -51,7 +56,9 @@ module.exports = {
|
||||
// Support nesting.
|
||||
postcssNested(),
|
||||
// Sass style variables to be used in media queries.
|
||||
postcssAdvancedVariables({ variables: mediaQueryVariables }),
|
||||
postcssAdvancedVariables({ variables: postCssVariables }),
|
||||
// Reduce some calc()
|
||||
postcssCalcFunction(),
|
||||
// Provides a modern CSS environment.
|
||||
postcssPresetEnv(),
|
||||
// Does all the font handling logic.
|
||||
@@ -60,12 +67,6 @@ module.exports = {
|
||||
postcssFlexbugsFixes,
|
||||
// Vendor prefixing.
|
||||
autoprefixer({
|
||||
browsers: [
|
||||
">1%",
|
||||
"last 4 versions",
|
||||
"Firefox ESR",
|
||||
"not ie < 9", // React doesn't support IE8 anyway
|
||||
],
|
||||
flexbox: "no-2009",
|
||||
}),
|
||||
],
|
||||
|
||||
@@ -1,20 +1,3 @@
|
||||
const lodashOptimizations = ["use-lodash-es", "lodash"];
|
||||
|
||||
module.exports = {
|
||||
presets: ["@babel/react"],
|
||||
plugins: ["@babel/syntax-dynamic-import"],
|
||||
env: {
|
||||
production: {
|
||||
presets: [["@babel/env", { targets: "last 2 versions", modules: false }]],
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
development: {
|
||||
presets: [["@babel/env", { targets: "last 2 versions", modules: false }]],
|
||||
plugins: [...lodashOptimizations],
|
||||
},
|
||||
test: {
|
||||
presets: [["@babel/env", { targets: { node: "current" } }]],
|
||||
plugins: ["@babel/transform-modules-commonjs"],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -2,4 +2,7 @@
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
input::-ms-clear, input::-ms-reveal {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
.bar {
|
||||
height: calc(6 * var(--mini-unit));
|
||||
background-color: var(--palette-text-primary);
|
||||
background-color: var(--palette-text-500);
|
||||
}
|
||||
|
||||
.centered {
|
||||
margin: 10% auto;
|
||||
padding: 0 calc(0.5 * var(--mini-unit));
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Delay, Flex, Spinner } from "coral-ui/components";
|
||||
import { Delay, Flex, Spinner } from "coral-ui/components/v2";
|
||||
|
||||
const Loading: FunctionComponent = () => (
|
||||
<Flex justifyContent="center">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<title>Coral - Account</title>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="Content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no" />
|
||||
<meta name="viewport" content="width=device-width" />
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
import ReactDOM from "react-dom";
|
||||
|
||||
import injectConditionalPolyfills from "coral-framework/helpers/injectConditionalPolyfills";
|
||||
import potentiallyInjectAxe from "coral-framework/helpers/potentiallyInjectAxe";
|
||||
import { createManaged } from "coral-framework/lib/bootstrap";
|
||||
|
||||
import App from "./App";
|
||||
@@ -8,9 +10,12 @@ import { initLocalState } from "./local";
|
||||
import localesData from "./locales";
|
||||
|
||||
// Import css variables.
|
||||
import "coral-ui/theme/variables.css";
|
||||
import "coral-ui/theme/stream.css";
|
||||
|
||||
async function main() {
|
||||
await injectConditionalPolyfills();
|
||||
// Potentially inject react-axe for runtime a11y checks.
|
||||
await potentiallyInjectAxe();
|
||||
const ManagedCoralContextProvider = await createManaged({
|
||||
initLocalState,
|
||||
localesData,
|
||||
@@ -25,4 +30,4 @@ async function main() {
|
||||
ReactDOM.render(<Index />, document.getElementById("app"));
|
||||
}
|
||||
|
||||
main();
|
||||
void main();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import { AuthState } from "coral-framework/lib/auth";
|
||||
import { CoralContext } from "coral-framework/lib/bootstrap";
|
||||
import { initLocalBaseState } from "coral-framework/lib/relay";
|
||||
|
||||
@@ -8,7 +9,8 @@ import { initLocalBaseState } from "coral-framework/lib/relay";
|
||||
*/
|
||||
export default async function initLocalState(
|
||||
environment: Environment,
|
||||
context: CoralContext
|
||||
context: CoralContext,
|
||||
auth?: AuthState
|
||||
) {
|
||||
await initLocalBaseState(environment, context);
|
||||
initLocalBaseState(environment, context, auth);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
.content {
|
||||
font-family: var(--font-family-secondary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-secondary-bold);
|
||||
font-size: var(--font-size-6);
|
||||
line-height: 1.17;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
@@ -1,10 +1,15 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import { HorizontalGutter } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./NotFound.css";
|
||||
|
||||
const NotFound: FunctionComponent = () => (
|
||||
<HorizontalGutter>
|
||||
<Typography variant="heading3">Not Found</Typography>
|
||||
<HorizontalGutter container="main">
|
||||
<Localized id="notFound">
|
||||
<div className={styles.content}>Not Found</div>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
);
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
.title {
|
||||
font-family: var(--font-family-serif);
|
||||
font-weight: 600;
|
||||
font-family: var(--font-family-secondary);
|
||||
font-style: normal;
|
||||
font-size: calc(48rem / var(--rem-base));
|
||||
line-height: calc(42em / 48);
|
||||
text-align: center;
|
||||
font-weight: var(--font-weight-secondary-bold);
|
||||
font-size: var(--font-size-6);
|
||||
line-height: 1.17;
|
||||
|
||||
color: var(--palette-common-black);
|
||||
color: var(--palette-text-500);
|
||||
|
||||
padding-left: var(--spacing-2);
|
||||
}
|
||||
|
||||
.content {
|
||||
@@ -18,14 +19,15 @@
|
||||
}
|
||||
|
||||
.sectionText {
|
||||
font-family: var(--font-family-sans-serif);
|
||||
font-weight: normal;
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-size: calc(24rem / var(--rem-base));
|
||||
line-height: calc(34em / 24);
|
||||
letter-spacing: 0.3px;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-3);
|
||||
line-height: 1.25;
|
||||
|
||||
color: var(--palette-text-primary);
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
.list {
|
||||
@@ -34,7 +36,7 @@
|
||||
}
|
||||
|
||||
.list li {
|
||||
margin-bottom: calc(16em / 24);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.listContent {
|
||||
@@ -43,6 +45,6 @@
|
||||
|
||||
.bullet {
|
||||
display: inline-block;
|
||||
min-width: 24px;
|
||||
min-width: 16px;
|
||||
margin-right: var(--spacing-2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import cn from "classnames";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex, HorizontalGutter, Icon, Typography } from "coral-ui/components";
|
||||
import { Flex, HorizontalGutter, Icon } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./DownloadDescription.css";
|
||||
|
||||
@@ -10,84 +10,72 @@ const DownloadDescription: FunctionComponent = () => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<Localized id="download-landingPage-title">
|
||||
<div className={styles.title}>Download Your Comment History</div>
|
||||
<h1 className={styles.title}>Download your comment history</h1>
|
||||
</Localized>
|
||||
<div className={styles.content}>
|
||||
<div className={styles.section}>
|
||||
<Localized id="download-landingPage-description">
|
||||
<Typography variant="bodyCopy" className={styles.sectionText}>
|
||||
<div className={styles.sectionText}>
|
||||
Your comment history will be downloaded into a .zip file. After
|
||||
your comment history is unzipped you will have a comma separated
|
||||
value (or .csv) file that you can easily import into your favorite
|
||||
spreadsheet application.
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</div>
|
||||
|
||||
<div className={styles.section}>
|
||||
<Localized id="download-landingPage-contentsDescription">
|
||||
<Typography variant="bodyCopy" className={styles.sectionText}>
|
||||
<div className={styles.sectionText}>
|
||||
For each of your comments the following information is included:
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
<ul className={styles.list}>
|
||||
<li>
|
||||
<Flex alignItems="center">
|
||||
<Icon size="lg" className={styles.bullet}>
|
||||
<Flex alignItems="flex-start">
|
||||
<Icon size="md" className={styles.bullet}>
|
||||
check
|
||||
</Icon>
|
||||
<Localized id="download-landingPage-contentsDate">
|
||||
<Typography
|
||||
variant="bodyCopy"
|
||||
className={cn(styles.sectionText, styles.listContent)}
|
||||
>
|
||||
<div className={cn(styles.sectionText, styles.listContent)}>
|
||||
When you wrote the comment
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</li>
|
||||
<li>
|
||||
<Flex alignItems="center">
|
||||
<Icon size="lg" className={styles.bullet}>
|
||||
<Flex alignItems="flex-start">
|
||||
<Icon size="md" className={styles.bullet}>
|
||||
check
|
||||
</Icon>
|
||||
<Localized id="download-landingPage-contentsUrl">
|
||||
<Typography
|
||||
variant="bodyCopy"
|
||||
className={cn(styles.sectionText, styles.listContent)}
|
||||
>
|
||||
<div className={cn(styles.sectionText, styles.listContent)}>
|
||||
The permalink URL for the comment
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</li>
|
||||
<li>
|
||||
<Flex alignItems="center">
|
||||
<Icon size="lg" className={styles.bullet}>
|
||||
<Flex alignItems="flex-start">
|
||||
<Icon size="md" className={styles.bullet}>
|
||||
check
|
||||
</Icon>
|
||||
<Localized id="download-landingPage-contentsText">
|
||||
<Typography
|
||||
variant="bodyCopy"
|
||||
className={cn(styles.sectionText, styles.listContent)}
|
||||
>
|
||||
<div className={cn(styles.sectionText, styles.listContent)}>
|
||||
The comment text
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</li>
|
||||
<li>
|
||||
<Flex alignItems="center">
|
||||
<Icon size="lg" className={styles.bullet}>
|
||||
<Flex alignItems="flex-start">
|
||||
<Icon size="md" className={styles.bullet}>
|
||||
check
|
||||
</Icon>
|
||||
<Localized id="download-landingPage-contentsStoryUrl">
|
||||
<Typography
|
||||
variant="bodyCopy"
|
||||
className={cn(styles.sectionText, styles.listContent)}
|
||||
>
|
||||
<div className={cn(styles.sectionText, styles.listContent)}>
|
||||
The URL on the article or story where the comment appears
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</Flex>
|
||||
</li>
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
.form {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.downloadButton {
|
||||
font-size: calc(18rem / var(--rem-base));
|
||||
line-height: calc(23em / 18);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent, useCallback, useState } from "react";
|
||||
|
||||
import { Button, HorizontalGutter } from "coral-ui/components";
|
||||
import { HorizontalGutter } from "coral-ui/components/v2";
|
||||
import { Button } from "coral-ui/components/v3";
|
||||
|
||||
import styles from "./DownloadForm.css";
|
||||
|
||||
@@ -25,15 +26,16 @@ const DownloadForm: FunctionComponent<Props> = ({ token }) => {
|
||||
onSubmit={onSubmit}
|
||||
>
|
||||
<input name="token" type="hidden" value={token} />
|
||||
<Localized id="download-landingPage-downloadComments">
|
||||
<Localized id="download-landingPage-download">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="filled"
|
||||
color="primary"
|
||||
paddingSize="medium"
|
||||
disabled={submitted}
|
||||
className={styles.downloadButton}
|
||||
upperCase
|
||||
>
|
||||
Download My Comment History
|
||||
Download
|
||||
</Button>
|
||||
</Localized>
|
||||
</form>
|
||||
|
||||
@@ -6,11 +6,11 @@
|
||||
.root {
|
||||
display: inline-block;
|
||||
|
||||
max-width: calc(70 * var(--mini-unit));
|
||||
max-width: calc(70 * var(--spacing-2));
|
||||
|
||||
@media (min-width: $breakpoints-xs) {
|
||||
max-width: calc(90 * var(--mini-unit));
|
||||
max-width: calc(60 * var(--spacing-2));
|
||||
}
|
||||
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import { useToken } from "coral-framework/hooks";
|
||||
import { createFetch } from "coral-framework/lib/relay";
|
||||
import { withRouteConfig } from "coral-framework/lib/router";
|
||||
import { parseHashQuery } from "coral-framework/utils";
|
||||
import { HorizontalGutter } from "coral-ui/components";
|
||||
import { HorizontalGutter } from "coral-ui/components/v2";
|
||||
|
||||
import DownloadDescription from "./DownloadDescription";
|
||||
import DownloadForm from "./DownloadForm";
|
||||
@@ -32,35 +32,35 @@ const DownloadRoute: FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<HorizontalGutter size="double">
|
||||
<Loading />
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<HorizontalGutter size="double">
|
||||
<DownloadDescription />
|
||||
<Sorry />
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<DownloadDescription />
|
||||
<DownloadForm token={token!} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
.icon {
|
||||
margin-right: var(--spacing-2);
|
||||
}
|
||||
|
||||
.callout {
|
||||
justify-content: left;
|
||||
}
|
||||
@@ -1,22 +1,21 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { CallOut, Flex, Icon } from "coral-ui/components";
|
||||
|
||||
import styles from "./Sorry.css";
|
||||
import { Icon } from "coral-ui/components/v2";
|
||||
import { CallOut } from "coral-ui/components/v3";
|
||||
|
||||
const Sorry: FunctionComponent = () => {
|
||||
return (
|
||||
<CallOut color="error" fullWidth className={styles.callout}>
|
||||
<Flex>
|
||||
<Icon size="md" className={styles.icon}>
|
||||
error
|
||||
</Icon>
|
||||
<CallOut
|
||||
color="error"
|
||||
icon={<Icon>error</Icon>}
|
||||
titleWeight="semiBold"
|
||||
title={
|
||||
<Localized id="download-landingPage-sorry">
|
||||
Your download link is invalid.
|
||||
</Localized>
|
||||
</Flex>
|
||||
</CallOut>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.title {
|
||||
font-family: var(--font-family-secondary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-secondary-bold);
|
||||
font-size: var(--font-size-6);
|
||||
line-height: 1.17;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-3);
|
||||
line-height: 1.25;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-bold);
|
||||
font-size: var(--font-size-2);
|
||||
line-height: 1.14;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
|
||||
.labelDescription {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-2);
|
||||
line-height: 1.285;
|
||||
|
||||
color: var(--palette-text-100);
|
||||
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: var(--spacing-5);
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
@@ -1,14 +1,16 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { useCallback } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { useMutation } from "coral-framework/lib/relay";
|
||||
import { Button, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import { Button } from "coral-ui/components/v3";
|
||||
|
||||
import ConfirmMutation from "./ConfirmMutation";
|
||||
|
||||
import styles from "./Confirm.css";
|
||||
|
||||
interface Props {
|
||||
token: string;
|
||||
disabled?: boolean;
|
||||
@@ -33,18 +35,18 @@ const ConfirmForm: React.FunctionComponent<Props> = ({ onSuccess, token }) => {
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<HorizontalGutter size="double">
|
||||
<HorizontalGutter>
|
||||
<Localized id="confirmEmail-emailConfirmation">
|
||||
<Typography variant="heading1">Email Confirmation</Typography>
|
||||
<div>
|
||||
<div>
|
||||
<Localized id="confirmEmail-confirmYourEmailAddress">
|
||||
<div className={styles.title}>Confirm your email address</div>
|
||||
</Localized>
|
||||
<Localized id="confirmEmail-pleaseClickToConfirm">
|
||||
<Typography variant="bodyCopy">
|
||||
<div className={styles.description}>
|
||||
Click below to confirm your email address.
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter>
|
||||
</div>
|
||||
<div>
|
||||
<Localized id="confirmEmail-confirmEmail">
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -52,12 +54,14 @@ const ConfirmForm: React.FunctionComponent<Props> = ({ onSuccess, token }) => {
|
||||
color="primary"
|
||||
disabled={submitting}
|
||||
fullWidth
|
||||
upperCase
|
||||
className={styles.submit}
|
||||
>
|
||||
Confirm email
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -35,36 +35,36 @@ const ConfirmRoute: React.FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Sorry reason={error} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return !finished ? (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<ConfirmForm token={token!} onSuccess={onSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Success />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { CallOut, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import { CallOut } from "coral-ui/components/v3";
|
||||
|
||||
import styles from "./Confirm.css";
|
||||
|
||||
interface Props {
|
||||
reason: React.ReactNode;
|
||||
@@ -9,23 +11,27 @@ interface Props {
|
||||
|
||||
const Sorry: React.FunctionComponent<Props> = ({ reason }) => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<div>
|
||||
<Localized id="confirmEmail-oopsSorry">
|
||||
<Typography variant="heading1">Oops Sorry!</Typography>
|
||||
<div className={styles.title}>Oops Sorry!</div>
|
||||
</Localized>
|
||||
<CallOut color="error" fullWidth>
|
||||
{reason ? (
|
||||
reason
|
||||
) : (
|
||||
<Localized id="account-tokenNotFound">
|
||||
<span data-testid="invalid-link">
|
||||
The specified link is invalid, check to see if it was copied
|
||||
correctly.
|
||||
</span>
|
||||
</Localized>
|
||||
)}
|
||||
</CallOut>
|
||||
</HorizontalGutter>
|
||||
<CallOut
|
||||
color="error"
|
||||
titleWeight="semiBold"
|
||||
title={
|
||||
reason ? (
|
||||
reason
|
||||
) : (
|
||||
<Localized id="account-tokenNotFound">
|
||||
<span data-testid="invalid-link">
|
||||
The specified link is invalid, check to see if it was copied
|
||||
correctly.
|
||||
</span>
|
||||
</Localized>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import styles from "./Confirm.css";
|
||||
|
||||
const Success: React.FunctionComponent = () => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<div>
|
||||
<Localized id="confirmEmail-successfullyConfirmed">
|
||||
<Typography variant="heading1">Email successfully confirmed</Typography>
|
||||
<div className={styles.title}>Email successfully confirmed</div>
|
||||
</Localized>
|
||||
<Localized id="confirmEmail-youMayClose">
|
||||
<Typography variant="bodyCopy">
|
||||
You may now close this window.
|
||||
</Typography>
|
||||
<div className={styles.description}>You may now close this window.</div>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { CallOut, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import { CallOut } from "coral-ui/components/v3";
|
||||
|
||||
import styles from "./Unsubscribe.css";
|
||||
|
||||
interface Props {
|
||||
reason: React.ReactNode;
|
||||
@@ -9,23 +11,27 @@ interface Props {
|
||||
|
||||
const Sorry: React.FunctionComponent<Props> = ({ reason }) => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<div>
|
||||
<Localized id="unsubscribe-oopsSorry">
|
||||
<Typography variant="heading1">Oops Sorry!</Typography>
|
||||
<div className={styles.title}>Oops Sorry!</div>
|
||||
</Localized>
|
||||
<CallOut color="error" fullWidth>
|
||||
{reason ? (
|
||||
reason
|
||||
) : (
|
||||
<Localized id="account-tokenNotFound">
|
||||
<span data-testid="invalid-link">
|
||||
The specified link is invalid, check to see if it was copied
|
||||
correctly.
|
||||
</span>
|
||||
</Localized>
|
||||
)}
|
||||
</CallOut>
|
||||
</HorizontalGutter>
|
||||
<CallOut
|
||||
color="error"
|
||||
titleWeight="semiBold"
|
||||
title={
|
||||
reason ? (
|
||||
reason
|
||||
) : (
|
||||
<Localized id="account-tokenNotFound">
|
||||
<span data-testid="invalid-link">
|
||||
The specified link is invalid, check to see if it was copied
|
||||
correctly.
|
||||
</span>
|
||||
</Localized>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import styles from "./Unsubscribe.css";
|
||||
|
||||
const Success: React.FunctionComponent = () => {
|
||||
return (
|
||||
<HorizontalGutter data-testid="success" size="double">
|
||||
<Localized id="unsubscribe-successfullyUnsubscribed">
|
||||
<Typography variant="heading1">
|
||||
You are now unsubscribed from all notifications
|
||||
</Typography>
|
||||
<div data-testid="success">
|
||||
<Localized id="unsubscribe-unsubscribedSuccessfully">
|
||||
<div className={styles.title}>
|
||||
Unsubscribed successfully from email notifications
|
||||
</div>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
<Localized id="unsubscribe-youMayNowClose">
|
||||
<div className={styles.description}>You may now close this window</div>
|
||||
</Localized>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.title {
|
||||
font-family: var(--font-family-secondary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-secondary-bold);
|
||||
font-size: var(--font-size-6);
|
||||
line-height: 1.17;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-3);
|
||||
line-height: 1.25;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-bold);
|
||||
font-size: var(--font-size-2);
|
||||
line-height: 1.14;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
|
||||
.labelDescription {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-2);
|
||||
line-height: 1.285;
|
||||
|
||||
color: var(--palette-text-100);
|
||||
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: var(--spacing-5);
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
@@ -1,19 +1,16 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { useCallback } from "react";
|
||||
import { Form } from "react-final-form";
|
||||
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { useMutation } from "coral-framework/lib/relay";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
HorizontalGutter,
|
||||
Typography,
|
||||
} from "coral-ui/components";
|
||||
import { Button, CallOut } from "coral-ui/components/v3";
|
||||
|
||||
import UnsubscribeNotificationsMutation from "./UnsubscribeNotificationsMutation";
|
||||
|
||||
import styles from "./Unsubscribe.css";
|
||||
|
||||
interface Props {
|
||||
token: string;
|
||||
disabled?: boolean;
|
||||
@@ -42,30 +39,34 @@ const UnsubscribeForm: React.FunctionComponent<Props> = ({
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<HorizontalGutter>
|
||||
<div>
|
||||
<Localized id="unsubscribe-unsubscribeFromEmails">
|
||||
<div className={styles.title}>
|
||||
Unsubscribe from email notifications
|
||||
</div>
|
||||
</Localized>
|
||||
<Localized id="unsubscribe-clickToConfirm">
|
||||
<Typography variant="heading1">
|
||||
<div className={styles.description}>
|
||||
Click below to confirm that you want to unsubscribe from all
|
||||
notifications.
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
{submitError && (
|
||||
<CallOut color="error" fullWidth>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
<Localized id="unsubscribe-confirm">
|
||||
{submitError && <CallOut color="error" title={submitError} />}
|
||||
<Localized id="unsubscribe-submit-unsubscribe">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="filled"
|
||||
paddingSize="medium"
|
||||
color="primary"
|
||||
disabled={submitting}
|
||||
upperCase
|
||||
fullWidth
|
||||
className={styles.submit}
|
||||
>
|
||||
Confirm
|
||||
Unsubscribe
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -35,36 +35,36 @@ const UnsubscribeRoute: React.FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Sorry reason={error} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return !finished ? (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<UnsubscribeForm token={token!} onSuccess={onSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Success />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
.title {
|
||||
font-family: var(--font-family-secondary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-secondary-bold);
|
||||
font-size: var(--font-size-6);
|
||||
line-height: 1.17;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.description {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-3);
|
||||
line-height: 1.25;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-3);
|
||||
}
|
||||
|
||||
.label {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-bold);
|
||||
font-size: var(--font-size-2);
|
||||
line-height: 1.14;
|
||||
|
||||
color: var(--palette-text-500);
|
||||
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
|
||||
.labelDescription {
|
||||
font-family: var(--font-family-primary);
|
||||
font-style: normal;
|
||||
font-weight: var(--font-weight-primary-regular);
|
||||
font-size: var(--font-size-2);
|
||||
line-height: 1.285;
|
||||
|
||||
color: var(--palette-text-100);
|
||||
|
||||
margin-bottom: var(--spacing-2);
|
||||
}
|
||||
|
||||
.submit {
|
||||
margin-top: var(--spacing-5);
|
||||
margin-bottom: var(--spacing-1);
|
||||
}
|
||||
@@ -1,29 +1,23 @@
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import { FORM_ERROR } from "final-form";
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import React, { useCallback } from "react";
|
||||
import { Field, Form } from "react-final-form";
|
||||
|
||||
import { InvalidRequestError } from "coral-framework/lib/errors";
|
||||
import { colorFromMeta, ValidationMessage } from "coral-framework/lib/form";
|
||||
import { colorFromMeta } from "coral-framework/lib/form";
|
||||
import { useMutation } from "coral-framework/lib/relay";
|
||||
import {
|
||||
composeValidators,
|
||||
required,
|
||||
validatePassword,
|
||||
} from "coral-framework/lib/validation";
|
||||
import {
|
||||
Button,
|
||||
CallOut,
|
||||
FormField,
|
||||
HorizontalGutter,
|
||||
InputDescription,
|
||||
InputLabel,
|
||||
PasswordField,
|
||||
Typography,
|
||||
} from "coral-ui/components";
|
||||
import { FormField, PasswordField } from "coral-ui/components/v2";
|
||||
import { Button, CallOut, ValidationMessage } from "coral-ui/components/v3";
|
||||
|
||||
import ResetPasswordMutation from "./ResetPasswordMutation";
|
||||
|
||||
import styles from "./Reset.css";
|
||||
|
||||
interface Props {
|
||||
token: string;
|
||||
disabled?: boolean;
|
||||
@@ -60,22 +54,20 @@ const ResetPasswordForm: React.FunctionComponent<Props> = ({
|
||||
<Form onSubmit={onSubmit}>
|
||||
{({ handleSubmit, submitting, submitError }) => (
|
||||
<form autoComplete="off" onSubmit={handleSubmit}>
|
||||
<HorizontalGutter size="double">
|
||||
<HorizontalGutter>
|
||||
<div>
|
||||
<div>
|
||||
<Localized id="resetPassword-resetYourPassword">
|
||||
<Typography variant="heading1">
|
||||
Reset your password
|
||||
</Typography>
|
||||
<div className={styles.title}>Reset your password</div>
|
||||
</Localized>
|
||||
<Localized id="resetPassword-pleaseEnterNewPassword">
|
||||
<Typography variant="bodyCopy">
|
||||
<div className={styles.description}>
|
||||
Please enter a new password to use to sign in to your
|
||||
account. Make sure it is unique and be sure to keep it
|
||||
secure.
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
<HorizontalGutter>
|
||||
</div>
|
||||
<div>
|
||||
<Field
|
||||
name="password"
|
||||
validate={composeValidators(required, validatePassword)}
|
||||
@@ -83,15 +75,17 @@ const ResetPasswordForm: React.FunctionComponent<Props> = ({
|
||||
{({ input, meta }) => (
|
||||
<FormField>
|
||||
<Localized id="resetPassword-passwordLabel">
|
||||
<InputLabel htmlFor={input.name}>Password</InputLabel>
|
||||
<label className={styles.label} htmlFor={input.name}>
|
||||
Password
|
||||
</label>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="resetPassword-passwordDescription"
|
||||
$minLength={8}
|
||||
>
|
||||
<InputDescription>
|
||||
<div className={styles.labelDescription}>
|
||||
{"Must be at least {$minLength} characters"}
|
||||
</InputDescription>
|
||||
</div>
|
||||
</Localized>
|
||||
<Localized
|
||||
id="resetPassword-passwordTextField"
|
||||
@@ -107,28 +101,27 @@ const ResetPasswordForm: React.FunctionComponent<Props> = ({
|
||||
{...input}
|
||||
/>
|
||||
</Localized>
|
||||
<ValidationMessage meta={meta} fullWidth />
|
||||
<ValidationMessage meta={meta} />
|
||||
</FormField>
|
||||
)}
|
||||
</Field>
|
||||
{submitError && (
|
||||
<CallOut color="error" fullWidth>
|
||||
{submitError}
|
||||
</CallOut>
|
||||
)}
|
||||
{submitError && <CallOut color="error" title={submitError} />}
|
||||
<Localized id="resetPassword-resetPassword">
|
||||
<Button
|
||||
type="submit"
|
||||
variant="filled"
|
||||
color="primary"
|
||||
paddingSize="medium"
|
||||
disabled={submitting}
|
||||
upperCase
|
||||
fullWidth
|
||||
className={styles.submit}
|
||||
>
|
||||
Reset Password
|
||||
</Button>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@@ -35,36 +35,36 @@ const ResetRoute: React.FunctionComponent<Props> = ({ token }) => {
|
||||
|
||||
if (state === "UNCHECKED") {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Loading />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
if (state !== "VALID" || error) {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Sorry reason={error} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
return !finished ? (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<ResetPasswordForm token={token!} onSuccess={onSuccess} />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
) : (
|
||||
<div className={styles.container}>
|
||||
<main className={styles.container}>
|
||||
<div className={styles.root}>
|
||||
<Success />
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { CallOut, HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import { CallOut } from "coral-ui/components/v3";
|
||||
|
||||
import styles from "./Reset.css";
|
||||
|
||||
interface Props {
|
||||
reason: React.ReactNode;
|
||||
@@ -9,23 +11,27 @@ interface Props {
|
||||
|
||||
const Sorry: React.FunctionComponent<Props> = ({ reason }) => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<div>
|
||||
<Localized id="resetPassword-oopsSorry">
|
||||
<Typography variant="heading1">Oops Sorry!</Typography>
|
||||
<div className={styles.title}>Oops Sorry!</div>
|
||||
</Localized>
|
||||
<CallOut color="error" fullWidth>
|
||||
{reason ? (
|
||||
reason
|
||||
) : (
|
||||
<Localized id="account-tokenNotFound">
|
||||
<span data-testid="invalid-link">
|
||||
The specified link is invalid, check to see if it was copied
|
||||
correctly.
|
||||
</span>
|
||||
</Localized>
|
||||
)}
|
||||
</CallOut>
|
||||
</HorizontalGutter>
|
||||
<CallOut
|
||||
color="error"
|
||||
titleWeight="semiBold"
|
||||
title={
|
||||
reason ? (
|
||||
reason
|
||||
) : (
|
||||
<Localized id="account-tokenNotFound">
|
||||
<span data-testid="invalid-link">
|
||||
The specified link is invalid, check to see if it was copied
|
||||
correctly.
|
||||
</span>
|
||||
</Localized>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React from "react";
|
||||
|
||||
import { HorizontalGutter, Typography } from "coral-ui/components";
|
||||
import styles from "./Reset.css";
|
||||
|
||||
const Success: React.FunctionComponent = () => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<div>
|
||||
<Localized id="resetPassword-successfullyReset">
|
||||
<Typography variant="heading1">Password successfully reset</Typography>
|
||||
<div className={styles.title}>Password successfully reset</div>
|
||||
</Localized>
|
||||
<Localized id="resetPassword-youMayClose">
|
||||
<Typography variant="bodyCopy">
|
||||
<div className={styles.description}>
|
||||
You may now close this window and sign in to your account with your
|
||||
new password.
|
||||
</Typography>
|
||||
</div>
|
||||
</Localized>
|
||||
</HorizontalGutter>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`renders form 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ConfirmRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -20,29 +20,22 @@ exports[`renders form 1`] = `
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary"
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
className="Confirm-title"
|
||||
>
|
||||
Email Confirmation
|
||||
</h1>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
Confirm your email address
|
||||
</div>
|
||||
<div
|
||||
className="Confirm-description"
|
||||
>
|
||||
Click below to confirm your email address.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
className="BaseButton-root Button-base Button-filled Button-fontSizeSmall Button-textAlignCenter Button-fontFamilyPrimary Button-fontWeightPrimaryBold Button-paddingSizeSmall Button-colorPrimary Button-upperCase Button-fullWidth Confirm-submit"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
@@ -58,7 +51,7 @@ exports[`renders form 1`] = `
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -73,34 +66,45 @@ exports[`renders missing confirm token 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ConfirmRoute-container"
|
||||
>
|
||||
<div
|
||||
className="ConfirmRoute-root"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary"
|
||||
<div>
|
||||
<div
|
||||
className="Confirm-title"
|
||||
>
|
||||
Oops Sorry!
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
className="CallOut-root CallOut-error CallOut-leftBorder"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-justifyFlexStart Flex-alignFlexStart"
|
||||
>
|
||||
<div
|
||||
className="CallOut-content"
|
||||
>
|
||||
The specified link is invalid, check to see if it was copied correctly.
|
||||
</span>
|
||||
<div
|
||||
className="CallOut-title CallOut-titleSemiBold"
|
||||
>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
>
|
||||
The specified link is invalid, check to see if it was copied correctly.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-body"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`renders form 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ResetRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -21,41 +21,35 @@ exports[`renders form 1`] = `
|
||||
autoComplete="off"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary"
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
className="Reset-title"
|
||||
>
|
||||
Reset your password
|
||||
</h1>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
</div>
|
||||
<div
|
||||
className="Reset-description"
|
||||
>
|
||||
Please enter a new password to use to sign in to your account.
|
||||
Make sure it is unique and be sure to keep it secure.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-half"
|
||||
className="Box-root HorizontalGutter-root FormField-root HorizontalGutter-spacing-2"
|
||||
>
|
||||
<label
|
||||
className="Box-root Typography-root Typography-inputLabel Typography-colorTextPrimary InputLabel-root"
|
||||
className="Reset-label"
|
||||
htmlFor="password"
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<p
|
||||
className="Box-root Typography-root Typography-fieldDescription Typography-colorTextSecondary"
|
||||
<div
|
||||
className="Reset-labelDescription"
|
||||
>
|
||||
Must be at least 8 characters
|
||||
</p>
|
||||
</div>
|
||||
<div
|
||||
className="PasswordField-fullWidth PasswordField-root"
|
||||
>
|
||||
@@ -67,6 +61,7 @@ Make sure it is unique and be sure to keep it secure.
|
||||
autoComplete="new-password"
|
||||
autoCorrect="off"
|
||||
className="PasswordField-colorRegular PasswordField-fullWidth PasswordField-input"
|
||||
data-testid="password-field"
|
||||
disabled={false}
|
||||
id="password"
|
||||
name="password"
|
||||
@@ -97,8 +92,7 @@ Make sure it is unique and be sure to keep it secure.
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
className="BaseButton-root Button-base Button-filled Button-fontSizeSmall Button-textAlignCenter Button-fontFamilyPrimary Button-fontWeightPrimaryBold Button-paddingSizeMedium Button-colorPrimary Button-upperCase Button-fullWidth Reset-submit"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
@@ -115,7 +109,7 @@ Make sure it is unique and be sure to keep it secure.
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -130,34 +124,45 @@ exports[`renders missing reset token 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="ResetRoute-container"
|
||||
>
|
||||
<div
|
||||
className="ResetRoute-root"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary"
|
||||
<div>
|
||||
<div
|
||||
className="Reset-title"
|
||||
>
|
||||
Oops Sorry!
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
className="CallOut-root CallOut-error CallOut-leftBorder"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-justifyFlexStart Flex-alignFlexStart"
|
||||
>
|
||||
<div
|
||||
className="CallOut-content"
|
||||
>
|
||||
The specified link is invalid, check to see if it was copied correctly.
|
||||
</span>
|
||||
<div
|
||||
className="CallOut-title CallOut-titleSemiBold"
|
||||
>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
>
|
||||
The specified link is invalid, check to see if it was copied correctly.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-body"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -10,7 +10,7 @@ exports[`renders form 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="UnsubscribeRoute-container"
|
||||
>
|
||||
<div
|
||||
@@ -22,17 +22,19 @@ exports[`renders form 1`] = `
|
||||
<form
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary"
|
||||
<div>
|
||||
<div
|
||||
className="Unsubscribe-title"
|
||||
>
|
||||
Unsubscribe from email notifications
|
||||
</div>
|
||||
<div
|
||||
className="Unsubscribe-description"
|
||||
>
|
||||
Click below to confirm that you want to unsubscribe from all notifications.
|
||||
</h1>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth"
|
||||
data-color="primary"
|
||||
className="BaseButton-root Button-base Button-filled Button-fontSizeSmall Button-textAlignCenter Button-fontFamilyPrimary Button-fontWeightPrimaryBold Button-paddingSizeMedium Button-colorPrimary Button-upperCase Button-fullWidth Unsubscribe-submit"
|
||||
data-variant="filled"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
@@ -42,13 +44,13 @@ exports[`renders form 1`] = `
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Confirm
|
||||
Unsubscribe
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -63,34 +65,45 @@ exports[`renders missing confirm token 1`] = `
|
||||
<div
|
||||
className="MainLayout-centered"
|
||||
>
|
||||
<div
|
||||
<main
|
||||
className="UnsubscribeRoute-container"
|
||||
>
|
||||
<div
|
||||
className="UnsubscribeRoute-root"
|
||||
>
|
||||
<div
|
||||
className="Box-root HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<h1
|
||||
className="Box-root Typography-root Typography-heading1 Typography-colorTextPrimary"
|
||||
<div>
|
||||
<div
|
||||
className="Unsubscribe-title"
|
||||
>
|
||||
Oops Sorry!
|
||||
</h1>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorError CallOut-fullWidth"
|
||||
className="CallOut-root CallOut-error CallOut-leftBorder"
|
||||
>
|
||||
<div>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
<div
|
||||
className="Box-root Flex-root Flex-flex Flex-justifyFlexStart Flex-alignFlexStart"
|
||||
>
|
||||
<div
|
||||
className="CallOut-content"
|
||||
>
|
||||
The specified link is invalid, check to see if it was copied correctly.
|
||||
</span>
|
||||
<div
|
||||
className="CallOut-title CallOut-titleSemiBold"
|
||||
>
|
||||
<span
|
||||
data-testid="invalid-link"
|
||||
>
|
||||
The specified link is invalid, check to see if it was copied correctly.
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-body"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -31,7 +31,9 @@ it("renders missing confirm token", async () => {
|
||||
replaceHistoryLocation("http://localhost/account/email/confirm");
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).getByTestID("invalid-link"));
|
||||
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("renders form", async () => {
|
||||
@@ -51,12 +53,11 @@ it("renders form", async () => {
|
||||
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(root).getByText("Email Confirmation", {
|
||||
exact: false,
|
||||
})
|
||||
within(root).getByText("Confirm your email address")
|
||||
);
|
||||
});
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
@@ -98,6 +99,7 @@ it("renders error from server", async () => {
|
||||
);
|
||||
});
|
||||
restMock.verify();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -126,9 +128,7 @@ it("submits form", async () => {
|
||||
|
||||
await act(async () => {
|
||||
await waitForElement(() =>
|
||||
within(root).getByText("Email Confirmation", {
|
||||
exact: false,
|
||||
})
|
||||
within(root).getByText("Confirm your email address")
|
||||
);
|
||||
});
|
||||
const form = within(root).getByType("form");
|
||||
|
||||
@@ -32,6 +32,7 @@ it("renders missing reset token", async () => {
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).getByTestID("invalid-link"));
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("renders form", async () => {
|
||||
@@ -57,6 +58,7 @@ it("renders form", async () => {
|
||||
);
|
||||
});
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
@@ -98,6 +100,7 @@ it("renders error from server", async () => {
|
||||
);
|
||||
});
|
||||
restMock.verify();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ it("renders missing confirm token", async () => {
|
||||
const { root } = await createTestRenderer();
|
||||
await waitForElement(() => within(root).getByTestID("invalid-link"));
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
});
|
||||
|
||||
it("renders form", async () => {
|
||||
@@ -53,6 +54,7 @@ it("renders form", async () => {
|
||||
await waitForElement(() => within(root).getByTestID("unsubscribe-form"));
|
||||
});
|
||||
expect(within(root).toJSON()).toMatchSnapshot();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
restMock.verify();
|
||||
});
|
||||
|
||||
@@ -93,6 +95,7 @@ it("renders error from server", async () => {
|
||||
);
|
||||
});
|
||||
restMock.verify();
|
||||
expect(await within(root).axe()).toHaveNoViolations();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Typography } from "coral-ui/components";
|
||||
|
||||
import ApprovedIcon from "./ApprovedIcon";
|
||||
import DecisionItem from "./DecisionItem";
|
||||
import DotDivider from "./DotDivider";
|
||||
@@ -10,6 +8,7 @@ import Footer from "./Footer";
|
||||
import GoToCommentLink from "./GoToCommentLink";
|
||||
import Info from "./Info";
|
||||
import Timestamp from "./Timestamp";
|
||||
import Username from "./Username";
|
||||
|
||||
interface Props {
|
||||
href: string;
|
||||
@@ -18,11 +17,7 @@ interface Props {
|
||||
onGotoComment?: React.EventHandler<React.MouseEvent>;
|
||||
}
|
||||
|
||||
const Username: FunctionComponent<{ username: string }> = ({ username }) => (
|
||||
<strong>{username}</strong>
|
||||
);
|
||||
|
||||
const ApprovedComment: FunctionComponent<Props> = props => (
|
||||
const ApprovedComment: FunctionComponent<Props> = (props) => (
|
||||
<DecisionItem icon={<ApprovedIcon />}>
|
||||
<Localized
|
||||
id="decisionHistory-approvedCommentBy"
|
||||
@@ -31,9 +26,7 @@ const ApprovedComment: FunctionComponent<Props> = props => (
|
||||
<Info>{"Approved comment by <Username></Username>"}</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<Typography variant="timestamp">
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
</Typography>
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
<DotDivider />
|
||||
<GoToCommentLink href={props.href} onClick={props.onGotoComment} />
|
||||
</Footer>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
.root {
|
||||
color: var(--palette-success-main);
|
||||
color: var(--palette-success-500);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Icon } from "coral-ui/components";
|
||||
import { Icon } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./ApprovedIcon.css";
|
||||
|
||||
const ApprovedIcon: FunctionComponent = () => (
|
||||
<Icon className={styles.root}>check</Icon>
|
||||
<Icon size="md" className={styles.root}>
|
||||
check_circled
|
||||
</Icon>
|
||||
);
|
||||
|
||||
export default ApprovedIcon;
|
||||
|
||||
@@ -19,13 +19,13 @@ interface Props {
|
||||
onClosePopover: () => void;
|
||||
}
|
||||
|
||||
const DecisionHistory: FunctionComponent<Props> = props => (
|
||||
const DecisionHistory: FunctionComponent<Props> = (props) => (
|
||||
<div data-testid="decisionHistory-container">
|
||||
<Title />
|
||||
<Main>
|
||||
<DecisionList>
|
||||
{props.actions.length === 0 && <Empty />}
|
||||
{props.actions.map(action => (
|
||||
{props.actions.map((action) => (
|
||||
<DecisionHistoryItemContainer
|
||||
key={action.id}
|
||||
action={action}
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
}
|
||||
|
||||
.historyIcon {
|
||||
color: var(--palette-text-secondary);
|
||||
margin-right: calc(1.5 * var(--mini-unit));
|
||||
color: var(--palette-text-100);
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import { Localized } from "@fluent/react/compat";
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { BaseButton, ClickOutside, Icon, Popover } from "coral-ui/components";
|
||||
import {
|
||||
BaseButton,
|
||||
ClickOutside,
|
||||
Icon,
|
||||
Popover,
|
||||
} from "coral-ui/components/v2";
|
||||
|
||||
import DecisionHistoryQuery from "./DecisionHistoryQuery";
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ export class DecisionHistoryContainer extends React.Component<
|
||||
|
||||
public render() {
|
||||
const actions = this.props.viewer.commentModerationActionHistory.edges.map(
|
||||
edge => edge.node
|
||||
(edge) => edge.node
|
||||
);
|
||||
return (
|
||||
<DecisionHistory
|
||||
@@ -43,7 +43,7 @@ export class DecisionHistoryContainer extends React.Component<
|
||||
this.setState({ disableLoadMore: true });
|
||||
this.props.relay.loadMore(
|
||||
10, // Fetch the next 10 feed items
|
||||
error => {
|
||||
(error) => {
|
||||
this.setState({ disableLoadMore: false });
|
||||
if (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Delay, Flex, Spinner } from "coral-ui/components";
|
||||
import { Delay, Flex, Spinner } from "coral-ui/components/v2";
|
||||
|
||||
import Main from "./Main";
|
||||
import Title from "./Title";
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { Component } from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { graphql, QueryRenderer } from "coral-framework/lib/relay";
|
||||
import { QueryRenderer } from "coral-framework/lib/relay";
|
||||
|
||||
import { DecisionHistoryQuery as QueryTypes } from "coral-admin/__generated__/DecisionHistoryQuery.graphql";
|
||||
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
.root {
|
||||
padding: calc(0.5 * var(--mini-unit)) calc(0.5 * var(--mini-unit))
|
||||
calc(0.5 * var(--mini-unit)) 0;
|
||||
padding: var(--spacing-2) var(--spacing-1);
|
||||
}
|
||||
|
||||
.leftCol {
|
||||
@@ -8,6 +7,6 @@
|
||||
justify-content: center;
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
width: calc(3 * var(--mini-unit));
|
||||
padding-top: 3px;
|
||||
margin-right: var(--spacing-2);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React, { FunctionComponent } from "react";
|
||||
|
||||
import { Flex } from "coral-ui/components";
|
||||
import { Flex } from "coral-ui/components/v2";
|
||||
|
||||
import styles from "./DecisionItem.css";
|
||||
|
||||
@@ -9,7 +9,7 @@ interface Props {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
const DecisionItem: FunctionComponent<Props> = props => (
|
||||
const DecisionItem: FunctionComponent<Props> = (props) => (
|
||||
<li className={styles.root}>
|
||||
<Flex>
|
||||
<div className={styles.leftCol}>{props.icon}</div>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user