mirror of
https://github.com/scratchfoundation/scratch-www.git
synced 2024-11-23 23:57:55 -05:00
Merge pull request #5262 from LLK/release/2021-04-15
[Master] Release 2021-04-15
This commit is contained in:
commit
2367d8e143
31 changed files with 1506 additions and 809 deletions
|
@ -81,7 +81,7 @@ aliases:
|
|||
- run:
|
||||
name: "setup python"
|
||||
command: |
|
||||
curl https://bootstrap.pypa.io/3.5/get-pip.py -o get-pip.py
|
||||
curl https://bootstrap.pypa.io/pip/3.5/get-pip.py -o get-pip.py
|
||||
python3 get-pip.py pip==21.0.1
|
||||
pip install s3cmd==2.1.0
|
||||
- run:
|
||||
|
@ -134,7 +134,7 @@ jobs:
|
|||
# <<: *integration_tap
|
||||
|
||||
workflows:
|
||||
build-staging-production: # build-test-deploy
|
||||
build-test-deploy:
|
||||
jobs:
|
||||
- build-staging:
|
||||
context:
|
||||
|
@ -154,42 +154,39 @@ workflows:
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
# - deploy-staging:
|
||||
# context:
|
||||
# - scratch-www-all
|
||||
# - scratch-www-staging
|
||||
# requires:
|
||||
# - build-staging
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - develop
|
||||
# - /^hotfix\/.*/
|
||||
# - /^release\/.*/
|
||||
# - circleCI-configure-tests
|
||||
# - integration-staging-jest:
|
||||
# context:
|
||||
# - scratch-www-all
|
||||
# - scratch-www-staging
|
||||
# requires:
|
||||
# - deploy-staging
|
||||
# filters:
|
||||
# branches:
|
||||
# only:
|
||||
# - 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
|
||||
- deploy-staging:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
requires:
|
||||
- build-staging
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- integration-staging-jest:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
requires:
|
||||
- deploy-staging
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
- integration-staging-tap:
|
||||
context:
|
||||
- scratch-www-all
|
||||
- scratch-www-staging
|
||||
requires:
|
||||
- deploy-staging
|
||||
filters:
|
||||
branches:
|
||||
only:
|
||||
- develop
|
||||
- /^hotfix\/.*/
|
||||
- /^release\/.*/
|
||||
|
|
11
.travis.yml
11
.travis.yml
|
@ -114,15 +114,6 @@ jobs:
|
|||
include:
|
||||
- stage: test
|
||||
deploy:
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: npm run deploy
|
||||
on:
|
||||
repo: LLK/scratch-www
|
||||
branch:
|
||||
- develop
|
||||
- hotfix/*
|
||||
- release/*
|
||||
- provider: script
|
||||
skip_cleanup: $SKIP_CLEANUP
|
||||
script: npm run deploy
|
||||
|
@ -138,6 +129,6 @@ stages:
|
|||
- name: test
|
||||
if: type != cron
|
||||
- name: smoke
|
||||
if: type NOT IN (cron, pull_request) AND (branch =~ /^(develop|master|release\/|hotfix\/)/)
|
||||
if: type NOT IN (cron, pull_request) AND (branch =~ /^(master)/)
|
||||
- name: update translations
|
||||
if: branch == develop AND type == cron
|
||||
|
|
421
package-lock.json
generated
421
package-lock.json
generated
|
@ -16,9 +16,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/cli": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.10.tgz",
|
||||
"integrity": "sha512-lYSBC7B4B9hJ7sv0Ojx1BrGhuzCoOIYfLjd+Xpd4rOzdS+a47yi8voV8vFkfjlZR1N5qZO7ixOCbobUdT304PQ==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.13.14.tgz",
|
||||
"integrity": "sha512-zmEFV8WBRsW+mPQumO1/4b34QNALBVReaiHJOkxhUsdo/AvYM62c+SKSuLi2aZ42t3ocK6OI0uwUXRvrIbREZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@nicolo-ribaudo/chokidar-2": "2.1.8-no-fsevents",
|
||||
|
@ -34,9 +34,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"anymatch": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
|
||||
"integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz",
|
||||
"integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
|
@ -226,31 +226,30 @@
|
|||
}
|
||||
},
|
||||
"@babel/compat-data": {
|
||||
"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==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.13.15.tgz",
|
||||
"integrity": "sha512-ltnibHKR1VnrU4ymHyQ/CXtNXI6yZC0oJThyW78Hft8XndANwi+9H+UIklBDraIjFEJzw8wmcM427oDd9KS5wA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/core": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.10.tgz",
|
||||
"integrity": "sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.13.15.tgz",
|
||||
"integrity": "sha512-6GXmNYeNjS2Uz+uls5jalOemgIhnTMeaXo+yBUA72kC2uX/8VW6XyhVIo2L8/q0goKQA3EVKx0KOQpVKSeWadQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/helper-compilation-targets": "^7.13.10",
|
||||
"@babel/helper-module-transforms": "^7.13.0",
|
||||
"@babel/helper-compilation-targets": "^7.13.13",
|
||||
"@babel/helper-module-transforms": "^7.13.14",
|
||||
"@babel/helpers": "^7.13.10",
|
||||
"@babel/parser": "^7.13.10",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/template": "^7.12.13",
|
||||
"@babel/traverse": "^7.13.0",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@babel/traverse": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"convert-source-map": "^1.7.0",
|
||||
"debug": "^4.1.0",
|
||||
"gensync": "^1.0.0-beta.2",
|
||||
"json5": "^2.1.2",
|
||||
"lodash": "^4.17.19",
|
||||
"semver": "^6.3.0",
|
||||
"source-map": "^0.5.0"
|
||||
},
|
||||
|
@ -316,9 +315,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
|
||||
"integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -333,26 +332,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz",
|
||||
"integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
|
||||
"integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.0",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.13.0",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.19"
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -494,34 +492,34 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-compilation-targets": {
|
||||
"version": "7.13.10",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz",
|
||||
"integrity": "sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA==",
|
||||
"version": "7.13.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.13.tgz",
|
||||
"integrity": "sha512-q1kcdHNZehBwD9jYPh3WyXcsFERi39X4I59I3NadciWtNDyZ6x+GboOxncFK0kXlKIv6BJm5acncehXWUjWQMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/compat-data": "^7.13.8",
|
||||
"@babel/compat-data": "^7.13.12",
|
||||
"@babel/helper-validator-option": "^7.12.17",
|
||||
"browserslist": "^4.14.5",
|
||||
"semver": "^6.3.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"browserslist": {
|
||||
"version": "4.16.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz",
|
||||
"integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==",
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz",
|
||||
"integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001181",
|
||||
"colorette": "^1.2.1",
|
||||
"electron-to-chromium": "^1.3.649",
|
||||
"caniuse-lite": "^1.0.30001208",
|
||||
"colorette": "^1.2.2",
|
||||
"electron-to-chromium": "^1.3.712",
|
||||
"escalade": "^3.1.1",
|
||||
"node-releases": "^1.1.70"
|
||||
"node-releases": "^1.1.71"
|
||||
}
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"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==",
|
||||
"version": "1.3.717",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz",
|
||||
"integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
|
@ -562,9 +560,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -596,9 +594,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -621,9 +619,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/helper-module-transforms": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.13.14.tgz",
|
||||
"integrity": "sha512-QuU/OJ0iAOSIatyVZmfqB0lbkVP0kDRiKj34xy+QNsnVZi/PA6BoSoreeqnxxa9EHFAIL0R9XOaAR/G9WlIy5g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-module-imports": "^7.13.12",
|
||||
|
@ -632,8 +630,8 @@
|
|||
"@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.12"
|
||||
"@babel/traverse": "^7.13.13",
|
||||
"@babel/types": "^7.13.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@babel/code-frame": {
|
||||
|
@ -697,9 +695,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
|
||||
"integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -714,26 +712,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz",
|
||||
"integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
|
||||
"integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.0",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.13.0",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.19"
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -827,9 +824,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -930,9 +927,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
|
||||
"integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -947,26 +944,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz",
|
||||
"integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
|
||||
"integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.0",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.13.0",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.19"
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -1060,9 +1056,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -1177,9 +1173,9 @@
|
|||
}
|
||||
},
|
||||
"@babel/parser": {
|
||||
"version": "7.13.12",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.12.tgz",
|
||||
"integrity": "sha512-4T7Pb244rxH24yR116LAuJ+adxXXnHhZaLJjegJVKSdoNCe4x1eDBaud5YIcQFcqzsaD5BHvJw5BQ0AZapdCRw==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.13.15.tgz",
|
||||
"integrity": "sha512-b9COtcAlVEQljy/9fbcMHpG+UIW9ReF+gpaxDHTlZd0c6/UU9ng8zdySAW9sRTzpvcdCHn6bUcbuYUgGzLAWVQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/template": {
|
||||
|
@ -1194,26 +1190,25 @@
|
|||
}
|
||||
},
|
||||
"@babel/traverse": {
|
||||
"version": "7.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.0.tgz",
|
||||
"integrity": "sha512-xys5xi5JEhzC3RzEmSGrs/b3pJW/o87SypZ+G/PhaE7uqVQNv/jlmVIBXuoh5atqQ434LfXV+sf23Oxj0bchJQ==",
|
||||
"version": "7.13.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.13.15.tgz",
|
||||
"integrity": "sha512-/mpZMNvj6bce59Qzl09fHEs8Bt8NnpEDQYleHUPZQ3wXUMvXi+HJPLars68oAbmp839fGoOkv2pSL2z9ajCIaQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/code-frame": "^7.12.13",
|
||||
"@babel/generator": "^7.13.0",
|
||||
"@babel/generator": "^7.13.9",
|
||||
"@babel/helper-function-name": "^7.12.13",
|
||||
"@babel/helper-split-export-declaration": "^7.12.13",
|
||||
"@babel/parser": "^7.13.0",
|
||||
"@babel/types": "^7.13.0",
|
||||
"@babel/parser": "^7.13.15",
|
||||
"@babel/types": "^7.13.14",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0",
|
||||
"lodash": "^4.17.19"
|
||||
"globals": "^11.1.0"
|
||||
}
|
||||
},
|
||||
"@babel/types": {
|
||||
"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==",
|
||||
"version": "7.13.14",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.13.14.tgz",
|
||||
"integrity": "sha512-A2aa3QTkWoyqsZZFl56MLUsfmh7O0gN41IPvXAE/++8ojpbz12SszD7JEGYVdn4f9Kt4amIei07swF1h4AqmmQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/helper-validator-identifier": "^7.12.11",
|
||||
|
@ -1437,26 +1432,26 @@
|
|||
}
|
||||
},
|
||||
"@formatjs/ecma402-abstract": {
|
||||
"version": "1.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.6.3.tgz",
|
||||
"integrity": "sha512-7ijswObmYXabVy5GvcpKG29jbyJ9rGtFdRBdmdQvoDmMo0PwlOl/L08GtrjA4YWLAZ0j2owb2YrRLGNAvLBk+Q==",
|
||||
"version": "1.6.5",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-1.6.5.tgz",
|
||||
"integrity": "sha512-dhRWSoPPw8PhB5tSOEP9Gi5XZNFC2IkfP95Va70ouIuED0wBlsU1WmO4jDHITL7/kSNqvzKFTT+2S+6jHPq6jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
|
||||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-getcanonicallocales": {
|
||||
"version": "1.5.7",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-1.5.7.tgz",
|
||||
"integrity": "sha512-raPV3Dw7CBC9kPvKdgxkVGgwzYBsQDDG9qXGWblpj/zR+ZJ6Q2V+Co5jZhrviy6lq3qaM2T1Itc0ibvvil1tBw==",
|
||||
"version": "1.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-getcanonicallocales/-/intl-getcanonicallocales-1.5.9.tgz",
|
||||
"integrity": "sha512-bO0J3IaamFM3rU7noXo1bWSmPA8xuAL8NPk+k5Dy08ehiu/STT3sN+6DGLEvRCpb465CpjUWGCNknDFIcdu9hA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"cldr-core": "38",
|
||||
|
@ -1464,65 +1459,65 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
|
||||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-locale": {
|
||||
"version": "2.4.20",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-locale/-/intl-locale-2.4.20.tgz",
|
||||
"integrity": "sha512-ZrVFxKab+W6jFP6WEYsNW0b7IlGYnCS20fdLN6u0LwPCPYRP5oqHBl0FFVD2+aNnQ1T/21Aol54fCr5LdN/49Q==",
|
||||
"version": "2.4.22",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-locale/-/intl-locale-2.4.22.tgz",
|
||||
"integrity": "sha512-/GECNZDORaz1sHsnhUZHTCmoPRux5w1D2dX1vHb2QY/f3YENsA4U/WqmIBHk+77RU/KTFbTQgxGcfejneAOPoA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.6.3",
|
||||
"@formatjs/intl-getcanonicallocales": "1.5.7",
|
||||
"@formatjs/ecma402-abstract": "1.6.5",
|
||||
"@formatjs/intl-getcanonicallocales": "1.5.9",
|
||||
"cldr-core": "38",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
|
||||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-pluralrules": {
|
||||
"version": "4.0.12",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.0.12.tgz",
|
||||
"integrity": "sha512-jXXsWGQbBMvuhvxuG1AXBMMNMS1ZphSt/rWsGo6bE3KyWmddJnnVokeUD8E2sTtXoCJZoGUQkOxxjFa/gGLyxw==",
|
||||
"version": "4.0.14",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-pluralrules/-/intl-pluralrules-4.0.14.tgz",
|
||||
"integrity": "sha512-smfNDGaVZwFqKKqQlJfuKO45+RJs+UqyMD/8OJpIRpnYSss29T6T4jM5jzI9ZcbuTc79axI4no8wyViLBuGFMQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.6.3",
|
||||
"@formatjs/ecma402-abstract": "1.6.5",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
|
||||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"@formatjs/intl-relativetimeformat": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-8.1.3.tgz",
|
||||
"integrity": "sha512-uUbtr4NRKgHK66bxO98RQlXypfRA5cto6nsFpgwe5wf1SWLBqxcNoo+zdAIftdvHnxPC4QH6oykMf2aYLm+pbA==",
|
||||
"version": "8.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@formatjs/intl-relativetimeformat/-/intl-relativetimeformat-8.1.5.tgz",
|
||||
"integrity": "sha512-vCZ54nn2sPfIie7KQwbwT2xjRurvdGfJ036wPQFnOpICdqe8/vFKaS/kYRSw42fREGh1sefVcH0DZAz/GZczyw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@formatjs/ecma402-abstract": "1.6.3",
|
||||
"@formatjs/ecma402-abstract": "1.6.5",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz",
|
||||
"integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==",
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.2.0.tgz",
|
||||
"integrity": "sha512-gS9GVHRU+RGn5KQM2rllAlR3dU6m7AcpJKdtH8gFvQiC4Otgk98XnmMU+nZenHt/+VhnBPWwgrJsyrdcw6i23w==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -2041,9 +2036,9 @@
|
|||
}
|
||||
},
|
||||
"@polka/url": {
|
||||
"version": "1.0.0-next.11",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.11.tgz",
|
||||
"integrity": "sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==",
|
||||
"version": "1.0.0-next.12",
|
||||
"resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.12.tgz",
|
||||
"integrity": "sha512-6RglhutqrGFMO1MNUXp95RBuYIuc8wTnMAV5MUhLmjTOy78ncwOw7RgeQ/HeymkKXRhZd0s2DNrM1rL7unk3MQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@scratch/paper": {
|
||||
|
@ -4700,12 +4695,6 @@
|
|||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -4860,9 +4849,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"caniuse-lite": {
|
||||
"version": "1.0.30001191",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001191.tgz",
|
||||
"integrity": "sha512-xJJqzyd+7GCJXkcoBiQ1GuxEiOBCLQ0aVW9HMekifZsAVGdj5eJ4mFB9fEhSHipq9IOk/QXFJUiIr9lZT+EsGw==",
|
||||
"version": "1.0.30001208",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001208.tgz",
|
||||
"integrity": "sha512-OE5UE4+nBOro8Dyvv0lfx+SRtfVIOM9uhKqFmJeUbGriqhhStgp1A0OyBpgy3OUF8AhYCT+PVwPC1gMl2ZcQMA==",
|
||||
"dev": true
|
||||
},
|
||||
"canvas-fit": {
|
||||
|
@ -5350,9 +5339,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"colorette": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz",
|
||||
"integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==",
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz",
|
||||
"integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==",
|
||||
"dev": true
|
||||
},
|
||||
"colormap": {
|
||||
|
@ -12665,6 +12654,12 @@
|
|||
"integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
|
||||
"dev": true
|
||||
},
|
||||
"y18n": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz",
|
||||
"integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
|
||||
|
@ -13411,6 +13406,12 @@
|
|||
"signal-exit": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz",
|
||||
"integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-11.1.0.tgz",
|
||||
|
@ -14271,6 +14272,11 @@
|
|||
"integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
|
||||
"dev": true
|
||||
},
|
||||
"lodash.get": {
|
||||
"version": "4.4.2",
|
||||
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
|
||||
"integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk="
|
||||
},
|
||||
"lodash.isarguments": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz",
|
||||
|
@ -15809,9 +15815,9 @@
|
|||
}
|
||||
},
|
||||
"node-releases": {
|
||||
"version": "1.1.70",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.70.tgz",
|
||||
"integrity": "sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw==",
|
||||
"version": "1.1.71",
|
||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz",
|
||||
"integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==",
|
||||
"dev": true
|
||||
},
|
||||
"node-sass": {
|
||||
|
@ -16247,12 +16253,6 @@
|
|||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
|
@ -20797,12 +20797,6 @@
|
|||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
|
@ -21019,16 +21013,16 @@
|
|||
"dev": true
|
||||
},
|
||||
"browserslist": {
|
||||
"version": "4.16.3",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.3.tgz",
|
||||
"integrity": "sha512-vIyhWmIkULaq04Gt93txdh+j02yX/JzlyhLYbV3YQCn/zvES3JnY7TifHHvvr1w5hTDluNKMkV05cs4vy8Q7sw==",
|
||||
"version": "4.16.4",
|
||||
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.4.tgz",
|
||||
"integrity": "sha512-d7rCxYV8I9kj41RH8UKYnvDYCRENUlHRgyXy/Rhr/1BaeLGfiCptEdFE8MIrvGfWbBFNjVYx76SQWvNX1j+/cQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"caniuse-lite": "^1.0.30001181",
|
||||
"colorette": "^1.2.1",
|
||||
"electron-to-chromium": "^1.3.649",
|
||||
"caniuse-lite": "^1.0.30001208",
|
||||
"colorette": "^1.2.2",
|
||||
"electron-to-chromium": "^1.3.712",
|
||||
"escalade": "^3.1.1",
|
||||
"node-releases": "^1.1.70"
|
||||
"node-releases": "^1.1.71"
|
||||
}
|
||||
},
|
||||
"chalk": {
|
||||
|
@ -21142,9 +21136,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"electron-to-chromium": {
|
||||
"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==",
|
||||
"version": "1.3.717",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.717.tgz",
|
||||
"integrity": "sha512-OfzVPIqD1MkJ7fX+yTl2nKyOE4FReeVfMCzzxQS+Kp43hZYwHwThlGP+EGIZRXJsxCM7dqo8Y65NOX/HP12iXQ==",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
|
@ -21364,6 +21358,19 @@
|
|||
"integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=",
|
||||
"dev": true
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"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",
|
||||
"@babel/core": "^7.1.2",
|
||||
"babel-plugin-react-intl": "^3.0.1",
|
||||
"react-intl": "^2.8.0",
|
||||
"transifex": "1.6.6"
|
||||
}
|
||||
},
|
||||
"scratch-storage": {
|
||||
"version": "1.3.3",
|
||||
"resolved": "https://registry.npmjs.org/scratch-storage/-/scratch-storage-1.3.3.tgz",
|
||||
|
@ -21431,9 +21438,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-l10n": {
|
||||
"version": "3.11.20210324031512",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210324031512.tgz",
|
||||
"integrity": "sha512-dyv2cjNWVHrL78XpW64uF2azYaUhMKLjfJVH6vSCPJxm3EKCvO6EoaUSlIAwhsGoMwgfxWZ8D74+IaifGJfnCQ==",
|
||||
"version": "3.11.20210413031556",
|
||||
"resolved": "https://registry.npmjs.org/scratch-l10n/-/scratch-l10n-3.11.20210413031556.tgz",
|
||||
"integrity": "sha512-mmD0wvRCgkVgx7scN14CQ6NPDqaL0uT//ZFQWpp5foTBuXokXPh9jRrJ+KScCeXK7jaXMLeJa6r4q1ivpnVXGQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@babel/cli": "^7.1.2",
|
||||
|
@ -21457,7 +21464,7 @@
|
|||
"minilog": "3.1.0",
|
||||
"parse-color": "1.0.0",
|
||||
"prop-types": "^15.5.10",
|
||||
"scratch-render-fonts": "^1.0.0-prerelease.20200507182347"
|
||||
"scratch-render-fonts": "^1.0.0-prerelease.20210401210003"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash.omit": {
|
||||
|
@ -21482,9 +21489,9 @@
|
|||
}
|
||||
},
|
||||
"scratch-render-fonts": {
|
||||
"version": "1.0.0-prerelease.20200507182347",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render-fonts/-/scratch-render-fonts-1.0.0-prerelease.20200507182347.tgz",
|
||||
"integrity": "sha512-tVt2s7lxsBhme9WKIZTnluMerdJVGEc80QSrDdIIzUvHXGCIYkLh6j7ytwXcyq2UsA34d93op9+I9Bh1SPkQkA==",
|
||||
"version": "1.0.0-prerelease.20210401210003",
|
||||
"resolved": "https://registry.npmjs.org/scratch-render-fonts/-/scratch-render-fonts-1.0.0-prerelease.20210401210003.tgz",
|
||||
"integrity": "sha512-sgU+LIXTLKk4f7FZOv/B61dpvmfpnlXFf912T6T4GpOfzx99JPRhXPyErZWuwPz8NEzthkhpO7iF2AqgzUxJfA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"base64-loader": "1.0.0"
|
||||
|
@ -25223,12 +25230,6 @@
|
|||
"xtend": "~4.0.1"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
|
||||
|
@ -27478,9 +27479,9 @@
|
|||
}
|
||||
},
|
||||
"webpack-bundle-analyzer": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.0.tgz",
|
||||
"integrity": "sha512-9DhNa+aXpqdHk8LkLPTBU/dMfl84Y+WE2+KnfI6rSpNRNVKa0VGLjPd2pjFubDeqnWmulFggxmWBxhfJXZnR0g==",
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.4.1.tgz",
|
||||
"integrity": "sha512-j5m7WgytCkiVBoOGavzNokBOqxe6Mma13X1asfVYtKWM3wxBiRRu1u1iG0Iol5+qp9WgyhkMmBAcvjEfJ2bdDw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"acorn": "^8.0.4",
|
||||
|
@ -27495,9 +27496,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"acorn": {
|
||||
"version": "8.0.5",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.5.tgz",
|
||||
"integrity": "sha512-v+DieK/HJkJOpFBETDJioequtc3PfxsWMaxIdIwujtF7FEV/MAyDQLlm6/zPvr7Mix07mLh6ccVwIsloceodlg==",
|
||||
"version": "8.1.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.1.1.tgz",
|
||||
"integrity": "sha512-xYiIVjNuqtKXMxlRMDc6mZUhXehod4a3gbZ1qRlM7icK4EbxUFNLhWoPblCvFtB2Y9CIqHP3CF/rdxLItaQv8g==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn-walk": {
|
||||
|
@ -27558,12 +27559,6 @@
|
|||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"dev": true
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
|
||||
|
@ -27574,9 +27569,9 @@
|
|||
}
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz",
|
||||
"integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==",
|
||||
"version": "7.4.4",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.4.tgz",
|
||||
"integrity": "sha512-Qm8k8ojNQIMx7S+Zp8u/uHOx7Qazv3Yv4q68MiWWWOJhiwG5W3x7iqmRtJo8xxrciZUY4vRxUTJCKuRnF28ZZw==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -27785,12 +27780,6 @@
|
|||
"strip-ansi": "^5.0.0"
|
||||
}
|
||||
},
|
||||
"y18n": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yargs": {
|
||||
"version": "13.3.2",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz",
|
||||
|
@ -28159,9 +28148,9 @@
|
|||
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
|
||||
},
|
||||
"y18n": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz",
|
||||
"integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=",
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.1.tgz",
|
||||
"integrity": "sha512-wNcy4NvjMYL8gogWWYAO7ZFWFfHcbdbE57tZO8e4cbpj8tfUcwrwqSl3ad8HxpYWCdXcJUCeKKZS62Av1affwQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
|
|
|
@ -19,9 +19,9 @@
|
|||
"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:tap": "tap ./test/{unit-legacy,localization-legacy}/ --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",
|
||||
"test:coverage": "tap ./test/{unit-legacy,localization-legacy}/ --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",
|
||||
|
@ -53,6 +53,7 @@
|
|||
"express": "4.16.1",
|
||||
"express-http-proxy": "1.1.0",
|
||||
"lodash.defaults": "4.0.1",
|
||||
"lodash.get": "^4.4.2",
|
||||
"react-helmet": "5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
"scratch-parser": "^5.0.0",
|
||||
|
|
|
@ -2,129 +2,145 @@
|
|||
{
|
||||
"id": 1,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 9,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 11,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 13,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 14,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 15,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
},
|
||||
{
|
||||
"id": 16,
|
||||
"type": "project",
|
||||
"title": "Project",
|
||||
"title": "Project Title",
|
||||
"thumbnailUrl": "",
|
||||
"creator": "",
|
||||
"href": "#"
|
||||
"author": {"username": "project creator"},
|
||||
"href": "#",
|
||||
"stats": {}
|
||||
}
|
||||
]
|
||||
|
|
191
src/redux/comments.js
Normal file
191
src/redux/comments.js
Normal file
|
@ -0,0 +1,191 @@
|
|||
const keyMirror = require('keymirror');
|
||||
const mergeWith = require('lodash.mergewith');
|
||||
const uniqBy = require('lodash.uniqby');
|
||||
|
||||
const COMMENT_LIMIT = 20;
|
||||
|
||||
module.exports.Status = keyMirror({
|
||||
FETCHED: null,
|
||||
NOT_FETCHED: null,
|
||||
FETCHING: null,
|
||||
ERROR: null
|
||||
});
|
||||
|
||||
module.exports.getInitialState = () => ({
|
||||
status: {
|
||||
comments: module.exports.Status.NOT_FETCHED
|
||||
},
|
||||
comments: [],
|
||||
replies: {},
|
||||
moreCommentsToLoad: false
|
||||
});
|
||||
|
||||
module.exports.commentsReducer = (state, action) => {
|
||||
if (typeof state === 'undefined') {
|
||||
state = module.exports.getInitialState();
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case 'RESET_TO_INTIAL_STATE':
|
||||
return module.exports.getInitialState();
|
||||
case 'RESET_COMMENTS':
|
||||
return Object.assign({}, state, {
|
||||
comments: [],
|
||||
replies: {}
|
||||
});
|
||||
case 'SET_COMMENT_FETCH_STATUS':
|
||||
return Object.assign({}, state, {
|
||||
status: Object.assign({}, state.status, {
|
||||
[action.infoType]: action.status
|
||||
})
|
||||
});
|
||||
case 'SET_COMMENTS':
|
||||
return Object.assign({}, state, {
|
||||
comments: uniqBy(state.comments.concat(action.items), 'id')
|
||||
});
|
||||
case 'UPDATE_COMMENT':
|
||||
if (action.topLevelCommentId) {
|
||||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
return Object.assign({}, comment, action.comment);
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, state, {
|
||||
comments: state.comments.map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
return Object.assign({}, comment, action.comment);
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
case 'ADD_NEW_COMMENT':
|
||||
if (action.topLevelCommentId) {
|
||||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
// Replies to comments go at the end of the thread
|
||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].concat(action.comment)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Reply to the top level project, put the reply at the beginning
|
||||
return Object.assign({}, state, {
|
||||
comments: [action.comment, ...state.comments],
|
||||
replies: Object.assign({}, state.replies, {[action.comment.id]: []})
|
||||
});
|
||||
case 'UPDATE_ALL_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
[action.commentId]: state.replies[action.commentId].map(reply =>
|
||||
Object.assign({}, reply, action.comment)
|
||||
)
|
||||
})
|
||||
});
|
||||
case 'SET_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
// Append new replies to the state.replies structure
|
||||
replies: mergeWith({}, state.replies, action.replies, (replies, newReplies) => (
|
||||
uniqBy((replies || []).concat(newReplies || []), 'id')
|
||||
)),
|
||||
// Also set the `moreRepliesToLoad` property on the top-level comments
|
||||
comments: state.comments.map(comment => {
|
||||
if (action.replies[comment.id]) {
|
||||
return Object.assign({}, comment, {
|
||||
moreRepliesToLoad: action.replies[comment.id].length === COMMENT_LIMIT
|
||||
});
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
case 'SET_MORE_COMMENTS_TO_LOAD':
|
||||
return Object.assign({}, state, {
|
||||
moreCommentsToLoad: action.moreCommentsToLoad
|
||||
});
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports.setFetchStatus = (type, status) => ({
|
||||
type: 'SET_COMMENT_FETCH_STATUS',
|
||||
infoType: type,
|
||||
status: status
|
||||
});
|
||||
|
||||
module.exports.setComments = items => ({
|
||||
type: 'SET_COMMENTS',
|
||||
items: items
|
||||
});
|
||||
|
||||
module.exports.setReplies = replies => ({
|
||||
type: 'SET_REPLIES',
|
||||
replies: replies
|
||||
});
|
||||
|
||||
module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({
|
||||
type: 'UPDATE_COMMENT',
|
||||
commentId: commentId,
|
||||
topLevelCommentId: topLevelCommentId,
|
||||
comment: {
|
||||
visibility: 'deleted'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setRepliesDeleted = commentId => ({
|
||||
type: 'UPDATE_ALL_REPLIES',
|
||||
commentId: commentId,
|
||||
comment: {
|
||||
visibility: 'deleted'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setCommentReported = (commentId, topLevelCommentId) => ({
|
||||
type: 'UPDATE_COMMENT',
|
||||
commentId: commentId,
|
||||
topLevelCommentId: topLevelCommentId,
|
||||
comment: {
|
||||
visibility: 'reported'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({
|
||||
type: 'UPDATE_COMMENT',
|
||||
commentId: commentId,
|
||||
topLevelCommentId: topLevelCommentId,
|
||||
comment: {
|
||||
visibility: 'visible'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setRepliesRestored = commentId => ({
|
||||
type: 'UPDATE_ALL_REPLIES',
|
||||
commentId: commentId,
|
||||
comment: {
|
||||
visibility: 'visible'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.addNewComment = (comment, topLevelCommentId) => ({
|
||||
type: 'ADD_NEW_COMMENT',
|
||||
comment: comment,
|
||||
topLevelCommentId: topLevelCommentId
|
||||
});
|
||||
|
||||
module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({
|
||||
type: 'SET_MORE_COMMENTS_TO_LOAD',
|
||||
moreCommentsToLoad: moreCommentsToLoad
|
||||
});
|
||||
|
||||
module.exports.resetComments = () => ({
|
||||
type: 'RESET_COMMENTS'
|
||||
});
|
||||
|
||||
// Selectors
|
||||
module.exports.selectCommentCount = state => state.comments.comments.length;
|
|
@ -1,14 +1,9 @@
|
|||
const defaults = require('lodash.defaults');
|
||||
const keyMirror = require('keymirror');
|
||||
const eachLimit = require('async/eachLimit');
|
||||
const mergeWith = require('lodash.mergewith');
|
||||
const uniqBy = require('lodash.uniqby');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const COMMENT_LIMIT = 20;
|
||||
|
||||
module.exports.Status = keyMirror({
|
||||
FETCHED: null,
|
||||
NOT_FETCHED: null,
|
||||
|
@ -19,7 +14,6 @@ module.exports.Status = keyMirror({
|
|||
module.exports.getInitialState = () => ({
|
||||
status: {
|
||||
project: module.exports.Status.NOT_FETCHED,
|
||||
comments: module.exports.Status.NOT_FETCHED,
|
||||
faved: module.exports.Status.NOT_FETCHED,
|
||||
loved: module.exports.Status.NOT_FETCHED,
|
||||
original: module.exports.Status.NOT_FETCHED,
|
||||
|
@ -33,8 +27,6 @@ module.exports.getInitialState = () => ({
|
|||
},
|
||||
projectInfo: {},
|
||||
remixes: [],
|
||||
comments: [],
|
||||
replies: {},
|
||||
faved: false,
|
||||
loved: false,
|
||||
original: {},
|
||||
|
@ -42,7 +34,6 @@ module.exports.getInitialState = () => ({
|
|||
projectStudios: [],
|
||||
curatedStudios: [],
|
||||
currentStudioIds: [],
|
||||
moreCommentsToLoad: false,
|
||||
projectNotAvailable: false,
|
||||
visibilityInfo: {}
|
||||
});
|
||||
|
@ -96,76 +87,6 @@ module.exports.previewReducer = (state, action) => {
|
|||
item !== action.studioId
|
||||
))
|
||||
});
|
||||
case 'RESET_COMMENTS':
|
||||
return Object.assign({}, state, {
|
||||
comments: [],
|
||||
replies: {}
|
||||
});
|
||||
case 'SET_COMMENTS':
|
||||
return Object.assign({}, state, {
|
||||
comments: uniqBy(state.comments.concat(action.items), 'id')
|
||||
});
|
||||
case 'UPDATE_COMMENT':
|
||||
if (action.topLevelCommentId) {
|
||||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
return Object.assign({}, comment, action.comment);
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
return Object.assign({}, state, {
|
||||
comments: state.comments.map(comment => {
|
||||
if (comment.id === action.commentId) {
|
||||
return Object.assign({}, comment, action.comment);
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
case 'ADD_NEW_COMMENT':
|
||||
if (action.topLevelCommentId) {
|
||||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
// Replies to comments go at the end of the thread
|
||||
[action.topLevelCommentId]: state.replies[action.topLevelCommentId].concat(action.comment)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Reply to the top level project, put the reply at the beginning
|
||||
return Object.assign({}, state, {
|
||||
comments: [action.comment, ...state.comments],
|
||||
replies: Object.assign({}, state.replies, {[action.comment.id]: []})
|
||||
});
|
||||
case 'UPDATE_ALL_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
replies: Object.assign({}, state.replies, {
|
||||
[action.commentId]: state.replies[action.commentId].map(reply =>
|
||||
Object.assign({}, reply, action.comment)
|
||||
)
|
||||
})
|
||||
});
|
||||
case 'SET_REPLIES':
|
||||
return Object.assign({}, state, {
|
||||
// Append new replies to the state.replies structure
|
||||
replies: mergeWith({}, state.replies, action.replies, (replies, newReplies) => (
|
||||
uniqBy((replies || []).concat(newReplies || []), 'id')
|
||||
)),
|
||||
// Also set the `moreRepliesToLoad` property on the top-level comments
|
||||
comments: state.comments.map(comment => {
|
||||
if (action.replies[comment.id]) {
|
||||
return Object.assign({}, comment, {
|
||||
moreRepliesToLoad: action.replies[comment.id].length === COMMENT_LIMIT
|
||||
});
|
||||
}
|
||||
return comment;
|
||||
})
|
||||
});
|
||||
case 'SET_LOVED':
|
||||
return Object.assign({}, state, {
|
||||
loved: action.info
|
||||
|
@ -182,10 +103,6 @@ module.exports.previewReducer = (state, action) => {
|
|||
state = JSON.parse(JSON.stringify(state));
|
||||
state.status.studioRequests[action.studioId] = action.status;
|
||||
return state;
|
||||
case 'SET_MORE_COMMENTS_TO_LOAD':
|
||||
return Object.assign({}, state, {
|
||||
moreCommentsToLoad: action.moreCommentsToLoad
|
||||
});
|
||||
case 'SET_VISIBILITY_INFO':
|
||||
return Object.assign({}, state, {
|
||||
visibilityInfo: action.visibilityInfo
|
||||
|
@ -247,16 +164,6 @@ module.exports.setProjectStudios = items => ({
|
|||
items: items
|
||||
});
|
||||
|
||||
module.exports.setComments = items => ({
|
||||
type: 'SET_COMMENTS',
|
||||
items: items
|
||||
});
|
||||
|
||||
module.exports.setReplies = replies => ({
|
||||
type: 'SET_REPLIES',
|
||||
replies: replies
|
||||
});
|
||||
|
||||
module.exports.setCuratedStudios = items => ({
|
||||
type: 'SET_CURATED_STUDIOS',
|
||||
items: items
|
||||
|
@ -284,64 +191,6 @@ module.exports.setStudioFetchStatus = (studioId, status) => ({
|
|||
status: status
|
||||
});
|
||||
|
||||
module.exports.setCommentDeleted = (commentId, topLevelCommentId) => ({
|
||||
type: 'UPDATE_COMMENT',
|
||||
commentId: commentId,
|
||||
topLevelCommentId: topLevelCommentId,
|
||||
comment: {
|
||||
visibility: 'deleted'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setRepliesDeleted = commentId => ({
|
||||
type: 'UPDATE_ALL_REPLIES',
|
||||
commentId: commentId,
|
||||
comment: {
|
||||
visibility: 'deleted'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setCommentReported = (commentId, topLevelCommentId) => ({
|
||||
type: 'UPDATE_COMMENT',
|
||||
commentId: commentId,
|
||||
topLevelCommentId: topLevelCommentId,
|
||||
comment: {
|
||||
visibility: 'reported'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setCommentRestored = (commentId, topLevelCommentId) => ({
|
||||
type: 'UPDATE_COMMENT',
|
||||
commentId: commentId,
|
||||
topLevelCommentId: topLevelCommentId,
|
||||
comment: {
|
||||
visibility: 'visible'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.setRepliesRestored = commentId => ({
|
||||
type: 'UPDATE_ALL_REPLIES',
|
||||
commentId: commentId,
|
||||
comment: {
|
||||
visibility: 'visible'
|
||||
}
|
||||
});
|
||||
|
||||
module.exports.addNewComment = (comment, topLevelCommentId) => ({
|
||||
type: 'ADD_NEW_COMMENT',
|
||||
comment: comment,
|
||||
topLevelCommentId: topLevelCommentId
|
||||
});
|
||||
|
||||
module.exports.setMoreCommentsToLoad = moreCommentsToLoad => ({
|
||||
type: 'SET_MORE_COMMENTS_TO_LOAD',
|
||||
moreCommentsToLoad: moreCommentsToLoad
|
||||
});
|
||||
|
||||
module.exports.resetComments = () => ({
|
||||
type: 'RESET_COMMENTS'
|
||||
});
|
||||
|
||||
module.exports.setVisibilityInfo = visibilityInfo => ({
|
||||
type: 'SET_VISIBILITY_INFO',
|
||||
visibilityInfo: visibilityInfo
|
||||
|
@ -462,94 +311,6 @@ module.exports.getFavedStatus = (id, username, token) => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
module.exports.getTopLevelComments = (id, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${id}/comments`,
|
||||
authentication: token ? token : null,
|
||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setComments(body));
|
||||
dispatch(module.exports.getReplies(id, body.map(comment => comment.id), 0, ownerUsername, isAdmin, token));
|
||||
|
||||
// If we loaded a full page of comments, assume there are more to load.
|
||||
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
||||
// any more server query complexity, so seems worth it. In the case of a project with
|
||||
// number of comments divisible by the COMMENT_LIMIT, the load more button will be
|
||||
// clickable, but upon clicking it will go away.
|
||||
dispatch(module.exports.setMoreCommentsToLoad(body.length === COMMENT_LIMIT));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getCommentById = (projectId, commentId, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHING));
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${commentId}`,
|
||||
authentication: token ? token : null
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
if (!body || res.statusCode >= 400) { // NotFound
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.parent_id) {
|
||||
// If the comment is a reply, load the parent
|
||||
return dispatch(module.exports.getCommentById(projectId, body.parent_id, ownerUsername, isAdmin, token));
|
||||
}
|
||||
|
||||
// If the comment is not a reply, show it as top level and load replies
|
||||
dispatch(module.exports.setFetchStatus('comments', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setComments([body]));
|
||||
dispatch(module.exports.getReplies(projectId, [body.id], 0, ownerUsername, isAdmin, token));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.getReplies = (projectId, commentIds, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHING));
|
||||
const fetchedReplies = {};
|
||||
eachLimit(commentIds, 10, (parentId, callback) => {
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${parentId}/replies`,
|
||||
authentication: token ? token : null,
|
||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
return callback(`Error fetching comment replies: ${err}`);
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
return callback('No comment reply information');
|
||||
}
|
||||
fetchedReplies[parentId] = body;
|
||||
callback(null, body);
|
||||
});
|
||||
}, err => {
|
||||
if (err) {
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.ERROR));
|
||||
dispatch(module.exports.setError(err));
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setFetchStatus('replies', module.exports.Status.FETCHED));
|
||||
dispatch(module.exports.setReplies(fetchedReplies));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.setFavedStatus = (faved, id, username, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('faved', module.exports.Status.FETCHING));
|
||||
if (faved) {
|
||||
|
@ -882,62 +643,6 @@ module.exports.updateProject = (id, jsonData, username, token) => (dispatch => {
|
|||
});
|
||||
});
|
||||
|
||||
module.exports.deleteComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
/* TODO fetching/fetched/error states updates for comment deleting */
|
||||
api({
|
||||
uri: `/proxy/comments/project/${projectId}/comment/${commentId}`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'DELETE',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setCommentDeleted(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(module.exports.setRepliesDeleted(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.reportComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/proxy/project/${projectId}/comment/${commentId}/report`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'POST',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
// TODO use the reportId in the response for unreporting functionality
|
||||
dispatch(module.exports.setCommentReported(commentId, topLevelCommentId));
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.restoreComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/proxy/admin/project/${projectId}/comment/${commentId}/undelete`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'PUT',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(module.exports.setCommentRestored(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(module.exports.setRepliesRestored(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports.shareProject = (projectId, token) => (dispatch => {
|
||||
dispatch(module.exports.setFetchStatus('project', module.exports.Status.FETCHING));
|
||||
api({
|
||||
|
|
180
src/redux/project-comment-actions.js
Normal file
180
src/redux/project-comment-actions.js
Normal file
|
@ -0,0 +1,180 @@
|
|||
const eachLimit = require('async/eachLimit');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const COMMENT_LIMIT = 20;
|
||||
|
||||
const {
|
||||
addNewComment,
|
||||
resetComments,
|
||||
Status,
|
||||
setFetchStatus,
|
||||
setCommentDeleted,
|
||||
setCommentReported,
|
||||
setCommentRestored,
|
||||
setMoreCommentsToLoad,
|
||||
setComments,
|
||||
setError,
|
||||
setReplies,
|
||||
setRepliesDeleted,
|
||||
setRepliesRestored
|
||||
} = require('../redux/comments.js');
|
||||
|
||||
const getReplies = (projectId, commentIds, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('replies', Status.FETCHING));
|
||||
const fetchedReplies = {};
|
||||
eachLimit(commentIds, 10, (parentId, callback) => {
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${parentId}/replies`,
|
||||
authentication: token ? token : null,
|
||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
return callback(`Error fetching comment replies: ${err}`);
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
return callback('No comment reply information');
|
||||
}
|
||||
fetchedReplies[parentId] = body;
|
||||
callback(null, body);
|
||||
});
|
||||
}, err => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('replies', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('replies', Status.FETCHED));
|
||||
dispatch(setReplies(fetchedReplies));
|
||||
});
|
||||
});
|
||||
|
||||
const getTopLevelComments = (id, offset, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('comments', Status.FETCHING));
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${id}/comments`,
|
||||
authentication: token ? token : null,
|
||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments(body));
|
||||
dispatch(getReplies(id, body.map(comment => comment.id), 0, ownerUsername, isAdmin, token));
|
||||
|
||||
// If we loaded a full page of comments, assume there are more to load.
|
||||
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
||||
// any more server query complexity, so seems worth it. In the case of a project with
|
||||
// number of comments divisible by the COMMENT_LIMIT, the load more button will be
|
||||
// clickable, but upon clicking it will go away.
|
||||
dispatch(setMoreCommentsToLoad(body.length === COMMENT_LIMIT));
|
||||
});
|
||||
});
|
||||
|
||||
const getCommentById = (projectId, commentId, ownerUsername, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('comments', Status.FETCHING));
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : `/users/${ownerUsername}`}/projects/${projectId}/comments/${commentId}`,
|
||||
authentication: token ? token : null
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
if (!body || res.statusCode >= 400) { // NotFound
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.parent_id) {
|
||||
// If the comment is a reply, load the parent
|
||||
return dispatch(getCommentById(projectId, body.parent_id, ownerUsername, isAdmin, token));
|
||||
}
|
||||
|
||||
// If the comment is not a reply, show it as top level and load replies
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments([body]));
|
||||
dispatch(getReplies(projectId, [body.id], 0, ownerUsername, isAdmin, token));
|
||||
});
|
||||
});
|
||||
|
||||
const deleteComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
/* TODO fetching/fetched/error states updates for comment deleting */
|
||||
api({
|
||||
uri: `/proxy/comments/project/${projectId}/comment/${commentId}`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'DELETE',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(setCommentDeleted(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(setRepliesDeleted(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const reportComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/proxy/project/${projectId}/comment/${commentId}/report`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'POST',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
// TODO use the reportId in the response for unreporting functionality
|
||||
dispatch(setCommentReported(commentId, topLevelCommentId));
|
||||
});
|
||||
});
|
||||
|
||||
const restoreComment = (projectId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/proxy/admin/project/${projectId}/comment/${commentId}/undelete`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'PUT',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(setCommentRestored(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(setRepliesRestored(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getTopLevelComments,
|
||||
getCommentById,
|
||||
getReplies,
|
||||
deleteComment,
|
||||
reportComment,
|
||||
restoreComment,
|
||||
|
||||
// Re-export these specific action creators directly so the implementer
|
||||
// does not need to go to two places for comment actions
|
||||
addNewComment,
|
||||
resetComments
|
||||
};
|
|
@ -1,5 +1,6 @@
|
|||
const keyMirror = require('keymirror');
|
||||
const defaults = require('lodash.defaults');
|
||||
const get = require('lodash.get');
|
||||
|
||||
const {requestSession, requestSessionWithRetry} = require('../lib/session');
|
||||
const messageCountActions = require('./message-count.js');
|
||||
|
@ -118,3 +119,13 @@ module.exports.refreshSessionWithRetry = () => (dispatch => {
|
|||
dispatch(module.exports.setSessionError(err));
|
||||
});
|
||||
});
|
||||
|
||||
// Selectors
|
||||
module.exports.selectIsLoggedIn = state => get(state, ['session', 'session', 'user'], false);
|
||||
module.exports.selectUsername = state => get(state, ['session', 'session', 'user', 'username'], null);
|
||||
module.exports.selectToken = state => get(state, ['session', 'session', 'user', 'token'], null);
|
||||
module.exports.selectIsAdmin = state => get(state, ['session', 'session', 'permissions', 'admin'], false);
|
||||
module.exports.selectIsSocial = state => get(state, ['session', 'session', 'permissions', 'social'], false);
|
||||
|
||||
// NB logged out user id as NaN so that it can never be used in equality testing since NaN !== NaN
|
||||
module.exports.selectUserId = state => get(state, ['session', 'session', 'user', 'id'], NaN);
|
||||
|
|
195
src/redux/studio-comment-actions.js
Normal file
195
src/redux/studio-comment-actions.js
Normal file
|
@ -0,0 +1,195 @@
|
|||
const eachLimit = require('async/eachLimit');
|
||||
|
||||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const COMMENT_LIMIT = 20;
|
||||
|
||||
const {
|
||||
addNewComment,
|
||||
resetComments,
|
||||
Status,
|
||||
setFetchStatus,
|
||||
setCommentDeleted,
|
||||
setCommentReported,
|
||||
setCommentRestored,
|
||||
setMoreCommentsToLoad,
|
||||
setComments,
|
||||
setError,
|
||||
setReplies,
|
||||
setRepliesDeleted,
|
||||
setRepliesRestored,
|
||||
selectCommentCount
|
||||
} = require('../redux/comments.js');
|
||||
|
||||
const {
|
||||
selectIsAdmin,
|
||||
selectToken
|
||||
} = require('./session');
|
||||
|
||||
const {
|
||||
selectStudioId
|
||||
} = require('./studio');
|
||||
|
||||
const getReplies = (studioId, commentIds, offset, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('replies', Status.FETCHING));
|
||||
const fetchedReplies = {};
|
||||
eachLimit(commentIds, 10, (parentId, callback) => {
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : ''}/studios/${studioId}/comments/${parentId}/replies`,
|
||||
authentication: token ? token : null,
|
||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
return callback(`Error fetching comment replies: ${err}`);
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
return callback('No comment reply information');
|
||||
}
|
||||
fetchedReplies[parentId] = body;
|
||||
callback(null, body);
|
||||
});
|
||||
}, err => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('replies', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('replies', Status.FETCHED));
|
||||
dispatch(setReplies(fetchedReplies));
|
||||
});
|
||||
});
|
||||
|
||||
const getTopLevelComments = () => ((dispatch, getState) => {
|
||||
dispatch(setFetchStatus('comments', Status.FETCHING));
|
||||
const state = getState();
|
||||
const id = selectStudioId(state);
|
||||
const offset = selectCommentCount(state);
|
||||
const isAdmin = selectIsAdmin(state);
|
||||
const token = selectToken(state);
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : ''}/studios/${id}/comments`,
|
||||
authentication: token ? token : null,
|
||||
params: {offset: offset || 0, limit: COMMENT_LIMIT}
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
if (typeof body === 'undefined' || res.statusCode >= 400) { // NotFound
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments(body));
|
||||
dispatch(getReplies(id, body.map(comment => comment.id), 0, isAdmin, token));
|
||||
|
||||
// If we loaded a full page of comments, assume there are more to load.
|
||||
// This will be wrong (1 / COMMENT_LIMIT) of the time, but does not require
|
||||
// any more server query complexity, so seems worth it. In the case of a project with
|
||||
// number of comments divisible by the COMMENT_LIMIT, the load more button will be
|
||||
// clickable, but upon clicking it will go away.
|
||||
dispatch(setMoreCommentsToLoad(body.length === COMMENT_LIMIT));
|
||||
});
|
||||
});
|
||||
|
||||
const getCommentById = (studioId, commentId, isAdmin, token) => (dispatch => {
|
||||
dispatch(setFetchStatus('comments', Status.FETCHING));
|
||||
api({
|
||||
uri: `${isAdmin ? '/admin' : ''}/studios/${studioId}/comments/${commentId}`,
|
||||
authentication: token ? token : null
|
||||
}, (err, body, res) => {
|
||||
if (err) {
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError(err));
|
||||
return;
|
||||
}
|
||||
if (!body || res.statusCode >= 400) { // NotFound
|
||||
dispatch(setFetchStatus('comments', Status.ERROR));
|
||||
dispatch(setError('No comment info'));
|
||||
return;
|
||||
}
|
||||
|
||||
if (body.parent_id) {
|
||||
// If the comment is a reply, load the parent
|
||||
return dispatch(getCommentById(studioId, body.parent_id, isAdmin, token));
|
||||
}
|
||||
|
||||
// If the comment is not a reply, show it as top level and load replies
|
||||
dispatch(setFetchStatus('comments', Status.FETCHED));
|
||||
dispatch(setComments([body]));
|
||||
dispatch(getReplies(studioId, [body.id], 0, isAdmin, token));
|
||||
});
|
||||
});
|
||||
|
||||
const deleteComment = (studioId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
/* TODO fetching/fetched/error states updates for comment deleting */
|
||||
api({
|
||||
uri: `/proxy/comments/studio/${studioId}/comment/${commentId}`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'DELETE',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(setCommentDeleted(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(setRepliesDeleted(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const reportComment = (studioId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/proxy/studio/${studioId}/comment/${commentId}/report`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'POST',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
// TODO use the reportId in the response for unreporting functionality
|
||||
dispatch(setCommentReported(commentId, topLevelCommentId));
|
||||
});
|
||||
});
|
||||
|
||||
const restoreComment = (studioId, commentId, topLevelCommentId, token) => (dispatch => {
|
||||
api({
|
||||
uri: `/proxy/admin/studio/${studioId}/comment/${commentId}/undelete`,
|
||||
authentication: token,
|
||||
withCredentials: true,
|
||||
method: 'PUT',
|
||||
useCsrf: true
|
||||
}, (err, body, res) => {
|
||||
if (err || res.statusCode !== 200) {
|
||||
log.error(err || res.body);
|
||||
return;
|
||||
}
|
||||
dispatch(setCommentRestored(commentId, topLevelCommentId));
|
||||
if (!topLevelCommentId) {
|
||||
dispatch(setRepliesRestored(commentId));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = {
|
||||
getTopLevelComments,
|
||||
getCommentById,
|
||||
getReplies,
|
||||
deleteComment,
|
||||
reportComment,
|
||||
restoreComment,
|
||||
|
||||
// Re-export these specific action creators directly so the implementer
|
||||
// does not need to go to two places for comment actions
|
||||
addNewComment,
|
||||
resetComments
|
||||
};
|
|
@ -3,6 +3,8 @@ const keyMirror = require('keymirror');
|
|||
const api = require('../lib/api');
|
||||
const log = require('../lib/log');
|
||||
|
||||
const {selectUserId, selectIsAdmin, selectIsSocial, selectUsername, selectToken} = require('./session');
|
||||
|
||||
const Status = keyMirror({
|
||||
FETCHED: null,
|
||||
NOT_FETCHED: null,
|
||||
|
@ -18,6 +20,7 @@ const getInitialState = () => ({
|
|||
commentingAllowed: false,
|
||||
thumbnail: '',
|
||||
followers: 0,
|
||||
owner: null,
|
||||
|
||||
rolesStatus: Status.NOT_FETCHED,
|
||||
manager: false,
|
||||
|
@ -55,6 +58,8 @@ const studioReducer = (state, action) => {
|
|||
}
|
||||
};
|
||||
|
||||
// Action Creators
|
||||
|
||||
const setFetchStatus = (fetchType, fetchStatus, error) => ({
|
||||
type: 'SET_FETCH_STATUS',
|
||||
fetchType,
|
||||
|
@ -72,8 +77,31 @@ const setRoles = roles => ({
|
|||
roles: roles
|
||||
});
|
||||
|
||||
const getInfo = studioId => (dispatch => {
|
||||
// Selectors
|
||||
|
||||
// Fine-grain selector helpers - not exported, use the higher level selectors below
|
||||
const isCreator = state => selectUserId(state) === state.studio.owner;
|
||||
const isCurator = state => state.studio.curator;
|
||||
const isManager = state => state.studio.manager || isCreator(state);
|
||||
|
||||
// Action-based permissions selectors
|
||||
const selectCanEditInfo = state => selectIsAdmin(state) || isManager(state);
|
||||
const selectCanAddProjects = state =>
|
||||
isManager(state) ||
|
||||
isCurator(state) ||
|
||||
(selectIsSocial(state) && state.studio.openToAll);
|
||||
|
||||
// This isn't "canComment" since they could be muted, but comment composer handles that
|
||||
const selectShowCommentComposer = state => selectIsSocial(state);
|
||||
|
||||
// Data selectors
|
||||
const selectStudioId = state => state.studio.id;
|
||||
|
||||
|
||||
// Thunks
|
||||
const getInfo = () => ((dispatch, getState) => {
|
||||
dispatch(setFetchStatus('infoStatus', Status.FETCHING));
|
||||
const studioId = selectStudioId(getState());
|
||||
api({uri: `/studios/${studioId}`}, (err, body, res) => {
|
||||
if (err || typeof body === 'undefined' || res.statusCode !== 200) {
|
||||
dispatch(setFetchStatus('infoStatus', Status.ERROR, err));
|
||||
|
@ -86,13 +114,18 @@ const getInfo = studioId => (dispatch => {
|
|||
openToAll: body.open_to_all,
|
||||
commentingAllowed: body.commenting_allowed,
|
||||
updated: new Date(body.history.modified),
|
||||
followers: body.stats.followers
|
||||
followers: body.stats.followers,
|
||||
owner: body.owner
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
const getRoles = (studioId, username, token) => (dispatch => {
|
||||
const getRoles = () => ((dispatch, getState) => {
|
||||
dispatch(setFetchStatus('rolesStatus', Status.FETCHING));
|
||||
const state = getState();
|
||||
const studioId = selectStudioId(state);
|
||||
const username = selectUsername(state);
|
||||
const token = selectToken(state);
|
||||
api({
|
||||
uri: `/studios/${studioId}/users/${username}`,
|
||||
authentication: token
|
||||
|
@ -115,6 +148,14 @@ module.exports = {
|
|||
getInitialState,
|
||||
studioReducer,
|
||||
Status,
|
||||
|
||||
// Thunks
|
||||
getInfo,
|
||||
getRoles
|
||||
getRoles,
|
||||
|
||||
// Selectors
|
||||
selectStudioId,
|
||||
selectCanEditInfo,
|
||||
selectCanAddProjects,
|
||||
selectShowCommentComposer
|
||||
};
|
||||
|
|
|
@ -4,12 +4,5 @@
|
|||
"pattern": "^/components/?$",
|
||||
"view": "components/components",
|
||||
"title": "Components"
|
||||
},
|
||||
{
|
||||
"name": "studio",
|
||||
"pattern": "^/studios-playground/\\d+(/projects|/curators|/activity|/comments)?/?(\\?.*)?$",
|
||||
"routeAlias": "/studios-playground/?$",
|
||||
"view": "studio/studio",
|
||||
"title": "Studio Playground"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -296,6 +296,13 @@
|
|||
"view": "studentregistration/studentregistration",
|
||||
"title": "Class Registration"
|
||||
},
|
||||
{
|
||||
"name": "studio",
|
||||
"pattern": "^/studios-playground/\\d+(/projects|/curators|/activity|/comments)?/?(\\?.*)?$",
|
||||
"routeAlias": "/studios-playground/?$",
|
||||
"view": "studio/studio",
|
||||
"title": "Studio Playground"
|
||||
},
|
||||
{
|
||||
"name": "teacher-faq",
|
||||
"pattern": "^/educators/faq/?$",
|
||||
|
|
|
@ -8,22 +8,78 @@ 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');
|
||||
const Grid = require('../../components/grid/grid.jsx');
|
||||
const TextArea = require('../../components/forms/textarea.jsx');
|
||||
const SubNavigation = require('../../components/subnavigation/subnavigation.jsx');
|
||||
const Select = require('../../components/forms/select.jsx');
|
||||
|
||||
require('./components.scss');
|
||||
|
||||
const Components = () => (
|
||||
<div className="components">
|
||||
<div className="inner">
|
||||
<h1>Nav Bubbles</h1>
|
||||
<div className="subnavigation">
|
||||
<SubNavigation>
|
||||
<a href="">
|
||||
<li className="active">
|
||||
cats
|
||||
</li>
|
||||
</a>
|
||||
<a href="">
|
||||
<li>
|
||||
also cats
|
||||
</li>
|
||||
</a>
|
||||
<a href="">
|
||||
<li>
|
||||
not cats
|
||||
</li>
|
||||
</a>
|
||||
</SubNavigation>
|
||||
</div>
|
||||
<h1>Grid</h1>
|
||||
<Grid
|
||||
showAvatar
|
||||
/>
|
||||
<h1>Button</h1>
|
||||
<Button>I love button</Button>
|
||||
<Button>I love buttons</Button>
|
||||
<h1>Form</h1>
|
||||
<Form>
|
||||
<Input
|
||||
maxLength="30"
|
||||
name="test"
|
||||
type="text"
|
||||
/>
|
||||
</Form>
|
||||
<div className="form">
|
||||
<Form>
|
||||
<Select
|
||||
label="Drop-down"
|
||||
required
|
||||
options={[
|
||||
{
|
||||
label: 'first option',
|
||||
value: 1
|
||||
},
|
||||
{
|
||||
label: 'second option',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: 'third option',
|
||||
value: 3
|
||||
}
|
||||
]}
|
||||
name="name"
|
||||
value={1}
|
||||
/>
|
||||
<Input
|
||||
label="Text input"
|
||||
required
|
||||
maxLength="30"
|
||||
name="test"
|
||||
/>
|
||||
<TextArea
|
||||
label="Text area"
|
||||
name="textarea1"
|
||||
required
|
||||
/>
|
||||
</Form>
|
||||
</div>
|
||||
<h1>Box Component</h1>
|
||||
<Box
|
||||
more="Cat Gifs"
|
||||
|
|
|
@ -5,6 +5,19 @@
|
|||
margin: 0 0 10px 0;
|
||||
}
|
||||
|
||||
.subnavigation {
|
||||
li {
|
||||
background-color: $active-gray;
|
||||
&.active {
|
||||
background-color: $ui-blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.colors {
|
||||
span {
|
||||
display: inline-block;
|
||||
|
|
|
@ -110,7 +110,7 @@ class Comment extends React.Component {
|
|||
highlighted,
|
||||
id,
|
||||
parentId,
|
||||
projectId,
|
||||
postURI,
|
||||
replyUsername,
|
||||
visibility
|
||||
} = this.props;
|
||||
|
@ -234,7 +234,7 @@ class Comment extends React.Component {
|
|||
isReply
|
||||
commenteeId={author.id}
|
||||
parentId={parentId || id}
|
||||
projectId={projectId}
|
||||
postURI={postURI}
|
||||
onAddComment={this.handlePostReply}
|
||||
onCancel={this.handleToggleReplying}
|
||||
/>
|
||||
|
@ -285,7 +285,7 @@ Comment.propTypes = {
|
|||
onReport: PropTypes.func,
|
||||
onRestore: PropTypes.func,
|
||||
parentId: PropTypes.number,
|
||||
projectId: PropTypes.string,
|
||||
postURI: PropTypes.string,
|
||||
replyUsername: PropTypes.string,
|
||||
visibility: PropTypes.string
|
||||
};
|
||||
|
|
|
@ -79,7 +79,7 @@ class ComposeComment extends React.Component {
|
|||
handlePost () {
|
||||
this.setState({status: ComposeStatus.SUBMITTING});
|
||||
api({
|
||||
uri: `/proxy/comments/project/${this.props.projectId}`,
|
||||
uri: this.props.postURI,
|
||||
authentication: this.props.user.token,
|
||||
withCredentials: true,
|
||||
method: 'POST',
|
||||
|
@ -434,7 +434,7 @@ ComposeComment.propTypes = {
|
|||
onAddComment: PropTypes.func,
|
||||
onCancel: PropTypes.func,
|
||||
parentId: PropTypes.number,
|
||||
projectId: PropTypes.string,
|
||||
postURI: PropTypes.string,
|
||||
user: PropTypes.shape({
|
||||
id: PropTypes.number,
|
||||
username: PropTypes.string,
|
||||
|
|
|
@ -87,7 +87,7 @@ class TopLevelComment extends React.Component {
|
|||
onReport,
|
||||
onRestore,
|
||||
replies,
|
||||
projectId,
|
||||
postURI,
|
||||
visibility
|
||||
} = this.props;
|
||||
|
||||
|
@ -97,7 +97,7 @@ class TopLevelComment extends React.Component {
|
|||
<FlexRow className="comment-container">
|
||||
<Comment
|
||||
highlighted={highlightedCommentId === id}
|
||||
projectId={projectId}
|
||||
postURI={postURI}
|
||||
onAddComment={this.handleAddComment}
|
||||
{...{
|
||||
author,
|
||||
|
@ -138,7 +138,7 @@ class TopLevelComment extends React.Component {
|
|||
id={reply.id}
|
||||
key={reply.id}
|
||||
parentId={id}
|
||||
projectId={projectId}
|
||||
postURI={postURI}
|
||||
replyUsername={this.authorUsername(reply.commentee_id)}
|
||||
visibility={reply.visibility}
|
||||
onAddComment={this.handleAddComment}
|
||||
|
@ -188,7 +188,7 @@ TopLevelComment.propTypes = {
|
|||
onReport: PropTypes.func,
|
||||
onRestore: PropTypes.func,
|
||||
parentId: PropTypes.number,
|
||||
projectId: PropTypes.string,
|
||||
postURI: PropTypes.string,
|
||||
replies: PropTypes.arrayOf(PropTypes.object),
|
||||
visibility: PropTypes.string
|
||||
};
|
||||
|
|
|
@ -581,7 +581,7 @@ const PreviewPresentation = ({
|
|||
{projectInfo.comments_allowed ? (
|
||||
isLoggedIn ? (
|
||||
isShared && <ComposeComment
|
||||
projectId={projectId}
|
||||
postURI={`/proxy/comments/project/${projectId}`}
|
||||
onAddComment={onAddComment}
|
||||
/>
|
||||
) : (
|
||||
|
@ -613,7 +613,7 @@ const PreviewPresentation = ({
|
|||
key={comment.id}
|
||||
moreRepliesToLoad={comment.moreRepliesToLoad}
|
||||
parentId={comment.parent_id}
|
||||
projectId={projectId}
|
||||
postURI={`/proxy/comments/project/${projectId}`}
|
||||
replies={replies && replies[comment.id] ? replies[comment.id] : []}
|
||||
visibility={comment.visibility}
|
||||
onAddComment={onAddComment}
|
||||
|
|
|
@ -5,6 +5,7 @@ const Page = require('../../components/page/www/page.jsx');
|
|||
const render = require('../../lib/render.jsx');
|
||||
|
||||
const previewActions = require('../../redux/preview.js');
|
||||
const commentActions = require('../../redux/comments.js');
|
||||
|
||||
const isSupportedBrowser = require('../../lib/supported-browser').default;
|
||||
const UnsupportedBrowser = require('./unsupported-browser.jsx');
|
||||
|
@ -16,6 +17,7 @@ if (isSupportedBrowser()) {
|
|||
document.getElementById('app'),
|
||||
{
|
||||
preview: previewActions.previewReducer,
|
||||
comments: commentActions.commentsReducer,
|
||||
...ProjectView.guiReducers
|
||||
},
|
||||
{
|
||||
|
|
|
@ -29,6 +29,7 @@ const Meta = require('./meta.jsx');
|
|||
const sessionActions = require('../../redux/session.js');
|
||||
const navigationActions = require('../../redux/navigation.js');
|
||||
const previewActions = require('../../redux/preview.js');
|
||||
const projectCommentActions = require('../../redux/project-comment-actions.js');
|
||||
|
||||
const frameless = require('../../lib/frameless');
|
||||
|
||||
|
@ -998,7 +999,7 @@ const mapStateToProps = state => {
|
|||
canShare: userOwnsProject && state.permissions.social,
|
||||
canToggleComments: userOwnsProject || isAdmin,
|
||||
canUseBackpack: isLoggedIn,
|
||||
comments: state.preview.comments,
|
||||
comments: state.comments.comments,
|
||||
enableCommunity: projectInfoPresent,
|
||||
faved: state.preview.faved,
|
||||
favedLoaded: state.preview.status.faved === previewActions.Status.FETCHED,
|
||||
|
@ -1013,7 +1014,7 @@ const mapStateToProps = state => {
|
|||
isShared: isShared,
|
||||
loved: state.preview.loved,
|
||||
lovedLoaded: state.preview.status.loved === previewActions.Status.FETCHED,
|
||||
moreCommentsToLoad: state.preview.moreCommentsToLoad,
|
||||
moreCommentsToLoad: state.comments.moreCommentsToLoad,
|
||||
original: state.preview.original,
|
||||
parent: state.preview.parent,
|
||||
playerMode: state.scratchGui.mode.isPlayerOnly,
|
||||
|
@ -1022,7 +1023,7 @@ const mapStateToProps = state => {
|
|||
projectStudios: state.preview.projectStudios,
|
||||
registrationOpen: state.navigation.registrationOpen,
|
||||
remixes: state.preview.remixes,
|
||||
replies: state.preview.replies,
|
||||
replies: state.comments.replies,
|
||||
sessionStatus: state.session.status, // check if used
|
||||
useScratch3Registration: state.navigation.useScratch3Registration,
|
||||
user: state.session.session.user,
|
||||
|
@ -1034,16 +1035,16 @@ const mapStateToProps = state => {
|
|||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
handleAddComment: (comment, topLevelCommentId) => {
|
||||
dispatch(previewActions.addNewComment(comment, topLevelCommentId));
|
||||
dispatch(projectCommentActions.addNewComment(comment, topLevelCommentId));
|
||||
},
|
||||
handleDeleteComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||
dispatch(previewActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
||||
dispatch(projectCommentActions.deleteComment(projectId, commentId, topLevelCommentId, token));
|
||||
},
|
||||
handleReportComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||
dispatch(previewActions.reportComment(projectId, commentId, topLevelCommentId, token));
|
||||
dispatch(projectCommentActions.reportComment(projectId, commentId, topLevelCommentId, token));
|
||||
},
|
||||
handleRestoreComment: (projectId, commentId, topLevelCommentId, token) => {
|
||||
dispatch(previewActions.restoreComment(projectId, commentId, topLevelCommentId, token));
|
||||
dispatch(projectCommentActions.restoreComment(projectId, commentId, topLevelCommentId, token));
|
||||
},
|
||||
handleOpenRegistration: event => {
|
||||
event.preventDefault();
|
||||
|
@ -1061,8 +1062,8 @@ const mapDispatchToProps = dispatch => ({
|
|||
dispatch(navigationActions.toggleLoginOpen());
|
||||
},
|
||||
handleSeeAllComments: (id, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.resetComments());
|
||||
dispatch(previewActions.getTopLevelComments(id, 0, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.resetComments());
|
||||
dispatch(projectCommentActions.getTopLevelComments(id, 0, ownerUsername, isAdmin, token));
|
||||
},
|
||||
handleUpdateProjectThumbnail: (id, blob) => {
|
||||
dispatch(previewActions.updateProjectThumbnail(id, blob));
|
||||
|
@ -1093,13 +1094,13 @@ const mapDispatchToProps = dispatch => ({
|
|||
}
|
||||
},
|
||||
getTopLevelComments: (id, offset, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.getTopLevelComments(id, offset, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.getTopLevelComments(id, offset, ownerUsername, isAdmin, token));
|
||||
},
|
||||
getCommentById: (projectId, commentId, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.getCommentById(projectId, commentId, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.getCommentById(projectId, commentId, ownerUsername, isAdmin, token));
|
||||
},
|
||||
getMoreReplies: (projectId, commentId, offset, ownerUsername, isAdmin, token) => {
|
||||
dispatch(previewActions.getReplies(projectId, [commentId], offset, ownerUsername, isAdmin, token));
|
||||
dispatch(projectCommentActions.getReplies(projectId, [commentId], offset, ownerUsername, isAdmin, token));
|
||||
},
|
||||
getFavedStatus: (id, username, token) => {
|
||||
dispatch(previewActions.getFavedStatus(id, username, token));
|
||||
|
@ -1136,7 +1137,7 @@ const mapDispatchToProps = dispatch => ({
|
|||
},
|
||||
remixProject: () => {
|
||||
dispatch(GUI.remixProject());
|
||||
dispatch(previewActions.resetComments());
|
||||
dispatch(projectCommentActions.resetComments());
|
||||
},
|
||||
setPlayer: player => {
|
||||
dispatch(GUI.setPlayer(player));
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"onePointFour.introNote": "{noteLabel} You can still share projects from 1.4 to the Scratch website. However, projects created in newer versions of Scratch cannot be opened in 1.4.",
|
||||
"onePointFour.downloads": "Downloads",
|
||||
"onePointFour.macTitle": "Mac OS X",
|
||||
"onePointFour.macBody": "Compatible with Mac OSX 10.4 or later",
|
||||
"onePointFour.macBody": "Compatible with Mac OSX 10.4 through 10.14",
|
||||
"onePointFour.windowsTitle": "Windows",
|
||||
"onePointFour.windowsBody": "Compatible with Windows 2000, XP, Vista, 7, and 8",
|
||||
"onePointFour.windowsNetworkInstaller": "installer",
|
||||
|
|
|
@ -1,15 +1,88 @@
|
|||
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 {FormattedMessage} from 'react-intl';
|
||||
|
||||
const StudioComments = () => {
|
||||
import Button from '../../components/forms/button.jsx';
|
||||
import ComposeComment from '../preview/comment/compose-comment.jsx';
|
||||
import TopLevelComment from '../preview/comment/top-level-comment.jsx';
|
||||
import studioCommentActions from '../../redux/studio-comment-actions.js';
|
||||
|
||||
import {selectShowCommentComposer} from '../../redux/studio.js';
|
||||
|
||||
const StudioComments = ({
|
||||
comments,
|
||||
handleLoadMoreComments,
|
||||
handleNewComment,
|
||||
moreCommentsToLoad,
|
||||
replies,
|
||||
shouldShowCommentComposer
|
||||
}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
useEffect(() => {
|
||||
if (comments.length === 0) handleLoadMoreComments();
|
||||
}, []); // Only runs once after the first render
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2>Comments</h2>
|
||||
<p>Studio {studioId}</p>
|
||||
<div>
|
||||
{shouldShowCommentComposer &&
|
||||
<ComposeComment
|
||||
postURI={`/proxy/comments/studio/${studioId}`}
|
||||
onAddComment={handleNewComment}
|
||||
/>
|
||||
}
|
||||
{comments.map(comment => (
|
||||
<TopLevelComment
|
||||
author={comment.author}
|
||||
canReply={shouldShowCommentComposer}
|
||||
content={comment.content}
|
||||
datetimeCreated={comment.datetime_created}
|
||||
id={comment.id}
|
||||
key={comment.id}
|
||||
moreRepliesToLoad={comment.moreRepliesToLoad}
|
||||
parentId={comment.parent_id}
|
||||
postURI={`/proxy/comments/studio/${studioId}`}
|
||||
replies={replies && replies[comment.id] ? replies[comment.id] : []}
|
||||
visibility={comment.visibility}
|
||||
onAddComment={handleNewComment}
|
||||
/>
|
||||
))}
|
||||
{moreCommentsToLoad &&
|
||||
<Button
|
||||
className="button load-more-button"
|
||||
onClick={handleLoadMoreComments}
|
||||
>
|
||||
<FormattedMessage id="general.loadMore" />
|
||||
</Button>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudioComments;
|
||||
StudioComments.propTypes = {
|
||||
comments: PropTypes.arrayOf(PropTypes.shape({})),
|
||||
handleLoadMoreComments: PropTypes.func,
|
||||
handleNewComment: PropTypes.func,
|
||||
moreCommentsToLoad: PropTypes.bool,
|
||||
replies: PropTypes.shape({}),
|
||||
shouldShowCommentComposer: PropTypes.bool
|
||||
};
|
||||
|
||||
|
||||
export default connect(
|
||||
state => ({
|
||||
comments: state.comments.comments,
|
||||
moreCommentsToLoad: state.comments.moreCommentsToLoad,
|
||||
replies: state.comments.replies,
|
||||
shouldShowCommentComposer: selectShowCommentComposer(state)
|
||||
}),
|
||||
{
|
||||
handleLoadMoreComments: studioCommentActions.getTopLevelComments,
|
||||
handleNewComment: studioCommentActions.addNewComment
|
||||
}
|
||||
)(StudioComments);
|
||||
|
|
|
@ -1,20 +1,19 @@
|
|||
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 = ({username, studio, token, onLoadInfo, onLoadRoles}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
import {selectIsLoggedIn} from '../../redux/session';
|
||||
import {getInfo, getRoles, selectCanEditInfo} from '../../redux/studio';
|
||||
|
||||
const StudioInfo = ({isLoggedIn, studio, canEditInfo, onLoadInfo, onLoadRoles}) => {
|
||||
useEffect(() => { // Load studio info after first render
|
||||
if (studioId) onLoadInfo(studioId);
|
||||
}, [studioId]);
|
||||
onLoadInfo();
|
||||
}, []);
|
||||
|
||||
useEffect(() => { // Load roles info once the username is available
|
||||
if (studioId && username && token) onLoadRoles(studioId, username, token);
|
||||
}, [studioId, username, token]);
|
||||
useEffect(() => { // Load roles info once the user is logged in is available
|
||||
if (isLoggedIn) onLoadRoles();
|
||||
}, [isLoggedIn]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -23,13 +22,17 @@ const StudioInfo = ({username, studio, token, onLoadInfo, onLoadRoles}) => {
|
|||
label="Studio Info"
|
||||
data={studio}
|
||||
/>
|
||||
<Debug
|
||||
label="Studio Info Permissions"
|
||||
data={{canEditInfo}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
StudioInfo.propTypes = {
|
||||
username: PropTypes.string,
|
||||
token: PropTypes.string,
|
||||
canEditInfo: PropTypes.bool,
|
||||
isLoggedIn: PropTypes.bool,
|
||||
studio: PropTypes.shape({
|
||||
// Fill this in as the data is used, just for demo now
|
||||
}),
|
||||
|
@ -38,17 +41,13 @@ StudioInfo.propTypes = {
|
|||
};
|
||||
|
||||
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))
|
||||
})
|
||||
state => ({
|
||||
studio: state.studio,
|
||||
isLoggedIn: selectIsLoggedIn(state),
|
||||
canEditInfo: selectCanEditInfo(state)
|
||||
}),
|
||||
{
|
||||
onLoadInfo: getInfo,
|
||||
onLoadRoles: getRoles
|
||||
}
|
||||
)(StudioInfo);
|
||||
|
|
|
@ -7,10 +7,11 @@ import {projectFetcher} from './lib/fetchers';
|
|||
import {projects} from './lib/redux-modules';
|
||||
import Debug from './debug.jsx';
|
||||
|
||||
const {actions, selector} = projects;
|
||||
const {actions, selector: projectsSelector} = projects;
|
||||
import {selectCanAddProjects} from '../../redux/studio';
|
||||
|
||||
const StudioProjects = ({
|
||||
items, error, loading, moreToLoad, onLoadMore
|
||||
canAddProjects, items, error, loading, moreToLoad, onLoadMore
|
||||
}) => {
|
||||
const {studioId} = useParams();
|
||||
|
||||
|
@ -27,6 +28,10 @@ const StudioProjects = ({
|
|||
label="Error"
|
||||
data={error}
|
||||
/>}
|
||||
<Debug
|
||||
label="Project Permissions"
|
||||
data={{canAddProjects}}
|
||||
/>
|
||||
<div>
|
||||
{items.map((item, index) =>
|
||||
(<Debug
|
||||
|
@ -48,6 +53,7 @@ const StudioProjects = ({
|
|||
};
|
||||
|
||||
StudioProjects.propTypes = {
|
||||
canAddProjects: PropTypes.bool,
|
||||
items: PropTypes.array, // eslint-disable-line react/forbid-prop-types
|
||||
loading: PropTypes.bool,
|
||||
error: PropTypes.object, // eslint-disable-line react/forbid-prop-types
|
||||
|
@ -55,7 +61,10 @@ StudioProjects.propTypes = {
|
|||
onLoadMore: PropTypes.func
|
||||
};
|
||||
|
||||
const mapStateToProps = state => selector(state);
|
||||
const mapStateToProps = state => ({
|
||||
...projectsSelector(state),
|
||||
canAddProjects: selectCanAddProjects(state)
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onLoadMore: (studioId, offset) => dispatch(
|
||||
|
|
|
@ -25,6 +25,7 @@ import {
|
|||
} from './lib/redux-modules';
|
||||
|
||||
const {studioReducer} = require('../../redux/studio');
|
||||
const {commentsReducer} = require('../../redux/comments');
|
||||
|
||||
const StudioShell = () => {
|
||||
const match = useRouteMatch();
|
||||
|
@ -75,6 +76,15 @@ render(
|
|||
[curators.key]: curators.reducer,
|
||||
[managers.key]: managers.reducer,
|
||||
[activity.key]: activity.reducer,
|
||||
studio: studioReducer
|
||||
studio: studioReducer,
|
||||
comments: commentsReducer
|
||||
},
|
||||
{
|
||||
studio: {
|
||||
// Include the studio id in the initial state to allow us
|
||||
// to stop passing around the studio id in components
|
||||
// when it is only needed for data fetching, not for rendering.
|
||||
id: window.location.pathname.split('/')[2]
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
51
test/helpers/state-fixtures.json
Normal file
51
test/helpers/state-fixtures.json
Normal file
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"studios": {
|
||||
"isManager": {
|
||||
"manager": true
|
||||
},
|
||||
"isCurator": {
|
||||
"curator": true
|
||||
},
|
||||
"creator1": {
|
||||
"owner": 1
|
||||
},
|
||||
"openToAll": {
|
||||
"openToAll": true
|
||||
}
|
||||
},
|
||||
"sessions": {
|
||||
"user1Admin": {
|
||||
"session": {
|
||||
"user": {
|
||||
"id": 1
|
||||
},
|
||||
"permissions": {
|
||||
"admin": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"user1Social": {
|
||||
"session": {
|
||||
"user": {
|
||||
"id": 1
|
||||
},
|
||||
"permissions": {
|
||||
"social": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"user1": {
|
||||
"session": {
|
||||
"user": {
|
||||
"id": 1,
|
||||
"username": "user1-username",
|
||||
"token": "user1-token"
|
||||
},
|
||||
"permissions": {
|
||||
"admin": false,
|
||||
"social": false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
151
test/unit-legacy/redux/comments-test.js
Normal file
151
test/unit-legacy/redux/comments-test.js
Normal file
|
@ -0,0 +1,151 @@
|
|||
const tap = require('tap');
|
||||
const Comments = require('../../../src/redux/comments');
|
||||
const initialState = Comments.getInitialState();
|
||||
const reducer = Comments.commentsReducer;
|
||||
|
||||
let state;
|
||||
|
||||
tap.tearDown(() => process.nextTick(process.exit));
|
||||
|
||||
tap.test('Reducer', t => {
|
||||
t.type(reducer, 'function');
|
||||
t.type(initialState, 'object');
|
||||
|
||||
// Reducers should return their default state when called without state
|
||||
let undefinedState;
|
||||
t.deepEqual(initialState, reducer(undefinedState, {type: 'fake action'}));
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setFetchStatus', t => {
|
||||
// initial value
|
||||
t.equal(initialState.status.comments, Comments.Status.NOT_FETCHED);
|
||||
|
||||
state = reducer(initialState, Comments.setFetchStatus('comments', Comments.Status.FETCHING));
|
||||
t.equal(state.status.comments, Comments.Status.FETCHING);
|
||||
|
||||
state = reducer(state, Comments.setFetchStatus('comments', Comments.Status.FETCHED));
|
||||
t.equal(state.status.comments, Comments.Status.FETCHED);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setComments', t => {
|
||||
// Initial value
|
||||
t.deepEqual(initialState.comments, []);
|
||||
|
||||
state = reducer(initialState, Comments.setComments([{id: 1}, {id: 2}]));
|
||||
state = reducer(state, Comments.setComments([{id: 3}, {id: 4}]));
|
||||
t.deepEqual(state.comments, [{id: 1}, {id: 2}, {id: 3}, {id: 4}]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
const commentState = {
|
||||
comments: [
|
||||
{id: 'id1', visibility: 'visible'},
|
||||
{id: 'id2', visibility: 'visible'},
|
||||
{id: 'id3', visibility: 'visible'}
|
||||
],
|
||||
replies: {
|
||||
id1: [
|
||||
{id: 'id4', visibility: 'visible'},
|
||||
{id: 'id5', visibility: 'visible'}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
tap.test('setComments, discards duplicates', t => {
|
||||
state = reducer(commentState, Comments.setComments([{id: 'id1'}]));
|
||||
// Does not increase the number of comments, still 3
|
||||
t.equal(state.comments.length, 3);
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentDeleted, top level comment', t => {
|
||||
state = reducer(commentState, Comments.setCommentDeleted('id2'));
|
||||
t.equal(state.comments[1].visibility, 'deleted');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentDeleted, reply comment', t => {
|
||||
state = reducer(commentState, Comments.setCommentDeleted('id4', 'id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'deleted');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setRepliesDeleted/Restored', t => {
|
||||
state = reducer(commentState, Comments.setRepliesDeleted('id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'deleted');
|
||||
t.equal(state.replies.id1[1].visibility, 'deleted');
|
||||
|
||||
state = reducer(state, Comments.setRepliesRestored('id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'visible');
|
||||
t.equal(state.replies.id1[1].visibility, 'visible');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentReported, top level comment', t => {
|
||||
state = reducer(commentState, Comments.setCommentReported('id2'));
|
||||
t.equal(state.comments[1].visibility, 'reported');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentReported, reply comment', t => {
|
||||
state = reducer(commentState, Comments.setCommentReported('id4', 'id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'reported');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('addNewComment, top level comment', t => {
|
||||
state = reducer(commentState, Comments.addNewComment({id: 'new comment'}));
|
||||
// Adds comment to beginning of list
|
||||
t.equal(state.comments[0].id, 'new comment');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('addNewComment, reply comment', t => {
|
||||
state = reducer(commentState, Comments.addNewComment({id: 'new comment'}, 'id1'));
|
||||
// Adds replies to the end of the replies list
|
||||
t.equal(state.replies.id1[2].id, 'new comment');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setReplies', t => {
|
||||
// setReplies should append new replies
|
||||
state = reducer(commentState, Comments.setReplies({
|
||||
id1: {id: 'id6'}
|
||||
}));
|
||||
t.equal(state.replies.id1[2].id, 'id6');
|
||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||
|
||||
// setReplies should ignore duplicates, do the same as above again
|
||||
t.equal(state.replies.id1.length, 3);
|
||||
state = reducer(state, Comments.setReplies({id1: {id: 'id6'}}));
|
||||
t.equal(state.replies.id1.length, 3);
|
||||
|
||||
// setReplies can add replies to a comment that didn't have any
|
||||
state = reducer(state, Comments.setReplies({
|
||||
id2: {id: 'id7'}
|
||||
}));
|
||||
t.equal(state.replies.id1.length, 3);
|
||||
t.equal(state.replies.id2.length, 1);
|
||||
t.equal(state.replies.id2[0].id, 'id7');
|
||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||
t.equal(state.comments[1].moreRepliesToLoad, false);
|
||||
|
||||
// Getting 20 (COMMENT_LIMIT) replies sets moreRepliesToLoad to true
|
||||
state = reducer(state, Comments.setReplies({
|
||||
id3: (new Array(20)).map((_, i) => ({id: `id${i + 1}`}))
|
||||
}));
|
||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||
t.equal(state.comments[1].moreRepliesToLoad, false);
|
||||
t.equal(state.comments[2].moreRepliesToLoad, true);
|
||||
|
||||
// Getting one more reply sets moreRepliesToLoad back to false
|
||||
state = reducer(state, Comments.setReplies({
|
||||
id3: {id: 'id21'}
|
||||
}));
|
||||
t.equal(state.comments[2].moreRepliesToLoad, false);
|
||||
t.end();
|
||||
});
|
|
@ -54,123 +54,3 @@ tap.test('updateProjectInfo', t => {
|
|||
});
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setComments', t => {
|
||||
// Initial value
|
||||
t.deepEqual(initialState.comments, []);
|
||||
|
||||
state = reducer(initialState, Preview.setComments([{id: 1}, {id: 2}]));
|
||||
state = reducer(state, Preview.setComments([{id: 3}, {id: 4}]));
|
||||
t.deepEqual(state.comments, [{id: 1}, {id: 2}, {id: 3}, {id: 4}]);
|
||||
|
||||
t.end();
|
||||
});
|
||||
|
||||
const commentState = {
|
||||
comments: [
|
||||
{id: 'id1', visibility: 'visible'},
|
||||
{id: 'id2', visibility: 'visible'},
|
||||
{id: 'id3', visibility: 'visible'}
|
||||
],
|
||||
replies: {
|
||||
id1: [
|
||||
{id: 'id4', visibility: 'visible'},
|
||||
{id: 'id5', visibility: 'visible'}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
tap.test('setComments, discards duplicates', t => {
|
||||
state = reducer(commentState, Preview.setComments([{id: 'id1'}]));
|
||||
// Does not increase the number of comments, still 3
|
||||
t.equal(state.comments.length, 3);
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentDeleted, top level comment', t => {
|
||||
state = reducer(commentState, Preview.setCommentDeleted('id2'));
|
||||
t.equal(state.comments[1].visibility, 'deleted');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentDeleted, reply comment', t => {
|
||||
state = reducer(commentState, Preview.setCommentDeleted('id4', 'id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'deleted');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setRepliesDeleted/Restored', t => {
|
||||
state = reducer(commentState, Preview.setRepliesDeleted('id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'deleted');
|
||||
t.equal(state.replies.id1[1].visibility, 'deleted');
|
||||
|
||||
state = reducer(state, Preview.setRepliesRestored('id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'visible');
|
||||
t.equal(state.replies.id1[1].visibility, 'visible');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentReported, top level comment', t => {
|
||||
state = reducer(commentState, Preview.setCommentReported('id2'));
|
||||
t.equal(state.comments[1].visibility, 'reported');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setCommentReported, reply comment', t => {
|
||||
state = reducer(commentState, Preview.setCommentReported('id4', 'id1'));
|
||||
t.equal(state.replies.id1[0].visibility, 'reported');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('addNewComment, top level comment', t => {
|
||||
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}));
|
||||
// Adds comment to beginning of list
|
||||
t.equal(state.comments[0].id, 'new comment');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('addNewComment, reply comment', t => {
|
||||
state = reducer(commentState, Preview.addNewComment({id: 'new comment'}, 'id1'));
|
||||
// Adds replies to the end of the replies list
|
||||
t.equal(state.replies.id1[2].id, 'new comment');
|
||||
t.end();
|
||||
});
|
||||
|
||||
tap.test('setReplies', t => {
|
||||
// setReplies should append new replies
|
||||
state = reducer(commentState, Preview.setReplies({
|
||||
id1: {id: 'id6'}
|
||||
}));
|
||||
t.equal(state.replies.id1[2].id, 'id6');
|
||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||
|
||||
// setReplies should ignore duplicates, do the same as above again
|
||||
t.equal(state.replies.id1.length, 3);
|
||||
state = reducer(state, Preview.setReplies({id1: {id: 'id6'}}));
|
||||
t.equal(state.replies.id1.length, 3);
|
||||
|
||||
// setReplies can add replies to a comment that didn't have any
|
||||
state = reducer(state, Preview.setReplies({
|
||||
id2: {id: 'id7'}
|
||||
}));
|
||||
t.equal(state.replies.id1.length, 3);
|
||||
t.equal(state.replies.id2.length, 1);
|
||||
t.equal(state.replies.id2[0].id, 'id7');
|
||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||
t.equal(state.comments[1].moreRepliesToLoad, false);
|
||||
|
||||
// Getting 20 (COMMENT_LIMIT) replies sets moreRepliesToLoad to true
|
||||
state = reducer(state, Preview.setReplies({
|
||||
id3: (new Array(20)).map((_, i) => ({id: `id${i + 1}`}))
|
||||
}));
|
||||
t.equal(state.comments[0].moreRepliesToLoad, false);
|
||||
t.equal(state.comments[1].moreRepliesToLoad, false);
|
||||
t.equal(state.comments[2].moreRepliesToLoad, true);
|
||||
|
||||
// Getting one more reply sets moreRepliesToLoad back to false
|
||||
state = reducer(state, Preview.setReplies({
|
||||
id3: {id: 'id21'}
|
||||
}));
|
||||
t.equal(state.comments[2].moreRepliesToLoad, false);
|
||||
t.end();
|
||||
});
|
||||
|
|
46
test/unit/redux/session.test.js
Normal file
46
test/unit/redux/session.test.js
Normal file
|
@ -0,0 +1,46 @@
|
|||
import {
|
||||
getInitialState, selectIsAdmin, selectIsSocial, selectUserId,
|
||||
selectUsername, selectToken, sessionReducer, setSession
|
||||
} from '../../../src/redux/session';
|
||||
|
||||
import {sessions} from '../../helpers/state-fixtures.json';
|
||||
|
||||
describe('session selectors', () => {
|
||||
test('logged out', () => {
|
||||
const state = {session: getInitialState()};
|
||||
expect(selectIsAdmin(state)).toBe(false);
|
||||
expect(selectIsSocial(state)).toBe(false);
|
||||
expect(selectUserId(state)).toBeNaN();
|
||||
expect(selectToken(state)).toBeNull();
|
||||
expect(selectUsername(state)).toBeNull();
|
||||
});
|
||||
|
||||
test('user data', () => {
|
||||
let state = {session: getInitialState()};
|
||||
const newSession = sessions.user1.session;
|
||||
state.session = sessionReducer(state.session, setSession(newSession));
|
||||
expect(selectUserId(state)).toBe(1);
|
||||
expect(selectUsername(state)).toBe('user1-username');
|
||||
expect(selectToken(state)).toBe('user1-token');
|
||||
});
|
||||
|
||||
describe('permissions', () => {
|
||||
test('selectIsAdmin', () => {
|
||||
let state = {session: getInitialState()};
|
||||
const newSession = sessions.user1Admin.session;
|
||||
state.session = sessionReducer(state.session, setSession(newSession));
|
||||
expect(selectIsAdmin(state)).toBe(true);
|
||||
// Confirm that admin/social are totally separate and just read directly from the state
|
||||
expect(selectIsSocial(state)).toBe(false);
|
||||
});
|
||||
|
||||
test('selectIsSocial', () => {
|
||||
let state = {session: getInitialState()};
|
||||
const newSession = sessions.user1Social.session;
|
||||
state.session = sessionReducer(state.session, setSession(newSession));
|
||||
expect(selectIsSocial(state)).toBe(true);
|
||||
// Confirm that admin/social are totally separate and just read directly from the state
|
||||
expect(selectIsAdmin(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
89
test/unit/redux/studio.test.js
Normal file
89
test/unit/redux/studio.test.js
Normal file
|
@ -0,0 +1,89 @@
|
|||
import {
|
||||
getInitialState as getInitialStudioState,
|
||||
selectCanEditInfo,
|
||||
selectCanAddProjects,
|
||||
selectShowCommentComposer
|
||||
} from '../../../src/redux/studio';
|
||||
|
||||
import {
|
||||
getInitialState as getInitialSessionState
|
||||
} from '../../../src/redux/session';
|
||||
|
||||
import {sessions, studios} from '../../helpers/state-fixtures.json';
|
||||
|
||||
describe('studio selectors', () => {
|
||||
let state;
|
||||
|
||||
beforeEach(() => {
|
||||
state = {
|
||||
session: getInitialSessionState(),
|
||||
studio: getInitialStudioState()
|
||||
};
|
||||
});
|
||||
|
||||
describe('studio info', () => {
|
||||
test('is editable by admin', () => {
|
||||
state.session = sessions.user1Admin;
|
||||
expect(selectCanEditInfo(state)).toBe(true);
|
||||
});
|
||||
test('is editable by managers and studio creator', () => {
|
||||
state.studio = studios.isManager;
|
||||
expect(selectCanEditInfo(state)).toBe(true);
|
||||
|
||||
state.studio = studios.creator1;
|
||||
state.session = sessions.user1;
|
||||
expect(selectCanEditInfo(state)).toBe(true);
|
||||
});
|
||||
test('is not editable by curators', () => {
|
||||
state.studio = studios.isCurator;
|
||||
state.session = sessions.user1;
|
||||
expect(selectCanEditInfo(state)).toBe(false);
|
||||
});
|
||||
test('is not editable by other logged in users', () => {
|
||||
state.session = sessions.user1;
|
||||
expect(selectCanEditInfo(state)).toBe(false);
|
||||
});
|
||||
test('is not editable by logged out users', () => {
|
||||
expect(selectCanEditInfo(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('studio projects', () => {
|
||||
test('cannot be added by admin', () => {
|
||||
state.session = sessions.user1Admin;
|
||||
expect(selectCanAddProjects(state)).toBe(false);
|
||||
});
|
||||
test('can be added by managers and studio creator', () => {
|
||||
state.studio = studios.isManager;
|
||||
expect(selectCanAddProjects(state)).toBe(true);
|
||||
|
||||
state.studio = studios.creator1;
|
||||
state.session = sessions.user1;
|
||||
expect(selectCanAddProjects(state)).toBe(true);
|
||||
});
|
||||
test('can be added by curators', () => {
|
||||
state.studio = studios.isCurator;
|
||||
state.session = sessions.user1;
|
||||
expect(selectCanAddProjects(state)).toBe(true);
|
||||
});
|
||||
test('can be added by social users if studio is openToAll', () => {
|
||||
state.studio = studios.openToAll;
|
||||
state.session = sessions.user1Social;
|
||||
expect(selectCanAddProjects(state)).toBe(true);
|
||||
});
|
||||
test('cannot be added by social users if not openToAll', () => {
|
||||
state.session = sessions.user1Social;
|
||||
expect(selectCanAddProjects(state)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('studio comments', () => {
|
||||
test('show comment composer only for social users', () => {
|
||||
expect(selectShowCommentComposer(state)).toBe(false);
|
||||
state.session = sessions.user1;
|
||||
expect(selectShowCommentComposer(state)).toBe(false);
|
||||
state.session = sessions.user1Social;
|
||||
expect(selectShowCommentComposer(state)).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue