mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 15:47:53 -05:00
Merge branch 'develop' into comments-reducer
This commit is contained in:
commit
9252d56376
24 changed files with 1250 additions and 416 deletions
|
@ -3,7 +3,7 @@ version: 2.1
|
|||
aliases:
|
||||
- &defaults
|
||||
docker:
|
||||
- image: circleci/node:12-browsers
|
||||
- image: cimg/node:12.20.1-browsers
|
||||
auth:
|
||||
username: $DOCKERHUB_USERNAME
|
||||
password: $DOCKERHUB_PASSWORD
|
||||
|
@ -27,7 +27,6 @@ aliases:
|
|||
restore_cache:
|
||||
keys:
|
||||
- v1-npm-{{ checksum "package-lock.json" }}
|
||||
- v1-npm-
|
||||
- &save_build_cache
|
||||
save_cache:
|
||||
paths:
|
||||
|
@ -45,15 +44,33 @@ aliases:
|
|||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run:
|
||||
name: "Run npm test to build"
|
||||
name: "setup"
|
||||
command: |
|
||||
npm --production=false install
|
||||
WWW_VERSION=${CIRCLE_SHA1:0:5} npm run test
|
||||
npm --production=false ci
|
||||
mkdir ./test/results
|
||||
- run:
|
||||
name: "run lint tests"
|
||||
command: |
|
||||
npm run test:lint:ci
|
||||
- run:
|
||||
name: "run npm build"
|
||||
command: |
|
||||
WWW_VERSION=${CIRCLE_SHA1:0:5} npm run build
|
||||
- run:
|
||||
name: "Run unit tests"
|
||||
command: |
|
||||
JEST_JUNIT_OUTPUT_NAME=unit-jest-results.xml npm run test:unit:jest:unit -- --reporters=jest-junit
|
||||
JEST_JUNIT_OUTPUT_NAME=localization-jest-results.xml npm run test:unit:jest:localization -- --reporters=jest-junit
|
||||
npm run test:unit:tap -- --output-file ./test/results/unit-raw.tap
|
||||
npm run test:unit:convertReportToXunit
|
||||
- *save_npm_cache
|
||||
- *save_git_cache
|
||||
- *save_build_cache
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- store_artifacts:
|
||||
path: build
|
||||
- &deploy
|
||||
<<: *defaults
|
||||
steps:
|
||||
|
@ -61,41 +78,63 @@ aliases:
|
|||
- checkout
|
||||
- *restore_npm_cache
|
||||
- *restore_build_cache
|
||||
- run:
|
||||
name: "setup python"
|
||||
command: |
|
||||
curl https://bootstrap.pypa.io/3.5/get-pip.py -o get-pip.py
|
||||
python3 get-pip.py pip==21.0.1
|
||||
pip install s3cmd==2.1.0
|
||||
- run:
|
||||
name: "deploy to staging"
|
||||
command: |
|
||||
curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
|
||||
python get-pip.py
|
||||
pip install -r requirements.txt
|
||||
npm run deploy
|
||||
- &integration
|
||||
- &integration_jest
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run:
|
||||
name: "integration tests"
|
||||
name: "integration tests with Jest"
|
||||
command: |
|
||||
npm run test:integration:remote
|
||||
|
||||
JEST_JUNIT_OUTPUT_NAME=integration-jest-results.xml npm run test:integration:jest:remote -- --reporters=jest-junit
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
- &integration_tap
|
||||
<<: *defaults
|
||||
steps:
|
||||
- *restore_git_cache
|
||||
- checkout
|
||||
- *restore_npm_cache
|
||||
- run:
|
||||
name: "integration tests with Tap"
|
||||
command: |
|
||||
mkdir ./test/results
|
||||
npm run test:smoke:sauce -- --output-file ./test/results/integration-raw-tap.tap
|
||||
npm run test:smoke:convertReportToXunit
|
||||
- store_test_results:
|
||||
path: test/results
|
||||
|
||||
jobs:
|
||||
build-staging:
|
||||
<<: *build
|
||||
build-production:
|
||||
# <<: *build
|
||||
<<: *build
|
||||
deploy-staging:
|
||||
<<: *deploy
|
||||
deploy-production:
|
||||
# <<: *deploy
|
||||
integration-staging:
|
||||
<<: *integration
|
||||
integration-production:
|
||||
# <<: *integration
|
||||
integration-staging-jest:
|
||||
<<: *integration_jest
|
||||
integration-staging-tap:
|
||||
<<: *integration_tap
|
||||
integration-production-jest:
|
||||
# <<: *integration_jest
|
||||
integration-production-tap:
|
||||
# <<: *integration_tap
|
||||
|
||||
workflows:
|
||||
build-test-deploy:
|
||||
build-staging-production: # build-test-deploy
|
||||
jobs:
|
||||
- build-staging:
|
||||
context:
|
||||
|
@ -107,6 +146,14 @@ workflows:
|
|||
- develop
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- build-production:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-production
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
# - deploy-staging:
|
||||
# context:
|
||||
# - scratch-www-all
|
||||
|
@ -119,7 +166,8 @@ workflows:
|
|||
# - develop
|
||||
# - /^hotfix\/.*/
|
||||
# - /^release\/.*/
|
||||
# - integration-staging:
|
||||
# - circleCI-configure-tests
|
||||
# - integration-staging-jest:
|
||||
# context:
|
||||
# - scratch-www-all
|
||||
# - scratch-www-staging
|
||||
|
@ -131,3 +179,17 @@ workflows:
|
|||
# - develop
|
||||
# - /^hotfix\/.*/
|
||||
# - /^release\/.*/
|
||||
# - circleCI-configure-tests
|
||||
# - integration-staging-tap:
|
||||
# context:
|
||||
# - scratch-www-all
|
||||
# - scratch-www-staging
|
||||
# requires:
|
||||
# - deploy-staging
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - develop
|
||||
# - /^hotfix\/.*/
|
||||
# - /^release\/.*/
|
||||
# - circleCI-configure-tests
|
||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -20,6 +20,7 @@ deploy.zip
|
|||
ENV
|
||||
|
||||
# Test
|
||||
/test/results/*
|
||||
/.nyc_output
|
||||
/coverage
|
||||
/bin/lib/localized-urls.json
|
||||
|
|
524
package-lock.json
generated
524
package-lock.json
generated
|
@ -226,9 +226,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"version": "7.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.11.tgz",
|
||||
"integrity": "sha512-BwKEkO+2a67DcFeS3RLl0Z3Gs2OvdXewuWjc1Hfokhb5eQWP9YRYH1/+VrVZvql2CfjOiNGqSAFOYt4lsqTHzg==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.12.tgz",
|
||||
"integrity": "sha512-3eJJ841uKxeV8dcN/2yGEUy+RfgQspPEgQat85umsE1rotuquQ2AbIub4S6j7c50a2d+4myc+zSlnXeIHrOnhQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
|
@ -316,9 +316,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
|
||||
"integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -350,9 +350,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -519,9 +519,9 @@
|
|||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.691",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.691.tgz",
|
||||
"integrity": "sha512-ZqiO69KImmOGCyoH0icQPU3SndJiW93juEvf63gQngyhODO6SpQIPMTOHldtCs5DS5GMKvAkquk230E2zt2vpw==",
|
||||
"version": "1.3.697",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.697.tgz",
|
||||
"integrity": "sha512-VTAS+IWwGlfaL7VtfUMzFeV55PT/HglNFqQ6eW9E3PfjvPqhZfqJj+8dd9zrqrJYcouUfCgQw0OIse85Dz9V9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
|
@ -553,18 +553,18 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-member-expression-to-functions": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.0.tgz",
|
||||
"integrity": "sha512-yvRf8Ivk62JwisqV1rFRMxiSMDGnN6KH1/mDMmIrij4jztpQNRoHqqMG3U6apYbGRPJpgPalhva9Yd06HlUxJQ==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz",
|
||||
"integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.13.0"
|
||||
"@babel/types": "^7.13.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -587,18 +587,18 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-module-imports": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.13.tgz",
|
||||
"integrity": "sha512-NGmfvRp9Rqxy0uHSSVP+SRIW1q31a7Ji10cLBcqSDUngGentY4FRiHOFZFE1CLU5eiL0oE8reH7Tg1y99TDM/g==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz",
|
||||
"integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.12.13"
|
||||
"@babel/types": "^7.13.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -621,20 +621,19 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.0.tgz",
|
||||
"integrity": "sha512-Ls8/VBwH577+pw7Ku1QkUWIyRRNHpYlts7+qSqBBFCW3I8QteB9DxfcZ5YJpOwH6Ihe/wn8ch7fMGOP1OhEIvw==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.12.tgz",
|
||||
"integrity": "sha512-7zVQqMO3V+K4JOOj40kxiCrMf6xlQAkewBB0eu2b03OO/Q21ZutOzjpfD79A5gtE/2OWi1nv625MrDlGlkbknQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.12.13",
|
||||
"@babel/helper-replace-supers": "^7.13.0",
|
||||
"@babel/helper-simple-access": "^7.12.13",
|
||||
"@babel/helper-module-imports": "^7.13.12",
|
||||
"@babel/helper-replace-supers": "^7.13.12",
|
||||
"@babel/helper-simple-access": "^7.13.12",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.13.0",
|
||||
"@babel/types": "^7.13.0",
|
||||
"lodash": "^4.17.19"
|
||||
"@babel/types": "^7.13.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
|
@ -698,9 +697,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
|
||||
"integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -732,9 +731,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -828,9 +827,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -859,15 +858,15 @@
|
|||
"dev": true
|
||||
},
|
||||
"@babel/helper-replace-supers": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.0.tgz",
|
||||
"integrity": "sha512-Segd5me1+Pz+rmN/NFBOplMbZG3SqRJOBlY+mA0SxAv6rjj7zJqr1AVr3SfzUVTLCv7ZLU5FycOM/SBGuLPbZw==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.13.12.tgz",
|
||||
"integrity": "sha512-Gz1eiX+4yDO8mT+heB94aLVNCL+rbuT2xy4YfyNqu8F+OI6vMvJK891qGBTqL9Uc8wxEvRW92Id6G7sDen3fFw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-member-expression-to-functions": "^7.13.0",
|
||||
"@babel/helper-member-expression-to-functions": "^7.13.12",
|
||||
"@babel/helper-optimise-call-expression": "^7.12.13",
|
||||
"@babel/traverse": "^7.13.0",
|
||||
"@babel/types": "^7.13.0"
|
||||
"@babel/types": "^7.13.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
|
@ -931,9 +930,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
|
||||
"integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -965,9 +964,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -1052,18 +1051,18 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-simple-access": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.13.tgz",
|
||||
"integrity": "sha512-0ski5dyYIHEfwpWGx5GPWhH35j342JaflmCeQmsPWcrOQDtCN6C1zKAVRFVbK53lPW2c9TsuLLSUDf0tIGJ5hA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz",
|
||||
"integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/types": "^7.12.13"
|
||||
"@babel/types": "^7.13.12"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -1178,9 +1177,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.11",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.11.tgz",
|
||||
"integrity": "sha512-PhuoqeHoO9fc4ffMEVk4qb/w/s2iOSWohvbHxLtxui0eBg3Lg5gN1U8wp1V1u61hOWkPQJJyJzGH6Y+grwkq8Q==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -1212,9 +1211,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.0.tgz",
|
||||
"integrity": "sha512-hE+HE8rnG1Z6Wzo+MhaKE5lM5eMx71T4EHJgku2E3xIfaULhDcxiiRxUYgwX8qwP1BBSlag+TdGOt6JAidIZTA==",
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.12.tgz",
|
||||
"integrity": "sha512-K4nY2xFN4QMvQwkQ+zmBDp6ANMbVNw6BbxWmYA4qNjhR9W+Lj/8ky5MEY2Me5r+B2c6/v6F53oMndG+f9s3IiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -2117,9 +2116,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"@types/babel__core": {
|
||||
"version": "7.1.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.13.tgz",
|
||||
"integrity": "sha512-CC6amBNND16pTk4K3ZqKIaba6VGKAQs3gMjEY17FVd56oI/ZWt9OhS6riYiWv9s8ENbYUi7p8lgqb0QHQvUKQQ==",
|
||||
"version": "7.1.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.14.tgz",
|
||||
"integrity": "sha512-zGZJzzBUVDo/eV6KgbE0f0ZI7dInEYvo12Rb70uNQDshC3SkRMb67ja0GgRHZgAX3Za6rhaWlvbDO8rrGyAb1g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/parser": "^7.1.0",
|
||||
|
@ -2933,24 +2932,6 @@
|
|||
"dev": true,
|
||||
"requires": {
|
||||
"source-map-support": "^0.5.11"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"async-limiter": {
|
||||
|
@ -4143,9 +4124,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"bind-obj-methods": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bind-obj-methods/-/bind-obj-methods-2.0.0.tgz",
|
||||
"integrity": "sha512-3/qRXczDi2Cdbz6jE+W3IflJOutRVica8frpBn14de1mBOkzDo+6tY33kNhvkw54Kn3PzRRD2VnGbGPcTAk4sw==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/bind-obj-methods/-/bind-obj-methods-2.0.1.tgz",
|
||||
"integrity": "sha512-kKzUyCuc+jsWH4C2nW5KB2nh+rQRbQcdphfo9UN3j1uwIFGZ3JB8njtRZOiUAQCkxazH0nDQPN6x/zhvFcbZIw==",
|
||||
"dev": true
|
||||
},
|
||||
"bit-twiddle": {
|
||||
|
@ -5731,9 +5712,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"js-yaml": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
|
@ -5745,56 +5726,6 @@
|
|||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.5.2",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
|
||||
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==",
|
||||
"dev": true
|
||||
},
|
||||
"request": {
|
||||
"version": "2.88.2",
|
||||
"resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz",
|
||||
"integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"aws-sign2": "~0.7.0",
|
||||
"aws4": "^1.8.0",
|
||||
"caseless": "~0.12.0",
|
||||
"combined-stream": "~1.0.6",
|
||||
"extend": "~3.0.2",
|
||||
"forever-agent": "~0.6.1",
|
||||
"form-data": "~2.3.2",
|
||||
"har-validator": "~5.1.3",
|
||||
"http-signature": "~1.2.0",
|
||||
"is-typedarray": "~1.0.0",
|
||||
"isstream": "~0.1.2",
|
||||
"json-stringify-safe": "~5.0.1",
|
||||
"mime-types": "~2.1.19",
|
||||
"oauth-sign": "~0.9.0",
|
||||
"performance-now": "^2.1.0",
|
||||
"qs": "~6.5.2",
|
||||
"safe-buffer": "^5.1.2",
|
||||
"tough-cookie": "~2.5.0",
|
||||
"tunnel-agent": "^0.6.0",
|
||||
"uuid": "^3.3.2"
|
||||
}
|
||||
},
|
||||
"safe-buffer": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"tough-cookie": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz",
|
||||
"integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"psl": "^1.1.28",
|
||||
"punycode": "^2.1.1"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -6591,11 +6522,6 @@
|
|||
"strip-bom": "^3.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"strip-bom": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
|
||||
|
@ -7669,9 +7595,9 @@
|
|||
}
|
||||
},
|
||||
"eslint-config-scratch": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-scratch/-/eslint-config-scratch-6.0.0.tgz",
|
||||
"integrity": "sha512-2aW7EVWhjTrwaM54hjAZeRETh/habWeQ5xzTbPO2dG//RtixBztUAmdxqAHKH8MEtmAMsUhf3mRjO1BstO4dGg==",
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-scratch/-/eslint-config-scratch-7.0.0.tgz",
|
||||
"integrity": "sha512-8Ko7aaC+Gv09lyz3x7+EaVZ68ga/4mnfGgwH1Jz/MnR4UJyJyYtUOtlWxsPspPOlPZzDAVL5CKEcxmCX24SrJA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eslint-plugin-react": ">=7.14.2"
|
||||
|
@ -8932,18 +8858,18 @@
|
|||
"dev": true
|
||||
},
|
||||
"flow-parser": {
|
||||
"version": "0.129.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.129.0.tgz",
|
||||
"integrity": "sha512-kzxyoEl8vG0JF0/h/u0UjALXmsGvwU2NBfKczCSNO/It2fKb8hz1gMt05OuZAlMLYXcvgjntWJadIABeKGPK4g==",
|
||||
"version": "0.147.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-parser/-/flow-parser-0.147.0.tgz",
|
||||
"integrity": "sha512-z+b/pgp2QLvsWJkzhKXU8yC5TmaNyXGRmHac3x0Swmn9uQESRXhNIJq9TPHKPPeWgFym33OLO+5BlIdy/tXRCQ==",
|
||||
"dev": true
|
||||
},
|
||||
"flow-remove-types": {
|
||||
"version": "2.129.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.129.0.tgz",
|
||||
"integrity": "sha512-ucESHZUDQvEFzjRKstZMFBVIciRvXtKpVyPsJT+poIyOIxuPoCLiU/8HHnMBN9XHDWSJ2YJ91mv97n17NmI1Bg==",
|
||||
"version": "2.147.0",
|
||||
"resolved": "https://registry.npmjs.org/flow-remove-types/-/flow-remove-types-2.147.0.tgz",
|
||||
"integrity": "sha512-ppZdtui3daAhBe0dkghBdtdIdSRbc/p/IEXw3t6vtlbS2KNpY8G3cu5RupXT3pGvFQbbXHP7mwNCEKSEYGU6Aw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"flow-parser": "^0.129.0",
|
||||
"flow-parser": "^0.147.0",
|
||||
"pirates": "^3.0.2",
|
||||
"vlq": "^0.2.1"
|
||||
}
|
||||
|
@ -12404,12 +12330,6 @@
|
|||
"semver": "^5.6.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "2.7.1",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
|
||||
|
@ -13063,6 +12983,47 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"jest-junit": {
|
||||
"version": "12.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-junit/-/jest-junit-12.0.0.tgz",
|
||||
"integrity": "sha512-+8K35LlboWiPuCnXSyiid7rFdxNlpCWWM20WEYe6IZH6psfUWKZmSpSRQ5tk0C0cBeDsvsnIzcef5mYhyJsbug==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"mkdirp": "^1.0.4",
|
||||
"strip-ansi": "^5.2.0",
|
||||
"uuid": "^3.3.3",
|
||||
"xml": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"ansi-regex": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
|
||||
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
},
|
||||
"strip-ansi": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
|
||||
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
|
||||
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"jest-leak-detector": {
|
||||
"version": "23.6.0",
|
||||
"resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-23.6.0.tgz",
|
||||
|
@ -16126,12 +16087,6 @@
|
|||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
|
||||
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
|
||||
"dev": true
|
||||
},
|
||||
"cliui": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
|
||||
|
@ -16184,9 +16139,9 @@
|
|||
}
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz",
|
||||
"integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==",
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
|
@ -16281,12 +16236,6 @@
|
|||
"ansi-regex": "^4.1.0"
|
||||
}
|
||||
},
|
||||
"which-module": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
|
||||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
|
||||
"dev": true
|
||||
},
|
||||
"wrap-ansi": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
|
||||
|
@ -16299,9 +16248,9 @@
|
|||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
|
||||
"integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
|
@ -16321,16 +16270,6 @@
|
|||
"y18n": "^4.0.0",
|
||||
"yargs-parser": "^13.1.2"
|
||||
}
|
||||
},
|
||||
"yargs-parser": {
|
||||
"version": "13.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
|
||||
"integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "^5.0.0",
|
||||
"decamelize": "^1.2.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -16517,9 +16456,9 @@
|
|||
}
|
||||
},
|
||||
"opener": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.1.tgz",
|
||||
"integrity": "sha512-goYSy5c2UXE4Ra1xixabeVh1guIX/ZV/YokJksb6q2lubWu6UbvPQ20p542/sFIll1nl8JnCyK9oBaOcCWXwvA==",
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true
|
||||
},
|
||||
"optical-properties": {
|
||||
|
@ -20970,9 +20909,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-blocks": {
|
||||
"version": "0.1.0-prerelease.20210318033822",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210318033822.tgz",
|
||||
"integrity": "sha512-nKOMgQwrvH/VKH7mHliwpQyddD/wM68UaA/Vh37/XkknKkY8g6qX2Jj5XgxziMIZQTGgNsu+LEkbyNOhqp0QOg==",
|
||||
"version": "0.1.0-prerelease.20210324033606",
|
||||
"resolved": "https://registry.npmjs.org/scratch-blocks/-/scratch-blocks-0.1.0-prerelease.20210324033606.tgz",
|
||||
"integrity": "sha512-zCf7mN64RLME1tA9t2HcDEnf5h5+ziMyksbQj3gsWOUylYLrrYksMBw7wprVEMdPBJwz+4HhpcpkkrCQV1NVnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"exports-loader": "0.6.3",
|
||||
|
@ -20980,9 +20919,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-gui": {
|
||||
"version": "0.1.0-prerelease.20210318040059",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210318040059.tgz",
|
||||
"integrity": "sha512-dGrtJutjsAMqq7sotMYwhAAmJQbyRmi3b7vKlLQYTqV15cY5oa2GrVAso7PBrWxRLcd1XDbVUrcEpvgKaWZNJg==",
|
||||
"version": "0.1.0-prerelease.20210324120840",
|
||||
"resolved": "https://registry.npmjs.org/scratch-gui/-/scratch-gui-0.1.0-prerelease.20210324120840.tgz",
|
||||
"integrity": "sha512-6KLZfZdJLAMLBDJo/LC6f0ckUg0CefE941NvBiNmV2XOIAoMPe5gq/eX3C1JT6BDLAFw4ogw4+K1KmQX/PYibw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"arraybuffer-loader": "^1.0.6",
|
||||
|
@ -21033,14 +20972,14 @@
|
|||
"redux": "3.7.2",
|
||||
"redux-throttle": "0.1.1",
|
||||
"scratch-audio": "0.1.0-prerelease.20200528195344",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210318033822",
|
||||
"scratch-l10n": "3.11.20210318031539",
|
||||
"scratch-paint": "0.2.0-prerelease.20210308034725",
|
||||
"scratch-blocks": "0.1.0-prerelease.20210324033606",
|
||||
"scratch-l10n": "3.11.20210324031512",
|
||||
"scratch-paint": "0.2.0-prerelease.20210319222931",
|
||||
"scratch-render": "0.1.0-prerelease.20210317200605",
|
||||
"scratch-render-fonts": "1.0.0-prerelease.20200507182347",
|
||||
"scratch-storage": "1.3.3",
|
||||
"scratch-svg-renderer": "0.2.0-prerelease.20210317184701",
|
||||
"scratch-vm": "0.2.0-prerelease.20210317111523",
|
||||
"scratch-vm": "0.2.0-prerelease.20210324111836",
|
||||
"startaudiocontext": "1.2.1",
|
||||
"style-loader": "^0.23.0",
|
||||
"text-encoding": "0.7.0",
|
||||
|
@ -21203,9 +21142,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"version": "1.3.691",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.691.tgz",
|
||||
"integrity": "sha512-ZqiO69KImmOGCyoH0icQPU3SndJiW93juEvf63gQngyhODO6SpQIPMTOHldtCs5DS5GMKvAkquk230E2zt2vpw==",
|
||||
"version": "1.3.698",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.698.tgz",
|
||||
"integrity": "sha512-VEXDzYblnlT+g8Q3gedwzgKOso1evkeJzV8lih7lV8mL8eAnGVnKyC3KsFT6S+R5PQO4ffdr1PI16/ElibY/kQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
|
@ -21492,9 +21431,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"version": "3.11.20210318031539",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210318031539.tgz",
|
||||
"integrity": "sha512-uxOUzuP4J1DwVP7yySzQkMhKeuti1ecNWAgeypL8lW5HAWPlIOvBeC4oT05hRua2zF5i/+ozeZpKh0stW+1aeg==",
|
||||
"version": "3.11.20210324031512",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210324031512.tgz",
|
||||
"integrity": "sha512-dyv2cjNWVHrL78XpW64uF2azYaUhMKLjfJVH6vSCPJxm3EKCvO6EoaUSlIAwhsGoMwgfxWZ8D74+IaifGJfnCQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
|
@ -21505,9 +21444,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-paint": {
|
||||
"version": "0.2.0-prerelease.20210308034725",
|
||||
"resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-0.2.0-prerelease.20210308034725.tgz",
|
||||
"integrity": "sha512-DBhOVZ1Q7TpCcUJXAUMQACWWOZMFJyBmXvOf11ifP6opiQgjLbSBvUqYySPQckgkj38KDqDED90zAHVFga//eA==",
|
||||
"version": "0.2.0-prerelease.20210319222931",
|
||||
"resolved": "https://registry.npmjs.org/scratch-paint/-/scratch-paint-0.2.0-prerelease.20210319222931.tgz",
|
||||
"integrity": "sha512-O5tIIQ6ECsLZ3iol2S3OV3yaVyM1i6Kk0YVAR219t6GnSv1ux6RCD3qs7g8HCa+G3f+PHKBcCIk4DCR8Qv59Uw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@scratch/paper": "0.11.20200728195508",
|
||||
|
@ -21742,9 +21681,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"scratch-vm": {
|
||||
"version": "0.2.0-prerelease.20210317111523",
|
||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-0.2.0-prerelease.20210317111523.tgz",
|
||||
"integrity": "sha512-TVlh0hmvl8hUeZfvfmTn8KeBPAW2zYkAWZm32EI+sJUC7dmG7J3+7YrlqnvtubPv1q/bVeAE9dkYe/EmktbKOg==",
|
||||
"version": "0.2.0-prerelease.20210324111836",
|
||||
"resolved": "https://registry.npmjs.org/scratch-vm/-/scratch-vm-0.2.0-prerelease.20210324111836.tgz",
|
||||
"integrity": "sha512-vOayLHJJ3ZYS2XUIVPnIMZmaW/JuazC9x32Lc/i64ZxPsQjO8pANgeUN/8A5LdEc5ZO2YG2I3JWRBLnK6o+LEw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@vernier/godirect": "1.5.0",
|
||||
|
@ -22479,6 +22418,24 @@
|
|||
"urix": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"source-map-url": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz",
|
||||
|
@ -23395,9 +23352,9 @@
|
|||
}
|
||||
},
|
||||
"tap": {
|
||||
"version": "14.10.8",
|
||||
"resolved": "https://registry.npmjs.org/tap/-/tap-14.10.8.tgz",
|
||||
"integrity": "sha512-aamkWefJ0G8GGf9t5LWFtrNF5tfVd8ut/tDUianLF6N4621ERITIl0qkocWCVEnsM6hZnaMKa+SggSAaBlC2tA==",
|
||||
"version": "14.11.0",
|
||||
"resolved": "https://registry.npmjs.org/tap/-/tap-14.11.0.tgz",
|
||||
"integrity": "sha512-z8qnNFVyIjLh/bNoTLFRkEk09XZUDAZbCkz/BjvHHly3ao5H+y60gPnedALfheEjA6dA4tpp/mrKq2NWlMuq0A==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/react": "^16.9.16",
|
||||
|
@ -23430,7 +23387,7 @@
|
|||
"rimraf": "^2.7.1",
|
||||
"signal-exit": "^3.0.0",
|
||||
"source-map-support": "^0.5.16",
|
||||
"stack-utils": "^1.0.2",
|
||||
"stack-utils": "^1.0.3",
|
||||
"tap-mocha-reporter": "^5.0.0",
|
||||
"tap-parser": "^10.0.1",
|
||||
"tap-yaml": "^1.0.0",
|
||||
|
@ -23812,9 +23769,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"binary-extensions": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz",
|
||||
"integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
|
||||
"integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
|
||||
"dev": true
|
||||
},
|
||||
"braces": {
|
||||
|
@ -23867,19 +23824,19 @@
|
|||
}
|
||||
},
|
||||
"chokidar": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.1.tgz",
|
||||
"integrity": "sha512-TQTJyr2stihpC4Sya9hs2Xh+O2wf+igjL36Y75xx2WdHuiICcn/XJza46Jwt0eT5hVpQOzo3FpY3cj3RVYLX0g==",
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz",
|
||||
"integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"anymatch": "~3.1.1",
|
||||
"braces": "~3.0.2",
|
||||
"fsevents": "~2.1.2",
|
||||
"fsevents": "~2.3.1",
|
||||
"glob-parent": "~5.1.0",
|
||||
"is-binary-path": "~2.1.0",
|
||||
"is-glob": "~4.0.1",
|
||||
"normalize-path": "~3.0.0",
|
||||
"readdirp": "~3.4.0"
|
||||
"readdirp": "~3.5.0"
|
||||
}
|
||||
},
|
||||
"ci-info": {
|
||||
|
@ -23938,12 +23895,11 @@
|
|||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
|
||||
"integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
|
||||
"version": "4.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
|
@ -23982,9 +23938,9 @@
|
|||
}
|
||||
},
|
||||
"fsevents": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz",
|
||||
"integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==",
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
|
@ -24008,9 +23964,9 @@
|
|||
}
|
||||
},
|
||||
"glob-parent": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz",
|
||||
"integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
|
||||
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"is-glob": "^4.0.1"
|
||||
|
@ -24357,17 +24313,6 @@
|
|||
"bundled": true,
|
||||
"dev": true
|
||||
},
|
||||
"react": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-16.13.1.tgz",
|
||||
"integrity": "sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"loose-envify": "^1.1.0",
|
||||
"object-assign": "^4.1.1",
|
||||
"prop-types": "^15.6.2"
|
||||
}
|
||||
},
|
||||
"react-is": {
|
||||
"version": "16.13.1",
|
||||
"bundled": true,
|
||||
|
@ -24385,9 +24330,9 @@
|
|||
}
|
||||
},
|
||||
"readdirp": {
|
||||
"version": "3.4.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.4.0.tgz",
|
||||
"integrity": "sha512-0xe001vZBnJEK+uKcj8qOhyAKPzIT+gStxWr3LCB0DwcXR5NZJ3IaC+yGnHCYzB/S7ov3m3EEbZI2zeNvX+hGQ==",
|
||||
"version": "3.5.0",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz",
|
||||
"integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"picomatch": "^2.2.1"
|
||||
|
@ -24485,20 +24430,21 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"stack-utils": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.4.tgz",
|
||||
"integrity": "sha512-IPDJfugEGbfizBwBZRZ3xpccMdRyP5lqsBWXGQWimVjua/ccLCeMOAVjlc1R7LxFjo5sEDhyNIXd8mo/AiDS9w==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
"escape-string-regexp": "^2.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"escape-string-regexp": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
|
||||
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"string-length": {
|
||||
|
@ -24794,12 +24740,6 @@
|
|||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -25006,9 +24946,9 @@
|
|||
}
|
||||
},
|
||||
"tcompare": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/tcompare/-/tcompare-3.0.4.tgz",
|
||||
"integrity": "sha512-Q3TitMVK59NyKgQyFh+857wTAUE329IzLDehuPgU4nF5e8g+EUQ+yUbjUy1/6ugiNnXztphT+NnqlCXolv9P3A==",
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/tcompare/-/tcompare-3.0.5.tgz",
|
||||
"integrity": "sha512-+tmloQj1buaShBX+LP1i1NF5riJm110Yr0flIJAEoKf01tFVoMZvW2jq1JLqaW8fspOUVPm5NKKW5qLwT0ETDQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"diff-frag": "^1.0.1"
|
||||
|
@ -25802,22 +25742,6 @@
|
|||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.5.19",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz",
|
||||
"integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"buffer-from": "^1.0.0",
|
||||
"source-map": "^0.6.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -25930,9 +25854,9 @@
|
|||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "3.9.7",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz",
|
||||
"integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==",
|
||||
"version": "3.9.9",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.9.tgz",
|
||||
"integrity": "sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==",
|
||||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
|
@ -28201,6 +28125,12 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"xml": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/xml/-/xml-1.0.1.tgz",
|
||||
"integrity": "sha1-eLpyAgApxbyHuKgaPPzXS0ovweU=",
|
||||
"dev": true
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
|
||||
|
@ -28241,9 +28171,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"yaml": {
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.0.tgz",
|
||||
"integrity": "sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==",
|
||||
"version": "1.10.2",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
|
||||
"integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
|
||||
"dev": true
|
||||
},
|
||||
"yapool": {
|
||||
|
|
31
package.json
31
package.json
|
@ -6,17 +6,22 @@
|
|||
"start": "node ./dev-server/index.js",
|
||||
"test": "npm run test:lint && npm run build && npm run test:unit",
|
||||
"test:lint": "eslint . --ext .js,.jsx,.json",
|
||||
"test:lint:ci": "eslint . --ext .js,.jsx,.json --format junit -o ./test/results/lint-results.xml",
|
||||
"test:integration": "npm run test:integration:jest && npm run test:smoke",
|
||||
"test:integration:jest": "jest ./test/integration/*.test.js",
|
||||
"test:integration:jest": "jest ./test/integration/*.test.js --reporters=default",
|
||||
"test:integration:remote": "npm run test:integration:jest:remote && npm run test:smoke:sauce",
|
||||
"test:integration:jest:remote": "SMOKE_REMOTE=true jest ./test/integration/*.test.js",
|
||||
"test:integration:jest:remote": "SMOKE_REMOTE=true jest ./test/integration/*.test.js --reporters=default",
|
||||
"test:smoke": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R classic",
|
||||
"test:smoke:verbose": "tap ./test/integration-legacy/smoke-testing/*.js --timeout=3600 --no-coverage -R spec",
|
||||
"test:smoke:sauce": "SMOKE_REMOTE=true tap ./test/integration-legacy/smoke-testing/*.js --timeout=60000 --no-coverage -R classic",
|
||||
"test:smoke:convertReportToXunit": "tap ./test/results/integration-raw-tap.tap --no-coverage -R xunit > ./test/results/integration-tap-results.xml",
|
||||
"test:unit": "npm run test:unit:jest && npm run test:unit:tap",
|
||||
"test:unit:jest": "jest ./test/unit/ && jest ./test/localization/*.test.js",
|
||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy} --no-coverage -R classic",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy} --coverage --coverage-report=lcov",
|
||||
"test:unit:jest": "npm run test:unit:jest:unit && npm run test:unit:jest:localization",
|
||||
"test:unit:jest:unit": "jest ./test/unit/ --reporters=default",
|
||||
"test:unit:jest:localization": "jest ./test/localization/*.test.js --reporters=default",
|
||||
"test:unit:tap": "tap ./test/{unit-legacy,localization-legacy}/*.js --no-coverage -R classic",
|
||||
"test:unit:convertReportToXunit": "tap ./test/results/unit-raw.tap --no-coverage -R xunit > ./test/results/unit-tap-results.xml",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/*.js --coverage --coverage-report=lcov",
|
||||
"build": "npm run clean && npm run translate && NODE_OPTIONS=--max_old_space_size=8000 webpack --bail",
|
||||
"clean": "rm -rf ./build && rm -rf ./intl && mkdir -p build && mkdir -p intl",
|
||||
"deploy": "npm run deploy:s3 && npm run deploy:fastly",
|
||||
|
@ -77,7 +82,7 @@
|
|||
"enzyme": "3.10.0",
|
||||
"enzyme-adapter-react-16": "1.14.0",
|
||||
"eslint": "5.16.0",
|
||||
"eslint-config-scratch": "6.0.0",
|
||||
"eslint-config-scratch": "7.0.0",
|
||||
"eslint-plugin-json": "2.0.1",
|
||||
"eslint-plugin-react": "7.14.2",
|
||||
"fastly": "1.2.1",
|
||||
|
@ -90,6 +95,7 @@
|
|||
"html-webpack-plugin": "^3.2.0",
|
||||
"iso-3166-2": "0.4.0",
|
||||
"jest": "^23.6.0",
|
||||
"jest-junit": "12.0.0",
|
||||
"keymirror": "0.1.1",
|
||||
"lodash.bindall": "4.4.0",
|
||||
"lodash.defaultsdeep": "4.6.1",
|
||||
|
@ -119,12 +125,12 @@
|
|||
"redux-mock-store": "^1.2.3",
|
||||
"redux-thunk": "2.0.1",
|
||||
"sass-loader": "6.0.6",
|
||||
"scratch-gui": "0.1.0-prerelease.20210318040059",
|
||||
"scratch-gui": "0.1.0-prerelease.20210324120840",
|
||||
"scratch-l10n": "latest",
|
||||
"selenium-webdriver": "3.6.0",
|
||||
"slick-carousel": "1.6.0",
|
||||
"style-loader": "0.12.3",
|
||||
"tap": "14.10.8",
|
||||
"tap": "14.11.0",
|
||||
"url-loader": "2.3.0",
|
||||
"webpack": "^4.46.0",
|
||||
"webpack-bundle-analyzer": "^4.4.0",
|
||||
|
@ -139,7 +145,14 @@
|
|||
"moduleNameMapper": {
|
||||
"\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$": "<rootDir>/test/__mocks__/fileMock.js",
|
||||
"\\.(css|less|scss)$": "<rootDir>/test/__mocks__/styleMock.js"
|
||||
}
|
||||
},
|
||||
"reporters": [
|
||||
"default",
|
||||
"jest-junit"
|
||||
]
|
||||
},
|
||||
"jest-junit": {
|
||||
"outputDirectory": "./test/results"
|
||||
},
|
||||
"nyc": {
|
||||
"include": [
|
||||
|
|
|
@ -4,47 +4,53 @@
|
|||
"type": "project",
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": "",
|
||||
"href": "#",
|
||||
"stats": {"loves": 0, "remixes": 0}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": "",
|
||||
"href": "#",
|
||||
"stats": {"loves": 0, "remixes": 0}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": "",
|
||||
"href": "#",
|
||||
"stats": {"loves": 0, "remixes": 0}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": "",
|
||||
"href": "#",
|
||||
"stats": {"loves": 0, "remixes": 0}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": "",
|
||||
"href": "#",
|
||||
"stats": {"loves": 0, "remixes": 0}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": "",
|
||||
"href": "#",
|
||||
"stats": {"loves": 0, "remixes": 0}
|
||||
}
|
||||
]
|
||||
|
|
|
@ -327,8 +327,6 @@
|
|||
"comments.isBad": "Hmm...the bad word detector thinks there is a problem with your comment. Please change it and remember to be respectful.",
|
||||
"comments.hasChatSite": "Uh oh! The comment contains a link to a website with unmoderated chat. For safety reasons, please do not link to these sites!",
|
||||
"comments.isSpam": "Hmm, seems like you've posted the same comment a bunch of times. Please don't spam.",
|
||||
"comments.isMuted": "Hmm, the filterbot is pretty sure your recent comments weren't ok for Scratch, so your account has been muted for the rest of the day. :/",
|
||||
"comments.isUnconstructive": "Hmm, the filterbot thinks your comment may be mean or disrespectful. Remember, most projects on Scratch are made by people who are just learning how to program.",
|
||||
"comments.isDisallowed": "Hmm, it looks like comments have been turned off for this page. :/",
|
||||
"comments.isIPMuted": "Sorry, the Scratch Team had to prevent your network from sharing comments or projects because it was used to break our community guidelines too many times. You can still share comments and projects from another network. If you'd like to appeal this block, you can contact appeals@scratch.mit.edu and reference Case Number {appealId}.",
|
||||
"comments.isTooLong": "That comment is too long! Please find a way to shorten your text.",
|
||||
|
|
127
src/redux/infinite-list.js
Normal file
127
src/redux/infinite-list.js
Normal file
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* @typedef ReduxModule
|
||||
* A redux "module" for reusable functionality. The module exports
|
||||
* a reducer function, a set of action creators and a selector
|
||||
* that are all scoped to the given "key". This allows us to reuse
|
||||
* this reducer multiple times in the same redux store.
|
||||
*
|
||||
* @property {string} key The key to use when registering this
|
||||
* modules reducer in the redux state tree.
|
||||
* @property {function} selector Function called with the full
|
||||
* state tree to select only this modules slice of the state.
|
||||
* @property {object} actions An object of action creator functions
|
||||
* to call to make changes to the data in this reducer.
|
||||
* @property {function} reducer A redux reducer that takes an action
|
||||
* from the action creators and the current state and returns
|
||||
* the next state.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {function} InfiniteListFetcher
|
||||
* A function to call that returns more data for the InfiniteList
|
||||
* loadMore action. It must resolve to {items: [], moreToLoad} or
|
||||
* reject with the error {statusCode}.
|
||||
* @returns {Promise<{items:[], moreToLoad:boolean}>}
|
||||
*/
|
||||
|
||||
/**
|
||||
* A redux module to create a list of items where more items can be loaded
|
||||
* using an API. Additionally, there are actions for prepending items
|
||||
* to the list, removing items and handling load errors.
|
||||
*
|
||||
* @param {string} key - used to scope action names and the selector
|
||||
* This key must be unique among other instances of this module.
|
||||
* @returns {ReduxModule} the redux module
|
||||
*/
|
||||
const InfiniteList = key => {
|
||||
|
||||
const initialState = {
|
||||
items: [],
|
||||
offset: 0,
|
||||
error: null,
|
||||
loading: true,
|
||||
moreToLoad: false
|
||||
};
|
||||
|
||||
const reducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = initialState;
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case `${key}_LOADING`:
|
||||
return {
|
||||
...state,
|
||||
error: null,
|
||||
loading: true
|
||||
};
|
||||
case `${key}_APPEND`:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.concat(action.items),
|
||||
loading: false,
|
||||
error: null,
|
||||
moreToLoad: action.moreToLoad
|
||||
};
|
||||
case `${key}_REPLACE`:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.map((item, i) => {
|
||||
if (i === action.index) return action.item;
|
||||
return item;
|
||||
})
|
||||
};
|
||||
case `${key}_REMOVE`:
|
||||
return {
|
||||
...state,
|
||||
items: state.items.filter((_, i) => i !== action.index)
|
||||
};
|
||||
case `${key}_PREPEND`:
|
||||
return {
|
||||
...state,
|
||||
items: [action.item].concat(state.items)
|
||||
};
|
||||
case `${key}_ERROR`:
|
||||
return {
|
||||
...state,
|
||||
error: action.error,
|
||||
loading: false,
|
||||
moreToLoad: false
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const actions = {
|
||||
create: item => ({type: `${key}_PREPEND`, item}),
|
||||
remove: index => ({type: `${key}_REMOVE`, index}),
|
||||
replace: (index, item) => ({type: `${key}_REPLACE`, index, item}),
|
||||
error: error => ({type: `${key}_ERROR`, error}),
|
||||
loading: () => ({type: `${key}_LOADING`}),
|
||||
append: (items, moreToLoad) => ({type: `${key}_APPEND`, items, moreToLoad}),
|
||||
|
||||
/**
|
||||
* Load more action returns a thunk. It takes a function to call to get more items.
|
||||
* It will call the LOADING action before calling the fetcher, and call
|
||||
* APPEND with the results or call ERROR.
|
||||
* @param {InfiniteListFetcher} fetcher - function that returns a promise
|
||||
* which must resolve to {items: [], moreToLoad}.
|
||||
* @returns {function} a thunk that sequences the load and dispatches
|
||||
*/
|
||||
loadMore: fetcher => (dispatch => {
|
||||
dispatch(actions.loading());
|
||||
return fetcher()
|
||||
.then(({items, moreToLoad}) => dispatch(actions.append(items, moreToLoad)))
|
||||
.catch(error => dispatch(actions.error(error)));
|
||||
})
|
||||
};
|
||||
|
||||
const selector = state => state[key];
|
||||
|
||||
return {
|
||||
key, actions, reducer, selector
|
||||
};
|
||||
};
|
||||
|
||||
export default InfiniteList;
|
120
src/redux/studio.js
Normal file
120
src/redux/studio.js
Normal file
|
@ -0,0 +1,120 @@
|
|||
const keyMirror = require('keymirror');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const Status = keyMirror({
|
||||
FETCHED: null,
|
||||
NOT_FETCHED: null,
|
||||
FETCHING: null,
|
||||
ERROR: null
|
||||
});
|
||||
|
||||
const getInitialState = () => ({
|
||||
infoStatus: Status.NOT_FETCHED,
|
||||
title: '',
|
||||
description: '',
|
||||
openToAll: false,
|
||||
commentingAllowed: false,
|
||||
thumbnail: '',
|
||||
followers: 0,
|
||||
|
||||
rolesStatus: Status.NOT_FETCHED,
|
||||
manager: false,
|
||||
curator: false,
|
||||
follower: false,
|
||||
invited: false
|
||||
});
|
||||
|
||||
const studioReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = getInitialState();
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case 'SET_INFO':
|
||||
return {
|
||||
...state,
|
||||
...action.info
|
||||
};
|
||||
case 'SET_ROLES':
|
||||
return {
|
||||
...state,
|
||||
...action.roles
|
||||
};
|
||||
case 'SET_FETCH_STATUS':
|
||||
if (action.error) {
|
||||
log.error(action.error);
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
[action.fetchType]: action.fetchStatus
|
||||
};
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
const setFetchStatus = (fetchType, fetchStatus, error) => ({
|
||||
type: 'SET_FETCH_STATUS',
|
||||
fetchType,
|
||||
fetchStatus,
|
||||
error
|
||||
});
|
||||
|
||||
const setInfo = info => ({
|
||||
type: 'SET_INFO',
|
||||
info: info
|
||||
});
|
||||
|
||||
const setRoles = roles => ({
|
||||
type: 'SET_ROLES',
|
||||
roles: roles
|
||||
});
|
||||
|
||||
const getInfo = studioId => (dispatch => {
|
||||
dispatch(setFetchStatus('infoStatus', Status.FETCHING));
|
||||
api({uri: `/studios/${studioId}`}, (err, body, res) => {
|
||||
if (err || typeof body === 'undefined' || res.statusCode !== 200) {
|
||||
dispatch(setFetchStatus('infoStatus', Status.ERROR, err));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('infoStatus', Status.FETCHED));
|
||||
dispatch(setInfo({
|
||||
title: body.title,
|
||||
description: body.description,
|
||||
openToAll: body.open_to_all,
|
||||
commentingAllowed: body.commenting_allowed,
|
||||
updated: new Date(body.history.modified),
|
||||
followers: body.stats.followers
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
const getRoles = (studioId, username, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('rolesStatus', Status.FETCHING));
|
||||
api({
|
||||
uri: `/studios/${studioId}/users/${username}`,
|
||||
authentication: token
|
||||
}, (err, body, res) => {
|
||||
if (err || typeof body === 'undefined' || res.statusCode !== 200) {
|
||||
dispatch(setFetchStatus('rolesStatus', Status.ERROR, err));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('rolesStatus', Status.FETCHED));
|
||||
dispatch(setRoles({
|
||||
manager: body.manager,
|
||||
curator: body.curator,
|
||||
following: body.following,
|
||||
invited: body.invited
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getInitialState,
|
||||
studioReducer,
|
||||
Status,
|
||||
getInfo,
|
||||
getRoles
|
||||
};
|
|
@ -5,6 +5,7 @@ const Page = require('../../components/page/www/page.jsx');
|
|||
const Box = require('../../components/box/box.jsx');
|
||||
const Button = require('../../components/forms/button.jsx');
|
||||
const Carousel = require('../../components/carousel/carousel.jsx');
|
||||
const Form = require('../../components/forms/form.jsx');
|
||||
const Input = require('../../components/forms/input.jsx');
|
||||
const Spinner = require('../../components/spinner/spinner.jsx');
|
||||
|
||||
|
@ -16,11 +17,13 @@ const Components = () => (
|
|||
<h1>Button</h1>
|
||||
<Button>I love button</Button>
|
||||
<h1>Form</h1>
|
||||
<Input
|
||||
maxLength="30"
|
||||
name="test"
|
||||
type="text"
|
||||
/>
|
||||
<Form>
|
||||
<Input
|
||||
maxLength="30"
|
||||
name="test"
|
||||
type="text"
|
||||
/>
|
||||
</Form>
|
||||
<h1>Box Component</h1>
|
||||
<Box
|
||||
more="Cat Gifs"
|
||||
|
|
|
@ -51,13 +51,6 @@ const ConferenceSplash = () => (
|
|||
value={new Date(2021, 6, 22)}
|
||||
year="numeric"
|
||||
/>
|
||||
{' - '}
|
||||
<FormattedDate
|
||||
day="2-digit"
|
||||
month="long"
|
||||
value={new Date(2021, 6, 24)}
|
||||
year="numeric"
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr className="conf2020-panel-row">
|
||||
|
@ -75,7 +68,7 @@ const ConferenceSplash = () => (
|
|||
</table>
|
||||
<a
|
||||
className="button mod-2020-panel"
|
||||
href="http://scratch2020.eventbrite.com/"
|
||||
href="http://scratch2021.eventbrite.com/"
|
||||
>
|
||||
<FormattedMessage id="conference-2020.register" />
|
||||
</a>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"conference-2020.title": "Scratch Around the World:",
|
||||
"conference-2020.subtitle": "An Online Conference",
|
||||
"conference-2020.dateDesc": "July 22",
|
||||
"conference-2020.dateDesc": "July 22, 2021",
|
||||
"conference-2020.locationDetails": "Online",
|
||||
|
||||
"conference-2020.date": "When:",
|
||||
|
|
|
@ -28,8 +28,9 @@ const JUST_MUTED_ERROR = 'isBad';
|
|||
const ComposeStatus = keyMirror({
|
||||
EDITING: null,
|
||||
SUBMITTING: null,
|
||||
REJECTED: null,
|
||||
REJECTED_MUTE: null
|
||||
REJECTED: null, // comment rejected for a reason other than muting (such as commenting too quickly)
|
||||
REJECTED_MUTE: null, // comment made in this ComposeComment was rejected and muted the user
|
||||
COMPOSE_DISALLOWED: null // user is already muted due to past behavior
|
||||
});
|
||||
|
||||
class ComposeComment extends React.Component {
|
||||
|
@ -48,7 +49,7 @@ class ComposeComment extends React.Component {
|
|||
this.props.muteStatus.muteExpiresAt * 1000 : 0; // convert to ms
|
||||
this.state = {
|
||||
message: '',
|
||||
status: ComposeStatus.EDITING,
|
||||
status: muteExpiresAtMs > Date.now() ? ComposeStatus.COMPOSE_DISALLOWED : ComposeStatus.EDITING,
|
||||
error: null,
|
||||
appealId: null,
|
||||
muteOpen: muteExpiresAtMs > Date.now() && this.props.isReply,
|
||||
|
@ -96,14 +97,23 @@ class ComposeComment extends React.Component {
|
|||
let muteOpen = false;
|
||||
let muteExpiresAtMs = 0;
|
||||
let rejectedStatus = ComposeStatus.REJECTED;
|
||||
let justMuted = true;
|
||||
let showWarning = false;
|
||||
let muteType = null;
|
||||
if (body.status && body.status.mute_status) {
|
||||
muteExpiresAtMs = body.status.mute_status.muteExpiresAt * 1000; // convert to ms
|
||||
rejectedStatus = ComposeStatus.REJECTED_MUTE;
|
||||
if (this.shouldShowMuteModal(body.status.mute_status)) {
|
||||
|
||||
if (body.rejected === JUST_MUTED_ERROR) {
|
||||
rejectedStatus = ComposeStatus.REJECTED_MUTE;
|
||||
} else {
|
||||
rejectedStatus = ComposeStatus.COMPOSE_DISALLOWED;
|
||||
justMuted = false;
|
||||
}
|
||||
|
||||
if (this.shouldShowMuteModal(body.status.mute_status, justMuted)) {
|
||||
muteOpen = true;
|
||||
}
|
||||
|
||||
showWarning = body.status.mute_status.showWarning;
|
||||
muteType = body.status.mute_status.currentMessageType;
|
||||
this.setupMuteExpirationTimeout(muteExpiresAtMs);
|
||||
|
@ -152,7 +162,7 @@ class ComposeComment extends React.Component {
|
|||
// Cancel (i.e. complete) the reply action if the user clicked on the reply button while
|
||||
// alreay muted. This "closes" the reply. If they just got muted, we want to leave it open
|
||||
// so the blue CommentingStatus box shows.
|
||||
if (this.props.isReply && this.state.status !== ComposeStatus.REJECTED_MUTE) {
|
||||
if (this.props.isReply && this.state.status === ComposeStatus.COMPOSE_DISALLOWED) {
|
||||
this.handleCancel();
|
||||
}
|
||||
}
|
||||
|
@ -162,7 +172,7 @@ class ComposeComment extends React.Component {
|
|||
muteOpen: true
|
||||
});
|
||||
}
|
||||
shouldShowMuteModal (muteStatus) {
|
||||
shouldShowMuteModal (muteStatus, justMuted) {
|
||||
// We should show the mute modal if the user is in danger of being blocked or
|
||||
// when the user is newly muted or hasn't seen it for a while.
|
||||
// We don't want to show it more than about once a week.
|
||||
|
@ -176,6 +186,17 @@ class ComposeComment extends React.Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
// If the user is already muted (for example, in a different tab),
|
||||
// do not show modal unless the comment is a reply. We always want to show
|
||||
// the modal on replies when the user is already muted because the blue box
|
||||
// may be out-of-sight for them.
|
||||
if (!justMuted) {
|
||||
if (this.props.isReply) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// If the backend tells us to show a warning about getting blocked, we should show the modal
|
||||
// regardless of what the offenses list looks like.
|
||||
if (muteStatus.showWarning) {
|
||||
|
@ -199,41 +220,47 @@ class ComposeComment extends React.Component {
|
|||
// Decides which step of the mute modal to start on. If this was a reply button click,
|
||||
// we show them the step that tells them how much time is left on their mute, otherwise
|
||||
// they start at the beginning of the progression.
|
||||
return this.props.isReply && this.state.status !== ComposeStatus.REJECTED_MUTE ?
|
||||
return this.props.isReply && this.state.status === ComposeStatus.COMPOSE_DISALLOWED ?
|
||||
MuteModal.steps.MUTE_INFO : MuteModal.steps.COMMENT_ISSUE;
|
||||
}
|
||||
|
||||
getMuteMessageInfo () {
|
||||
getMuteMessageInfo (justMuted) {
|
||||
// return the ids for the messages that are shown for this mute type
|
||||
// If mute modals have more than one unique "step" we could pass an array of steps
|
||||
const messageInfo = {
|
||||
pii: {
|
||||
name: 'pii',
|
||||
commentType: 'comment.type.pii',
|
||||
commentType: justMuted ? 'comment.type.pii' : 'comment.type.pii.past',
|
||||
muteStepHeader: 'comment.pii.header',
|
||||
muteStepContent: ['comment.pii.content1', 'comment.pii.content2', 'comment.pii.content3']
|
||||
},
|
||||
unconstructive: {
|
||||
name: 'unconstructive',
|
||||
commentType: 'comment.type.unconstructive',
|
||||
commentType: justMuted ? 'comment.type.unconstructive' : 'comment.type.unconstructive.past',
|
||||
muteStepHeader: 'comment.unconstructive.header',
|
||||
muteStepContent: ['comment.unconstructive.content1', 'comment.unconstructive.content2']
|
||||
muteStepContent: [
|
||||
justMuted ? 'comment.unconstructive.content1' : 'comment.type.unconstructive.past',
|
||||
'comment.unconstructive.content2'
|
||||
]
|
||||
},
|
||||
vulgarity: {
|
||||
name: 'vulgarity',
|
||||
commentType: 'comment.type.vulgarity',
|
||||
commentType: justMuted ? 'comment.type.vulgarity' : 'comment.type.vulgarity.past',
|
||||
muteStepHeader: 'comment.vulgarity.header',
|
||||
muteStepContent: ['comment.vulgarity.content1', 'comment.vulgarity.content2']
|
||||
muteStepContent: [
|
||||
justMuted ? 'comment.vulgarity.content1' : 'comment.type.vulgarity.past',
|
||||
'comment.vulgarity.content2'
|
||||
]
|
||||
},
|
||||
spam: {
|
||||
name: 'spam',
|
||||
commentType: 'comment.type.spam',
|
||||
commentType: justMuted ? 'comment.type.spam' : 'comment.type.spam.past',
|
||||
muteStepHeader: 'comment.spam.header',
|
||||
muteStepContent: ['comment.spam.content1', 'comment.spam.content2']
|
||||
},
|
||||
general: {
|
||||
name: 'general',
|
||||
commentType: 'comment.type.general',
|
||||
commentType: justMuted ? 'comment.type.general' : 'comment.type.general.past',
|
||||
muteStepHeader: 'comment.general.header',
|
||||
muteStepContent: ['comment.general.content1']
|
||||
}
|
||||
|
@ -258,10 +285,20 @@ class ComposeComment extends React.Component {
|
|||
render () {
|
||||
return (
|
||||
<React.Fragment>
|
||||
{(this.isMuted() && !(this.props.isReply && this.state.status !== ComposeStatus.REJECTED_MUTE)) ? (
|
||||
{/* If a user is muted, show the blue mute box, unless
|
||||
the comment is a reply and the user was already muted before attempting to make it. */}
|
||||
{(this.isMuted() && !(this.props.isReply && this.state.status === ComposeStatus.COMPOSE_DISALLOWED)) ? (
|
||||
<FlexRow className="comment">
|
||||
<CommentingStatus>
|
||||
<p><FormattedMessage id={this.getMuteMessageInfo().commentType} /></p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id={
|
||||
this.getMuteMessageInfo(
|
||||
this.state.status === ComposeStatus.REJECTED_MUTE
|
||||
).commentType
|
||||
}
|
||||
/>
|
||||
</p>
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="comments.muted.duration"
|
||||
|
@ -287,7 +324,7 @@ class ComposeComment extends React.Component {
|
|||
</CommentingStatus>
|
||||
</FlexRow>
|
||||
) : null }
|
||||
{!this.isMuted() || (this.isMuted() && this.state.status === ComposeStatus.REJECTED_MUTE) ? (
|
||||
{this.state.status === ComposeStatus.COMPOSE_DISALLOWED ? null : (
|
||||
<div
|
||||
className={classNames('flex-row',
|
||||
'comment',
|
||||
|
@ -298,7 +335,7 @@ class ComposeComment extends React.Component {
|
|||
<Avatar src={this.props.user.thumbnailUrl} />
|
||||
</a>
|
||||
<FlexRow className="compose-comment column">
|
||||
{this.state.error && this.state.status !== ComposeStatus.REJECTED_MUTE ? (
|
||||
{this.state.status === ComposeStatus.REJECTED ? (
|
||||
<FlexRow className="compose-error-row">
|
||||
<div className="compose-error-tip">
|
||||
<FormattedMessage
|
||||
|
@ -360,7 +397,7 @@ class ComposeComment extends React.Component {
|
|||
</Formsy>
|
||||
</FlexRow>
|
||||
</div>
|
||||
) : null }
|
||||
)}
|
||||
{this.state.muteOpen ? (
|
||||
<MuteModal
|
||||
isOpen
|
||||
|
@ -368,10 +405,10 @@ class ComposeComment extends React.Component {
|
|||
useStandardSizes
|
||||
className="mod-mute"
|
||||
commentContent={this.state.message}
|
||||
muteModalMessages={this.getMuteMessageInfo()}
|
||||
muteModalMessages={this.getMuteMessageInfo(this.state.status === ComposeStatus.REJECTED_MUTE)}
|
||||
shouldCloseOnOverlayClick={false}
|
||||
showFeedback={
|
||||
this.state.status === ComposeStatus.REJECTED_MUTE && this.state.error === JUST_MUTED_ERROR
|
||||
this.state.status === ComposeStatus.REJECTED_MUTE
|
||||
}
|
||||
showWarning={this.state.showWarning}
|
||||
startStep={this.getMuteModalStartStep()}
|
||||
|
|
|
@ -47,22 +47,27 @@
|
|||
"project.usernameBlockAlert": "This project can detect who is using it, through the \"username\" block. To hide your identity, sign out before using the project.",
|
||||
"project.inappropriateUpdate": "Hmm...the bad word detector thinks there is a problem with your text. Please change it and remember to be respectful.",
|
||||
"comment.type.general": "It appears that your most recent comment didn't follow the Scratch Community Guidelines.",
|
||||
"comment.type.general.past": "It appears that one of your recent comments didn’t follow the Scratch Community Guidelines.",
|
||||
"comment.general.header": "We encourage you to post comments that follow the Scratch Community Guidelines.",
|
||||
"comment.general.content1": "On Scratch, it's important for comments to be kind, to be appropriate for all ages, and to not contain spam.",
|
||||
"comment.type.pii": "Your most recent comment appeared to be sharing or asking for private information.",
|
||||
"comment.type.pii.past": "It appears that one of your recent comments was sharing or asking for private information.",
|
||||
"comment.pii.header": "Please be sure not to share private information on Scratch.",
|
||||
"comment.pii.content1": "It appears that you were sharing or asking for private information.",
|
||||
"comment.pii.content2": "Things you share on Scratch can be seen by everyone, and can appear in search engines. Private information can be used by other people in harmful ways, so it’s important to keep it private.",
|
||||
"comment.pii.content3": "This is a serious safety issue.",
|
||||
"comment.type.unconstructive": "It appears that your most recent comment was saying something that might have been hurtful.",
|
||||
"comment.type.unconstructive.past": "It appears that one of your recent comments was saying something that might have been hurtful.",
|
||||
"comment.unconstructive.header": "We encourage you to be supportive when commenting on other people’s projects",
|
||||
"comment.unconstructive.content1": "It appears that your comment was saying something that might have been hurtful.",
|
||||
"comment.unconstructive.content2": "If you think something could be better, you can say something you like about the project, and make a suggestion about how to improve it.",
|
||||
"comment.type.vulgarity": "Your most recent comment appeared to include a bad word.",
|
||||
"comment.type.vulgarity.past": "It appears that one of your recent comments contained a bad word.",
|
||||
"comment.vulgarity.header": "We encourage you to use language that’s appropriate for all ages.",
|
||||
"comment.vulgarity.content1": "It appears that your comment contains a bad word.",
|
||||
"comment.vulgarity.content2": "Scratch has users of all ages, so it’s important to use language that is appropriate for all Scratchers.",
|
||||
"comment.type.spam": "Your most recent comment appeared to contain advertising, text art, or a chain message.",
|
||||
"comment.type.spam.past": "It appears that one of your recent comments contained advertising, text art, or a chain message.",
|
||||
"comment.spam.header": "We encourage you not to advertise, copy and paste text art, or ask others to copy comments.",
|
||||
"comment.spam.content1": "Even though advertisements, text art, and chain mail can be fun, they start to fill up the website, and we want to make sure there is room for other comments.",
|
||||
"comment.spam.content2": "Thank you for helping us keep Scratch a friendly, creative community!"
|
||||
|
|
18
src/views/studio/debug.jsx
Normal file
18
src/views/studio/debug.jsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Debug = ({label, data}) => (<div style={{padding: '2rem', border: '1px solid red', margin: '2rem'}}>
|
||||
<small>{label}</small>
|
||||
<code>
|
||||
<pre style={{fontSize: '0.75rem'}}>
|
||||
{JSON.stringify(data, null, ' ')}
|
||||
</pre>
|
||||
</code>
|
||||
</div>);
|
||||
|
||||
Debug.propTypes = {
|
||||
label: PropTypes.string,
|
||||
data: PropTypes.any // eslint-disable-line react/forbid-prop-types
|
||||
};
|
||||
|
||||
export default Debug;
|
28
src/views/studio/lib/fetchers.js
Normal file
28
src/views/studio/lib/fetchers.js
Normal file
|
@ -0,0 +1,28 @@
|
|||
const ITEM_LIMIT = 4;
|
||||
|
||||
const projectFetcher = (studioId, offset) =>
|
||||
fetch(`${process.env.API_HOST}/studios/${studioId}/projects?limit=${ITEM_LIMIT}&offset=${offset}`)
|
||||
.then(response => response.json())
|
||||
.then(data => ({items: data, moreToLoad: data.length === ITEM_LIMIT}));
|
||||
|
||||
const curatorFetcher = (studioId, offset) =>
|
||||
fetch(`${process.env.API_HOST}/studios/${studioId}/curators?limit=${ITEM_LIMIT}&offset=${offset}`)
|
||||
.then(response => response.json())
|
||||
.then(data => ({items: data, moreToLoad: data.length === ITEM_LIMIT}));
|
||||
|
||||
const managerFetcher = (studioId, offset) =>
|
||||
fetch(`${process.env.API_HOST}/studios/${studioId}/managers?limit=${ITEM_LIMIT}&offset=${offset}`)
|
||||
.then(response => response.json())
|
||||
.then(data => ({items: data, moreToLoad: data.length === ITEM_LIMIT}));
|
||||
|
||||
const activityFetcher = studioId =>
|
||||
fetch(`${process.env.API_HOST}/studios/${studioId}/activity`)
|
||||
.then(response => response.json())
|
||||
.then(data => ({items: data, moreToLoad: false})); // No pagination on the activity feed
|
||||
|
||||
export {
|
||||
activityFetcher,
|
||||
projectFetcher,
|
||||
curatorFetcher,
|
||||
managerFetcher
|
||||
};
|
10
src/views/studio/lib/redux-modules.js
Normal file
10
src/views/studio/lib/redux-modules.js
Normal file
|
@ -0,0 +1,10 @@
|
|||
import InfiniteList from '../../../redux/infinite-list';
|
||||
|
||||
const projects = InfiniteList('projects');
|
||||
const curators = InfiniteList('curators');
|
||||
const managers = InfiniteList('managers');
|
||||
const activity = InfiniteList('activity');
|
||||
|
||||
export {
|
||||
projects, curators, managers, activity
|
||||
};
|
|
@ -1,15 +1,54 @@
|
|||
import React from 'react';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import React, {useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const StudioActivity = () => {
|
||||
import {connect} from 'react-redux';
|
||||
import {useParams} from 'react-router';
|
||||
|
||||
import {activity} from './lib/redux-modules';
|
||||
import {activityFetcher} from './lib/fetchers';
|
||||
import Debug from './debug.jsx';
|
||||
|
||||
const StudioActivity = ({items, loading, error, onInitialLoad}) => {
|
||||
const {studioId} = useParams();
|
||||
// Fetch the data if none has been loaded yet. This would run only once,
|
||||
// since studioId doesnt change, but the component is potentially mounted
|
||||
// multiple times because of tab routing, so need to check for empty items.
|
||||
useEffect(() => {
|
||||
if (studioId && items.length === 0) onInitialLoad(studioId);
|
||||
}, [studioId]); // items.length intentionally left out
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Activity</h2>
|
||||
<p>Studio {studioId}</p>
|
||||
{loading && <div>Loading...</div>}
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
<div>
|
||||
{items.map((item, index) =>
|
||||
(<Debug
|
||||
label="Activity Item"
|
||||
data={item}
|
||||
key={index}
|
||||
/>)
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudioActivity;
|
||||
StudioActivity.propTypes = {
|
||||
items: PropTypes.array, // eslint-disable-line react/forbid-prop-types
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
onInitialLoad: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => activity.selector(state),
|
||||
dispatch => ({
|
||||
onInitialLoad: studioId => dispatch(
|
||||
activity.actions.loadMore(activityFetcher.bind(null, studioId, 0)))
|
||||
})
|
||||
)(StudioActivity);
|
||||
|
|
|
@ -1,16 +1,77 @@
|
|||
import React from 'react';
|
||||
import React, {useEffect, useCallback} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {curators, managers} from './lib/redux-modules';
|
||||
import {curatorFetcher, managerFetcher} from './lib/fetchers';
|
||||
import Debug from './debug.jsx';
|
||||
|
||||
const StudioCurators = () => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Curators</h2>
|
||||
<p>Studio {studioId}</p>
|
||||
|
||||
<h3>Managers</h3>
|
||||
<ManagerList studioId={studioId} />
|
||||
<hr />
|
||||
<h3>Curators</h3>
|
||||
<CuratorList studioId={studioId} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const MemberList = ({studioId, items, error, loading, moreToLoad, onLoadMore}) => {
|
||||
useEffect(() => {
|
||||
if (studioId && items.length === 0) onLoadMore(studioId, 0);
|
||||
}, [studioId]);
|
||||
|
||||
const handleLoadMore = useCallback(() => onLoadMore(studioId, items.length), [studioId, items.length]);
|
||||
|
||||
return (<React.Fragment>
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
{items.map((item, index) =>
|
||||
(<Debug
|
||||
label="Member"
|
||||
data={item}
|
||||
key={index}
|
||||
/>)
|
||||
)}
|
||||
{loading ? <small>Loading...</small> : (
|
||||
moreToLoad ?
|
||||
<button onClick={handleLoadMore}>
|
||||
Load more
|
||||
</button> :
|
||||
<small>No more to load</small>
|
||||
)}
|
||||
</React.Fragment>);
|
||||
};
|
||||
|
||||
MemberList.propTypes = {
|
||||
studioId: PropTypes.string,
|
||||
items: PropTypes.array, // eslint-disable-line react/forbid-prop-types
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
moreToLoad: PropTypes.bool,
|
||||
onLoadMore: PropTypes.func
|
||||
};
|
||||
|
||||
const ManagerList = connect(
|
||||
state => managers.selector(state),
|
||||
dispatch => ({
|
||||
onLoadMore: (studioId, offset) => dispatch(
|
||||
managers.actions.loadMore(managerFetcher.bind(null, studioId, offset)))
|
||||
})
|
||||
)(MemberList);
|
||||
|
||||
const CuratorList = connect(
|
||||
state => curators.selector(state),
|
||||
dispatch => ({
|
||||
onLoadMore: (studioId, offset) => dispatch(
|
||||
curators.actions.loadMore(curatorFetcher.bind(null, studioId, offset)))
|
||||
})
|
||||
)(MemberList);
|
||||
|
||||
export default StudioCurators;
|
||||
|
|
|
@ -1,15 +1,54 @@
|
|||
import React from 'react';
|
||||
import React, {useEffect} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {connect} from 'react-redux';
|
||||
import Debug from './debug.jsx';
|
||||
import {getInfo, getRoles} from '../../redux/studio';
|
||||
|
||||
const StudioInfo = () => {
|
||||
const StudioInfo = ({username, studio, token, onLoadInfo, onLoadRoles}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
useEffect(() => { // Load studio info after first render
|
||||
if (studioId) onLoadInfo(studioId);
|
||||
}, [studioId]);
|
||||
|
||||
useEffect(() => { // Load roles info once the username is available
|
||||
if (studioId && username && token) onLoadRoles(studioId, username, token);
|
||||
}, [studioId, username, token]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Studio Info</h2>
|
||||
<p>Studio {studioId}</p>
|
||||
<Debug
|
||||
label="Studio Info"
|
||||
data={studio}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudioInfo;
|
||||
StudioInfo.propTypes = {
|
||||
username: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
studio: PropTypes.shape({
|
||||
// Fill this in as the data is used, just for demo now
|
||||
}),
|
||||
onLoadInfo: PropTypes.func,
|
||||
onLoadRoles: PropTypes.func
|
||||
};
|
||||
|
||||
export default connect(
|
||||
state => {
|
||||
const user = state.session.session.user;
|
||||
return {
|
||||
studio: state.studio,
|
||||
username: user && user.username,
|
||||
token: user && user.token
|
||||
};
|
||||
},
|
||||
dispatch => ({
|
||||
onLoadInfo: studioId => dispatch(getInfo(studioId)),
|
||||
onLoadRoles: (studioId, username, token) => dispatch(
|
||||
getRoles(studioId, username, token))
|
||||
})
|
||||
)(StudioInfo);
|
||||
|
|
|
@ -1,15 +1,65 @@
|
|||
import React from 'react';
|
||||
import React, {useEffect, useCallback} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {useParams} from 'react-router-dom';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
const StudioProjects = () => {
|
||||
import {projectFetcher} from './lib/fetchers';
|
||||
import {projects} from './lib/redux-modules';
|
||||
import Debug from './debug.jsx';
|
||||
|
||||
const {actions, selector} = projects;
|
||||
|
||||
const StudioProjects = ({
|
||||
items, error, loading, moreToLoad, onLoadMore
|
||||
}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (studioId && items.length === 0) onLoadMore(studioId, 0);
|
||||
}, [studioId]);
|
||||
|
||||
const handleLoadMore = useCallback(() => onLoadMore(studioId, items.length), [studioId, items.length]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Projects</h2>
|
||||
<p>Studio {studioId}</p>
|
||||
{error && <Debug
|
||||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
<div>
|
||||
{items.map((item, index) =>
|
||||
(<Debug
|
||||
label="Project"
|
||||
data={item}
|
||||
key={index}
|
||||
/>)
|
||||
)}
|
||||
{loading ? <small>Loading...</small> : (
|
||||
moreToLoad ?
|
||||
<button onClick={handleLoadMore}>
|
||||
Load more
|
||||
</button> :
|
||||
<small>No more to load</small>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudioProjects;
|
||||
StudioProjects.propTypes = {
|
||||
items: PropTypes.array, // eslint-disable-line react/forbid-prop-types
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
moreToLoad: PropTypes.bool,
|
||||
onLoadMore: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => selector(state);
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onLoadMore: (studioId, offset) => dispatch(
|
||||
actions.loadMore(projectFetcher.bind(null, studioId, offset))
|
||||
)
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(StudioProjects);
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
|
||||
import Page from '../../components/page/www/page.jsx';
|
||||
import render from '../../lib/render.jsx';
|
||||
|
||||
import StudioTabNav from './studio-tab-nav.jsx';
|
||||
import StudioProjects from './studio-projects.jsx';
|
||||
import StudioInfo from './studio-info.jsx';
|
||||
|
@ -16,6 +17,15 @@ import StudioCurators from './studio-curators.jsx';
|
|||
import StudioComments from './studio-comments.jsx';
|
||||
import StudioActivity from './studio-activity.jsx';
|
||||
|
||||
import {
|
||||
projects,
|
||||
curators,
|
||||
managers,
|
||||
activity
|
||||
} from './lib/redux-modules';
|
||||
|
||||
const {studioReducer} = require('../../redux/studio');
|
||||
|
||||
const StudioShell = () => {
|
||||
const match = useRouteMatch();
|
||||
|
||||
|
@ -59,5 +69,12 @@ render(
|
|||
</Switch>
|
||||
</Router>
|
||||
</Page>,
|
||||
document.getElementById('app')
|
||||
document.getElementById('app'),
|
||||
{
|
||||
[projects.key]: projects.reducer,
|
||||
[curators.key]: curators.reducer,
|
||||
[managers.key]: managers.reducer,
|
||||
[activity.key]: activity.reducer,
|
||||
studio: studioReducer
|
||||
}
|
||||
);
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 117 KiB After Width: | Height: | Size: 142 KiB |
|
@ -51,6 +51,36 @@ describe('Compose Comment test', () => {
|
|||
return wrapper.dive(); // unwrap redux connect(injectIntl(ComposeComment))
|
||||
};
|
||||
|
||||
test('status is EDITING when props do not contain a muteStatus ', () => {
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.state.status).toBe('EDITING');
|
||||
});
|
||||
|
||||
test('status is COMPOSE_DISALLOWED when props contain a future mute', () => {
|
||||
jest.useFakeTimers();
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
const mutedStore = mockStore({
|
||||
session: {
|
||||
session: {
|
||||
user: {},
|
||||
permissions: {
|
||||
mute_status: {
|
||||
muteExpiresAt: 5,
|
||||
offenses: [],
|
||||
showWarning: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const component = getComposeCommentWrapper({}, mutedStore);
|
||||
const commentInstance = component.instance();
|
||||
|
||||
expect(commentInstance.state.status).toBe('COMPOSE_DISALLOWED');
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('Modal & Comment status do not show ', () => {
|
||||
const component = getComposeCommentWrapper({});
|
||||
// Comment compsoe box is there
|
||||
|
@ -68,7 +98,10 @@ describe('Compose Comment test', () => {
|
|||
test('Error messages shows when comment rejected ', () => {
|
||||
const component = getComposeCommentWrapper({});
|
||||
const commentInstance = component.instance();
|
||||
commentInstance.setState({error: 'isFlood'});
|
||||
commentInstance.setState({
|
||||
error: 'isFlood',
|
||||
status: 'REJECTED'
|
||||
});
|
||||
component.update();
|
||||
expect(component.find('FlexRow.compose-error-row').exists()).toEqual(true);
|
||||
// Buttons stay enabled when comment rejected for non-mute reasons
|
||||
|
@ -76,24 +109,25 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('Button.compose-cancel').props().disabled).toBe(false);
|
||||
});
|
||||
|
||||
test('No error message shows when comment rejected because user muted ', () => {
|
||||
test('No error message shows when comment rejected because user is already muted ', () => {
|
||||
const component = getComposeCommentWrapper({});
|
||||
const commentInstance = component.instance();
|
||||
commentInstance.setState({
|
||||
error: 'isMuted',
|
||||
status: 'REJECTED_MUTE'
|
||||
status: 'COMPOSE_DISALLOWED'
|
||||
});
|
||||
component.update();
|
||||
expect(component.find('FlexRow.compose-error-row').exists()).toEqual(false);
|
||||
});
|
||||
|
||||
test('Comment Status shows but compose box does not when mute expiration in the future ', () => {
|
||||
test('Comment Status shows but compose box does not when you load the page and you are already muted', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
const component = getComposeCommentWrapper({});
|
||||
const commentInstance = component.instance();
|
||||
commentInstance.setState({muteExpiresAtMs: 100});
|
||||
commentInstance.setState({muteExpiresAtMs: 100, status: 'COMPOSE_DISALLOWED'});
|
||||
component.update();
|
||||
|
||||
// Compose box should be hidden if muted unless they got muted due to a comment they just posted.
|
||||
expect(component.find('FlexRow.compose-comment').exists()).toEqual(false);
|
||||
expect(component.find('MuteModal').exists()).toEqual(false);
|
||||
|
@ -172,7 +206,7 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('CommentingStatus').exists()).toEqual(true);
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
|
||||
test('Comment Status shows when user just submitted a reply comment that got them muted', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
|
@ -233,7 +267,7 @@ describe('Compose Comment test', () => {
|
|||
const commentInstance = component.instance();
|
||||
commentInstance.setState({
|
||||
error: 'some error',
|
||||
status: 'FLOOD'
|
||||
status: 'REJECTED'
|
||||
});
|
||||
component.update();
|
||||
expect(component.find('FlexRow.compose-error-row').exists()).toEqual(true);
|
||||
|
@ -335,7 +369,7 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('MuteModal').props().showFeedback).toBe(true);
|
||||
|
||||
commentInstance.setState({
|
||||
status: 'REJECTED_MUTE',
|
||||
status: 'COMPOSE_DISALLOWED',
|
||||
error: 'isMute',
|
||||
showWarning: true,
|
||||
muteOpen: true
|
||||
|
@ -356,7 +390,6 @@ describe('Compose Comment test', () => {
|
|||
expect(component.find('MuteModal').exists()).toEqual(true);
|
||||
expect(component.find('MuteModal').props().showFeedback).toBe(false);
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is false when muteStatus is undefined ', () => {
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal()).toBe(false);
|
||||
|
@ -389,7 +422,7 @@ describe('Compose Comment test', () => {
|
|||
offenses: [offense]
|
||||
};
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(true);
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus, true)).toBe(true);
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
|
@ -410,7 +443,7 @@ describe('Compose Comment test', () => {
|
|||
offenses: offenses
|
||||
};
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(false);
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus, true)).toBe(false);
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
|
@ -432,11 +465,47 @@ describe('Compose Comment test', () => {
|
|||
showWarning: true
|
||||
};
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus)).toBe(true);
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus, true)).toBe(true);
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('getMuteModalStartStep: not a reply ', () => {
|
||||
test('shouldShowMuteModal is false when the user is already muted, even when only 1 recent offesnse ', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
// Since Date.now mocked to 0 above, we just need a small number to make
|
||||
// it look like it was created < 2 minutes ago.
|
||||
const offense = {
|
||||
expiresAt: '1000',
|
||||
createdAt: '-60' // ~1 ago min given shouldShowMuteModal's conversions,
|
||||
};
|
||||
const muteStatus = {
|
||||
offenses: [offense]
|
||||
};
|
||||
const justMuted = false;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus, justMuted)).toBe(false);
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('shouldShowMuteModal is true when the user is already muted if the comment is a reply', () => {
|
||||
const realDateNow = Date.now.bind(global.Date);
|
||||
global.Date.now = () => 0;
|
||||
// Since Date.now mocked to 0 above, we just need a small number to make
|
||||
// it look like it was created < 2 minutes ago.
|
||||
const offense = {
|
||||
expiresAt: '1000',
|
||||
createdAt: '-60' // ~1 ago min given shouldShowMuteModal's conversions,
|
||||
};
|
||||
const muteStatus = {
|
||||
offenses: [offense]
|
||||
};
|
||||
const justMuted = false;
|
||||
const commentInstance = getComposeCommentWrapper({isReply: true}).instance();
|
||||
expect(commentInstance.shouldShowMuteModal(muteStatus, justMuted)).toBe(true);
|
||||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('getMuteModalStartStep: not a reply', () => {
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.getMuteModalStartStep()).toBe(0);
|
||||
});
|
||||
|
@ -452,7 +521,7 @@ describe('Compose Comment test', () => {
|
|||
test('getMuteModalStartStep: A reply click when already muted ', () => {
|
||||
const commentInstance = getComposeCommentWrapper({isReply: true}).instance();
|
||||
commentInstance.setState({
|
||||
status: 'EDITING'
|
||||
status: 'COMPOSE_DISALLOWED'
|
||||
});
|
||||
expect(commentInstance.getMuteModalStartStep()).toBe(1);
|
||||
});
|
||||
|
@ -486,20 +555,53 @@ describe('Compose Comment test', () => {
|
|||
global.Date.now = realDateNow;
|
||||
});
|
||||
|
||||
test('getMuteMessageInfo: muteType set', () => {
|
||||
test('getMuteMessageInfo: muteType set and just got muted', () => {
|
||||
const justMuted = true;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
commentInstance.setState({muteType: 'unconstructive'});
|
||||
expect(commentInstance.getMuteMessageInfo().commentType).toBe('comment.type.unconstructive');
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.unconstructive');
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted)
|
||||
.muteStepContent[0]).toBe('comment.unconstructive.content1');
|
||||
});
|
||||
|
||||
test('getMuteMessageInfo: muteType not set', () => {
|
||||
test('getMuteMessageInfo: muteType set and already muted', () => {
|
||||
const justMuted = false;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.getMuteMessageInfo().commentType).toBe('comment.type.general');
|
||||
commentInstance.setState({muteType: 'pii'});
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.pii.past');
|
||||
// PII has the same content1 regardless of whether you were just muted
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).muteStepContent[0]).toBe('comment.pii.content1');
|
||||
|
||||
commentInstance.setState({muteType: 'vulgarity'});
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.vulgarity.past');
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).muteStepContent[0]).toBe('comment.type.vulgarity.past');
|
||||
});
|
||||
|
||||
test('getMuteMessageInfo: muteType set to something we don\'t have messages for', () => {
|
||||
test('getMuteMessageInfo: muteType not set and just got muted', () => {
|
||||
const justMuted = true;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general');
|
||||
// general has the same content1 regardless of whether you were just muted
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).muteStepContent[0]).toBe('comment.general.content1');
|
||||
});
|
||||
|
||||
test('getMuteMessageInfo: muteType not set and already muted', () => {
|
||||
const justMuted = false;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general.past');
|
||||
});
|
||||
|
||||
test('getMuteMessageInfo: muteType set to something we don\'t have messages for and just got muted', () => {
|
||||
const justMuted = true;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
commentInstance.setState({muteType: 'spaghetti'});
|
||||
expect(commentInstance.getMuteMessageInfo().commentType).toBe('comment.type.general');
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general');
|
||||
});
|
||||
|
||||
test('getMuteMessageInfo: muteType set to something we don\'t have messages for and already muted', () => {
|
||||
const justMuted = false;
|
||||
const commentInstance = getComposeCommentWrapper({}).instance();
|
||||
commentInstance.setState({muteType: 'spaghetti'});
|
||||
expect(commentInstance.getMuteMessageInfo(justMuted).commentType).toBe('comment.type.general.past');
|
||||
});
|
||||
});
|
||||
|
|
175
test/unit/redux/infinite-list.test.js
Normal file
175
test/unit/redux/infinite-list.test.js
Normal file
|
@ -0,0 +1,175 @@
|
|||
/* global Promise */
|
||||
import InfiniteList from '../../../src/redux/infinite-list';
|
||||
|
||||
const module = InfiniteList('test-key');
|
||||
let initialState;
|
||||
describe('Infinite List redux module', () => {
|
||||
beforeEach(() => {
|
||||
initialState = module.reducer(undefined, {}); // eslint-disable-line no-undefined
|
||||
});
|
||||
|
||||
describe('reducer', () => {
|
||||
test('module contains a reducer', () => {
|
||||
expect(typeof module.reducer).toBe('function');
|
||||
});
|
||||
|
||||
test('initial state', () => {
|
||||
expect(initialState).toMatchObject({
|
||||
loading: true,
|
||||
error: null,
|
||||
items: [],
|
||||
moreToLoad: false
|
||||
});
|
||||
});
|
||||
|
||||
describe('LOADING', () => {
|
||||
let action;
|
||||
beforeEach(() => {
|
||||
action = module.actions.loading();
|
||||
initialState.loading = false;
|
||||
initialState.items = [1, 2, 3];
|
||||
initialState.error = new Error();
|
||||
});
|
||||
test('sets the loading state', () => {
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.loading).toBe(true);
|
||||
});
|
||||
test('maintains any existing data', () => {
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.items).toBe(initialState.items);
|
||||
});
|
||||
test('clears any existing error', () => {
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.error).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('APPEND', () => {
|
||||
let action;
|
||||
beforeEach(() => {
|
||||
action = module.actions.append([4, 5, 6], true);
|
||||
});
|
||||
test('appends the new items', () => {
|
||||
initialState.items = [1, 2, 3];
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.items).toEqual([1, 2, 3, 4, 5, 6]);
|
||||
});
|
||||
test('sets the moreToLoad state', () => {
|
||||
initialState.moreToLoad = false;
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.moreToLoad).toEqual(true);
|
||||
});
|
||||
test('clears any existing error and loading state', () => {
|
||||
initialState.error = new Error();
|
||||
initialState.loading = true;
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.error).toBe(null);
|
||||
expect(newState.error).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('REPLACE', () => {
|
||||
let action;
|
||||
beforeEach(() => {
|
||||
action = module.actions.replace(2, 55);
|
||||
});
|
||||
test('replaces the given index with the new item', () => {
|
||||
initialState.items = [8, 9, 10, 11];
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.items).toEqual([8, 9, 55, 11]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('REMOVE', () => {
|
||||
let action;
|
||||
beforeEach(() => {
|
||||
action = module.actions.remove(2);
|
||||
});
|
||||
test('removes the given index', () => {
|
||||
initialState.items = [8, 9, 10, 11];
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.items).toEqual([8, 9, 11]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CREATE', () => {
|
||||
let action;
|
||||
beforeEach(() => {
|
||||
action = module.actions.create(7);
|
||||
});
|
||||
test('prepends the given item', () => {
|
||||
initialState.items = [8, 9, 10, 11];
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.items).toEqual([7, 8, 9, 10, 11]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ERROR', () => {
|
||||
let action;
|
||||
let error = new Error();
|
||||
beforeEach(() => {
|
||||
action = module.actions.error(error);
|
||||
});
|
||||
test('sets the error state', () => {
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.error).toBe(error);
|
||||
});
|
||||
test('resets loading to false', () => {
|
||||
initialState.loading = true;
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.loading).toBe(false);
|
||||
});
|
||||
test('maintains any existing data', () => {
|
||||
initialState.items = [1, 2, 3];
|
||||
const newState = module.reducer(initialState, action);
|
||||
expect(newState.items).toEqual([1, 2, 3]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('action creators', () => {
|
||||
test('module contains actions creators', () => {
|
||||
// The actual action creators are tested above in the reducer tests
|
||||
for (let key in module.actions) {
|
||||
expect(typeof module.actions[key]).toBe('function');
|
||||
}
|
||||
});
|
||||
|
||||
describe('loadMore', () => {
|
||||
test('returns a thunk function, rather than a standard action object', () => {
|
||||
expect(typeof module.actions.loadMore()).toBe('function');
|
||||
});
|
||||
test('calls loading and the fetcher', () => {
|
||||
let dispatch = jest.fn();
|
||||
let fetcher = jest.fn(() => new Promise(() => { })); // that never resolves
|
||||
module.actions.loadMore(fetcher)(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(module.actions.loading());
|
||||
expect(fetcher).toHaveBeenCalled();
|
||||
});
|
||||
test('calls append with resolved result from fetcher', async () => {
|
||||
let dispatch = jest.fn();
|
||||
let fetcher = jest.fn(() => Promise.resolve({items: ['a', 'b'], moreToLoad: false}));
|
||||
await module.actions.loadMore(fetcher)(dispatch);
|
||||
expect(dispatch.mock.calls[1][0]) // the second call to dispatch, after LOADING
|
||||
.toEqual(module.actions.append(['a', 'b'], false));
|
||||
});
|
||||
test('calls error with rejecting promise from fetcher', async () => {
|
||||
let error = new Error();
|
||||
let dispatch = jest.fn();
|
||||
let fetcher = jest.fn(() => Promise.reject(error));
|
||||
await module.actions.loadMore(fetcher)(dispatch);
|
||||
expect(dispatch.mock.calls[1][0]) // the second call to dispatch, after LOADING
|
||||
.toEqual(module.actions.error(error));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('selector', () => {
|
||||
test('will return the slice of state defined by the key', () => {
|
||||
const state = {
|
||||
[module.key]: module.reducer(undefined, {}) // eslint-disable-line no-undefined
|
||||
};
|
||||
expect(module.selector(state)).toBe(initialState);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue